Skip to content

Commit 77c0540

Browse files
authored
Merge pull request #12425 from Microsoft/keyofOnlyStrings
Change 'keyof T' to always be string-like
2 parents 4c6b94f + b662a8b commit 77c0540

32 files changed

+526
-660
lines changed

src/compiler/checker.ts

+9-21
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ namespace ts {
142142
const voidType = createIntrinsicType(TypeFlags.Void, "void");
143143
const neverType = createIntrinsicType(TypeFlags.Never, "never");
144144
const silentNeverType = createIntrinsicType(TypeFlags.Never, "never");
145-
const stringOrNumberType = getUnionType([stringType, numberType]);
146145

147146
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
148147

@@ -3189,9 +3188,11 @@ namespace ts {
31893188
}
31903189
}
31913190

3192-
// A variable declared in a for..in statement is always of type string
3191+
// A variable declared in a for..in statement is of type string, or of type keyof T when the
3192+
// right hand expression is of a type parameter type.
31933193
if (declaration.parent.parent.kind === SyntaxKind.ForInStatement) {
3194-
return stringType;
3194+
const indexType = getIndexType(checkNonNullExpression((<ForInStatement>declaration.parent.parent).expression));
3195+
return indexType.flags & TypeFlags.Index ? indexType : stringType;
31953196
}
31963197

