diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 07801f5b5bc92..908e2ce665d95 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8109,12 +8109,16 @@ namespace ts { return links.resolvedType; } - function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType) { - if (!type.resolvedIndexType) { - type.resolvedIndexType = createType(TypeFlags.Index); - type.resolvedIndexType.type = type; + function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, includeDeclaredTypes?: boolean) { + const cacheLocation = includeDeclaredTypes ? "resolvedDeclaredIndexType" : "resolvedIndexType"; + if (!type[cacheLocation]) { + type[cacheLocation] = createType(TypeFlags.Index); + type[cacheLocation].type = type; + if (includeDeclaredTypes) { + type[cacheLocation].isDeclaredType = true; + } } - return type.resolvedIndexType; + return type[cacheLocation]; } function getLiteralTypeFromPropertyName(prop: Symbol) { @@ -8132,17 +8136,22 @@ namespace ts { return links.nameType; } - function getLiteralTypeFromPropertyNames(type: Type) { - return getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName)); + function isTypeString(type: Type) { + return isTypeAssignableToKind(type, TypeFlags.StringLike); + } + + function getLiteralTypeFromPropertyNames(type: Type, includeDeclaredTypes?: boolean) { + const originalKeys = map(getPropertiesOfType(type), getLiteralTypeFromPropertyName); + return getUnionType(includeDeclaredTypes ? originalKeys : filter(originalKeys, isTypeString)); } - function getIndexType(type: Type): Type { - return type.flags & TypeFlags.Intersection ? getUnionType(map((type).types, t => getIndexType(t))) : - maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(type) : + function getIndexType(type: Type, includeDeclaredTypes?: boolean): Type { + return type.flags & TypeFlags.Intersection ? getUnionType(map((type).types, t => getIndexType(t, includeDeclaredTypes))) : + maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(type, includeDeclaredTypes) : getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(type) : type === wildcardType ? wildcardType : type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringType : - getLiteralTypeFromPropertyNames(type); + getLiteralTypeFromPropertyNames(type, includeDeclaredTypes); } function getIndexTypeOrString(type: Type): Type { @@ -10318,7 +10327,7 @@ namespace ts { // constraint of T. const constraint = getConstraintForRelation((target).type); if (constraint) { - if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) { + if (result = isRelatedTo(source, getIndexType(constraint, (target as IndexType).isDeclaredType), reportErrors)) { return result; } } @@ -20840,7 +20849,7 @@ namespace ts { // Check if the index type is assignable to 'keyof T' for the object type. const objectType = (type).objectType; const indexType = (type).indexType; - if (isTypeAssignableTo(indexType, getIndexType(objectType))) { + if (isTypeAssignableTo(indexType, getIndexType(objectType, /*includeDeclaredTypes*/ true))) { if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType) & MappedTypeModifiers.IncludeReadonly) { error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 63829b0fa9aef..a526ecc5dff98 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3753,6 +3753,8 @@ namespace ts { /* @internal */ resolvedIndexType: IndexType; /* @internal */ + resolvedDeclaredIndexType: IndexType; + /* @internal */ resolvedBaseConstraint: Type; /* @internal */ couldContainTypeVariables: boolean; @@ -3839,6 +3841,8 @@ namespace ts { resolvedBaseConstraint?: Type; /* @internal */ resolvedIndexType?: IndexType; + /* @internal */ + resolvedDeclaredIndexType?: IndexType; } // Type parameters (TypeFlags.TypeParameter) @@ -3870,6 +3874,8 @@ namespace ts { // keyof T types (TypeFlags.Index) export interface IndexType extends InstantiableType { + /* @internal */ + isDeclaredType?: boolean; type: InstantiableType | UnionOrIntersectionType; } diff --git a/tests/baselines/reference/keyofDoesntContainSymbols.errors.txt b/tests/baselines/reference/keyofDoesntContainSymbols.errors.txt new file mode 100644 index 0000000000000..f87631f9dbae1 --- /dev/null +++ b/tests/baselines/reference/keyofDoesntContainSymbols.errors.txt @@ -0,0 +1,36 @@ +tests/cases/compiler/keyofDoesntContainSymbols.ts(11,30): error TS2345: Argument of type '""' is not assignable to parameter of type 'number'. +tests/cases/compiler/keyofDoesntContainSymbols.ts(14,23): error TS2345: Argument of type 'unique symbol' is not assignable to parameter of type '"str" | "num"'. +tests/cases/compiler/keyofDoesntContainSymbols.ts(17,23): error TS2345: Argument of type '0' is not assignable to parameter of type '"str" | "num"'. + + +==== tests/cases/compiler/keyofDoesntContainSymbols.ts (3 errors) ==== + const sym = Symbol(); + const num = 0; + const obj = { num: 0, str: 's', [num]: num as 0, [sym]: sym }; + + function set (obj: T, key: K, value: T[K]): T[K] { + return obj[key] = value; + } + + const val = set(obj, 'str', ''); + // string + const valB = set(obj, 'num', ''); + ~~ +!!! error TS2345: Argument of type '""' is not assignable to parameter of type 'number'. + // Expect type error + // Argument of type '""' is not assignable to parameter of type 'number'. + const valC = set(obj, sym, sym); + ~~~ +!!! error TS2345: Argument of type 'unique symbol' is not assignable to parameter of type '"str" | "num"'. + // Expect type error + // Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num" + const valD = set(obj, num, num); + ~~~ +!!! error TS2345: Argument of type '0' is not assignable to parameter of type '"str" | "num"'. + // Expect type error + // Argument of type '0' is not assignable to parameter of type "str" | "num" + type KeyofObj = keyof typeof obj; + // "str" | "num" + type Values = T[keyof T]; + + type ValuesOfObj = Values; \ No newline at end of file diff --git a/tests/baselines/reference/keyofDoesntContainSymbols.js b/tests/baselines/reference/keyofDoesntContainSymbols.js new file mode 100644 index 0000000000000..ad965aae1e4a5 --- /dev/null +++ b/tests/baselines/reference/keyofDoesntContainSymbols.js @@ -0,0 +1,43 @@ +//// [keyofDoesntContainSymbols.ts] +const sym = Symbol(); +const num = 0; +const obj = { num: 0, str: 's', [num]: num as 0, [sym]: sym }; + +function set (obj: T, key: K, value: T[K]): T[K] { + return obj[key] = value; +} + +const val = set(obj, 'str', ''); +// string +const valB = set(obj, 'num', ''); +// Expect type error +// Argument of type '""' is not assignable to parameter of type 'number'. +const valC = set(obj, sym, sym); +// Expect type error +// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num" +const valD = set(obj, num, num); +// Expect type error +// Argument of type '0' is not assignable to parameter of type "str" | "num" +type KeyofObj = keyof typeof obj; +// "str" | "num" +type Values = T[keyof T]; + +type ValuesOfObj = Values; + +//// [keyofDoesntContainSymbols.js] +var sym = Symbol(); +var num = 0; +var obj = (_a = { num: 0, str: 's' }, _a[num] = num, _a[sym] = sym, _a); +function set(obj, key, value) { + return obj[key] = value; +} +var val = set(obj, 'str', ''); +// string +var valB = set(obj, 'num', ''); +// Expect type error +// Argument of type '""' is not assignable to parameter of type 'number'. +var valC = set(obj, sym, sym); +// Expect type error +// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num" +var valD = set(obj, num, num); +var _a; diff --git a/tests/baselines/reference/keyofDoesntContainSymbols.symbols b/tests/baselines/reference/keyofDoesntContainSymbols.symbols new file mode 100644 index 0000000000000..31f208f40631f --- /dev/null +++ b/tests/baselines/reference/keyofDoesntContainSymbols.symbols @@ -0,0 +1,87 @@ +=== tests/cases/compiler/keyofDoesntContainSymbols.ts === +const sym = Symbol(); +>sym : Symbol(sym, Decl(keyofDoesntContainSymbols.ts, 0, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --)) + +const num = 0; +>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 1, 5)) + +const obj = { num: 0, str: 's', [num]: num as 0, [sym]: sym }; +>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5)) +>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 2, 13)) +>str : Symbol(str, Decl(keyofDoesntContainSymbols.ts, 2, 21)) +>[num] : Symbol([num], Decl(keyofDoesntContainSymbols.ts, 2, 31)) +>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 1, 5)) +>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 1, 5)) +>[sym] : Symbol([sym], Decl(keyofDoesntContainSymbols.ts, 2, 48)) +>sym : Symbol(sym, Decl(keyofDoesntContainSymbols.ts, 0, 5)) +>sym : Symbol(sym, Decl(keyofDoesntContainSymbols.ts, 0, 5)) + +function set (obj: T, key: K, value: T[K]): T[K] { +>set : Symbol(set, Decl(keyofDoesntContainSymbols.ts, 2, 62)) +>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 4, 14)) +>K : Symbol(K, Decl(keyofDoesntContainSymbols.ts, 4, 31)) +>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 4, 14)) +>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 4, 52)) +>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 4, 14)) +>key : Symbol(key, Decl(keyofDoesntContainSymbols.ts, 4, 59)) +>K : Symbol(K, Decl(keyofDoesntContainSymbols.ts, 4, 31)) +>value : Symbol(value, Decl(keyofDoesntContainSymbols.ts, 4, 67)) +>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 4, 14)) +>K : Symbol(K, Decl(keyofDoesntContainSymbols.ts, 4, 31)) +>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 4, 14)) +>K : Symbol(K, Decl(keyofDoesntContainSymbols.ts, 4, 31)) + + return obj[key] = value; +>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 4, 52)) +>key : Symbol(key, Decl(keyofDoesntContainSymbols.ts, 4, 59)) +>value : Symbol(value, Decl(keyofDoesntContainSymbols.ts, 4, 67)) +} + +const val = set(obj, 'str', ''); +>val : Symbol(val, Decl(keyofDoesntContainSymbols.ts, 8, 5)) +>set : Symbol(set, Decl(keyofDoesntContainSymbols.ts, 2, 62)) +>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5)) + +// string +const valB = set(obj, 'num', ''); +>valB : Symbol(valB, Decl(keyofDoesntContainSymbols.ts, 10, 5)) +>set : Symbol(set, Decl(keyofDoesntContainSymbols.ts, 2, 62)) +>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5)) + +// Expect type error +// Argument of type '""' is not assignable to parameter of type 'number'. +const valC = set(obj, sym, sym); +>valC : Symbol(valC, Decl(keyofDoesntContainSymbols.ts, 13, 5)) +>set : Symbol(set, Decl(keyofDoesntContainSymbols.ts, 2, 62)) +>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5)) +>sym : Symbol(sym, Decl(keyofDoesntContainSymbols.ts, 0, 5)) +>sym : Symbol(sym, Decl(keyofDoesntContainSymbols.ts, 0, 5)) + +// Expect type error +// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num" +const valD = set(obj, num, num); +>valD : Symbol(valD, Decl(keyofDoesntContainSymbols.ts, 16, 5)) +>set : Symbol(set, Decl(keyofDoesntContainSymbols.ts, 2, 62)) +>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5)) +>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 1, 5)) +>num : Symbol(num, Decl(keyofDoesntContainSymbols.ts, 1, 5)) + +// Expect type error +// Argument of type '0' is not assignable to parameter of type "str" | "num" +type KeyofObj = keyof typeof obj; +>KeyofObj : Symbol(KeyofObj, Decl(keyofDoesntContainSymbols.ts, 16, 32)) +>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5)) + +// "str" | "num" +type Values = T[keyof T]; +>Values : Symbol(Values, Decl(keyofDoesntContainSymbols.ts, 19, 33)) +>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 21, 12)) +>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 21, 12)) +>T : Symbol(T, Decl(keyofDoesntContainSymbols.ts, 21, 12)) + +type ValuesOfObj = Values; +>ValuesOfObj : Symbol(ValuesOfObj, Decl(keyofDoesntContainSymbols.ts, 21, 28)) +>Values : Symbol(Values, Decl(keyofDoesntContainSymbols.ts, 19, 33)) +>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5)) + diff --git a/tests/baselines/reference/keyofDoesntContainSymbols.types b/tests/baselines/reference/keyofDoesntContainSymbols.types new file mode 100644 index 0000000000000..e07f6a234c13e --- /dev/null +++ b/tests/baselines/reference/keyofDoesntContainSymbols.types @@ -0,0 +1,103 @@ +=== tests/cases/compiler/keyofDoesntContainSymbols.ts === +const sym = Symbol(); +>sym : unique symbol +>Symbol() : unique symbol +>Symbol : SymbolConstructor + +const num = 0; +>num : 0 +>0 : 0 + +const obj = { num: 0, str: 's', [num]: num as 0, [sym]: sym }; +>obj : { num: number; str: string; [num]: 0; [sym]: symbol; } +>{ num: 0, str: 's', [num]: num as 0, [sym]: sym } : { num: number; str: string; [num]: 0; [sym]: symbol; } +>num : number +>0 : 0 +>str : string +>'s' : "s" +>[num] : 0 +>num : 0 +>num as 0 : 0 +>num : 0 +>[sym] : symbol +>sym : unique symbol +>sym : unique symbol + +function set (obj: T, key: K, value: T[K]): T[K] { +>set : (obj: T, key: K, value: T[K]) => T[K] +>T : T +>K : K +>T : T +>obj : T +>T : T +>key : K +>K : K +>value : T[K] +>T : T +>K : K +>T : T +>K : K + + return obj[key] = value; +>obj[key] = value : T[K] +>obj[key] : T[K] +>obj : T +>key : K +>value : T[K] +} + +const val = set(obj, 'str', ''); +>val : string +>set(obj, 'str', '') : string +>set : (obj: T, key: K, value: T[K]) => T[K] +>obj : { num: number; str: string; [num]: 0; [sym]: symbol; } +>'str' : "str" +>'' : "" + +// string +const valB = set(obj, 'num', ''); +>valB : any +>set(obj, 'num', '') : any +>set : (obj: T, key: K, value: T[K]) => T[K] +>obj : { num: number; str: string; [num]: 0; [sym]: symbol; } +>'num' : "num" +>'' : "" + +// Expect type error +// Argument of type '""' is not assignable to parameter of type 'number'. +const valC = set(obj, sym, sym); +>valC : any +>set(obj, sym, sym) : any +>set : (obj: T, key: K, value: T[K]) => T[K] +>obj : { num: number; str: string; [num]: 0; [sym]: symbol; } +>sym : unique symbol +>sym : unique symbol + +// Expect type error +// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num" +const valD = set(obj, num, num); +>valD : any +>set(obj, num, num) : any +>set : (obj: T, key: K, value: T[K]) => T[K] +>obj : { num: number; str: string; [num]: 0; [sym]: symbol; } +>num : 0 +>num : 0 + +// Expect type error +// Argument of type '0' is not assignable to parameter of type "str" | "num" +type KeyofObj = keyof typeof obj; +>KeyofObj : "str" | "num" +>obj : { num: number; str: string; [num]: 0; [sym]: symbol; } + +// "str" | "num" +type Values = T[keyof T]; +>Values : T[keyof T] +>T : T +>T : T +>T : T + +type ValuesOfObj = Values; +>ValuesOfObj : string | number +>Values : T[keyof T] +>obj : { num: number; str: string; [num]: 0; [sym]: symbol; } + diff --git a/tests/cases/compiler/keyofDoesntContainSymbols.ts b/tests/cases/compiler/keyofDoesntContainSymbols.ts new file mode 100644 index 0000000000000..5690d4c6b8825 --- /dev/null +++ b/tests/cases/compiler/keyofDoesntContainSymbols.ts @@ -0,0 +1,25 @@ +// @lib: es6 +const sym = Symbol(); +const num = 0; +const obj = { num: 0, str: 's', [num]: num as 0, [sym]: sym }; + +function set (obj: T, key: K, value: T[K]): T[K] { + return obj[key] = value; +} + +const val = set(obj, 'str', ''); +// string +const valB = set(obj, 'num', ''); +// Expect type error +// Argument of type '""' is not assignable to parameter of type 'number'. +const valC = set(obj, sym, sym); +// Expect type error +// Argument of type 'unique symbol' is not assignable to parameter of type "str" | "num" +const valD = set(obj, num, num); +// Expect type error +// Argument of type '0' is not assignable to parameter of type "str" | "num" +type KeyofObj = keyof typeof obj; +// "str" | "num" +type Values = T[keyof T]; + +type ValuesOfObj = Values; \ No newline at end of file