From b810fa55c337772ee780bfd300400a27a7ed538f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 20 Aug 2026 15:03:07 +0200 Subject: [PATCH 1/2] Don't defer index types for non-generic substitution types --- tsc/internal/checker/checker.go | 3 ++ ...bstitutionTypeNonGenericIndexType1.symbols | 35 +++++++++++++++++++ ...substitutionTypeNonGenericIndexType1.types | 32 +++++++++++++++++ ...bstitutionTypeNonGenericIndexType2.symbols | 30 ++++++++++++++++ ...substitutionTypeNonGenericIndexType2.types | 24 +++++++++++++ .../substitutionTypeNonGenericIndexType1.ts | 18 ++++++++++ .../substitutionTypeNonGenericIndexType2.ts | 14 ++++++++ 7 files changed, 156 insertions(+) create mode 100644 tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType1.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType1.types create mode 100644 tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType2.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType2.types create mode 100644 tsc/testdata/tests/cases/compiler/substitutionTypeNonGenericIndexType1.ts create mode 100644 tsc/testdata/tests/cases/compiler/substitutionTypeNonGenericIndexType2.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 2a4ead5003a89..2574f2e15cccd 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -26809,6 +26809,9 @@ func (c *Checker) getIndexTypeEx(t *Type, indexFlags IndexFlags) *Type { switch { case c.isNoInferType(t): return c.getNoInferType(c.getIndexTypeEx(t.AsSubstitutionType().baseType, indexFlags)) + case t.flags&TypeFlagsSubstitution != 0 && !c.isGenericType(t): + st := t.AsSubstitutionType() + return c.getUnionType([]*Type{c.getIndexTypeEx(st.baseType, indexFlags), c.getIndexTypeEx(st.constraint, indexFlags)}) case c.shouldDeferIndexType(t, indexFlags): return c.getIndexTypeForGenericType(t, indexFlags) case t.flags&TypeFlagsUnion != 0: diff --git a/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType1.symbols b/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType1.symbols new file mode 100644 index 0000000000000..7ca5986fa6817 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType1.symbols @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/substitutionTypeNonGenericIndexType1.ts] //// + +=== substitutionTypeNonGenericIndexType1.ts === +// https://github.com/microsoft/TypeScript/issues/61728 + +type BasicConditional = keyof T extends any +>BasicConditional : Symbol(BasicConditional, Decl(substitutionTypeNonGenericIndexType1.ts, 0, 0)) +>T : Symbol(T, Decl(substitutionTypeNonGenericIndexType1.ts, 2, 22)) +>T : Symbol(T, Decl(substitutionTypeNonGenericIndexType1.ts, 2, 22)) + + ? true + : false; + +type Config = { rejectClose: true }; +>Config : Symbol(Config, Decl(substitutionTypeNonGenericIndexType1.ts, 4, 10)) +>rejectClose : Symbol(rejectClose, Decl(substitutionTypeNonGenericIndexType1.ts, 6, 15)) + +type Test = +>Test : Symbol(Test, Decl(substitutionTypeNonGenericIndexType1.ts, 6, 36)) + + Config extends {} +>Config : Symbol(Config, Decl(substitutionTypeNonGenericIndexType1.ts, 4, 10)) + + ? { + rejectClose: BasicConditional; +>rejectClose : Symbol(rejectClose, Decl(substitutionTypeNonGenericIndexType1.ts, 9, 7)) +>BasicConditional : Symbol(BasicConditional, Decl(substitutionTypeNonGenericIndexType1.ts, 0, 0)) +>Config : Symbol(Config, Decl(substitutionTypeNonGenericIndexType1.ts, 4, 10)) + } + : never; + +type RejectClose = Test["rejectClose"]; +>RejectClose : Symbol(RejectClose, Decl(substitutionTypeNonGenericIndexType1.ts, 12, 12)) +>Test : Symbol(Test, Decl(substitutionTypeNonGenericIndexType1.ts, 6, 36)) + diff --git a/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType1.types b/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType1.types new file mode 100644 index 0000000000000..0fdac10b02588 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType1.types @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/substitutionTypeNonGenericIndexType1.ts] //// + +=== substitutionTypeNonGenericIndexType1.ts === +// https://github.com/microsoft/TypeScript/issues/61728 + +type BasicConditional = keyof T extends any +>BasicConditional : BasicConditional + + ? true +>true : true + + : false; +>false : false + +type Config = { rejectClose: true }; +>Config : Config +>rejectClose : true +>true : true + +type Test = +>Test : { rejectClose: BasicConditional; } + + Config extends {} + ? { + rejectClose: BasicConditional; +>rejectClose : true + } + : never; + +type RejectClose = Test["rejectClose"]; +>RejectClose : true + diff --git a/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType2.symbols b/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType2.symbols new file mode 100644 index 0000000000000..b1801fd76cf52 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType2.symbols @@ -0,0 +1,30 @@ +//// [tests/cases/compiler/substitutionTypeNonGenericIndexType2.ts] //// + +=== substitutionTypeNonGenericIndexType2.ts === +type BasicConditional = keyof T extends infer R ? R : never; +>BasicConditional : Symbol(BasicConditional, Decl(substitutionTypeNonGenericIndexType2.ts, 0, 0)) +>T : Symbol(T, Decl(substitutionTypeNonGenericIndexType2.ts, 0, 22)) +>T : Symbol(T, Decl(substitutionTypeNonGenericIndexType2.ts, 0, 22)) +>R : Symbol(R, Decl(substitutionTypeNonGenericIndexType2.ts, 0, 48)) +>R : Symbol(R, Decl(substitutionTypeNonGenericIndexType2.ts, 0, 48)) + +type Config = { rejectClose: true }; +>Config : Symbol(Config, Decl(substitutionTypeNonGenericIndexType2.ts, 0, 63)) +>rejectClose : Symbol(rejectClose, Decl(substitutionTypeNonGenericIndexType2.ts, 2, 15)) + +type Test = Config extends {} +>Test : Symbol(Test, Decl(substitutionTypeNonGenericIndexType2.ts, 2, 36)) +>Config : Symbol(Config, Decl(substitutionTypeNonGenericIndexType2.ts, 0, 63)) + + ? { + rejectClose: BasicConditional; +>rejectClose : Symbol(rejectClose, Decl(substitutionTypeNonGenericIndexType2.ts, 5, 5)) +>BasicConditional : Symbol(BasicConditional, Decl(substitutionTypeNonGenericIndexType2.ts, 0, 0)) +>Config : Symbol(Config, Decl(substitutionTypeNonGenericIndexType2.ts, 0, 63)) + } + : never; + +const test: Test["rejectClose"] = "rejectClose"; +>test : Symbol(test, Decl(substitutionTypeNonGenericIndexType2.ts, 10, 5)) +>Test : Symbol(Test, Decl(substitutionTypeNonGenericIndexType2.ts, 2, 36)) + diff --git a/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType2.types b/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType2.types new file mode 100644 index 0000000000000..01591d4aa310b --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/substitutionTypeNonGenericIndexType2.types @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/substitutionTypeNonGenericIndexType2.ts] //// + +=== substitutionTypeNonGenericIndexType2.ts === +type BasicConditional = keyof T extends infer R ? R : never; +>BasicConditional : BasicConditional + +type Config = { rejectClose: true }; +>Config : Config +>rejectClose : true +>true : true + +type Test = Config extends {} +>Test : { rejectClose: BasicConditional; } + + ? { + rejectClose: BasicConditional; +>rejectClose : "rejectClose" + } + : never; + +const test: Test["rejectClose"] = "rejectClose"; +>test : "rejectClose" +>"rejectClose" : "rejectClose" + diff --git a/tsc/testdata/tests/cases/compiler/substitutionTypeNonGenericIndexType1.ts b/tsc/testdata/tests/cases/compiler/substitutionTypeNonGenericIndexType1.ts new file mode 100644 index 0000000000000..bd9a75f23f103 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/substitutionTypeNonGenericIndexType1.ts @@ -0,0 +1,18 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/61728 + +type BasicConditional = keyof T extends any + ? true + : false; + +type Config = { rejectClose: true }; +type Test = + Config extends {} + ? { + rejectClose: BasicConditional; + } + : never; + +type RejectClose = Test["rejectClose"]; diff --git a/tsc/testdata/tests/cases/compiler/substitutionTypeNonGenericIndexType2.ts b/tsc/testdata/tests/cases/compiler/substitutionTypeNonGenericIndexType2.ts new file mode 100644 index 0000000000000..fdc159e353c3f --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/substitutionTypeNonGenericIndexType2.ts @@ -0,0 +1,14 @@ +// @strict: true +// @noEmit: true + +type BasicConditional = keyof T extends infer R ? R : never; + +type Config = { rejectClose: true }; + +type Test = Config extends {} + ? { + rejectClose: BasicConditional; + } + : never; + +const test: Test["rejectClose"] = "rejectClose"; From 21f7d306757cc5aba7424c96661a9678df60c132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 20 Aug 2026 15:04:07 +0200 Subject: [PATCH 2/2] Resolve substitution index types through their intersection --- tsc/internal/checker/checker.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 2574f2e15cccd..9842f4f713e0d 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -26810,8 +26810,7 @@ func (c *Checker) getIndexTypeEx(t *Type, indexFlags IndexFlags) *Type { case c.isNoInferType(t): return c.getNoInferType(c.getIndexTypeEx(t.AsSubstitutionType().baseType, indexFlags)) case t.flags&TypeFlagsSubstitution != 0 && !c.isGenericType(t): - st := t.AsSubstitutionType() - return c.getUnionType([]*Type{c.getIndexTypeEx(st.baseType, indexFlags), c.getIndexTypeEx(st.constraint, indexFlags)}) + return c.getIndexTypeEx(c.getSubstitutionIntersection(t), indexFlags) case c.shouldDeferIndexType(t, indexFlags): return c.getIndexTypeForGenericType(t, indexFlags) case t.flags&TypeFlagsUnion != 0: