Skip to content

Commit 3f675b6

Browse files
authored
More complete check in isConstTypeVariable (#53341)
1 parent b40385b commit 3f675b6

File tree

6 files changed

+138
-10
lines changed

6 files changed

+138
-10
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13557,10 +13557,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1355713557
return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined;
1355813558
}
1355913559

13560-
function isConstTypeVariable(type: Type): boolean {
13561-
return !!(type.flags & TypeFlags.TypeParameter && some((type as TypeParameter).symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.Const)) ||
13562-
isGenericTupleType(type) && findIndex(getTypeArguments(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t)) >= 0 ||
13563-
type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType));
13560+
function isConstTypeVariable(type: Type | undefined): boolean {
13561+
return !!(type && (
13562+
type.flags & TypeFlags.TypeParameter && some((type as TypeParameter).symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.Const)) ||
13563+
type.flags & TypeFlags.Union && some((type as UnionType).types, isConstTypeVariable) ||
13564+
type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType) ||
13565+
type.flags & TypeFlags.Conditional && isConstTypeVariable(getConstraintOfConditionalType(type as ConditionalType)) ||
13566+
type.flags & TypeFlags.Substitution && isConstTypeVariable((type as SubstitutionType).baseType) ||
13567+
isGenericTupleType(type) && findIndex(getTypeArguments(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t)) >= 0));
1356413568
}
1356513569

1356613570
function getConstraintOfIndexedAccess(type: IndexedAccessType) {
@@ -37385,16 +37389,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3738537389
const parent = node.parent;
3738637390
return isAssertionExpression(parent) && isConstTypeReference(parent.type) ||
3738737391
isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) ||
37388-
isValidConstAssertionArgument(node) && isConstTypeParameterContext(node) ||
37392+
isValidConstAssertionArgument(node) && isConstTypeVariable(getContextualType(node, ContextFlags.None)) ||
3738937393
(isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) ||
3739037394
(isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent);
3739137395
}
3739237396

37393-
function isConstTypeParameterContext(node: Expression) {
37394-
const contextualType = getContextualType(node, ContextFlags.None);
37395-
return !!contextualType && someType(contextualType, isConstTypeVariable);
37396-
}
37397-
3739837397
function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, forceTuple?: boolean): Type {
3739937398
const type = checkExpression(node, checkMode, forceTuple);
3740037399
return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) :

tests/baselines/reference/typeParameterConstModifiers.errors.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,18 @@ tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterCon
9191
declare function inners2<const T extends readonly any[]>(args: readonly [unknown, ...T, unknown]): T;
9292

9393
const test2 = inners2([1,2,3,4,5]);
94+
95+
// Repro from #53307
96+
97+
type NotEmpty<T extends Record<string, any>> = keyof T extends never ? never : T;
98+
99+
const thing = <const O extends Record<string, any>>(o: NotEmpty<O>) => o;
100+
101+
const t = thing({ foo: '' }); // readonly { foo: "" }
102+
103+
type NotEmptyMapped<T extends Record<string, any>> = keyof T extends never ? never : { [K in keyof T]: T[K] };
104+
105+
const thingMapped = <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => o;
106+
107+
const tMapped = thingMapped({ foo: '' }); // { foo: "" }
94108

tests/baselines/reference/typeParameterConstModifiers.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,20 @@ const test = inners(1,2,3,4,5);
8383
declare function inners2<const T extends readonly any[]>(args: readonly [unknown, ...T, unknown]): T;
8484

8585
const test2 = inners2([1,2,3,4,5]);
86+
87+
// Repro from #53307
88+
89+
type NotEmpty<T extends Record<string, any>> = keyof T extends never ? never : T;
90+
91+
const thing = <const O extends Record<string, any>>(o: NotEmpty<O>) => o;
92+
93+
const t = thing({ foo: '' }); // readonly { foo: "" }
94+
95+
type NotEmptyMapped<T extends Record<string, any>> = keyof T extends never ? never : { [K in keyof T]: T[K] };
96+
97+
const thingMapped = <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => o;
98+
99+
const tMapped = thingMapped({ foo: '' }); // { foo: "" }
86100

87101

88102
//// [typeParameterConstModifiers.js]
@@ -130,3 +144,7 @@ function set(obj, path, value) { }
130144
set(obj, ['a', 'b', 'c'], value);
131145
var test = inners(1, 2, 3, 4, 5);
132146
var test2 = inners2([1, 2, 3, 4, 5]);
147+
var thing = function (o) { return o; };
148+
var t = thing({ foo: '' }); // readonly { foo: "" }
149+
var thingMapped = function (o) { return o; };
150+
var tMapped = thingMapped({ foo: '' }); // { foo: "" }

tests/baselines/reference/typeParameterConstModifiers.symbols

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,50 @@ const test2 = inners2([1,2,3,4,5]);
304304
>test2 : Symbol(test2, Decl(typeParameterConstModifiers.ts, 83, 5))
305305
>inners2 : Symbol(inners2, Decl(typeParameterConstModifiers.ts, 79, 31))
306306

