Only include unique symbols when getting index types for access checks (#23145) (#23175)

* Only include unique symbols when getting index types for access checks

* Filter all nonstrings

* Inline ternary
This commit is contained in:
Wesley Wigham 2018-04-05 12:12:44 -07:00 committed by GitHub
parent 16687e6ebf
commit a94e318604
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 322 additions and 13 deletions

View File

@ -8061,12 +8061,16 @@ namespace ts {
return links.resolvedType;
}
function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType) {
if (!type.resolvedIndexType) {
type.resolvedIndexType = <IndexType>createType(TypeFlags.Index);
type.resolvedIndexType.type = type;
function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, includeDeclaredTypes?: boolean) {
const cacheLocation = includeDeclaredTypes ? "resolvedDeclaredIndexType" : "resolvedIndexType";
if (!type[cacheLocation]) {
type[cacheLocation] = <IndexType>createType(TypeFlags.Index);
type[cacheLocation].type = type;
if (includeDeclaredTypes) {
type[cacheLocation].isDeclaredType = true;
}
}
return type.resolvedIndexType;
return type[cacheLocation];
}
function getLiteralTypeFromPropertyName(prop: Symbol) {
@ -8084,17 +8088,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 getIndexType(type: Type): Type {
return type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t))) :
maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type) :
function getLiteralTypeFromPropertyNames(type: Type, includeDeclaredTypes?: boolean) {
const originalKeys = map(getPropertiesOfType(type), getLiteralTypeFromPropertyName);
return getUnionType(includeDeclaredTypes ? originalKeys : filter(originalKeys, isTypeString));
}
function getIndexType(type: Type, includeDeclaredTypes?: boolean): Type {
return type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t, includeDeclaredTypes))) :
maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, includeDeclaredTypes) :
getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
type === wildcardType ? wildcardType :
type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringType :
getLiteralTypeFromPropertyNames(type);
getLiteralTypeFromPropertyNames(type, includeDeclaredTypes);
}
function getIndexTypeOrString(type: Type): Type {
@ -10191,7 +10200,7 @@ namespace ts {
// constraint of T.
const constraint = getConstraintForRelation((<IndexType>target).type);
if (constraint) {
if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) {
if (result = isRelatedTo(source, getIndexType(constraint, (target as IndexType).isDeclaredType), reportErrors)) {
return result;
}
}
@ -20632,7 +20641,7 @@ namespace ts {
// Check if the index type is assignable to 'keyof T' for the object type.
const objectType = (<IndexedAccessType>type).objectType;
const indexType = (<IndexedAccessType>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(<MappedType>objectType) & MappedTypeModifiers.IncludeReadonly) {
error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));

View File

@ -3706,6 +3706,8 @@ namespace ts {
/* @internal */
resolvedIndexType: IndexType;
/* @internal */
resolvedDeclaredIndexType: IndexType;
/* @internal */
resolvedBaseConstraint: Type;
/* @internal */
couldContainTypeVariables: boolean;
@ -3792,6 +3794,8 @@ namespace ts {
resolvedBaseConstraint?: Type;
/* @internal */
resolvedIndexType?: IndexType;
/* @internal */
resolvedDeclaredIndexType?: IndexType;
}
// Type parameters (TypeFlags.TypeParameter)
@ -3823,6 +3827,8 @@ namespace ts {
// keyof T types (TypeFlags.Index)
export interface IndexType extends InstantiableType {
/* @internal */
isDeclaredType?: boolean;
type: InstantiableType | UnionOrIntersectionType;
}

View File

@ -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 <T extends object, K extends keyof T> (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> = T[keyof T];
type ValuesOfObj = Values<typeof obj>;

View File

@ -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 <T extends object, K extends keyof T> (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> = T[keyof T];
type ValuesOfObj = Values<typeof obj>;
//// [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;

View File

@ -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 <T extends object, K extends keyof T> (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> = 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<typeof obj>;
>ValuesOfObj : Symbol(ValuesOfObj, Decl(keyofDoesntContainSymbols.ts, 21, 28))
>Values : Symbol(Values, Decl(keyofDoesntContainSymbols.ts, 19, 33))
>obj : Symbol(obj, Decl(keyofDoesntContainSymbols.ts, 2, 5))

View File

@ -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 <T extends object, K extends keyof T> (obj: T, key: K, value: T[K]): T[K] {
>set : <T extends object, K extends keyof T>(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 : <T extends object, K extends keyof T>(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 : <T extends object, K extends keyof T>(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 : <T extends object, K extends keyof T>(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 : <T extends object, K extends keyof T>(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> = T[keyof T];
>Values : T[keyof T]
>T : T
>T : T
>T : T
type ValuesOfObj = Values<typeof obj>;
>ValuesOfObj : string | number
>Values : T[keyof T]
>obj : { num: number; str: string; [num]: 0; [sym]: symbol; }

View File

@ -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 <T extends object, K extends keyof T> (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> = T[keyof T];
type ValuesOfObj = Values<typeof obj>;