Skip to content

Commit 753c463

Browse files
ahejlsbergAndarist
andauthored
Only infer readonly tuples for const type parameters when constraints permit (#55229)
Co-authored-by: Mateusz Burzyński <[email protected]>
1 parent 51e7a34 commit 753c463

File tree

6 files changed

+854
-5
lines changed

6 files changed

+854
-5
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23674,6 +23674,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2367423674
return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);
2367523675
}
2367623676

23677+
function isMutableArrayLikeType(type: Type): boolean {
23678+
// A type is mutable-array-like if it is a reference to the global Array type, or if it is not the
23679+
// any, undefined or null type and if it is assignable to Array<any>
23680+
return isMutableArrayOrTuple(type) || !(type.flags & (TypeFlags.Any | TypeFlags.Nullable)) && isTypeAssignableTo(type, anyArrayType);
23681+
}
23682+
2367723683
function getSingleBaseForNonAugmentingSubtype(type: Type) {
2367823684
if (!(getObjectFlags(type) & ObjectFlags.Reference) || !(getObjectFlags((type as TypeReference).target) & ObjectFlags.ClassOrInterface)) {
2367923685
return undefined;
@@ -24338,7 +24344,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2433824344
callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i));
2433924345
}
2434024346
if (targetRestType) {
24341-
callback(getRestTypeAtPosition(source, paramCount), targetRestType);
24347+
callback(getRestTypeAtPosition(source, paramCount, /*readonly*/ isConstTypeVariable(targetRestType) && !someType(targetRestType, isMutableArrayLikeType)), targetRestType);
2434224348
}
2434324349
}
2434424350

@@ -30541,7 +30547,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3054130547
return createTupleType(elementTypes, elementFlags);
3054230548
}
3054330549
if (forceTuple || inConstContext || inTupleContext) {
30544-
return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext));
30550+
return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext && !(contextualType && someType(contextualType, isMutableArrayLikeType))));
3054530551
}
3054630552
return createArrayLiteralType(createArrayType(
3054730553
elementTypes.length ?
@@ -33139,7 +33145,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3313933145
names.push((arg as SyntheticExpression).tupleNameSource!);
3314033146
}
3314133147
}
33142-
return createTupleType(types, flags, inConstContext, length(names) === length(types) ? names : undefined);
33148+
return createTupleType(types, flags, inConstContext && !someType(restType, isMutableArrayLikeType), length(names) === length(types) ? names : undefined);
3314333149
}
3314433150

3314533151
function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined {
@@ -35455,7 +35461,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3545535461
return undefined;
3545635462
}
3545735463

35458-
function getRestTypeAtPosition(source: Signature, pos: number): Type {
35464+
function getRestTypeAtPosition(source: Signature, pos: number, readonly?: boolean): Type {
3545935465
const parameterCount = getParameterCount(source);
3546035466
const minArgumentCount = getMinArgumentCount(source);
3546135467
const restType = getEffectiveRestType(source);
@@ -35479,7 +35485,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3547935485
names.push(name);
3548035486
}
3548135487
}
35482-
return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined);
35488+
return createTupleType(types, flags, readonly, length(names) === length(types) ? names : undefined);
3548335489
}
3548435490

3548535491
// Return the number of parameters in a signature. The rest parameter, if present, counts as one

tests/baselines/reference/typeParameterConstModifiers.errors.txt

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,87 @@ typeParameterConstModifiers.ts(55,9): error TS1277: 'const' modifier can only ap
107107
const thingMapped = <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => o;
108108

