From 738e2146aab999a167e8c5488146cb178cf893ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 30 Nov 2024 10:43:13 +0100 Subject: [PATCH 1/2] Improve `isTypeParameterAtTopLevel` to handle substitution types and template literal types --- src/compiler/checker.ts | 7 +- ...TopLevelInferenceLiteralDontWiden1.symbols | 128 ++++++++++++++ ...peTopLevelInferenceLiteralDontWiden1.types | 163 ++++++++++++++++++ ...nTypeTopLevelInferenceLiteralDontWiden1.ts | 44 +++++ 4 files changed, 340 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/returnTypeTopLevelInferenceLiteralDontWiden1.symbols create mode 100644 tests/baselines/reference/returnTypeTopLevelInferenceLiteralDontWiden1.types create mode 100644 tests/cases/compiler/returnTypeTopLevelInferenceLiteralDontWiden1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ed4cd7b03f75a..451b1a53ac6a6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25741,12 +25741,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function isTypeParameterAtTopLevel(type: Type, tp: TypeParameter, depth = 0): boolean { + if (type.flags & TypeFlags.Substitution) { + type = getActualTypeVariable(type); + } return !!(type === tp || type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, tp, depth)) || - depth < 3 && type.flags & TypeFlags.Conditional && ( + depth < 3 && (type.flags & TypeFlags.Conditional && ( isTypeParameterAtTopLevel(getTrueTypeFromConditionalType(type as ConditionalType), tp, depth + 1) || isTypeParameterAtTopLevel(getFalseTypeFromConditionalType(type as ConditionalType), tp, depth + 1) - )); + ) || type.flags & TypeFlags.TemplateLiteral && some((type as TemplateLiteralType).types, t => isTypeParameterAtTopLevel(t, tp, depth)))); } function isTypeParameterAtTopLevelInReturnType(signature: Signature, typeParameter: TypeParameter) { diff --git a/tests/baselines/reference/returnTypeTopLevelInferenceLiteralDontWiden1.symbols b/tests/baselines/reference/returnTypeTopLevelInferenceLiteralDontWiden1.symbols new file mode 100644 index 0000000000000..c1e875abc4b3c --- /dev/null +++ b/tests/baselines/reference/returnTypeTopLevelInferenceLiteralDontWiden1.symbols @@ -0,0 +1,128 @@ +//// [tests/cases/compiler/returnTypeTopLevelInferenceLiteralDontWiden1.ts] //// + +=== returnTypeTopLevelInferenceLiteralDontWiden1.ts === +declare function A(value: T): T extends string ? T : never; +>A : Symbol(A, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 0, 0)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 0, 19)) +>value : Symbol(value, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 0, 22)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 0, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 0, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 0, 19)) + +const ATest = A("foo"); +>ATest : Symbol(ATest, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 2, 5)) +>A : Symbol(A, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 0, 0)) + +declare function B(value: T): `test_${T & string}`; +>B : Symbol(B, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 2, 23)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 4, 19)) +>value : Symbol(value, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 4, 22)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 4, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 4, 19)) + +const BTest = B("foo"); +>BTest : Symbol(BTest, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 6, 5)) +>B : Symbol(B, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 2, 23)) + +declare function C( +>C : Symbol(C, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 6, 23)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 8, 19)) + + value: T, +>value : Symbol(value, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 8, 22)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 8, 19)) + +): T extends string +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 8, 19)) + + ? T +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 8, 19)) + + : T extends number | bigint | boolean | null | undefined +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 8, 19)) + + ? `test_${T}` +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 8, 19)) + + : never; + +const CTest = C("foo"); +>CTest : Symbol(CTest, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 16, 5)) +>C : Symbol(C, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 6, 23)) + +declare function D(value: T): T extends number ? `test_${T}` : T; +>D : Symbol(D, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 16, 23)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 18, 19)) +>value : Symbol(value, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 18, 22)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 18, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 18, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 18, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 18, 19)) + +const DTest = D("foo"); +>DTest : Symbol(DTest, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 20, 5)) +>D : Symbol(D, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 16, 23)) + +declare function E(value: T): T extends string ? `test_${T}` : T; +>E : Symbol(E, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 20, 23)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 22, 19)) +>value : Symbol(value, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 22, 22)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 22, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 22, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 22, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 22, 19)) + +const ETest = E("foo"); +>ETest : Symbol(ETest, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 24, 5)) +>E : Symbol(E, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 20, 23)) + +declare function F(value: T): T extends number ? `test_${T}` : [T]; +>F : Symbol(F, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 24, 23)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 26, 19)) +>value : Symbol(value, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 26, 22)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 26, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 26, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 26, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 26, 19)) + +const FTest1 = F("foo"); +>FTest1 : Symbol(FTest1, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 28, 5)) +>F : Symbol(F, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 24, 23)) + +const FTest2 = F(42); +>FTest2 : Symbol(FTest2, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 29, 5)) +>F : Symbol(F, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 24, 23)) + +declare function G(value: T): T extends string ? `test_${T}` : [T]; +>G : Symbol(G, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 29, 21)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 31, 19)) +>value : Symbol(value, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 31, 22)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 31, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 31, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 31, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 31, 19)) + +const GTest1 = G("foo"); +>GTest1 : Symbol(GTest1, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 33, 5)) +>G : Symbol(G, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 29, 21)) + +const GTest2 = G(42); +>GTest2 : Symbol(GTest2, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 34, 5)) +>G : Symbol(G, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 29, 21)) + +declare function H( +>H : Symbol(H, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 34, 21)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 36, 19)) + + value: T, +>value : Symbol(value, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 36, 22)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 36, 19)) + +): T extends number ? never : `test_${T & string}`; +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 36, 19)) +>T : Symbol(T, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 36, 19)) + +const HTest = H("foo"); +>HTest : Symbol(HTest, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 40, 5)) +>H : Symbol(H, Decl(returnTypeTopLevelInferenceLiteralDontWiden1.ts, 34, 21)) + diff --git a/tests/baselines/reference/returnTypeTopLevelInferenceLiteralDontWiden1.types b/tests/baselines/reference/returnTypeTopLevelInferenceLiteralDontWiden1.types new file mode 100644 index 0000000000000..25749fc234e42 --- /dev/null +++ b/tests/baselines/reference/returnTypeTopLevelInferenceLiteralDontWiden1.types @@ -0,0 +1,163 @@ +//// [tests/cases/compiler/returnTypeTopLevelInferenceLiteralDontWiden1.ts] //// + +=== returnTypeTopLevelInferenceLiteralDontWiden1.ts === +declare function A(value: T): T extends string ? T : never; +>A : (value: T) => T extends string ? T : never +> : ^ ^^ ^^ ^^^^^ +>value : T +> : ^ + +const ATest = A("foo"); +>ATest : "foo" +> : ^^^^^ +>A("foo") : "foo" +> : ^^^^^ +>A : (value: T) => T extends string ? T : never +> : ^ ^^ ^^ ^^^^^ +>"foo" : "foo" +> : ^^^^^ + +declare function B(value: T): `test_${T & string}`; +>B : (value: T) => `test_${T & string}` +> : ^ ^^ ^^ ^^^^^ +>value : T +> : ^ + +const BTest = B("foo"); +>BTest : "test_foo" +> : ^^^^^^^^^^ +>B("foo") : "test_foo" +> : ^^^^^^^^^^ +>B : (value: T) => `test_${T & string}` +> : ^ ^^ ^^ ^^^^^ +>"foo" : "foo" +> : ^^^^^ + +declare function C( +>C : (value: T) => T extends string ? T : T extends number | bigint | boolean | null | undefined ? `test_${T}` : never +> : ^ ^^ ^^ ^^^^^ + + value: T, +>value : T +> : ^ + +): T extends string + ? T + : T extends number | bigint | boolean | null | undefined + ? `test_${T}` + : never; + +const CTest = C("foo"); +>CTest : "foo" +> : ^^^^^ +>C("foo") : "foo" +> : ^^^^^ +>C : (value: T) => T extends string ? T : T extends number | bigint | boolean | null | undefined ? `test_${T}` : never +> : ^ ^^ ^^ ^^^^^ +>"foo" : "foo" +> : ^^^^^ + +declare function D(value: T): T extends number ? `test_${T}` : T; +>D : (value: T) => T extends number ? `test_${T}` : T +> : ^ ^^ ^^ ^^^^^ +>value : T +> : ^ + +const DTest = D("foo"); +>DTest : "foo" +> : ^^^^^ +>D("foo") : "foo" +> : ^^^^^ +>D : (value: T) => T extends number ? `test_${T}` : T +> : ^ ^^ ^^ ^^^^^ +>"foo" : "foo" +> : ^^^^^ + +declare function E(value: T): T extends string ? `test_${T}` : T; +>E : (value: T) => T extends string ? `test_${T}` : T +> : ^ ^^ ^^ ^^^^^ +>value : T +> : ^ + +const ETest = E("foo"); +>ETest : "test_foo" +> : ^^^^^^^^^^ +>E("foo") : "test_foo" +> : ^^^^^^^^^^ +>E : (value: T) => T extends string ? `test_${T}` : T +> : ^ ^^ ^^ ^^^^^ +>"foo" : "foo" +> : ^^^^^ + +declare function F(value: T): T extends number ? `test_${T}` : [T]; +>F : (value: T) => T extends number ? `test_${T}` : [T] +> : ^ ^^ ^^ ^^^^^ +>value : T +> : ^ + +const FTest1 = F("foo"); +>FTest1 : ["foo"] +> : ^^^^^^^ +>F("foo") : ["foo"] +> : ^^^^^^^ +>F : (value: T) => T extends number ? `test_${T}` : [T] +> : ^ ^^ ^^ ^^^^^ +>"foo" : "foo" +> : ^^^^^ + +const FTest2 = F(42); +>FTest2 : "test_42" +> : ^^^^^^^^^ +>F(42) : "test_42" +> : ^^^^^^^^^ +>F : (value: T) => T extends number ? `test_${T}` : [T] +> : ^ ^^ ^^ ^^^^^ +>42 : 42 +> : ^^ + +declare function G(value: T): T extends string ? `test_${T}` : [T]; +>G : (value: T) => T extends string ? `test_${T}` : [T] +> : ^ ^^ ^^ ^^^^^ +>value : T +> : ^ + +const GTest1 = G("foo"); +>GTest1 : "test_foo" +> : ^^^^^^^^^^ +>G("foo") : "test_foo" +> : ^^^^^^^^^^ +>G : (value: T) => T extends string ? `test_${T}` : [T] +> : ^ ^^ ^^ ^^^^^ +>"foo" : "foo" +> : ^^^^^ + +const GTest2 = G(42); +>GTest2 : [42] +> : ^^^^ +>G(42) : [42] +> : ^^^^ +>G : (value: T) => T extends string ? `test_${T}` : [T] +> : ^ ^^ ^^ ^^^^^ +>42 : 42 +> : ^^ + +declare function H( +>H : (value: T) => T extends number ? never : `test_${T & string}` +> : ^ ^^ ^^ ^^^^^ + + value: T, +>value : T +> : ^ + +): T extends number ? never : `test_${T & string}`; + +const HTest = H("foo"); +>HTest : "test_foo" +> : ^^^^^^^^^^ +>H("foo") : "test_foo" +> : ^^^^^^^^^^ +>H : (value: T) => T extends number ? never : `test_${T & string}` +> : ^ ^^ ^^ ^^^^^ +>"foo" : "foo" +> : ^^^^^ + diff --git a/tests/cases/compiler/returnTypeTopLevelInferenceLiteralDontWiden1.ts b/tests/cases/compiler/returnTypeTopLevelInferenceLiteralDontWiden1.ts new file mode 100644 index 0000000000000..b00f56eaa9fea --- /dev/null +++ b/tests/cases/compiler/returnTypeTopLevelInferenceLiteralDontWiden1.ts @@ -0,0 +1,44 @@ +// @strict: true +// @noEmit: true + +declare function A(value: T): T extends string ? T : never; + +const ATest = A("foo"); + +declare function B(value: T): `test_${T & string}`; + +const BTest = B("foo"); + +declare function C( + value: T, +): T extends string + ? T + : T extends number | bigint | boolean | null | undefined + ? `test_${T}` + : never; + +const CTest = C("foo"); + +declare function D(value: T): T extends number ? `test_${T}` : T; + +const DTest = D("foo"); + +declare function E(value: T): T extends string ? `test_${T}` : T; + +const ETest = E("foo"); + +declare function F(value: T): T extends number ? `test_${T}` : [T]; + +const FTest1 = F("foo"); +const FTest2 = F(42); + +declare function G(value: T): T extends string ? `test_${T}` : [T]; + +const GTest1 = G("foo"); +const GTest2 = G(42); + +declare function H( + value: T, +): T extends number ? never : `test_${T & string}`; + +const HTest = H("foo"); From 3dfc0e5632df72c54a58cd35f04ca2f95cae176c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 30 Nov 2024 10:55:02 +0100 Subject: [PATCH 2/2] format --- src/compiler/checker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 451b1a53ac6a6..86a8703ab3347 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25747,9 +25747,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return !!(type === tp || type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, tp, depth)) || depth < 3 && (type.flags & TypeFlags.Conditional && ( - isTypeParameterAtTopLevel(getTrueTypeFromConditionalType(type as ConditionalType), tp, depth + 1) || - isTypeParameterAtTopLevel(getFalseTypeFromConditionalType(type as ConditionalType), tp, depth + 1) - ) || type.flags & TypeFlags.TemplateLiteral && some((type as TemplateLiteralType).types, t => isTypeParameterAtTopLevel(t, tp, depth)))); + isTypeParameterAtTopLevel(getTrueTypeFromConditionalType(type as ConditionalType), tp, depth + 1) || + isTypeParameterAtTopLevel(getFalseTypeFromConditionalType(type as ConditionalType), tp, depth + 1) + ) || type.flags & TypeFlags.TemplateLiteral && some((type as TemplateLiteralType).types, t => isTypeParameterAtTopLevel(t, tp, depth)))); } function isTypeParameterAtTopLevelInReturnType(signature: Signature, typeParameter: TypeParameter) {