307+
// Repro from #53307
308+
309+
type NotEmpty<T extends Record<string, any>> = keyof T extends never ? never : T;
310+
>NotEmpty : Symbol(NotEmpty, Decl(typeParameterConstModifiers.ts, 83, 35))
311+
>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 87, 14))
312+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
313+
>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 87, 14))
314+
>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 87, 14))
315+
316+
const thing = <const O extends Record<string, any>>(o: NotEmpty<O>) => o;
317+
>thing : Symbol(thing, Decl(typeParameterConstModifiers.ts, 89, 5))
318+
>O : Symbol(O, Decl(typeParameterConstModifiers.ts, 89, 15))
319+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
320+
>o : Symbol(o, Decl(typeParameterConstModifiers.ts, 89, 52))
321+
>NotEmpty : Symbol(NotEmpty, Decl(typeParameterConstModifiers.ts, 83, 35))
322+
>O : Symbol(O, Decl(typeParameterConstModifiers.ts, 89, 15))
323+
>o : Symbol(o, Decl(typeParameterConstModifiers.ts, 89, 52))
324+
325+
const t = thing({ foo: '' }); // readonly { foo: "" }
326+
>t : Symbol(t, Decl(typeParameterConstModifiers.ts, 91, 5))
327+
>thing : Symbol(thing, Decl(typeParameterConstModifiers.ts, 89, 5))
328+
>foo : Symbol(foo, Decl(typeParameterConstModifiers.ts, 91, 17))
329+
330+
type NotEmptyMapped<T extends Record<string, any>> = keyof T extends never ? never : { [K in keyof T]: T[K] };
331+
>NotEmptyMapped : Symbol(NotEmptyMapped, Decl(typeParameterConstModifiers.ts, 91, 29))
332+
>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 93, 20))
333+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
334+
>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 93, 20))
335+
>K : Symbol(K, Decl(typeParameterConstModifiers.ts, 93, 88))
336+
>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 93, 20))
337+
>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 93, 20))
338+
>K : Symbol(K, Decl(typeParameterConstModifiers.ts, 93, 88))
339+
340+
const thingMapped = <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => o;
341+
>thingMapped : Symbol(thingMapped, Decl(typeParameterConstModifiers.ts, 95, 5))
342+
>O : Symbol(O, Decl(typeParameterConstModifiers.ts, 95, 21))
343+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
344+
>o : Symbol(o, Decl(typeParameterConstModifiers.ts, 95, 58))
345+
>NotEmptyMapped : Symbol(NotEmptyMapped, Decl(typeParameterConstModifiers.ts, 91, 29))
346+
>O : Symbol(O, Decl(typeParameterConstModifiers.ts, 95, 21))
347+
>o : Symbol(o, Decl(typeParameterConstModifiers.ts, 95, 58))
348+
349+
const tMapped = thingMapped({ foo: '' }); // { foo: "" }
350+
>tMapped : Symbol(tMapped, Decl(typeParameterConstModifiers.ts, 97, 5))
351+
>thingMapped : Symbol(thingMapped, Decl(typeParameterConstModifiers.ts, 95, 5))
352+
>foo : Symbol(foo, Decl(typeParameterConstModifiers.ts, 97, 29))
353+

tests/baselines/reference/typeParameterConstModifiers.types

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,39 @@ const test2 = inners2([1,2,3,4,5]);
350350
>4 : 4
351351
>5 : 5
352352

353+
// Repro from #53307
354+
355+
type NotEmpty<T extends Record<string, any>> = keyof T extends never ? never : T;
356+
>NotEmpty : NotEmpty<T>
357+
358+
const thing = <const O extends Record<string, any>>(o: NotEmpty<O>) => o;
359+
>thing : <const O extends Record<string, any>>(o: NotEmpty<O>) => NotEmpty<O>
360+
><const O extends Record<string, any>>(o: NotEmpty<O>) => o : <const O extends Record<string, any>>(o: NotEmpty<O>) => NotEmpty<O>
361+
>o : NotEmpty<O>
362+
>o : NotEmpty<O>
363+
364+
const t = thing({ foo: '' }); // readonly { foo: "" }
365+
>t : { readonly foo: ""; }
366+
>thing({ foo: '' }) : { readonly foo: ""; }
367+
>thing : <const O extends Record<string, any>>(o: NotEmpty<O>) => NotEmpty<O>
368+
>{ foo: '' } : { foo: ""; }
369+
>foo : ""
370+
>'' : ""
371+
372+
type NotEmptyMapped<T extends Record<string, any>> = keyof T extends never ? never : { [K in keyof T]: T[K] };
373+
>NotEmptyMapped : NotEmptyMapped<T>
374+
375+
const thingMapped = <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => o;
376+
>thingMapped : <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => NotEmptyMapped<O>
377+
><const O extends Record<string, any>>(o: NotEmptyMapped<O>) => o : <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => NotEmptyMapped<O>
378+
>o : NotEmptyMapped<O>
379+
>o : NotEmptyMapped<O>
380+
381+
const tMapped = thingMapped({ foo: '' }); // { foo: "" }
382+
>tMapped : { foo: ""; }
383+
>thingMapped({ foo: '' }) : { foo: ""; }
384+
>thingMapped : <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => NotEmptyMapped<O>
385+
>{ foo: '' } : { foo: ""; }
386+
>foo : ""
387+
>'' : ""
388+

tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,17 @@ const test = inners(1,2,3,4,5);
8484
declare function inners2<const T extends readonly any[]>(args: readonly [unknown, ...T, unknown]): T;
8585

8686
const test2 = inners2([1,2,3,4,5]);
87+
88+
// Repro from #53307
89+
90+
type NotEmpty<T extends Record<string, any>> = keyof T extends never ? never : T;
91+
92+
const thing = <const O extends Record<string, any>>(o: NotEmpty<O>) => o;
93+
94+
const t = thing({ foo: '' }); // readonly { foo: "" }
95+
96+
type NotEmptyMapped<T extends Record<string, any>> = keyof T extends never ? never : { [K in keyof T]: T[K] };
97+
98+
const thingMapped = <const O extends Record<string, any>>(o: NotEmptyMapped<O>) => o;
99+
100+
const tMapped = thingMapped({ foo: '' }); // { foo: "" }

0 commit comments

Comments
 (0)