Consider index signatures in type produced by 'keyof T'

This commit is contained in:
Anders Hejlsberg 2016-10-27 13:07:36 -07:00
parent e7cfbfe185
commit 4b50ef3585
3 changed files with 62 additions and 49 deletions

View File

@ -136,6 +136,7 @@ namespace ts {
const voidType = createIntrinsicType(TypeFlags.Void, "void");
const neverType = createIntrinsicType(TypeFlags.Never, "never");
const silentNeverType = createIntrinsicType(TypeFlags.Never, "never");
const stringOrNumberType = getUnionType([stringType, numberType]);
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
const emptyGenericType = <GenericType><ObjectType>createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
@ -4473,22 +4474,13 @@ namespace ts {
* type itself. Note that the apparent type of a union type is the union type itself.
*/
function getApparentType(type: Type): Type {
if (type.flags & TypeFlags.TypeParameter) {
type = getApparentTypeOfTypeParameter(<TypeParameter>type);
}
if (type.flags & TypeFlags.StringLike) {
type = globalStringType;
}
else if (type.flags & TypeFlags.NumberLike) {
type = globalNumberType;
}
else if (type.flags & TypeFlags.BooleanLike) {
type = globalBooleanType;
}
else if (type.flags & TypeFlags.ESSymbol) {
type = getGlobalESSymbolType();
}
return type;
const t = type.flags & TypeFlags.TypeParameter ? getApparentTypeOfTypeParameter(<TypeParameter>type) : type;
return t.flags & TypeFlags.StringLike ? globalStringType :
t.flags & TypeFlags.NumberLike ? globalNumberType :
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
t.flags & TypeFlags.ESSymbol ? getGlobalESSymbolType() :
t.flags & TypeFlags.Index ? stringOrNumberType :
t;
}
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: string): Symbol {
@ -5674,10 +5666,6 @@ namespace ts {
return links.resolvedType;
}
function getLiteralTypeFromPropertyName(prop: Symbol) {
return startsWith(prop.name, "__@") ? neverType : getLiteralTypeForText(TypeFlags.StringLiteral, unescapeIdentifier(prop.name));
}
function getIndexTypeForTypeParameter(type: TypeParameter) {
if (!type.resolvedIndexType) {
type.resolvedIndexType = <IndexType>createType(TypeFlags.Index);
@ -5686,10 +5674,19 @@ namespace ts {
return type.resolvedIndexType;
}
function getLiteralTypeFromPropertyName(prop: Symbol) {
return startsWith(prop.name, "__@") ? neverType : getLiteralTypeForText(TypeFlags.StringLiteral, unescapeIdentifier(prop.name));
}
function getLiteralTypeFromPropertyNames(type: Type) {
return getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName));
}
function getIndexType(type: Type): Type {
return type.flags & TypeFlags.TypeParameter ?
getIndexTypeForTypeParameter(<TypeParameter>type) :
getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName));
return type.flags & TypeFlags.TypeParameter ? getIndexTypeForTypeParameter(<TypeParameter>type) :
getIndexInfoOfType(type, IndexKind.String) ? stringOrNumberType :
getIndexInfoOfType(type, IndexKind.Number) ? getUnionType([numberType, getLiteralTypeFromPropertyNames(type)]) :
getLiteralTypeFromPropertyNames(type);
}
function getTypeFromTypeOperatorNode(node: TypeOperatorNode) {
@ -5712,33 +5709,45 @@ namespace ts {
return indexedAccessTypes[objectType.id] || (indexedAccessTypes[objectType.id] = createIndexedAccessType(objectType, keyType));
}
function getPropertyTypeForIndexType(objectType: Type, indexType: Type) {
return indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.EnumLiteral) && getTypeOfPropertyOfType(objectType, escapeIdentifier((<LiteralType>indexType).text)) ||
isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.NumberLike) && getIndexTypeOfType(objectType, IndexKind.Number) ||
isTypeAnyOrAllConstituentTypesHaveKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike) && getIndexTypeOfType(objectType, IndexKind.String) ||
undefined;
}
function getIndexedAccessType(objectType: Type, keyType: Type) {
if (keyType.flags & TypeFlags.TypeParameter) {
return getIndexedAccessTypeForTypeParameter(objectType, <TypeParameter>keyType);
}
if (isTypeOfKind(keyType, TypeFlags.StringLiteral) && !(keyType.flags & TypeFlags.Intersection)) {
return mapType(keyType, t => getTypeOfPropertyOfType(objectType, escapeIdentifier((<LiteralType>t).text)) || unknownType);
}
return keyType.flags & TypeFlags.Any ? anyType : unknownType;
return keyType.flags & TypeFlags.Any ? anyType :
keyType.flags & TypeFlags.TypeParameter ? getIndexedAccessTypeForTypeParameter(objectType, <TypeParameter>keyType) :
mapType(keyType, t => getPropertyTypeForIndexType(objectType, t) || unknownType);
}
function resolveIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
const objectType = getTypeFromTypeNodeNoAlias(node.objectType);
const keyType = getTypeFromTypeNodeNoAlias(node.indexType);
if (keyType.flags & TypeFlags.TypeParameter &&
getConstraintOfTypeParameter(<TypeParameter>keyType) === getIndexType(objectType)) {
return getIndexedAccessType(objectType, keyType);
}
if (isTypeOfKind(keyType, TypeFlags.StringLiteral) && !(keyType.flags & TypeFlags.Intersection)) {
const missing = forEachType(keyType, t => getTypeOfPropertyOfType(objectType, escapeIdentifier((<LiteralType>t).text)) ? undefined : (<LiteralType>t).text);
if (missing) {
error(node.indexType, Diagnostics.Property_0_is_missing_in_type_1, missing, typeToString(objectType));
const indexType = getTypeFromTypeNodeNoAlias(node.indexType);
if (indexType.flags & TypeFlags.TypeParameter) {
if (!isTypeAssignableTo(getConstraintOfTypeParameter(<TypeParameter>indexType), getIndexType(objectType))) {
error(node.indexType, Diagnostics.Type_0_is_not_constrained_to_keyof_1, typeToString(indexType), typeToString(objectType));
return unknownType;
}
return getIndexedAccessType(objectType, keyType);
return getIndexedAccessType(objectType, indexType);
}
error(node.indexType, Diagnostics.Property_access_element_type_must_be_a_string_literal_type_or_a_type_parameter_constrained_to_keyof_0, typeToString(objectType));
return unknownType;
const indexTypes = indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Primitive) ? (<UnionType>indexType).types : [indexType];
for (const t of indexTypes) {
if (!getPropertyTypeForIndexType(objectType, t)) {
if (t.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
error(node.indexType, Diagnostics.Property_0_does_not_exist_on_type_1, (<LiteralType>t).text, typeToString(objectType))
}
else if (t.flags & (TypeFlags.String | TypeFlags.Number)) {
error(node.indexType, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(t));
}
else {
error(node.indexType, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(t));
}
return unknownType;
}
}
return getIndexedAccessType(objectType, indexType);
}
function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
@ -8606,10 +8615,6 @@ namespace ts {
return containsType(target.types, source);
}
function forEachType<T>(type: Type, f: (t: Type) => T): T {
return type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, f) : f(type);
}
function filterType(type: Type, f: (t: Type) => boolean): Type {
if (type.flags & TypeFlags.Union) {
const types = (<UnionType>type).types;

View File

@ -1751,10 +1751,18 @@
"category": "Error",
"code": 2535
},
"Property access element type must be a string literal type or a type parameter constrained to 'keyof {0}'.": {
"Type '{0}' is not constrained to 'keyof {1}'.": {
"category": "Error",
"code": 2536
},
"Type '{0}' has no matching index signature for type '{1}'.": {
"category": "Error",
"code": 2537
},
"Type '{0}' cannot be used as an index type.": {
"category": "Error",
"code": 2538
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600

View File

@ -2652,7 +2652,7 @@ namespace ts {
Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never,
/* @internal */
Primitive = String | Number | Boolean | Enum | ESSymbol | Void | Undefined | Null | Literal,
StringLike = String | StringLiteral | Index,
StringLike = String | StringLiteral,
NumberLike = Number | NumberLiteral | Enum | EnumLiteral,
BooleanLike = Boolean | BooleanLiteral,
EnumLike = Enum | EnumLiteral,
@ -2662,7 +2662,7 @@ namespace ts {
// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Narrowable = Any | StructuredType | TypeParameter | IndexedAccess | StringLike | NumberLike | BooleanLike | ESSymbol,
Narrowable = Any | StructuredType | TypeParameter | Index | IndexedAccess | StringLike | NumberLike | BooleanLike | ESSymbol,
NotUnionOrUnit = Any | ESSymbol | Object,
/* @internal */
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,