109109
const tMapped = thingMapped({ foo: '' }); // { foo: "" }
110+
111+
// repro from https://github.com/microsoft/TypeScript/issues/55033
112+
113+
function factory_55033_minimal<const T extends readonly unknown[]>(cb: (...args: T) => void) {
114+
return {} as T
115+
}
116+
117+
const test_55033_minimal = factory_55033_minimal((b: string) => {})
118+
119+
function factory_55033<const T extends readonly unknown[]>(cb: (...args: T) => void) {
120+
return function call<const K extends T>(...args: K): K {
121+
return {} as K;
122+
};
123+
}
124+
125+
const t1_55033 = factory_55033((a: { test: number }, b: string) => {})(
126+
{ test: 123 },
127+
"some string"
128+
);
129+
130+
const t2_55033 = factory_55033((a: { test: number }, b: string) => {})(
131+
{ test: 123 } as const,
132+
"some string"
133+
);
134+
135+
// Same with non-readonly constraint
136+
137+
function factory_55033_2<const T extends unknown[]>(cb: (...args: T) => void) {
138+
return function call<const K extends T>(...args: K): K {
139+
return {} as K;
140+
};
141+
}
142+
143+
const t1_55033_2 = factory_55033_2((a: { test: number }, b: string) => {})(
144+
{ test: 123 },
145+
"some string"
146+
);
147+
148+
const t2_55033_2 = factory_55033_2((a: { test: number }, b: string) => {})(
149+
{ test: 123 } as const,
150+
"some string"
151+
);
152+
153+
// Repro from https://github.com/microsoft/TypeScript/issues/51931
154+
155+
declare function fn<const T extends any[]>(...args: T): T;
156+
157+
const a = fn("a", false);
158+
159+
// More examples of non-readonly constraints
160+
161+
declare function fa1<const T extends unknown[]>(args: T): T;
162+
declare function fa2<const T extends readonly unknown[]>(args: T): T;
163+
164+
fa1(["hello", 42]);
165+
fa2(["hello", 42]);
166+
167+
declare function fb1<const T extends unknown[]>(...args: T): T;
168+
declare function fb2<const T extends readonly unknown[]>(...args: T): T;
169+
170+
fb1("hello", 42);
171+
fb2("hello", 42);
172+
173+
declare function fc1<const T extends unknown[]>(f: (...args: T) => void, ...args: T): T;
174+
declare function fc2<const T extends readonly unknown[]>(f: (...args: T) => void, ...args: T): T;
175+
176+
fc1((a: string, b: number) => {}, "hello", 42);
177+
fc2((a: string, b: number) => {}, "hello", 42);
178+
179+
declare function fd1<const T extends string[] | number[]>(args: T): T;
180+
declare function fd2<const T extends string[] | readonly number[]>(args: T): T;
181+
declare function fd3<const T extends readonly string[] | readonly number[]>(args: T): T;
182+
183+
fd1(["hello", "world"]);
184+
fd1([1, 2, 3]);
185+
fd2(["hello", "world"]);
186+
fd2([1, 2, 3]);
187+
fd3(["hello", "world"]);
188+
fd3([1, 2, 3]);
189+
190+
declare function fn1<const T extends { foo: unknown[] }[]>(...args: T): T;
191+
192+
fn1({ foo: ["hello", 123] }, { foo: [true]});
110193

tests/baselines/reference/typeParameterConstModifiers.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,89 @@ type NotEmptyMapped<T extends Record<string, any>> = keyof T extends never ? nev
101101
const thingMapped = <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => o;
102102