31973198
if (declaration.parent.parent.kind === SyntaxKind.ForOfStatement) {
@@ -4660,7 +4661,6 @@ namespace ts {
46604661
t.flags & TypeFlags.NumberLike ? globalNumberType :
46614662
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
46624663
t.flags & TypeFlags.ESSymbol ? getGlobalESSymbolType() :
4663-
t.flags & TypeFlags.Index ? stringOrNumberType :
46644664
t;
46654665
}
46664666

@@ -5893,8 +5893,7 @@ namespace ts {
58935893
function getIndexType(type: Type): Type {
58945894
return type.flags & TypeFlags.TypeParameter ? getIndexTypeForTypeParameter(<TypeParameter>type) :
58955895
getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
5896-
type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringOrNumberType :
5897-
getIndexInfoOfType(type, IndexKind.Number) ? getUnionType([numberType, getLiteralTypeFromPropertyNames(type)]) :
5896+
type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringType :
58985897
getLiteralTypeFromPropertyNames(type);
58995898
}
59005899

@@ -6013,10 +6012,9 @@ namespace ts {
60136012
return indexedAccessTypes[id] || (indexedAccessTypes[id] = createIndexedAccessType(objectType, indexType));
60146013
}
60156014
const apparentObjectType = getApparentType(objectType);
6016-
const apparentIndexType = indexType.flags & TypeFlags.Index ? stringOrNumberType : indexType;
6017-
if (apparentIndexType.flags & TypeFlags.Union && !(apparentIndexType.flags & TypeFlags.Primitive)) {
6015+
if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Primitive)) {
60186016
const propTypes: Type[] = [];
6019-
for (const t of (<UnionType>apparentIndexType).types) {
6017+
for (const t of (<UnionType>indexType).types) {
60206018
const propType = getPropertyTypeForIndexType(apparentObjectType, t, accessNode, /*cacheSymbol*/ false);
60216019
if (propType === unknownType) {
60226020
return unknownType;
@@ -6025,7 +6023,7 @@ namespace ts {
60256023
}
60266024
return getUnionType(propTypes);
60276025
}
6028-
return getPropertyTypeForIndexType(apparentObjectType, apparentIndexType, accessNode, /*cacheSymbol*/ true);
6026+
return getPropertyTypeForIndexType(apparentObjectType, indexType, accessNode, /*cacheSymbol*/ true);
60296027
}
60306028

60316029
function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
@@ -7097,16 +7095,6 @@ namespace ts {
70977095

70987096
if (isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
70997097

7100-
if (source.flags & TypeFlags.Index) {
7101-
// A keyof T is related to a union type containing both string and number
7102-
const related = relation === comparableRelation ?
7103-
maybeTypeOfKind(target, TypeFlags.String | TypeFlags.Number) :
7104-
maybeTypeOfKind(target, TypeFlags.String) && maybeTypeOfKind(target, TypeFlags.Number);
7105-
if (related) {
7106-
return Ternary.True;
7107-
}
7108-
}
7109-
71107098
if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && source.flags & TypeFlags.FreshLiteral) {
71117099
if (hasExcessProperties(<FreshObjectLiteralType>source, target, reportErrors)) {
71127100
if (reportErrors) {
@@ -15627,7 +15615,7 @@ namespace ts {
1562715615
const type = <MappedType>getTypeFromMappedTypeNode(node);
1562815616
const constraintType = getConstraintTypeFromMappedType(type);
1562915617
const keyType = constraintType.flags & TypeFlags.TypeParameter ? getApparentTypeOfTypeParameter(<TypeParameter>constraintType) : constraintType;
15630-
checkTypeAssignableTo(keyType, stringOrNumberType, node.typeParameter.constraint);
15618+
checkTypeAssignableTo(keyType, stringType, node.typeParameter.constraint);
1563115619
}
1563215620

1563315621
function isPrivateWithinAmbient(node: Node): boolean {

src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2790,7 +2790,7 @@ namespace ts {
27902790
Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never,
27912791
/* @internal */
27922792
Primitive = String | Number | Boolean | Enum | ESSymbol | Void | Undefined | Null | Literal,
2793-
StringLike = String | StringLiteral,
2793+
StringLike = String | StringLiteral | Index,
27942794
NumberLike = Number | NumberLiteral | Enum | EnumLiteral,
27952795
BooleanLike = Boolean | BooleanLiteral,
27962796
EnumLike = Enum | EnumLiteral,

src/lib/es5.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1367,7 +1367,7 @@ type Pick<T, K extends keyof T> = {
13671367
/**
13681368
* Construct a type with a set of properties K of type T
13691369
*/
1370-
type Record<K extends string | number, T> = {
1370+
type Record<K extends string, T> = {
13711371
[P in K]: T;
13721372
}
13731373

tests/baselines/reference/for-inStatements.errors.txt

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
tests/cases/conformance/statements/for-inStatements/for-inStatements.ts(33,18): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'string', but here has type 'keyof this'.
2+
tests/cases/conformance/statements/for-inStatements/for-inStatements.ts(50,18): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'string', but here has type 'keyof this'.
13
tests/cases/conformance/statements/for-inStatements/for-inStatements.ts(79,15): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter.
24

35

4-
==== tests/cases/conformance/statements/for-inStatements/for-inStatements.ts (1 errors) ====
6+
==== tests/cases/conformance/statements/for-inStatements/for-inStatements.ts (3 errors) ====
57
var aString: string;
68
for (aString in {}) { }
79

@@ -35,6 +37,8 @@ tests/cases/conformance/statements/for-inStatements/for-inStatements.ts(79,15):
3537
for (var x in this.biz()) { }
3638
for (var x in this.biz) { }
3739
for (var x in this) { }
40+
~
41+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'string', but here has type 'keyof this'.
3842
return null;
3943
}
4044

@@ -52,6 +56,8 @@ tests/cases/conformance/statements/for-inStatements/for-inStatements.ts(79,15):
5256
for (var x in this.biz()) { }
5357
for (var x in this.biz) { }
5458
for (var x in this) { }
59+
~
60+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'string', but here has type 'keyof this'.
5561

5662
for (var x in super.biz) { }
5763
for (var x in super.biz()) { }

tests/baselines/reference/for-inStatementsInvalid.errors.txt

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(1
99
tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(20,15): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter.
1010
tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(22,15): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter.
1111
tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(29,23): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter.
12+
tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(31,18): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'string', but here has type 'keyof this'.
1213
tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(38,23): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter.
1314
tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(46,23): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter.
15+
tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(48,18): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'string', but here has type 'keyof this'.
1416
tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(51,23): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter.
1517
tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(62,15): error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter.
1618

1719

18-
==== tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts (15 errors) ====
20+
==== tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts (17 errors) ====
1921
var aNumber: number;
2022
for (aNumber in {}) { }
2123
~~~~~~~
@@ -69,6 +71,8 @@ tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(6
6971
!!! error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter.
7072
for (var x in this.biz) { }
7173
for (var x in this) { }
74+
~
75+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'string', but here has type 'keyof this'.
7276
return null;
7377
}
7478

@@ -90,6 +94,8 @@ tests/cases/conformance/statements/for-inStatements/for-inStatementsInvalid.ts(6
9094
!!! error TS2407: The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter.
9195
for (var x in this.biz) { }
9296
for (var x in this) { }
97+
~
98+
!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'string', but here has type 'keyof this'.
9399

94100
for (var x in super.biz) { }
95101
for (var x in super.biz()) { }

tests/baselines/reference/forInStatement3.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function F<T>() {
88
>T : T
99

1010
for (var a in expr) {
11-
>a : string
11+
>a : keyof T
1212
>expr : T
1313
}
1414
}

tests/baselines/reference/implicitAnyInCatch.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class C {
2222
>temp : () => void
2323

2424
for (var x in this) {
25-
>x : string
25+
>x : keyof this
2626
>this : this
2727
}
2828
}

tests/baselines/reference/inOperatorWithGeneric.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class C<T> {
99
>T : T
1010

1111
for (var p in x) {
12-
>p : string
12+
>p : keyof T
1313
>x : T
1414
}
1515
}

tests/baselines/reference/keyofAndIndexedAccess.js

+26-34
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,18 @@ function f10(shape: Shape) {
9595

9696
function f11(a: Shape[]) {
9797
let len = getProperty(a, "length"); // number
98-
let shape = getProperty(a, 1000); // Shape
99-
setProperty(a, 1000, getProperty(a, 1001));
98+
setProperty(a, "length", len);
10099
}
101100

102101
function f12(t: [Shape, boolean]) {
103102
let len = getProperty(t, "length");
104-
let s1 = getProperty(t, 0); // Shape
105103
let s2 = getProperty(t, "0"); // Shape
106-
let b1 = getProperty(t, 1); // boolean
107104
let b2 = getProperty(t, "1"); // boolean
108-
let x1 = getProperty(t, 2); // Shape | boolean
109105
}
110106

111107
function f13(foo: any, bar: any) {
112108
let x = getProperty(foo, "x"); // any
113-
let y = getProperty(foo, 100); // any
109+
let y = getProperty(foo, "100"); // any
114110
let z = getProperty(foo, bar); // any
115111
}
116112

@@ -181,20 +177,14 @@ function f40(c: C) {
181177
let z: Z = c["z"];
182178
}
183179

184-
function f50<T>(k: keyof T, s: string, n: number) {
180+
function f50<T>(k: keyof T, s: string) {
185181
const x1 = s as keyof T;
186-
const x2 = n as keyof T;
187-
const x3 = k as string;
188-
const x4 = k as number;
189-
const x5 = k as string | number;
182+
const x2 = k as string;
190183
}
191184

192-
function f51<T, K extends keyof T>(k: K, s: string, n: number) {
185+
function f51<T, K extends keyof T>(k: K, s: string) {
193186
const x1 = s as keyof T;
194-
const x2 = n as keyof T;
195-
const x3 = k as string;
196-
const x4 = k as number;
197-
const x5 = k as string | number;
187+
const x2 = k as string;
198188
}
199189

200190
function f52<T>(obj: { [x: string]: boolean }, k: keyof T, s: string, n: number) {
@@ -221,6 +211,12 @@ function f55<T, K extends keyof T>(obj: T, key: K) {
221211
const b = "foo" in obj[key];
222212
}
223213

214+
function f60<T>(source: T, target: T) {
215+
for (let k in source) {
216+
target[k] = source[k];
217+
}
218+
}
219+
224220
// Repros from #12011
225221

226222
class Base {
@@ -297,20 +293,16 @@ function f10(shape) {
297293
}
298294
function f11(a) {
299295
var len = getProperty(a, "length"); // number
300-
var shape = getProperty(a, 1000); // Shape
301-
setProperty(a, 1000, getProperty(a, 1001));
296+
setProperty(a, "length", len);
302297
}
303298
function f12(t) {
304299
var len = getProperty(t, "length");
305-
var s1 = getProperty(t, 0); // Shape
306300
var s2 = getProperty(t, "0"); // Shape
307-
var b1 = getProperty(t, 1); // boolean
308301
var b2 = getProperty(t, "1"); // boolean
309-
var x1 = getProperty(t, 2); // Shape | boolean
310302
}
311303
function f13(foo, bar) {
312304
var x = getProperty(foo, "x"); // any
313-
var y = getProperty(foo, 100); // any
305+
var y = getProperty(foo, "100"); // any
314306
var z = getProperty(foo, bar); // any
315307
}
316308
var Component = (function () {
@@ -369,19 +361,13 @@ function f40(c) {
369361
var y = c["y"];
370362
var z = c["z"];
371363
}
372-
function f50(k, s, n) {
364+
function f50(k, s) {
373365
var x1 = s;
374-
var x2 = n;
375-
var x3 = k;
376-
var x4 = k;
377-
var x5 = k;
366+
var x2 = k;
378367
}
379-
function f51(k, s, n) {
368+
function f51(k, s) {
380369
var x1 = s;
381-
var x2 = n;
382-
var x3 = k;
383-
var x4 = k;
384-
var x5 = k;
370+
var x2 = k;
385371
}
386372
function f52(obj, k, s, n) {
387373
var x1 = obj[s];
@@ -403,6 +389,11 @@ function f55(obj, key) {
403389
}
404390
var b = "foo" in obj[key];
405391
}
392+
function f60(source, target) {
393+
for (var k in source) {
394+
target[k] = source[k];
395+
}
396+
}
406397
// Repros from #12011
407398
var Base = (function () {
408399
function Base() {
@@ -528,8 +519,8 @@ declare class C {
528519
private z;
529520
}
530521
declare function f40(c: C): void;
531-
declare function f50<T>(k: keyof T, s: string, n: number): void;
532-
declare function f51<T, K extends keyof T>(k: K, s: string, n: number): void;
522+
declare function f50<T>(k: keyof T, s: string): void;
523+
declare function f51<T, K extends keyof T>(k: K, s: string): void;
533524
declare function f52<T>(obj: {
534525
[x: string]: boolean;
535526
}, k: keyof T, s: string, n: number): void;
@@ -538,6 +529,7 @@ declare function f53<T, K extends keyof T>(obj: {
538529
}, k: K, s: string, n: number): void;
539530
declare function f54<T>(obj: T, key: keyof T): void;
540531
declare function f55<T, K extends keyof T>(obj: T, key: K): void;
532+
declare function f60<T>(source: T, target: T): void;
541533
declare class Base {
542534
get<K extends keyof this>(prop: K): this[K];
543535
set<K extends keyof this>(prop: K, value: this[K]): void;

0 commit comments

Comments
 (0)