Skip to content

Commit 0b9ef8d

Browse files
committed
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.
1 parent 2dfdbba commit 0b9ef8d

File tree

5 files changed

+114
-1
lines changed

5 files changed

+114
-1
lines changed

src/compiler/checker.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19317,7 +19317,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1931719317
const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression);
1931819318
errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType));
1931919319
}
19320-
else if (indexType.flags & TypeFlags.StringLiteral) {
19320+
19321+
// Handle literals, type parameters (like K extends string), and keyof types
19322+
else if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.TypeParameter | TypeFlags.Index)) {
19323+
19324+
// 1. Try specific property (only for literals)
19325+
if (indexType.flags & TypeFlags.StringLiteral) {
19326+
const prop = getPropertyOfType(objectType, escapeLeadingUnderscores((indexType as StringLiteralType).value));
19327+
if (prop) return getTypeOfSymbol(prop);
19328+
}
19329+
19330+
// 2. Fallback to string index signature for anything string-like
19331+
if (isTypeAssignableTo(indexType, stringType)) {
19332+
const indexInfo = getIndexInfoOfType(objectType, stringType);
19333+
if (indexInfo) return indexInfo.type;
19334+
}
19335+
1932119336
errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType));
1932219337
}
1932319338
else if (indexType.flags & TypeFlags.NumberLiteral) {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [tests/cases/compiler/stringLiteralIndexingWithIndexSignature.ts] ////
2+
3+
//// [stringLiteralIndexingWithIndexSignature.ts]
4+
interface Dic<T> {
5+
[key: string]: T;
6+
}
7+
8+
function getVal<T, K extends string>(obj: Dic<T>, key: K) {
9+
// This is the classic failure point for #30408
10+
// It should resolve to 'T', but often resolves to 'any' or errors
11+
return obj[key];
12+
}
13+
14+
type Specific = Dic<number>["some_literal"]; // Should be 'number'
15+
16+
//// [stringLiteralIndexingWithIndexSignature.js]
17+
function getVal(obj, key) {
18+
// This is the classic failure point for #30408
19+
// It should resolve to 'T', but often resolves to 'any' or errors
20+
return obj[key];
21+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [tests/cases/compiler/stringLiteralIndexingWithIndexSignature.ts] ////
2+
3+
=== stringLiteralIndexingWithIndexSignature.ts ===
4+
interface Dic<T> {
5+
>Dic : Symbol(Dic, Decl(stringLiteralIndexingWithIndexSignature.ts, 0, 0))
6+
>T : Symbol(T, Decl(stringLiteralIndexingWithIndexSignature.ts, 0, 14))
7+
8+
[key: string]: T;
9+
>key : Symbol(key, Decl(stringLiteralIndexingWithIndexSignature.ts, 1, 5))
10+
>T : Symbol(T, Decl(stringLiteralIndexingWithIndexSignature.ts, 0, 14))
11+
}
12+
13+
function getVal<T, K extends string>(obj: Dic<T>, key: K) {
14+
>getVal : Symbol(getVal, Decl(stringLiteralIndexingWithIndexSignature.ts, 2, 1))
15+
>T : Symbol(T, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 16))
16+
>K : Symbol(K, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 18))
17+
>obj : Symbol(obj, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 37))
18+
>Dic : Symbol(Dic, Decl(stringLiteralIndexingWithIndexSignature.ts, 0, 0))
19+
>T : Symbol(T, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 16))
20+
>key : Symbol(key, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 49))
21+
>K : Symbol(K, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 18))
22+
23+
// This is the classic failure point for #30408
24+
// It should resolve to 'T', but often resolves to 'any' or errors
25+
return obj[key];
26+
>obj : Symbol(obj, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 37))
27+
>key : Symbol(key, Decl(stringLiteralIndexingWithIndexSignature.ts, 4, 49))
28+
}
29+
30+
type Specific = Dic<number>["some_literal"]; // Should be 'number'
31+
>Specific : Symbol(Specific, Decl(stringLiteralIndexingWithIndexSignature.ts, 8, 1))
32+
>Dic : Symbol(Dic, Decl(stringLiteralIndexingWithIndexSignature.ts, 0, 0))
33+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [tests/cases/compiler/stringLiteralIndexingWithIndexSignature.ts] ////
2+
3+
=== stringLiteralIndexingWithIndexSignature.ts ===
4+
interface Dic<T> {
5+
[key: string]: T;
6+
>key : string
7+
> : ^^^^^^
8+
}
9+
10+
function getVal<T, K extends string>(obj: Dic<T>, key: K) {
11+
>getVal : <T, K extends string>(obj: Dic<T>, key: K) => T
12+
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^^
13+
>obj : Dic<T>
14+
> : ^^^^^^
15+
>key : K
16+
> : ^
17+
18+
// This is the classic failure point for #30408
19+
// It should resolve to 'T', but often resolves to 'any' or errors
20+
return obj[key];
21+
>obj[key] : T
22+
> : ^
23+
>obj : Dic<T>
24+
> : ^^^^^^
25+
>key : K
26+
> : ^
27+
}
28+
29+
type Specific = Dic<number>["some_literal"]; // Should be 'number'
30+
>Specific : number
31+
> : ^^^^^^
32+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @noImplicitAny: true
2+
interface Dic<T> {
3+
[key: string]: T;
4+
}
5+
6+
function getVal<T, K extends string>(obj: Dic<T>, key: K) {
7+
// This is the classic failure point for #30408
8+
// It should resolve to 'T', but often resolves to 'any' or errors
9+
return obj[key];
10+
}
11+
12+
type Specific = Dic<number>["some_literal"]; // Should be 'number'

0 commit comments

Comments
 (0)