Skip to content

Commit 56a136a

Browse files
committed
Only include unique symbols when getting index types for access checks (microsoft#23145)
* Only include unique symbols when getting index types for access checks * Filter all nonstrings * Inline ternary
1 parent 16687e6 commit 56a136a

7 files changed

+322
-13
lines changed

src/compiler/checker.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8061,12 +8061,16 @@ namespace ts {
80618061
return links.resolvedType;
80628062
}
80638063

8064-
function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType) {
8065-
if (!type.resolvedIndexType) {
8066-
type.resolvedIndexType = <IndexType>createType(TypeFlags.Index);
8067-
type.resolvedIndexType.type = type;
8064+
function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, includeDeclaredTypes?: boolean) {
8065+
const cacheLocation = includeDeclaredTypes ? "resolvedDeclaredIndexType" : "resolvedIndexType";
8066+
if (!type[cacheLocation]) {
8067+
type[cacheLocation] = <IndexType>createType(TypeFlags.Index);
8068+
type[cacheLocation].type = type;
8069+
if (includeDeclaredTypes) {
8070+
type[cacheLocation].isDeclaredType = true;
8071+
}
80688072
}
8069-
return type.resolvedIndexType;
8073+
return type[cacheLocation];
80708074
}
80718075

80728076
function getLiteralTypeFromPropertyName(prop: Symbol) {
@@ -8084,17 +8088,22 @@ namespace ts {
80848088
return links.nameType;
80858089
}
80868090

8087-
function getLiteralTypeFromPropertyNames(type: Type) {
8088-
return getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName));
8091+
function isTypeString(type: Type) {
8092+
return isTypeAssignableToKind(type, TypeFlags.StringLike);
8093+
}
8094+
8095+
function getLiteralTypeFromPropertyNames(type: Type, includeDeclaredTypes?: boolean) {
8096+
const originalKeys = map(getPropertiesOfType(type), getLiteralTypeFromPropertyName);
8097+
return getUnionType(includeDeclaredTypes ? originalKeys : filter(originalKeys, isTypeString));
80898098
}
80908099

8091-
function getIndexType(type: Type): Type {
8092-
return type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t))) :
8093-
maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type) :
8100+
function getIndexType(type: Type, includeDeclaredTypes?: boolean): Type {
8101+
return type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t, includeDeclaredTypes))) :
8102+
maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, includeDeclaredTypes) :
80948103
getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
80958104
type === wildcardType ? wildcardType :
80968105
type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringType :
8097-
getLiteralTypeFromPropertyNames(type);
8106+
getLiteralTypeFromPropertyNames(type, includeDeclaredTypes);
80988107
}
80998108

