Skip to content

add index signature for anonymous object literal type #26710

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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 = (<ResolvedType>objectType).members;
let hasNotMatch = false;
const matchedIndex = filterType(indexType, type => {
if (!(type.flags & TypeFlags.StringOrNumberLiteral && members.has((<LiteralType>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((<LiteralType>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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ interface Function {
}

var a = {}[0]; // Should be Foo
>a : error
>{}[0] : error
>a : undefined
>{}[0] : undefined
>{} : {}
>0 : 0

Expand Down
60 changes: 60 additions & 0 deletions tests/baselines/reference/indexedAccessWithFreshObjectLiteral.js
Original file line number Diff line number Diff line change
@@ -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<string, number | string>)[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];
}
Original file line number Diff line number Diff line change
@@ -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<string, number | string>)[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))
}

Original file line number Diff line number Diff line change
@@ -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<string, number | string>)[id] : string | number
>({ a: 123, b: "" } as Record<string, number | string>) : Record<string, string | number>
>{ a: 123, b: "" } as Record<string, number | string> : Record<string, string | number>
>{ a: 123, b: "" } : { a: number; b: string; }

a: 123,
>a : number
>123 : 123

b: ""
>b : string
>"" : ""

} as Record<string, number | string>)[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
}

8 changes: 1 addition & 7 deletions tests/baselines/reference/noImplicitAnyIndexing.errors.txt
Original file line number Diff line number Diff line change
@@ -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
}
Expand All @@ -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";
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/noImplicitAnyIndexing.types
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
=== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts ===
var x = {}["hello"];
>x : any
>{}["hello"] : any
>x : undefined
>{}["hello"] : undefined
>{} : {}
>"hello" : "hello"

Expand Down
Loading