From e603aabf18e8287a5839c74cabd04978317b1859 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 2 Apr 2018 14:26:16 -0700 Subject: [PATCH] Unless checking access, do not include unqie symbols or numbers in index types --- src/compiler/checker.ts | 50 ++++++-- ...eyofZeroOrderOnlyReturnsStrings.errors.txt | 41 +++++++ .../keyofZeroOrderOnlyReturnsStrings.js | 51 ++++++++ .../keyofZeroOrderOnlyReturnsStrings.symbols | 96 +++++++++++++++ .../keyofZeroOrderOnlyReturnsStrings.types | 114 ++++++++++++++++++ .../keyofZeroOrderOnlyReturnsStrings.ts | 29 +++++ 6 files changed, 372 insertions(+), 9 deletions(-) create mode 100644 tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.errors.txt create mode 100644 tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.js create mode 100644 tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.symbols create mode 100644 tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.types create mode 100644 tests/cases/compiler/keyofZeroOrderOnlyReturnsStrings.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3c48319273abc..9f97af5172c77 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -561,6 +561,7 @@ namespace ts { const subtypeRelation = createMap(); const assignableRelation = createMap(); + const accessibleRelation = createMap(); const definitelyAssignableRelation = createMap(); const comparableRelation = createMap(); const identityRelation = createMap(); @@ -8130,17 +8131,44 @@ namespace ts { return links.nameType; } - function getLiteralTypeFromPropertyNames(type: Type) { - return getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName)); + function getStringifiedNumberType(type: Type): Type { + if (type === numberType) return stringType; + if (isLiteralType(type)) { + const value = (type as LiteralType).value; + if (typeof value !== "undefined") { + return getLiteralType("" + value); + } + } + return neverType; + } + + function getLiteralTypeFromPropertyNames(type: Type, isAccess: boolean) { + if (isAccess) { + // For access checking, include raw number/symbol keys + return getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName)); + } + // Otherwise only return stringified number/string keys + const results: Type[] = []; + const props = getPropertiesOfType(type); + for (const prop of props) { + const propType = getLiteralTypeFromPropertyName(prop); + if (propType.flags & TypeFlags.StringLike) { + results.push(propType); + } + if (propType.flags & TypeFlags.NumberLike) { + results.push(getStringifiedNumberType(propType)); + } + } + return getUnionType(results); } - function getIndexType(type: Type): Type { - return type.flags & TypeFlags.Intersection ? getUnionType(map((type).types, t => getIndexType(t))) : + function getIndexType(type: Type, isAccess?: boolean): Type { + return type.flags & TypeFlags.Intersection ? getUnionType(map((type).types, t => getIndexType(t, isAccess))) : maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(type) : getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(type) : type === wildcardType ? wildcardType : type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringType : - getLiteralTypeFromPropertyNames(type); + getLiteralTypeFromPropertyNames(type, isAccess); } function getIndexTypeOrString(type: Type): Type { @@ -9360,6 +9388,10 @@ namespace ts { return isTypeRelatedTo(source, target, assignableRelation); } + function isTypeAccessibleTo(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, accessibleRelation); + } + // An object type S is considered to be derived from an object type T if // S is a union type and every constituent of S is derived from T, // T is a union type and S is derived from at least one constituent of T, or @@ -9680,7 +9712,7 @@ namespace ts { if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) return true; if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) return true; if (s & TypeFlags.UniqueESSymbol || t & TypeFlags.UniqueESSymbol) return false; - if (relation === assignableRelation || relation === definitelyAssignableRelation || relation === comparableRelation) { + if (relation === assignableRelation || relation === accessibleRelation || relation === definitelyAssignableRelation || relation === comparableRelation) { if (s & TypeFlags.Any) return true; // Type number or any numeric literal type is assignable to any numeric enum type or any // numeric enum literal type. This rule exists for backwards compatibility reasons because @@ -10007,7 +10039,7 @@ namespace ts { function hasExcessProperties(source: FreshObjectLiteralType, target: Type, discriminant: Type | undefined, reportErrors: boolean): boolean { if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) { const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - if ((relation === assignableRelation || relation === definitelyAssignableRelation || relation === comparableRelation) && + if ((relation === assignableRelation || relation === accessibleRelation || relation === definitelyAssignableRelation || relation === comparableRelation) && (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) { return false; } @@ -10307,7 +10339,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, relation === accessibleRelation), reportErrors)) { return result; } } @@ -20814,7 +20846,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 (isTypeAccessibleTo(indexType, getIndexType(objectType, /*isAccess*/ 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/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.errors.txt b/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.errors.txt new file mode 100644 index 0000000000000..9a03dcbb846b1 --- /dev/null +++ b/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.errors.txt @@ -0,0 +1,41 @@ +tests/cases/compiler/keyofZeroOrderOnlyReturnsStrings.ts(11,30): error TS2345: Argument of type '""' is not assignable to parameter of type 'number'. +tests/cases/compiler/keyofZeroOrderOnlyReturnsStrings.ts(14,23): error TS2345: Argument of type 'unique symbol' is not assignable to parameter of type '"str" | "num" | "0"'. +tests/cases/compiler/keyofZeroOrderOnlyReturnsStrings.ts(17,23): error TS2345: Argument of type '0' is not assignable to parameter of type '"str" | "num" | "0"'. + + +==== tests/cases/compiler/keyofZeroOrderOnlyReturnsStrings.ts (3 errors) ==== + const sym = Symbol(); + const num = 0; + const obj = { num: 0, str: 's', [sym]: sym, [num]: num as 0 }; + + 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" | "0"'. + // Expect type error + // Argument of type 'unique symbol' is not assignable to parameter of type '"str" | "num" | "0" + const valD = set(obj, num, num); + ~~~ +!!! error TS2345: Argument of type '0' is not assignable to parameter of type '"str" | "num" | "0"'. + // Expect type error + // Argument of type '0' is not assignable to parameter of type '"str" | "num" | "0" + const valE = set(obj, "0", num); + // 0 + + type KeyofObj = keyof typeof obj; + // "str" | "num" | "0" + + type Values = T[keyof T]; + + type ValuesOfObj = Values; + \ No newline at end of file diff --git a/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.js b/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.js new file mode 100644 index 0000000000000..188d0b0fead55 --- /dev/null +++ b/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.js @@ -0,0 +1,51 @@ +//// [keyofZeroOrderOnlyReturnsStrings.ts] +const sym = Symbol(); +const num = 0; +const obj = { num: 0, str: 's', [sym]: sym, [num]: num as 0 }; + +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" | "0" +const valD = set(obj, num, num); +// Expect type error +// Argument of type '0' is not assignable to parameter of type '"str" | "num" | "0" +const valE = set(obj, "0", num); +// 0 + +type KeyofObj = keyof typeof obj; +// "str" | "num" | "0" + +type Values = T[keyof T]; + +type ValuesOfObj = Values; + + +//// [keyofZeroOrderOnlyReturnsStrings.js] +var sym = Symbol(); +var num = 0; +var obj = (_a = { num: 0, str: 's' }, _a[sym] = sym, _a[num] = num, _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" | "0" +var valD = set(obj, num, num); +// Expect type error +// Argument of type '0' is not assignable to parameter of type '"str" | "num" | "0" +var valE = set(obj, "0", num); +var _a; diff --git a/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.symbols b/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.symbols new file mode 100644 index 0000000000000..354a1248e8e58 --- /dev/null +++ b/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.symbols @@ -0,0 +1,96 @@ +=== tests/cases/compiler/keyofZeroOrderOnlyReturnsStrings.ts === +const sym = Symbol(); +>sym : Symbol(sym, Decl(keyofZeroOrderOnlyReturnsStrings.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(keyofZeroOrderOnlyReturnsStrings.ts, 1, 5)) + +const obj = { num: 0, str: 's', [sym]: sym, [num]: num as 0 }; +>obj : Symbol(obj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 5)) +>num : Symbol(num, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 13)) +>str : Symbol(str, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 21)) +>[sym] : Symbol([sym], Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 31)) +>sym : Symbol(sym, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 0, 5)) +>sym : Symbol(sym, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 0, 5)) +>[num] : Symbol([num], Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 43)) +>num : Symbol(num, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 1, 5)) +>num : Symbol(num, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 1, 5)) + +function set (obj: T, key: K, value: T[K]): T[K] { +>set : Symbol(set, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 62)) +>T : Symbol(T, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 14)) +>K : Symbol(K, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 31)) +>T : Symbol(T, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 14)) +>obj : Symbol(obj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 52)) +>T : Symbol(T, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 14)) +>key : Symbol(key, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 59)) +>K : Symbol(K, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 31)) +>value : Symbol(value, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 67)) +>T : Symbol(T, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 14)) +>K : Symbol(K, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 31)) +>T : Symbol(T, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 14)) +>K : Symbol(K, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 31)) + + return obj[key] = value; +>obj : Symbol(obj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 52)) +>key : Symbol(key, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 59)) +>value : Symbol(value, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 4, 67)) +} + +const val = set(obj, 'str', ''); +>val : Symbol(val, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 8, 5)) +>set : Symbol(set, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 62)) +>obj : Symbol(obj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 5)) + +// string +const valB = set(obj, 'num', ''); +>valB : Symbol(valB, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 10, 5)) +>set : Symbol(set, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 62)) +>obj : Symbol(obj, Decl(keyofZeroOrderOnlyReturnsStrings.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(keyofZeroOrderOnlyReturnsStrings.ts, 13, 5)) +>set : Symbol(set, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 62)) +>obj : Symbol(obj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 5)) +>sym : Symbol(sym, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 0, 5)) +>sym : Symbol(sym, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 0, 5)) + +// Expect type error +// Argument of type 'unique symbol' is not assignable to parameter of type '"str" | "num" | "0" +const valD = set(obj, num, num); +>valD : Symbol(valD, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 16, 5)) +>set : Symbol(set, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 62)) +>obj : Symbol(obj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 5)) +>num : Symbol(num, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 1, 5)) +>num : Symbol(num, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 1, 5)) + +// Expect type error +// Argument of type '0' is not assignable to parameter of type '"str" | "num" | "0" +const valE = set(obj, "0", num); +>valE : Symbol(valE, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 19, 5)) +>set : Symbol(set, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 62)) +>obj : Symbol(obj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 5)) +>num : Symbol(num, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 1, 5)) + +// 0 + +type KeyofObj = keyof typeof obj; +>KeyofObj : Symbol(KeyofObj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 19, 32)) +>obj : Symbol(obj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 5)) + +// "str" | "num" | "0" + +type Values = T[keyof T]; +>Values : Symbol(Values, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 22, 33)) +>T : Symbol(T, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 25, 12)) +>T : Symbol(T, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 25, 12)) +>T : Symbol(T, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 25, 12)) + +type ValuesOfObj = Values; +>ValuesOfObj : Symbol(ValuesOfObj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 25, 28)) +>Values : Symbol(Values, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 22, 33)) +>obj : Symbol(obj, Decl(keyofZeroOrderOnlyReturnsStrings.ts, 2, 5)) + diff --git a/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.types b/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.types new file mode 100644 index 0000000000000..9ad5c96548ad2 --- /dev/null +++ b/tests/baselines/reference/keyofZeroOrderOnlyReturnsStrings.types @@ -0,0 +1,114 @@ +=== tests/cases/compiler/keyofZeroOrderOnlyReturnsStrings.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', [sym]: sym, [num]: num as 0 }; +>obj : { num: number; str: string; [sym]: symbol; [num]: 0; } +>{ num: 0, str: 's', [sym]: sym, [num]: num as 0 } : { num: number; str: string; [sym]: symbol; [num]: 0; } +>num : number +>0 : 0 +>str : string +>'s' : "s" +>[sym] : symbol +>sym : unique symbol +>sym : unique symbol +>[num] : 0 +>num : 0 +>num as 0 : 0 +>num : 0 + +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; [sym]: symbol; [num]: 0; } +>'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; [sym]: symbol; [num]: 0; } +>'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; [sym]: symbol; [num]: 0; } +>sym : unique symbol +>sym : unique symbol + +// Expect type error +// Argument of type 'unique symbol' is not assignable to parameter of type '"str" | "num" | "0" +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; [sym]: symbol; [num]: 0; } +>num : 0 +>num : 0 + +// Expect type error +// Argument of type '0' is not assignable to parameter of type '"str" | "num" | "0" +const valE = set(obj, "0", num); +>valE : 0 +>set(obj, "0", num) : 0 +>set : (obj: T, key: K, value: T[K]) => T[K] +>obj : { num: number; str: string; [sym]: symbol; [num]: 0; } +>"0" : "0" +>num : 0 + +// 0 + +type KeyofObj = keyof typeof obj; +>KeyofObj : "str" | "num" | "0" +>obj : { num: number; str: string; [sym]: symbol; [num]: 0; } + +// "str" | "num" | "0" + +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; [sym]: symbol; [num]: 0; } + diff --git a/tests/cases/compiler/keyofZeroOrderOnlyReturnsStrings.ts b/tests/cases/compiler/keyofZeroOrderOnlyReturnsStrings.ts new file mode 100644 index 0000000000000..058cbc2bbe65c --- /dev/null +++ b/tests/cases/compiler/keyofZeroOrderOnlyReturnsStrings.ts @@ -0,0 +1,29 @@ +// @lib: es6 +const sym = Symbol(); +const num = 0; +const obj = { num: 0, str: 's', [sym]: sym, [num]: num as 0 }; + +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" | "0" +const valD = set(obj, num, num); +// Expect type error +// Argument of type '0' is not assignable to parameter of type '"str" | "num" | "0" +const valE = set(obj, "0", num); +// 0 + +type KeyofObj = keyof typeof obj; +// "str" | "num" | "0" + +type Values = T[keyof T]; + +type ValuesOfObj = Values;