diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 51eeb0fdb48f4..ec4866f0abd11 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9193,6 +9193,7 @@ namespace ts { const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) || getIndexInfoOfType(objectType, IndexKind.String) || undefined; + if (indexInfo) { if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { const indexNode = accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.indexType; @@ -13600,7 +13601,7 @@ namespace ts { return !!constraint && maybeTypeOfKind(constraint, TypeFlags.Primitive | TypeFlags.Index); } - function isObjectLiteralType(type: Type) { + function isObjectLiteralType(type: Type): boolean { return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral); } @@ -18378,6 +18379,32 @@ namespace ts { return errorType; } + const expression = walkUpParenthesizedExpressions(node.expression); + if (isObjectLiteralExpression(expression) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike) && !getIndexInfoOfType(objectType, IndexKind.Number | IndexKind.String)) { + const members = (objectType).members; + let hasNotMatch = false; + const matchedIndex = filterType(indexType, type => { + if (!(type.flags & TypeFlags.StringOrNumberLiteral && members.has((type).value as __String))) { + hasNotMatch = true; + return false; + } + return true; + }); + + // hasNotMatch and matchedIndex not never - partial match + // not hasNotMatch and matchedIndex not never - all match + if (matchedIndex !== neverType) { + return getUnionType(append([mapType(matchedIndex, type => getTypeOfSymbol(members.get((type).value as __String)!))], + hasNotMatch ? undefinedType : undefined + )); + } + // hasNotMatch and matchedIndex is never - not bounded + else if (hasNotMatch) { + return getUnionType(append(arrayFrom(members.values(), getTypeOfSymbol), undefinedType)); + } + // not hasNotMatch and matchedIndex is never - impossible, fallback + } + return checkIndexedAccessIndexType(getIndexedAccessType(objectType, isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType, node), node); } diff --git a/tests/baselines/reference/augmentedTypeBracketAccessIndexSignature.types b/tests/baselines/reference/augmentedTypeBracketAccessIndexSignature.types index 62e04728b390d..db4a3083ee9e2 100644 --- a/tests/baselines/reference/augmentedTypeBracketAccessIndexSignature.types +++ b/tests/baselines/reference/augmentedTypeBracketAccessIndexSignature.types @@ -16,8 +16,8 @@ interface Function { } var a = {}[0]; // Should be Foo ->a : error ->{}[0] : error +>a : undefined +>{}[0] : undefined >{} : {} >0 : 0 diff --git a/tests/baselines/reference/indexedAccessWithFreshObjectLiteral.js b/tests/baselines/reference/indexedAccessWithFreshObjectLiteral.js new file mode 100644 index 0000000000000..52c5fef4311b6 --- /dev/null +++ b/tests/baselines/reference/indexedAccessWithFreshObjectLiteral.js @@ -0,0 +1,60 @@ +//// [indexedAccessWithFreshObjectLiteral.ts] +function foo (id: string) { + return { + a: 1, + b: "", + c: true + }[id] +} + +function bar (id: 'a' | 'b') { + return { + a: 1, + b: "", + c: false + }[id] +} + +function baz(id: string) { + return ({ + a: 123, + b: "" + } as Record)[id] +} + +function bazz(id: string) { + return ({ + a: 123, + b: "" + } as { [k: string]: string | number})[id] +} + + +//// [indexedAccessWithFreshObjectLiteral.js] +"use strict"; +function foo(id) { + return { + a: 1, + b: "", + c: true + }[id]; +} +function bar(id) { + return { + a: 1, + b: "", + c: false + }[id]; +} +function baz(id) { + return { + a: 123, + b: "" + }[id]; +} +function bazz(id) { + return { + a: 123, + b: "" + }[id]; +} diff --git a/tests/baselines/reference/indexedAccessWithFreshObjectLiteral.symbols b/tests/baselines/reference/indexedAccessWithFreshObjectLiteral.symbols new file mode 100644 index 0000000000000..cfe78e70ef1e7 --- /dev/null +++ b/tests/baselines/reference/indexedAccessWithFreshObjectLiteral.symbols @@ -0,0 +1,69 @@ +=== tests/cases/compiler/indexedAccessWithFreshObjectLiteral.ts === +function foo (id: string) { +>foo : Symbol(foo, Decl(indexedAccessWithFreshObjectLiteral.ts, 0, 0)) +>id : Symbol(id, Decl(indexedAccessWithFreshObjectLiteral.ts, 0, 14)) + + return { + a: 1, +>a : Symbol(a, Decl(indexedAccessWithFreshObjectLiteral.ts, 1, 12)) + + b: "", +>b : Symbol(b, Decl(indexedAccessWithFreshObjectLiteral.ts, 2, 13)) + + c: true +>c : Symbol(c, Decl(indexedAccessWithFreshObjectLiteral.ts, 3, 14)) + + }[id] +>id : Symbol(id, Decl(indexedAccessWithFreshObjectLiteral.ts, 0, 14)) +} + +function bar (id: 'a' | 'b') { +>bar : Symbol(bar, Decl(indexedAccessWithFreshObjectLiteral.ts, 6, 1)) +>id : Symbol(id, Decl(indexedAccessWithFreshObjectLiteral.ts, 8, 14)) + + return { + a: 1, +>a : Symbol(a, Decl(indexedAccessWithFreshObjectLiteral.ts, 9, 12)) + + b: "", +>b : Symbol(b, Decl(indexedAccessWithFreshObjectLiteral.ts, 10, 13)) + + c: false +>c : Symbol(c, Decl(indexedAccessWithFreshObjectLiteral.ts, 11, 14)) + + }[id] +>id : Symbol(id, Decl(indexedAccessWithFreshObjectLiteral.ts, 8, 14)) +} + +function baz(id: string) { +>baz : Symbol(baz, Decl(indexedAccessWithFreshObjectLiteral.ts, 14, 1)) +>id : Symbol(id, Decl(indexedAccessWithFreshObjectLiteral.ts, 16, 13)) + + return ({ + a: 123, +>a : Symbol(a, Decl(indexedAccessWithFreshObjectLiteral.ts, 17, 13)) + + b: "" +>b : Symbol(b, Decl(indexedAccessWithFreshObjectLiteral.ts, 18, 15)) + + } as Record)[id] +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>id : Symbol(id, Decl(indexedAccessWithFreshObjectLiteral.ts, 16, 13)) +} + +function bazz(id: string) { +>bazz : Symbol(bazz, Decl(indexedAccessWithFreshObjectLiteral.ts, 21, 1)) +>id : Symbol(id, Decl(indexedAccessWithFreshObjectLiteral.ts, 23, 14)) + + return ({ + a: 123, +>a : Symbol(a, Decl(indexedAccessWithFreshObjectLiteral.ts, 24, 13)) + + b: "" +>b : Symbol(b, Decl(indexedAccessWithFreshObjectLiteral.ts, 25, 15)) + + } as { [k: string]: string | number})[id] +>k : Symbol(k, Decl(indexedAccessWithFreshObjectLiteral.ts, 27, 12)) +>id : Symbol(id, Decl(indexedAccessWithFreshObjectLiteral.ts, 23, 14)) +} + diff --git a/tests/baselines/reference/indexedAccessWithFreshObjectLiteral.types b/tests/baselines/reference/indexedAccessWithFreshObjectLiteral.types new file mode 100644 index 0000000000000..67de4f4785bd9 --- /dev/null +++ b/tests/baselines/reference/indexedAccessWithFreshObjectLiteral.types @@ -0,0 +1,94 @@ +=== tests/cases/compiler/indexedAccessWithFreshObjectLiteral.ts === +function foo (id: string) { +>foo : (id: string) => string | number | boolean | undefined +>id : string + + return { +>{ a: 1, b: "", c: true }[id] : string | number | boolean | undefined +>{ a: 1, b: "", c: true } : { a: number; b: string; c: boolean; } + + a: 1, +>a : number +>1 : 1 + + b: "", +>b : string +>"" : "" + + c: true +>c : boolean +>true : true + + }[id] +>id : string +} + +function bar (id: 'a' | 'b') { +>bar : (id: "a" | "b") => string | number +>id : "a" | "b" + + return { +>{ a: 1, b: "", c: false }[id] : string | number +>{ a: 1, b: "", c: false } : { a: number; b: string; c: boolean; } + + a: 1, +>a : number +>1 : 1 + + b: "", +>b : string +>"" : "" + + c: false +>c : boolean +>false : false + + }[id] +>id : "a" | "b" +} + +function baz(id: string) { +>baz : (id: string) => string | number +>id : string + + return ({ +>({ a: 123, b: "" } as Record)[id] : string | number +>({ a: 123, b: "" } as Record) : Record +>{ a: 123, b: "" } as Record : Record +>{ a: 123, b: "" } : { a: number; b: string; } + + a: 123, +>a : number +>123 : 123 + + b: "" +>b : string +>"" : "" + + } as Record)[id] +>id : string +} + +function bazz(id: string) { +>bazz : (id: string) => string | number +>id : string + + return ({ +>({ a: 123, b: "" } as { [k: string]: string | number})[id] : string | number +>({ a: 123, b: "" } as { [k: string]: string | number}) : { [k: string]: string | number; } +>{ a: 123, b: "" } as { [k: string]: string | number} : { [k: string]: string | number; } +>{ a: 123, b: "" } : { a: number; b: string; } + + a: 123, +>a : number +>123 : 123 + + b: "" +>b : string +>"" : "" + + } as { [k: string]: string | number})[id] +>k : string +>id : string +} + diff --git a/tests/baselines/reference/noImplicitAnyIndexing.errors.txt b/tests/baselines/reference/noImplicitAnyIndexing.errors.txt index 8d1d3ea6c467d..0536b6b588240 100644 --- a/tests/baselines/reference/noImplicitAnyIndexing.errors.txt +++ b/tests/baselines/reference/noImplicitAnyIndexing.errors.txt @@ -1,10 +1,8 @@ tests/cases/compiler/noImplicitAnyIndexing.ts(12,37): error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'. -tests/cases/compiler/noImplicitAnyIndexing.ts(19,9): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. -tests/cases/compiler/noImplicitAnyIndexing.ts(22,9): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. tests/cases/compiler/noImplicitAnyIndexing.ts(30,10): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. -==== tests/cases/compiler/noImplicitAnyIndexing.ts (4 errors) ==== +==== tests/cases/compiler/noImplicitAnyIndexing.ts (2 errors) ==== enum MyEmusEnum { emu } @@ -26,13 +24,9 @@ tests/cases/compiler/noImplicitAnyIndexing.ts(30,10): error TS7017: Element impl // Should report an implicit 'any'. var x = {}["hi"]; - ~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. // Should report an implicit 'any'. var y = {}[10]; - ~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. var hi: any = "hi"; diff --git a/tests/baselines/reference/noImplicitAnyIndexing.types b/tests/baselines/reference/noImplicitAnyIndexing.types index 702c763c7a822..9958379dbce88 100644 --- a/tests/baselines/reference/noImplicitAnyIndexing.types +++ b/tests/baselines/reference/noImplicitAnyIndexing.types @@ -39,15 +39,15 @@ var strRepresentation4 = MyEmusEnum["emu"]; // Should report an implicit 'any'. var x = {}["hi"]; ->x : any ->{}["hi"] : any +>x : undefined +>{}["hi"] : undefined >{} : {} >"hi" : "hi" // Should report an implicit 'any'. var y = {}[10]; ->y : any ->{}[10] : any +>y : undefined +>{}[10] : undefined >{} : {} >10 : 10 diff --git a/tests/baselines/reference/noImplicitAnyIndexingSuppressed.types b/tests/baselines/reference/noImplicitAnyIndexingSuppressed.types index fe34cdbc130b9..e27bb7b85485b 100644 --- a/tests/baselines/reference/noImplicitAnyIndexingSuppressed.types +++ b/tests/baselines/reference/noImplicitAnyIndexingSuppressed.types @@ -39,15 +39,15 @@ var strRepresentation4 = MyEmusEnum["emu"]; // Should be okay, as we suppress implicit 'any' property access checks var x = {}["hi"]; ->x : error ->{}["hi"] : error +>x : undefined +>{}["hi"] : undefined >{} : {} >"hi" : "hi" // Should be okay, as we suppress implicit 'any' property access checks var y = {}[10]; ->y : error ->{}[10] : error +>y : undefined +>{}[10] : undefined >{} : {} >10 : 10 diff --git a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt deleted file mode 100644 index b3f3b4bc6e050..0000000000000 --- a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt +++ /dev/null @@ -1,8 +0,0 @@ -tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(1,9): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. - - -==== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts (1 errors) ==== - var x = {}["hello"]; - ~~~~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. - var y: string = { '': 'foo' }['']; \ No newline at end of file diff --git a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types index a1b78eaca57da..17822e74f3253 100644 --- a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types +++ b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types @@ -1,7 +1,7 @@ === tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts === var x = {}["hello"]; ->x : any ->{}["hello"] : any +>x : undefined +>{}["hello"] : undefined >{} : {} >"hello" : "hello" diff --git a/tests/cases/compiler/indexedAccessWithFreshObjectLiteral.ts b/tests/cases/compiler/indexedAccessWithFreshObjectLiteral.ts new file mode 100644 index 0000000000000..14f194ec402b2 --- /dev/null +++ b/tests/cases/compiler/indexedAccessWithFreshObjectLiteral.ts @@ -0,0 +1,31 @@ +// @strict: true + +function foo (id: string) { + return { + a: 1, + b: "", + c: true + }[id] +} + +function bar (id: 'a' | 'b') { + return { + a: 1, + b: "", + c: false + }[id] +} + +function baz(id: string) { + return ({ + a: 123, + b: "" + } as Record)[id] +} + +function bazz(id: string) { + return ({ + a: 123, + b: "" + } as { [k: string]: string | number})[id] +}