Initial implementation of 'T[K]' property access types

This commit is contained in:
Anders Hejlsberg
2016-10-25 14:32:20 -07:00
parent 07478aae14
commit c21592ede1
8 changed files with 127 additions and 11 deletions

View File

@@ -3067,6 +3067,7 @@ namespace ts {
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.ThisType:
case SyntaxKind.TypeOperator:
case SyntaxKind.PropertyAccessType:
case SyntaxKind.LiteralType:
// Types and signatures are TypeScript syntax, and exclude all other facts.
transformFlags = TransformFlags.AssertTypeScript;

View File

@@ -2222,7 +2222,13 @@ namespace ts {
else if (type.flags & TypeFlags.PropertyName) {
writer.writeKeyword("keyof");
writeSpace(writer);
writeType((<PropertyNameType>type).type, TypeFormatFlags.None);
writeType((<PropertyNameType>type).type, TypeFormatFlags.InElementType);
}
else if (type.flags & TypeFlags.PropertyAccess) {
writeType((<PropertyAccessType>type).objectType, TypeFormatFlags.InElementType);
writePunctuation(writer, SyntaxKind.OpenBracketToken);
writeType((<PropertyAccessType>type).keyType, TypeFormatFlags.None);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
else {
// Should never get here
@@ -5694,6 +5700,55 @@ namespace ts {
return links.resolvedType;
}
function createPropertyAccessType(objectType: Type, keyType: TypeParameter) {
const type = <PropertyAccessType>createType(TypeFlags.PropertyAccess);
type.objectType = objectType;
type.keyType = keyType;
return type;
}
function getPropertyAccessTypeForTypeParameter(objectType: Type, keyType: TypeParameter) {
const propertyAccessTypes = keyType.resolvedPropertyAccessTypes || (keyType.resolvedPropertyAccessTypes = []);
return propertyAccessTypes[objectType.id] || (propertyAccessTypes[objectType.id] = createPropertyAccessType(objectType, keyType));
}
function getPropertyAccessType(objectType: Type, keyType: Type) {
if (keyType.flags & TypeFlags.TypeParameter) {
return getPropertyAccessTypeForTypeParameter(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;
}
function resolvePropertyAccessTypeNode(node: PropertyAccessTypeNode) {
const objectType = getTypeFromTypeNodeNoAlias(node.objectType);
const keyType = getTypeFromTypeNodeNoAlias(node.keyType);
if (keyType.flags & TypeFlags.TypeParameter &&
getConstraintOfTypeParameter(<TypeParameter>keyType) === getPropertyNameType(objectType)) {
return getPropertyAccessType(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.keyType, Diagnostics.Property_0_is_missing_in_type_1, missing, typeToString(objectType));
return unknownType;
}
return getPropertyAccessType(objectType, keyType);
}
error(node.keyType, Diagnostics.Property_access_element_type_must_be_a_string_literal_type_or_a_type_parameter_constrained_to_keyof_0, typeToString(objectType));
return unknownType;
}
function getTypeFromPropertyAccessTypeNode(node: PropertyAccessTypeNode) {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = resolvePropertyAccessTypeNode(node);
}
return links.resolvedType;
}
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: Node, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
@@ -5855,6 +5910,8 @@ namespace ts {
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node, aliasSymbol, aliasTypeArguments);
case SyntaxKind.TypeOperator:
return getTypeFromTypeOperatorNode(<TypeOperatorNode>node);
case SyntaxKind.PropertyAccessType:
return getTypeFromPropertyAccessTypeNode(<PropertyAccessTypeNode>node);
// This function assumes that an identifier or qualified name is a type expression
// Callers should first ensure this by calling isTypeNode
case SyntaxKind.Identifier:
@@ -6119,6 +6176,9 @@ namespace ts {
if (type.flags & TypeFlags.PropertyName) {
return getPropertyNameType(instantiateType((<PropertyNameType>type).type, mapper));
}
if (type.flags & TypeFlags.PropertyAccess) {
return getPropertyAccessType(instantiateType((<PropertyAccessType>type).objectType, mapper), instantiateType((<PropertyAccessType>type).keyType, mapper));
}
}
return type;
}
@@ -8054,7 +8114,7 @@ namespace ts {
function hasPrimitiveConstraint(type: TypeParameter): boolean {
const constraint = getConstraintOfTypeParameter(type);
return constraint && maybeTypeOfKind(constraint, TypeFlags.Primitive);
return constraint && maybeTypeOfKind(constraint, TypeFlags.Primitive | TypeFlags.PropertyName);
}
function getInferredType(context: InferenceContext, index: number): Type {
@@ -8546,6 +8606,10 @@ 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

@@ -415,6 +415,8 @@ namespace ts {
return emitParenType(<ParenthesizedTypeNode>type);
case SyntaxKind.TypeOperator:
return emitTypeOperator(<TypeOperatorNode>type);
case SyntaxKind.PropertyAccessType:
return emitPropertyAccessType(<PropertyAccessTypeNode>type);
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
return emitSignatureDeclarationWithJsDocComments(<FunctionOrConstructorTypeNode>type);
@@ -514,6 +516,13 @@ namespace ts {
emitType(type.type);
}
function emitPropertyAccessType(node: PropertyAccessTypeNode) {
emitType(node.objectType);
write("[");
emitType(node.keyType);
write("]");
}
function emitTypeLiteral(type: TypeLiteralNode) {
write("{");
if (type.members.length) {

View File

@@ -1751,6 +1751,10 @@
"category": "Error",
"code": 2535
},
"Property access element type must be a string literal type or a type parameter constrained to 'keyof {0}'.": {
"category": "Error",
"code": 2536
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600

View File

@@ -596,6 +596,8 @@ const _super = (function (geti, seti) {
return emitThisType();
case SyntaxKind.TypeOperator:
return emitTypeOperator(<TypeOperatorNode>node);
case SyntaxKind.PropertyAccessType:
return emitPropertyAccessType(<PropertyAccessTypeNode>node);
case SyntaxKind.LiteralType:
return emitLiteralType(<LiteralTypeNode>node);
@@ -1096,6 +1098,13 @@ const _super = (function (geti, seti) {
emit(node.type);
}
function emitPropertyAccessType(node: PropertyAccessTypeNode) {
emit(node.objectType);
write("[");
emit(node.keyType);
write("]");
}
function emitLiteralType(node: LiteralTypeNode) {
emitExpression(node.literal);
}

View File

@@ -136,6 +136,9 @@ namespace ts {
case SyntaxKind.ParenthesizedType:
case SyntaxKind.TypeOperator:
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
case SyntaxKind.PropertyAccessType:
return visitNode(cbNode, (<PropertyAccessTypeNode>node).objectType) ||
visitNode(cbNode, (<PropertyAccessTypeNode>node).keyType);
case SyntaxKind.LiteralType:
return visitNode(cbNode, (<LiteralTypeNode>node).literal);
case SyntaxKind.ObjectBindingPattern:
@@ -2519,10 +2522,19 @@ namespace ts {
function parseArrayTypeOrHigher(): TypeNode {
let type = parseNonArrayType();
while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) {
parseExpected(SyntaxKind.CloseBracketToken);
const node = <ArrayTypeNode>createNode(SyntaxKind.ArrayType, type.pos);
node.elementType = type;
type = finishNode(node);
if (isStartOfType()) {
const node = <PropertyAccessTypeNode>createNode(SyntaxKind.PropertyAccessType, type.pos);
node.objectType = type;
node.keyType = parseType();
parseExpected(SyntaxKind.CloseBracketToken);
type = finishNode(node);
}
else {
const node = <ArrayTypeNode>createNode(SyntaxKind.ArrayType, type.pos);
node.elementType = type;
parseExpected(SyntaxKind.CloseBracketToken);
type = finishNode(node);
}
}
return type;
}

View File

@@ -289,6 +289,7 @@ namespace ts {
case SyntaxKind.ParenthesizedType:
case SyntaxKind.ThisType:
case SyntaxKind.TypeOperator:
case SyntaxKind.PropertyAccessType:
case SyntaxKind.LiteralType:
// TypeScript type nodes are elided.
@@ -1858,6 +1859,7 @@ namespace ts {
// Fallthrough
case SyntaxKind.TypeQuery:
case SyntaxKind.TypeOperator:
case SyntaxKind.PropertyAccessType:
case SyntaxKind.TypeLiteral:
case SyntaxKind.AnyKeyword:
case SyntaxKind.ThisType:

View File

@@ -218,6 +218,7 @@ namespace ts {
ParenthesizedType,
ThisType,
TypeOperator,
PropertyAccessType,
LiteralType,
// Binding patterns
ObjectBindingPattern,
@@ -878,6 +879,12 @@ namespace ts {
type: TypeNode;
}
export interface PropertyAccessTypeNode extends TypeNode {
kind: SyntaxKind.PropertyAccessType;
objectType: TypeNode;
keyType: TypeNode;
}
export interface LiteralTypeNode extends TypeNode {
kind: SyntaxKind.LiteralType;
literal: Expression;
@@ -2624,14 +2631,15 @@ namespace ts {
Union = 1 << 16, // Union (T | U)
Intersection = 1 << 17, // Intersection (T & U)
PropertyName = 1 << 18, // keyof T
PropertyAccess = 1 << 19, // T[K]
/* @internal */
FreshLiteral = 1 << 19, // Fresh literal type
FreshLiteral = 1 << 20, // Fresh literal type
/* @internal */
ContainsWideningType = 1 << 20, // Type is or contains undefined or null widening type
ContainsWideningType = 1 << 21, // Type is or contains undefined or null widening type
/* @internal */
ContainsObjectLiteral = 1 << 21, // Type is or contains object literal type
ContainsObjectLiteral = 1 << 22, // Type is or contains object literal type
/* @internal */
ContainsAnyFunctionType = 1 << 22, // Type is or contains object literal type
ContainsAnyFunctionType = 1 << 23, // Type is or contains object literal type
/* @internal */
Nullable = Undefined | Null,
@@ -2654,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 | StringLike | NumberLike | BooleanLike | ESSymbol,
Narrowable = Any | StructuredType | TypeParameter | PropertyAccess | StringLike | NumberLike | BooleanLike | ESSymbol,
NotUnionOrUnit = Any | ESSymbol | Object,
/* @internal */
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,
@@ -2819,6 +2827,8 @@ namespace ts {
/* @internal */
resolvedPropertyNameType: PropertyNameType;
/* @internal */
resolvedPropertyAccessTypes: PropertyAccessType[];
/* @internal */
isThisType?: boolean;
}
@@ -2826,6 +2836,11 @@ namespace ts {
type: TypeParameter;
}
export interface PropertyAccessType extends Type {
objectType: Type;
keyType: TypeParameter;
}
export const enum SignatureKind {
Call,
Construct,