From 0b9ef8d41bd9a05b355ad89f135f1ef22de33c42 Mon Sep 17 00:00:00 2001 From: Michael Hughes Date: Tue, 23 Dec 2025 21:11:32 -0700 Subject: [PATCH 1/2] Fix #30408: Fallback to string index signature for literal and generic indexing Ensure that string literal indexing and type parameters constrained to string correctly fall back to an available string index signature if a specific property is not found. This prevents confusing 'any' or 'missing property' errors when a valid indexer exists. --- src/compiler/checker.ts | 17 +++++++++- ...stringLiteralIndexingWithIndexSignature.js | 21 ++++++++++++ ...gLiteralIndexingWithIndexSignature.symbols | 33 +++++++++++++++++++ ...ingLiteralIndexingWithIndexSignature.types | 32 ++++++++++++++++++ ...stringLiteralIndexingWithIndexSignature.ts | 12 +++++++ 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/stringLiteralIndexingWithIndexSignature.js create mode 100644 tests/baselines/reference/stringLiteralIndexingWithIndexSignature.symbols create mode 100644 tests/baselines/reference/stringLiteralIndexingWithIndexSignature.types create mode 100644 tests/cases/compiler/stringLiteralIndexingWithIndexSignature.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 51c9493b61721..49730ffb999d7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19317,7 +19317,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression); errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType)); } - else if (indexType.flags & TypeFlags.StringLiteral) { + + // Handle literals, type parameters (like K extends string), and keyof types + else if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.TypeParameter | TypeFlags.Index)) { + + // 1. Try specific property (only for literals) + if (indexType.flags & TypeFlags.StringLiteral) { + const prop = getPropertyOfType(objectType, escapeLeadingUnderscores((indexType as StringLiteralType).value)); + if (prop) return getTypeOfSymbol(prop); + } + + // 2. Fallback to string index signature for anything string-like + if (isTypeAssignableTo(indexType, stringType)) { + const indexInfo = getIndexInfoOfType(objectType, stringType); + if (indexInfo) return indexInfo.type; + } + errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType)); } else if (indexType.flags & TypeFlags.NumberLiteral) { diff --git a/tests/baselines/reference/stringLiteralIndexingWithIndexSignature.js b/tests/baselines/reference/stringLiteralIndexingWithIndexSignature.js new file mode 100644 index 0000000000000..ef63bad5df276 --- /dev/null +++ b/tests/baselines/reference/stringLiteralIndexingWithIndexSignature.js @@ -0,0 +1,21 @@ +//// [tests/cases/compiler/stringLiteralIndexingWithIndexSignature.ts] //// + +//// [stringLiteralIndexingWithIndexSignature.ts] +interface Dic { + [key: string]: T; +} + +function getVal(obj: Dic, key: K) { + // This is the classic failure point for #30408 + // It should resolve to 'T', but often resolves to 'any' or errors + return obj[key]; +} + +type Specific = Dic["some_literal"]; // Should be 'number' + +//// [stringLiteralIndexingWithIndexSignature.js] +function getVal(obj, key) { + // This is the classic failure point for #30408 + // It should resolve to 'T', but often resolves to 'any' or errors + return obj[key]; +} diff --git a/tests/baselines/reference/stringLiteralIndexingWithIndexSignature.symbols b/tests/baselines/reference/stringLiteralIndexingWithIndexSignature.symbols new file mode 100644 index 0000000000000..2e7eda8120579 --- /dev/null +++ b/tests/baselines/reference/stringLiteralIndexingWithIndexSignature.symbols @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/stringLiteralIndexingWithIndexSignature.ts] //// + +=== stringLiteralIndexingWithIndexSignature.ts === +interface Dic { +>Dic : Symbol(Dic, Decl(stringLiteralIndexingWithIndexSignature.ts, 0, 0)) +>T : Symbol(T, Decl(stringLiteralIndexingWithIndexSignature.ts, 0, 14)) + + [key: string]: T; +>key : Symbol(key, Decl(stringLiteralIndexingWithIndexSignature.ts, 1, 5)) +>T : Symbol(T, Decl(stringLiteralIndexingWithIndexSignature.ts, 0, 14)) +} + +function getVal(obj: Dic, key: K) { +>getVal : Symbol(getVal, Decl(stringLiteralIndexingWithIndexSignature.ts, 2, 1)) +>T : Symbol(T, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 16)) +>K : Symbol(K, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 18)) +>obj : Symbol(obj, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 37)) +>Dic : Symbol(Dic, Decl(stringLiteralIndexingWithIndexSignature.ts, 0, 0)) +>T : Symbol(T, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 16)) +>key : Symbol(key, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 49)) +>K : Symbol(K, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 18)) + + // This is the classic failure point for #30408 + // It should resolve to 'T', but often resolves to 'any' or errors + return obj[key]; +>obj : Symbol(obj, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 37)) +>key : Symbol(key, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 49)) +} + +type Specific = Dic["some_literal"]; // Should be 'number' +>Specific : Symbol(Specific, Decl(stringLiteralIndexingWithIndexSignature.ts, 8, 1)) +>Dic : Symbol(Dic, Decl(stringLiteralIndexingWithIndexSignature.ts, 0, 0)) + diff --git a/tests/baselines/reference/stringLiteralIndexingWithIndexSignature.types b/tests/baselines/reference/stringLiteralIndexingWithIndexSignature.types new file mode 100644 index 0000000000000..5a0684f4919b2 --- /dev/null +++ b/tests/baselines/reference/stringLiteralIndexingWithIndexSignature.types @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/stringLiteralIndexingWithIndexSignature.ts] //// + +=== stringLiteralIndexingWithIndexSignature.ts === +interface Dic { + [key: string]: T; +>key : string +> : ^^^^^^ +} + +function getVal(obj: Dic, key: K) { +>getVal : (obj: Dic, key: K) => T +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^^ +>obj : Dic +> : ^^^^^^ +>key : K +> : ^ + + // This is the classic failure point for #30408 + // It should resolve to 'T', but often resolves to 'any' or errors + return obj[key]; +>obj[key] : T +> : ^ +>obj : Dic +> : ^^^^^^ +>key : K +> : ^ +} + +type Specific = Dic["some_literal"]; // Should be 'number' +>Specific : number +> : ^^^^^^ + diff --git a/tests/cases/compiler/stringLiteralIndexingWithIndexSignature.ts b/tests/cases/compiler/stringLiteralIndexingWithIndexSignature.ts new file mode 100644 index 0000000000000..f588e506559a6 --- /dev/null +++ b/tests/cases/compiler/stringLiteralIndexingWithIndexSignature.ts @@ -0,0 +1,12 @@ +// @noImplicitAny: true +interface Dic { + [key: string]: T; +} + +function getVal(obj: Dic, key: K) { + // This is the classic failure point for #30408 + // It should resolve to 'T', but often resolves to 'any' or errors + return obj[key]; +} + +type Specific = Dic["some_literal"]; // Should be 'number' \ No newline at end of file From f83711c5a06326229b63456fe720fe7b176e945c Mon Sep 17 00:00:00 2001 From: Michael Hughes Date: Wed, 24 Dec 2025 11:38:18 -0700 Subject: [PATCH 2/2] chore: format code to pass CI --- src/compiler/checker.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 49730ffb999d7..6bb09143260e8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19317,10 +19317,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression); errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType)); } - // Handle literals, type parameters (like K extends string), and keyof types else if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.TypeParameter | TypeFlags.Index)) { - // 1. Try specific property (only for literals) if (indexType.flags & TypeFlags.StringLiteral) { const prop = getPropertyOfType(objectType, escapeLeadingUnderscores((indexType as StringLiteralType).value));