diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0d4c8c7459df3..a119fb300a6f7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13545,10 +13545,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; } - function isConstTypeVariable(type: Type): boolean { - return !!(type.flags & TypeFlags.TypeParameter && some((type as TypeParameter).symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.Const)) || - isGenericTupleType(type) && findIndex(getTypeArguments(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t)) >= 0 || - type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType)); + function isConstTypeVariable(type: Type | undefined): boolean { + return !!(type && ( + type.flags & TypeFlags.TypeParameter && some((type as TypeParameter).symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.Const)) || + type.flags & TypeFlags.Union && some((type as UnionType).types, isConstTypeVariable) || + type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType) || + type.flags & TypeFlags.Conditional && isConstTypeVariable(getConstraintOfConditionalType(type as ConditionalType)) || + type.flags & TypeFlags.Substitution && isConstTypeVariable((type as SubstitutionType).baseType) || + isGenericTupleType(type) && findIndex(getTypeArguments(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t)) >= 0)); } function getConstraintOfIndexedAccess(type: IndexedAccessType) { @@ -37258,16 +37262,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const parent = node.parent; return isAssertionExpression(parent) && isConstTypeReference(parent.type) || isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || - isValidConstAssertionArgument(node) && isConstTypeParameterContext(node) || + isValidConstAssertionArgument(node) && isConstTypeVariable(getContextualType(node, ContextFlags.None)) || (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); } - function isConstTypeParameterContext(node: Expression) { - const contextualType = getContextualType(node, ContextFlags.None); - return !!contextualType && someType(contextualType, isConstTypeVariable); - } - function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { const type = checkExpression(node, checkMode, forceTuple); return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) : diff --git a/tests/baselines/reference/typeParameterConstModifiers.errors.txt b/tests/baselines/reference/typeParameterConstModifiers.errors.txt index 38f60e13464fd..f30e71649b28d 100644 --- a/tests/baselines/reference/typeParameterConstModifiers.errors.txt +++ b/tests/baselines/reference/typeParameterConstModifiers.errors.txt @@ -91,4 +91,18 @@ tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterCon declare function inners2(args: readonly [unknown, ...T, unknown]): T; const test2 = inners2([1,2,3,4,5]); + + // Repro from #53307 + + type NotEmpty> = keyof T extends never ? never : T; + + const thing = >(o: NotEmpty) => o; + + const t = thing({ foo: '' }); // readonly { foo: "" } + + type NotEmptyMapped> = keyof T extends never ? never : { [K in keyof T]: T[K] }; + + const thingMapped = >(o: NotEmptyMapped) => o; + + const tMapped = thingMapped({ foo: '' }); // { foo: "" } \ No newline at end of file diff --git a/tests/baselines/reference/typeParameterConstModifiers.js b/tests/baselines/reference/typeParameterConstModifiers.js index 05d1c0ea43b82..77864a47814fb 100644 --- a/tests/baselines/reference/typeParameterConstModifiers.js +++ b/tests/baselines/reference/typeParameterConstModifiers.js @@ -83,6 +83,20 @@ const test = inners(1,2,3,4,5); declare function inners2(args: readonly [unknown, ...T, unknown]): T; const test2 = inners2([1,2,3,4,5]); + +// Repro from #53307 + +type NotEmpty> = keyof T extends never ? never : T; + +const thing = >(o: NotEmpty) => o; + +const t = thing({ foo: '' }); // readonly { foo: "" } + +type NotEmptyMapped> = keyof T extends never ? never : { [K in keyof T]: T[K] }; + +const thingMapped = >(o: NotEmptyMapped) => o; + +const tMapped = thingMapped({ foo: '' }); // { foo: "" } //// [typeParameterConstModifiers.js] @@ -130,3 +144,7 @@ function set(obj, path, value) { } set(obj, ['a', 'b', 'c'], value); var test = inners(1, 2, 3, 4, 5); var test2 = inners2([1, 2, 3, 4, 5]); +var thing = function (o) { return o; }; +var t = thing({ foo: '' }); // readonly { foo: "" } +var thingMapped = function (o) { return o; }; +var tMapped = thingMapped({ foo: '' }); // { foo: "" } diff --git a/tests/baselines/reference/typeParameterConstModifiers.symbols b/tests/baselines/reference/typeParameterConstModifiers.symbols index de269bedeb6d7..5e433d3d134ce 100644 --- a/tests/baselines/reference/typeParameterConstModifiers.symbols +++ b/tests/baselines/reference/typeParameterConstModifiers.symbols @@ -304,3 +304,50 @@ const test2 = inners2([1,2,3,4,5]); >test2 : Symbol(test2, Decl(typeParameterConstModifiers.ts, 83, 5)) >inners2 : Symbol(inners2, Decl(typeParameterConstModifiers.ts, 79, 31)) +// Repro from #53307 + +type NotEmpty> = keyof T extends never ? never : T; +>NotEmpty : Symbol(NotEmpty, Decl(typeParameterConstModifiers.ts, 83, 35)) +>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 87, 14)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 87, 14)) +>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 87, 14)) + +const thing = >(o: NotEmpty) => o; +>thing : Symbol(thing, Decl(typeParameterConstModifiers.ts, 89, 5)) +>O : Symbol(O, Decl(typeParameterConstModifiers.ts, 89, 15)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>o : Symbol(o, Decl(typeParameterConstModifiers.ts, 89, 52)) +>NotEmpty : Symbol(NotEmpty, Decl(typeParameterConstModifiers.ts, 83, 35)) +>O : Symbol(O, Decl(typeParameterConstModifiers.ts, 89, 15)) +>o : Symbol(o, Decl(typeParameterConstModifiers.ts, 89, 52)) + +const t = thing({ foo: '' }); // readonly { foo: "" } +>t : Symbol(t, Decl(typeParameterConstModifiers.ts, 91, 5)) +>thing : Symbol(thing, Decl(typeParameterConstModifiers.ts, 89, 5)) +>foo : Symbol(foo, Decl(typeParameterConstModifiers.ts, 91, 17)) + +type NotEmptyMapped> = keyof T extends never ? never : { [K in keyof T]: T[K] }; +>NotEmptyMapped : Symbol(NotEmptyMapped, Decl(typeParameterConstModifiers.ts, 91, 29)) +>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 93, 20)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 93, 20)) +>K : Symbol(K, Decl(typeParameterConstModifiers.ts, 93, 88)) +>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 93, 20)) +>T : Symbol(T, Decl(typeParameterConstModifiers.ts, 93, 20)) +>K : Symbol(K, Decl(typeParameterConstModifiers.ts, 93, 88)) + +const thingMapped = >(o: NotEmptyMapped) => o; +>thingMapped : Symbol(thingMapped, Decl(typeParameterConstModifiers.ts, 95, 5)) +>O : Symbol(O, Decl(typeParameterConstModifiers.ts, 95, 21)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>o : Symbol(o, Decl(typeParameterConstModifiers.ts, 95, 58)) +>NotEmptyMapped : Symbol(NotEmptyMapped, Decl(typeParameterConstModifiers.ts, 91, 29)) +>O : Symbol(O, Decl(typeParameterConstModifiers.ts, 95, 21)) +>o : Symbol(o, Decl(typeParameterConstModifiers.ts, 95, 58)) + +const tMapped = thingMapped({ foo: '' }); // { foo: "" } +>tMapped : Symbol(tMapped, Decl(typeParameterConstModifiers.ts, 97, 5)) +>thingMapped : Symbol(thingMapped, Decl(typeParameterConstModifiers.ts, 95, 5)) +>foo : Symbol(foo, Decl(typeParameterConstModifiers.ts, 97, 29)) + diff --git a/tests/baselines/reference/typeParameterConstModifiers.types b/tests/baselines/reference/typeParameterConstModifiers.types index 03fb352b0354b..dd5e44d68d5ca 100644 --- a/tests/baselines/reference/typeParameterConstModifiers.types +++ b/tests/baselines/reference/typeParameterConstModifiers.types @@ -350,3 +350,39 @@ const test2 = inners2([1,2,3,4,5]); >4 : 4 >5 : 5 +// Repro from #53307 + +type NotEmpty> = keyof T extends never ? never : T; +>NotEmpty : NotEmpty + +const thing = >(o: NotEmpty) => o; +>thing : >(o: NotEmpty) => NotEmpty +>>(o: NotEmpty) => o : >(o: NotEmpty) => NotEmpty +>o : NotEmpty +>o : NotEmpty + +const t = thing({ foo: '' }); // readonly { foo: "" } +>t : { readonly foo: ""; } +>thing({ foo: '' }) : { readonly foo: ""; } +>thing : >(o: NotEmpty) => NotEmpty +>{ foo: '' } : { foo: ""; } +>foo : "" +>'' : "" + +type NotEmptyMapped> = keyof T extends never ? never : { [K in keyof T]: T[K] }; +>NotEmptyMapped : NotEmptyMapped + +const thingMapped = >(o: NotEmptyMapped) => o; +>thingMapped : >(o: NotEmptyMapped) => NotEmptyMapped +>>(o: NotEmptyMapped) => o : >(o: NotEmptyMapped) => NotEmptyMapped +>o : NotEmptyMapped +>o : NotEmptyMapped + +const tMapped = thingMapped({ foo: '' }); // { foo: "" } +>tMapped : { foo: ""; } +>thingMapped({ foo: '' }) : { foo: ""; } +>thingMapped : >(o: NotEmptyMapped) => NotEmptyMapped +>{ foo: '' } : { foo: ""; } +>foo : "" +>'' : "" + diff --git a/tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts b/tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts index 79255566e2ed7..8f5c01f6a2719 100644 --- a/tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts +++ b/tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts @@ -84,3 +84,17 @@ const test = inners(1,2,3,4,5); declare function inners2(args: readonly [unknown, ...T, unknown]): T; const test2 = inners2([1,2,3,4,5]); + +// Repro from #53307 + +type NotEmpty> = keyof T extends never ? never : T; + +const thing = >(o: NotEmpty) => o; + +const t = thing({ foo: '' }); // readonly { foo: "" } + +type NotEmptyMapped> = keyof T extends never ? never : { [K in keyof T]: T[K] }; + +const thingMapped = >(o: NotEmptyMapped) => o; + +const tMapped = thingMapped({ foo: '' }); // { foo: "" }