81008109
function getIndexTypeOrString(type: Type): Type {
@@ -10191,7 +10200,7 @@ namespace ts {
1019110200
// constraint of T.
1019210201
const constraint = getConstraintForRelation((<IndexType>target).type);
1019310202
if (constraint) {
10194-
if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) {
10203+
if (result = isRelatedTo(source, getIndexType(constraint, (target as IndexType).isDeclaredType), reportErrors)) {
1019510204
return result;
1019610205
}
1019710206
}
@@ -20632,7 +20641,7 @@ namespace ts {
2063220641
// Check if the index type is assignable to 'keyof T' for the object type.
2063320642
const objectType = (<IndexedAccessType>type).objectType;
2063420643
const indexType = (<IndexedAccessType>type).indexType;
20635-
if (isTypeAssignableTo(indexType, getIndexType(objectType))) {
20644+
if (isTypeAssignableTo(indexType, getIndexType(objectType, /*includeDeclaredTypes*/ true))) {
2063620645
if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) &&
2063720646
getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(<MappedType>objectType) & MappedTypeModifiers.IncludeReadonly) {
2063820647
error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));

src/compiler/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3706,6 +3706,8 @@ namespace ts {
37063706
/* @internal */
37073707
resolvedIndexType: IndexType;
37083708
/* @internal */
3709+
resolvedDeclaredIndexType: IndexType;
3710+
/* @internal */
37093711
resolvedBaseConstraint: Type;
37103712
/* @internal */
37113713
couldContainTypeVariables: boolean;
@@ -3792,6 +3794,8 @@ namespace ts {
37923794
resolvedBaseConstraint?: Type;
37933795
/* @internal */
37943796
resolvedIndexType?: IndexType;
3797+
/* @internal */
3798+
resolvedDeclaredIndexType?: IndexType;
37953799
}
37963800

37973801
// Type parameters (TypeFlags.TypeParameter)
@@ -3823,6 +3827,8 @@ namespace ts {
38233827

38243828
// keyof T types (TypeFlags.Index)
38253829
export interface IndexType extends InstantiableType {
3830+
/* @internal */
3831+
isDeclaredType?: boolean;
38263832
type: InstantiableType | UnionOrIntersectionType;
38273833
}
38283834

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
tests/cases/compiler/keyofDoesntContainSymbols.ts(11,30): error TS2345: Argument of type '""' is not assignable to parameter of type 'number'.
2+
tests/cases/compiler/keyofDoesntContainSymbols.ts(14,23): error TS2345: Argument of type 'unique symbol' is not assignable to parameter of type '"str" | "num"'.
3+
tests/cases/compiler/keyofDoesntContainSymbols.ts(17,23): error TS2345: Argument of type '0' is not assignable to parameter of type '"str" | "num"'.
4+
5+
6+
==== tests/cases/compiler/keyofDoesntContainSymbols.ts (3 errors) ====
7+
const sym = Symbol();
8+
const num = 0;
9+
const obj = { num: 0, str: 's', [num]: num as 0, [sym]: sym };
10+
11+
function set <T extends object, K extends keyof T> (obj: T, key: K, value: T[K]): T[K] {
12+
return obj[key] = value;
13+
}
14+
15+
const val = set(obj, 'str', '');
16+
// string
17+
const valB = set(obj, 'num', '');
18+
~~
19+
!!! error TS2345: Argument of type '""' is not assignable to parameter of type 'number'.
20+
// Expect type error
21+
// Argument of type '""' is not assignable to parameter of type 'number'.
22+
const valC = set(obj, sym, sym);
23+
~~~
24+
!!! error TS2345: Argument of type 'unique symbol' is not assignable to parameter of type '"str" | "num"'.
25+
// Expect type error
26+
// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num"
27+
const valD = set(obj, num, num);
28+
~~~
29+
!!! error TS2345: Argument of type '0' is not assignable to parameter of type '"str" | "num"'.
30+
// Expect type error
31+
// Argument of type '0' is not assignable to parameter of type "str" | "num"
32+
type KeyofObj = keyof typeof obj;
33+
// "str" | "num"
34+
type Values<T> = T[keyof T];
35+
36+
type ValuesOfObj = Values<typeof obj>;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//// [keyofDoesntContainSymbols.ts]
2+
const sym = Symbol();
3+
const num = 0;
4+
const obj = { num: 0, str: 's', [num]: num as 0, [sym]: sym };
5+
6+
function set <T extends object, K extends keyof T> (obj: T, key: K, value: T[K]): T[K] {
7+
return obj[key] = value;
8+
}
9+
10+
const val = set(obj, 'str', '');
11+
// string
12+
const valB = set(obj, 'num', '');
13+
// Expect type error
14+
// Argument of type '""' is not assignable to parameter of type 'number'.
15+
const valC = set(obj, sym, sym);
16+
// Expect type error
17+
// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num"
18+
const valD = set(obj, num, num);
19+
// Expect type error
20+
// Argument of type '0' is not assignable to parameter of type "str" | "num"
21+
type KeyofObj = keyof typeof obj;
22+
// "str" | "num"
23+
type Values<T> = T[keyof T];
24+
25+
type ValuesOfObj = Values<typeof obj>;
26+
27+
//// [keyofDoesntContainSymbols.js]
28+
var sym = Symbol();
29+
var num = 0;
30+
var obj = (_a = { num: 0, str: 's' }, _a[num] = num, _a[sym] = sym, _a);
31+
function set(obj, key, value) {
32+
return obj[key] = value;
33+
}
34+
var val = set(obj, 'str', '');
35+
// string
36+
var valB = set(obj, 'num', '');
37+
// Expect type error
38+
// Argument of type '""' is not assignable to parameter of type 'number'.
39+
var valC = set(obj, sym, sym);
40+
// Expect type error
41+
// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num"
42+
var valD = set(obj, num, num);
43+
var _a;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
=== tests/cases/compiler/keyofDoesntContainSymbols.ts ===
2+
const sym = Symbol();
3+
>sym : Symbol(sym, Decl(keyofDoesntContainSymbols.ts, 0, 5))
4+
>Symbol : Symbol(Symbol, Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --))
5+
6+
const num = 0;
7+
>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 1, 5))
8+
9+
const obj = { num: 0, str: 's', [num]: num as 0, [sym]: sym };
10+
>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5))
11+
>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 2, 13))
12+
>str : Symbol(str, Decl(keyofDoesntContainSymbols.ts, 2, 21))
13+
>[num] : Symbol([num], Decl(keyofDoesntContainSymbols.ts, 2, 31))
14+
>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 1, 5))
15+
>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 1, 5))
16+
>[sym] : Symbol([sym], Decl(keyofDoesntContainSymbols.ts, 2, 48))
17+
>sym : Symbol(sym, Decl(keyofDoesntContainSymbols.ts, 0, 5))
18+
>sym : Symbol(sym, Decl(keyofDoesntContainSymbols.ts, 0, 5))
19+
20+
function set <T extends object, K extends keyof T> (obj: T, key: K, value: T[K]): T[K] {
21+
>set : Symbol(set, Decl(keyofDoesntContainSymbols.ts, 2, 62))
22+
>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 4, 14))
23+
>K : Symbol(K, Decl(keyofDoesntContainSymbols.ts, 4, 31))
24+
>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 4, 14))
25+
>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 4, 52))
26+
>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 4, 14))
27+
>key : Symbol(key, Decl(keyofDoesntContainSymbols.ts, 4, 59))
28+
>K : Symbol(K, Decl(keyofDoesntContainSymbols.ts, 4, 31))
29+
>value : Symbol(value, Decl(keyofDoesntContainSymbols.ts, 4, 67))
30+
>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 4, 14))
31+
>K : Symbol(K, Decl(keyofDoesntContainSymbols.ts, 4, 31))
32+
>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 4, 14))
33+
>K : Symbol(K, Decl(keyofDoesntContainSymbols.ts, 4, 31))
34+
35+
return obj[key] = value;
36+
>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 4, 52))
37+
>key : Symbol(key, Decl(keyofDoesntContainSymbols.ts, 4, 59))
38+
>value : Symbol(value, Decl(keyofDoesntContainSymbols.ts, 4, 67))
39+
}
40+
41+
const val = set(obj, 'str', '');
42+
>val : Symbol(val, Decl(keyofDoesntContainSymbols.ts, 8, 5))
43+
>set : Symbol(set, Decl(keyofDoesntContainSymbols.ts, 2, 62))
44+
>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5))
45+
46+
// string
47+
const valB = set(obj, 'num', '');
48+
>valB : Symbol(valB, Decl(keyofDoesntContainSymbols.ts, 10, 5))
49+
>set : Symbol(set, Decl(keyofDoesntContainSymbols.ts, 2, 62))
50+
>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5))
51+
52+
// Expect type error
53+
// Argument of type '""' is not assignable to parameter of type 'number'.
54+
const valC = set(obj, sym, sym);
55+
>valC : Symbol(valC, Decl(keyofDoesntContainSymbols.ts, 13, 5))
56+
>set : Symbol(set, Decl(keyofDoesntContainSymbols.ts, 2, 62))
57+
>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5))
58+
>sym : Symbol(sym, Decl(keyofDoesntContainSymbols.ts, 0, 5))
59+
>sym : Symbol(sym, Decl(keyofDoesntContainSymbols.ts, 0, 5))
60+
61+
// Expect type error
62+
// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num"
63+
const valD = set(obj, num, num);
64+
>valD : Symbol(valD, Decl(keyofDoesntContainSymbols.ts, 16, 5))
65+
>set : Symbol(set, Decl(keyofDoesntContainSymbols.ts, 2, 62))
66+
>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5))
67+
>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 1, 5))
68+
>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 1, 5))
69+
70+
// Expect type error
71+
// Argument of type '0' is not assignable to parameter of type "str" | "num"
72+
type KeyofObj = keyof typeof obj;
73+
>KeyofObj : Symbol(KeyofObj, Decl(keyofDoesntContainSymbols.ts, 16, 32))
74+
>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5))
75+
76+
// "str" | "num"
77+
type Values<T> = T[keyof T];
78+
>Values : Symbol(Values, Decl(keyofDoesntContainSymbols.ts, 19, 33))
79+
>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 21, 12))
80+
>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 21, 12))
81+
>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 21, 12))
82+
83+
type ValuesOfObj = Values<typeof obj>;
84+
>ValuesOfObj : Symbol(ValuesOfObj, Decl(keyofDoesntContainSymbols.ts, 21, 28))
85+
>Values : Symbol(Values, Decl(keyofDoesntContainSymbols.ts, 19, 33))
86+
>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5))
87+
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
=== tests/cases/compiler/keyofDoesntContainSymbols.ts ===
2+
const sym = Symbol();
3+
>sym : unique symbol
4+
>Symbol() : unique symbol
5+
>Symbol : SymbolConstructor
6+
7+
const num = 0;
8+
>num : 0
9+
>0 : 0
10+
11+
const obj = { num: 0, str: 's', [num]: num as 0, [sym]: sym };
12+
>obj : { num: number; str: string; [num]: 0; [sym]: symbol; }
13+
>{ num: 0, str: 's', [num]: num as 0, [sym]: sym } : { num: number; str: string; [num]: 0; [sym]: symbol; }
14+
>num : number
15+
>0 : 0
16+
>str : string
17+
>'s' : "s"
18+
>[num] : 0
19+
>num : 0
20+
>num as 0 : 0
21+
>num : 0
22+
>[sym] : symbol
23+
>sym : unique symbol
24+
>sym : unique symbol
25+
26+
function set <T extends object, K extends keyof T> (obj: T, key: K, value: T[K]): T[K] {
27+
>set : <T extends object, K extends keyof T>(obj: T, key: K, value: T[K]) => T[K]
28+
>T : T
29+
>K : K
30+
>T : T
31+
>obj : T
32+
>T : T
33+
>key : K
34+
>K : K
35+
>value : T[K]
36+
>T : T
37+
>K : K
38+
>T : T
39+
>K : K
40+
41+
return obj[key] = value;
42+
>obj[key] = value : T[K]
43+
>obj[key] : T[K]
44+
>obj : T
45+
>key : K
46+
>value : T[K]
47+
}
48+
49+
const val = set(obj, 'str', '');
50+
>val : string
51+
>set(obj, 'str', '') : string
52+
>set : <T extends object, K extends keyof T>(obj: T, key: K, value: T[K]) => T[K]
53+
>obj : { num: number; str: string; [num]: 0; [sym]: symbol; }
54+
>'str' : "str"
55+
>'' : ""
56+
57+
// string
58+
const valB = set(obj, 'num', '');
59+
>valB : any
60+
>set(obj, 'num', '') : any
61+
>set : <T extends object, K extends keyof T>(obj: T, key: K, value: T[K]) => T[K]
62+
>obj : { num: number; str: string; [num]: 0; [sym]: symbol; }
63+
>'num' : "num"
64+
>'' : ""
65+
66+
// Expect type error
67+
// Argument of type '""' is not assignable to parameter of type 'number'.
68+
const valC = set(obj, sym, sym);
69+
>valC : any
70+
>set(obj, sym, sym) : any
71+
>set : <T extends object, K extends keyof T>(obj: T, key: K, value: T[K]) => T[K]
72+
>obj : { num: number; str: string; [num]: 0; [sym]: symbol; }
73+
>sym : unique symbol
74+
>sym : unique symbol
75+
76+
// Expect type error
77+
// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num"
78+
const valD = set(obj, num, num);
79+
>valD : any
80+
>set(obj, num, num) : any
81+
>set : <T extends object, K extends keyof T>(obj: T, key: K, value: T[K]) => T[K]
82+
>obj : { num: number; str: string; [num]: 0; [sym]: symbol; }
83+
>num : 0
84+
>num : 0
85+
86+
// Expect type error
87+
// Argument of type '0' is not assignable to parameter of type "str" | "num"
88+
type KeyofObj = keyof typeof obj;
89+
>KeyofObj : "str" | "num"
90+
>obj : { num: number; str: string; [num]: 0; [sym]: symbol; }
91+
92+
// "str" | "num"
93+
type Values<T> = T[keyof T];
94+
>Values : T[keyof T]
95+
>T : T
96+
>T : T
97+
>T : T
98+
99+
type ValuesOfObj = Values<typeof obj>;
100+
>ValuesOfObj : string | number
101+
>Values : T[keyof T]
102+
>obj : { num: number; str: string; [num]: 0; [sym]: symbol; }
103+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// @lib: es6
2+
const sym = Symbol();
3+
const num = 0;
4+
const obj = { num: 0, str: 's', [num]: num as 0, [sym]: sym };
5+
6+
function set <T extends object, K extends keyof T> (obj: T, key: K, value: T[K]): T[K] {
7+
return obj[key] = value;
8+
}
9+
10+
const val = set(obj, 'str', '');
11+
// string
12+
const valB = set(obj, 'num', '');
13+
// Expect type error
14+
// Argument of type '""' is not assignable to parameter of type 'number'.
15+
const valC = set(obj, sym, sym);
16+
// Expect type error
17+
// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num"
18+
const valD = set(obj, num, num);
19+
// Expect type error
20+
// Argument of type '0' is not assignable to parameter of type "str" | "num"
21+
type KeyofObj = keyof typeof obj;
22+
// "str" | "num"
23+
type Values<T> = T[keyof T];
24+
25+
type ValuesOfObj = Values<typeof obj>;

0 commit comments

Comments
 (0)