103103
const tMapped = thingMapped({ foo: '' }); // { foo: "" }
104+
105+
// repro from https://github.com/microsoft/TypeScript/issues/55033
106+
107+
function factory_55033_minimal<const T extends readonly unknown[]>(cb: (...args: T) => void) {
108+
return {} as T
109+
}
110+
111+
const test_55033_minimal = factory_55033_minimal((b: string) => {})
112+
113+
function factory_55033<const T extends readonly unknown[]>(cb: (...args: T) => void) {
114+
return function call<const K extends T>(...args: K): K {
115+
return {} as K;
116+
};
117+
}
118+
119+
const t1_55033 = factory_55033((a: { test: number }, b: string) => {})(
120+
{ test: 123 },
121+
"some string"
122+
);
123+
124+
const t2_55033 = factory_55033((a: { test: number }, b: string) => {})(
125+
{ test: 123 } as const,
126+
"some string"
127+
);
128+
129+
// Same with non-readonly constraint
130+
131+
function factory_55033_2<const T extends unknown[]>(cb: (...args: T) => void) {
132+
return function call<const K extends T>(...args: K): K {
133+
return {} as K;
134+
};
135+
}
136+
137+
const t1_55033_2 = factory_55033_2((a: { test: number }, b: string) => {})(
138+
{ test: 123 },
139+
"some string"
140+
);
141+
142+
const t2_55033_2 = factory_55033_2((a: { test: number }, b: string) => {})(
143+
{ test: 123 } as const,
144+
"some string"
145+
);
146+
147+
// Repro from https://github.com/microsoft/TypeScript/issues/51931
148+
149+
declare function fn<const T extends any[]>(...args: T): T;
150+
151+
const a = fn("a", false);
152+
153+
// More examples of non-readonly constraints
154+
155+
declare function fa1<const T extends unknown[]>(args: T): T;
156+
declare function fa2<const T extends readonly unknown[]>(args: T): T;
157+
158+
fa1(["hello", 42]);
159+
fa2(["hello", 42]);
160+
161+
declare function fb1<const T extends unknown[]>(...args: T): T;
162+
declare function fb2<const T extends readonly unknown[]>(...args: T): T;
163+
164+
fb1("hello", 42);
165+
fb2("hello", 42);
166+
167+
declare function fc1<const T extends unknown[]>(f: (...args: T) => void, ...args: T): T;
168+
declare function fc2<const T extends readonly unknown[]>(f: (...args: T) => void, ...args: T): T;
169+
170+
fc1((a: string, b: number) => {}, "hello", 42);
171+
fc2((a: string, b: number) => {}, "hello", 42);
172+
173+
declare function fd1<const T extends string[] | number[]>(args: T): T;
174+
declare function fd2<const T extends string[] | readonly number[]>(args: T): T;
175+
declare function fd3<const T extends readonly string[] | readonly number[]>(args: T): T;
176+
177+
fd1(["hello", "world"]);
178+
fd1([1, 2, 3]);
179+
fd2(["hello", "world"]);
180+
fd2([1, 2, 3]);
181+
fd3(["hello", "world"]);
182+
fd3([1, 2, 3]);
183+
184+
declare function fn1<const T extends { foo: unknown[] }[]>(...args: T): T;
185+
186+
fn1({ foo: ["hello", 123] }, { foo: [true]});
104187

105188

106189
//// [typeParameterConstModifiers.js]
@@ -154,3 +237,45 @@ var thing = function (o) { return o; };
154237
var t = thing({ foo: '' }); // readonly { foo: "" }
155238
var thingMapped = function (o) { return o; };
156239
var tMapped = thingMapped({ foo: '' }); // { foo: "" }
240+
// repro from https://github.com/microsoft/TypeScript/issues/55033
241+
function factory_55033_minimal(cb) {
242+
return {};
243+
}
244+
var test_55033_minimal = factory_55033_minimal(function (b) { });
245+
function factory_55033(cb) {
246+
return function call() {
247+
var args = [];
248+
for (var _i = 0; _i < arguments.length; _i++) {
249+
args[_i] = arguments[_i];
250+
}
251+
return {};
252+
};
253+
}
254+
var t1_55033 = factory_55033(function (a, b) { })({ test: 123 }, "some string");
255+
var t2_55033 = factory_55033(function (a, b) { })({ test: 123 }, "some string");
256+
// Same with non-readonly constraint
257+
function factory_55033_2(cb) {
258+
return function call() {
259+
var args = [];
260+
for (var _i = 0; _i < arguments.length; _i++) {
261+
args[_i] = arguments[_i];
262+
}
263+
return {};
264+
};
265+
}
266+
var t1_55033_2 = factory_55033_2(function (a, b) { })({ test: 123 }, "some string");
267+
var t2_55033_2 = factory_55033_2(function (a, b) { })({ test: 123 }, "some string");
268+
var a = fn("a", false);
269+
fa1(["hello", 42]);
270+
fa2(["hello", 42]);
271+
fb1("hello", 42);
272+
fb2("hello", 42);
273+
fc1(function (a, b) { }, "hello", 42);
274+
fc2(function (a, b) { }, "hello", 42);
275+
fd1(["hello", "world"]);
276+
fd1([1, 2, 3]);
277+
fd2(["hello", "world"]);
278+
fd2([1, 2, 3]);
279+
fd3(["hello", "world"]);
280+
fd3([1, 2, 3]);
281+
fn1({ foo: ["hello", 123] }, { foo: [true] });

0 commit comments

Comments
 (0)