Skip to content

Commit c21592e

Browse files
committed
Initial implementation of 'T[K]' property access types
1 parent 07478aa commit c21592e

File tree

8 files changed

+127
-11
lines changed

8 files changed

+127
-11
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3067,6 +3067,7 @@ namespace ts {
30673067
case SyntaxKind.TypeAliasDeclaration:
30683068
case SyntaxKind.ThisType:
30693069
case SyntaxKind.TypeOperator:
3070+
case SyntaxKind.PropertyAccessType:
30703071
case SyntaxKind.LiteralType:
30713072
// Types and signatures are TypeScript syntax, and exclude all other facts.
30723073
transformFlags = TransformFlags.AssertTypeScript;

src/compiler/checker.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2222,7 +2222,13 @@ namespace ts {
22222222
else if (type.flags & TypeFlags.PropertyName) {
22232223
writer.writeKeyword("keyof");
22242224
writeSpace(writer);
2225-
writeType((<PropertyNameType>type).type, TypeFormatFlags.None);
2225+
writeType((<PropertyNameType>type).type, TypeFormatFlags.InElementType);
2226+
}
2227+
else if (type.flags & TypeFlags.PropertyAccess) {
2228+
writeType((<PropertyAccessType>type).objectType, TypeFormatFlags.InElementType);
2229+
writePunctuation(writer, SyntaxKind.OpenBracketToken);
2230+
writeType((<PropertyAccessType>type).keyType, TypeFormatFlags.None);
2231+
writePunctuation(writer, SyntaxKind.CloseBracketToken);
22262232
}
22272233
else {
22282234
// Should never get here
@@ -5694,6 +5700,55 @@ namespace ts {
56945700
return links.resolvedType;
56955701
}
56965702

5703+
function createPropertyAccessType(objectType: Type, keyType: TypeParameter) {
5704+
const type = <PropertyAccessType>createType(TypeFlags.PropertyAccess);
5705+
type.objectType = objectType;
5706+
type.keyType = keyType;
5707+
return type;
5708+
}
5709+
5710+
function getPropertyAccessTypeForTypeParameter(objectType: Type, keyType: TypeParameter) {
5711+
const propertyAccessTypes = keyType.resolvedPropertyAccessTypes || (keyType.resolvedPropertyAccessTypes = []);
5712+
return propertyAccessTypes[objectType.id] || (propertyAccessTypes[objectType.id] = createPropertyAccessType(objectType, keyType));
5713+
}
5714+
5715+
function getPropertyAccessType(objectType: Type, keyType: Type) {
5716+
if (keyType.flags & TypeFlags.TypeParameter) {
5717+
return getPropertyAccessTypeForTypeParameter(objectType, <TypeParameter>keyType);
5718+
}
5719+
if (isTypeOfKind(keyType, TypeFlags.StringLiteral) && !(keyType.flags & TypeFlags.Intersection)) {
5720+
return mapType(keyType, t => getTypeOfPropertyOfType(objectType, escapeIdentifier((<LiteralType>t).text)) || unknownType);
5721+
}
5722+
return keyType.flags & TypeFlags.Any ? anyType : unknownType;
5723+
}
5724+
5725+
function resolvePropertyAccessTypeNode(node: PropertyAccessTypeNode) {
5726+
const objectType = getTypeFromTypeNodeNoAlias(node.objectType);
5727+
const keyType = getTypeFromTypeNodeNoAlias(node.keyType);
5728+
if (keyType.flags & TypeFlags.TypeParameter &&
5729+
getConstraintOfTypeParameter(<TypeParameter>keyType) === getPropertyNameType(objectType)) {
5730+
return getPropertyAccessType(objectType, keyType);
5731+
}
5732+
if (isTypeOfKind(keyType, TypeFlags.StringLiteral) && !(keyType.flags & TypeFlags.Intersection)) {
5733+
const missing = forEachType(keyType, t => getTypeOfPropertyOfType(objectType, escapeIdentifier((<LiteralType>t).text)) ? undefined : (<LiteralType>t).text);
5734+
if (missing) {
5735+
error(node.keyType, Diagnostics.Property_0_is_missing_in_type_1, missing, typeToString(objectType));
5736+
return unknownType;
5737+
}
5738+
return getPropertyAccessType(objectType, keyType);
5739+
}
5740+
error(node.keyType, Diagnostics.Property_access_element_type_must_be_a_string_literal_type_or_a_type_parameter_constrained_to_keyof_0, typeToString(objectType));
5741+
return unknownType;
5742+
}
5743+
5744+
function getTypeFromPropertyAccessTypeNode(node: PropertyAccessTypeNode) {
5745+
const links = getNodeLinks(node);
5746+
if (!links.resolvedType) {
5747+
links.resolvedType = resolvePropertyAccessTypeNode(node);
5748+
}
5749+
return links.resolvedType;
5750+
}
5751+
56975752
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: Node, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
56985753
const links = getNodeLinks(node);
56995754
if (!links.resolvedType) {
@@ -5855,6 +5910,8 @@ namespace ts {
58555910
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node, aliasSymbol, aliasTypeArguments);
58565911
case SyntaxKind.TypeOperator:
58575912
return getTypeFromTypeOperatorNode(<TypeOperatorNode>node);
5913+
case SyntaxKind.PropertyAccessType:
5914+
return getTypeFromPropertyAccessTypeNode(<PropertyAccessTypeNode>node);
58585915
// This function assumes that an identifier or qualified name is a type expression
58595916
// Callers should first ensure this by calling isTypeNode
58605917
case SyntaxKind.Identifier:
@@ -6119,6 +6176,9 @@ namespace ts {
61196176
if (type.flags & TypeFlags.PropertyName) {
61206177
return getPropertyNameType(instantiateType((<PropertyNameType>type).type, mapper));
61216178
}
6179+
if (type.flags & TypeFlags.PropertyAccess) {
6180+
return getPropertyAccessType(instantiateType((<PropertyAccessType>type).objectType, mapper), instantiateType((<PropertyAccessType>type).keyType, mapper));
6181+
}
61226182
}
61236183
return type;
61246184
}
@@ -8054,7 +8114,7 @@ namespace ts {
80548114

80558115
function hasPrimitiveConstraint(type: TypeParameter): boolean {
80568116
const constraint = getConstraintOfTypeParameter(type);
8057-
return constraint && maybeTypeOfKind(constraint, TypeFlags.Primitive);
8117+
return constraint && maybeTypeOfKind(constraint, TypeFlags.Primitive | TypeFlags.PropertyName);
80588118
}
80598119

80608120
function getInferredType(context: InferenceContext, index: number): Type {
@@ -8546,6 +8606,10 @@ namespace ts {
85468606
return containsType(target.types, source);
85478607
}
85488608

8609+
function forEachType<T>(type: Type, f: (t: Type) => T): T {
8610+
return type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, f) : f(type);
8611+
}
8612+
85498613
function filterType(type: Type, f: (t: Type) => boolean): Type {
85508614
if (type.flags & TypeFlags.Union) {
85518615
const types = (<UnionType>type).types;

src/compiler/declarationEmitter.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@ namespace ts {
415415
return emitParenType(<ParenthesizedTypeNode>type);
416416
case SyntaxKind.TypeOperator:
417417
return emitTypeOperator(<TypeOperatorNode>type);
418+
case SyntaxKind.PropertyAccessType:
419+
return emitPropertyAccessType(<PropertyAccessTypeNode>type);
418420
case SyntaxKind.FunctionType:
419421
case SyntaxKind.ConstructorType:
420422
return emitSignatureDeclarationWithJsDocComments(<FunctionOrConstructorTypeNode>type);
@@ -514,6 +516,13 @@ namespace ts {
514516
emitType(type.type);
515517
}
516518

519+
function emitPropertyAccessType(node: PropertyAccessTypeNode) {
520+
emitType(node.objectType);
521+
write("[");
522+
emitType(node.keyType);
523+
write("]");
524+
}
525+
517526
function emitTypeLiteral(type: TypeLiteralNode) {
518527
write("{");
519528
if (type.members.length) {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1751,6 +1751,10 @@
17511751
"category": "Error",
17521752
"code": 2535
17531753
},
1754+
"Property access element type must be a string literal type or a type parameter constrained to 'keyof {0}'.": {
1755+
"category": "Error",
1756+
"code": 2536
1757+
},
17541758
"JSX element attributes type '{0}' may not be a union type.": {
17551759
"category": "Error",
17561760
"code": 2600

src/compiler/emitter.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,8 @@ const _super = (function (geti, seti) {
596596
return emitThisType();
597597
case SyntaxKind.TypeOperator:
598598
return emitTypeOperator(<TypeOperatorNode>node);
599+
case SyntaxKind.PropertyAccessType:
600+
return emitPropertyAccessType(<PropertyAccessTypeNode>node);
599601
case SyntaxKind.LiteralType:
600602
return emitLiteralType(<LiteralTypeNode>node);
601603

@@ -1096,6 +1098,13 @@ const _super = (function (geti, seti) {
10961098
emit(node.type);
10971099
}
10981100

1101+
function emitPropertyAccessType(node: PropertyAccessTypeNode) {
1102+
emit(node.objectType);
1103+
write("[");
1104+
emit(node.keyType);
1105+
write("]");
1106+
}
1107+
10991108
function emitLiteralType(node: LiteralTypeNode) {
11001109
emitExpression(node.literal);
11011110
}

src/compiler/parser.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ namespace ts {
136136
case SyntaxKind.ParenthesizedType:
137137
case SyntaxKind.TypeOperator:
138138
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
139+
case SyntaxKind.PropertyAccessType:
140+
return visitNode(cbNode, (<PropertyAccessTypeNode>node).objectType) ||
141+
visitNode(cbNode, (<PropertyAccessTypeNode>node).keyType);
139142
case SyntaxKind.LiteralType:
140143
return visitNode(cbNode, (<LiteralTypeNode>node).literal);
141144
case SyntaxKind.ObjectBindingPattern:
@@ -2519,10 +2522,19 @@ namespace ts {
25192522
function parseArrayTypeOrHigher(): TypeNode {
25202523
let type = parseNonArrayType();
25212524
while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) {
2522-
parseExpected(SyntaxKind.CloseBracketToken);
2523-
const node = <ArrayTypeNode>createNode(SyntaxKind.ArrayType, type.pos);
2524-
node.elementType = type;
2525-
type = finishNode(node);
2525+
if (isStartOfType()) {
2526+
const node = <PropertyAccessTypeNode>createNode(SyntaxKind.PropertyAccessType, type.pos);
2527+
node.objectType = type;
2528+
node.keyType = parseType();
2529+
parseExpected(SyntaxKind.CloseBracketToken);
2530+
type = finishNode(node);
2531+
}
2532+
else {
2533+
const node = <ArrayTypeNode>createNode(SyntaxKind.ArrayType, type.pos);
2534+
node.elementType = type;
2535+
parseExpected(SyntaxKind.CloseBracketToken);
2536+
type = finishNode(node);
2537+
}
25262538
}
25272539
return type;
25282540
}

src/compiler/transformers/ts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ namespace ts {
289289
case SyntaxKind.ParenthesizedType:
290290
case SyntaxKind.ThisType:
291291
case SyntaxKind.TypeOperator:
292+
case SyntaxKind.PropertyAccessType:
292293
case SyntaxKind.LiteralType:
293294
// TypeScript type nodes are elided.
294295

@@ -1858,6 +1859,7 @@ namespace ts {
18581859
// Fallthrough
18591860
case SyntaxKind.TypeQuery:
18601861
case SyntaxKind.TypeOperator:
1862+
case SyntaxKind.PropertyAccessType:
18611863
case SyntaxKind.TypeLiteral:
18621864
case SyntaxKind.AnyKeyword:
18631865
case SyntaxKind.ThisType:

src/compiler/types.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ namespace ts {
218218
ParenthesizedType,
219219
ThisType,
220220
TypeOperator,
221+
PropertyAccessType,
221222
LiteralType,
222223
// Binding patterns
223224
ObjectBindingPattern,
@@ -878,6 +879,12 @@ namespace ts {
878879
type: TypeNode;
879880
}
880881

882+
export interface PropertyAccessTypeNode extends TypeNode {
883+
kind: SyntaxKind.PropertyAccessType;
884+
objectType: TypeNode;
885+
keyType: TypeNode;
886+
}
887+
881888
export interface LiteralTypeNode extends TypeNode {
882889
kind: SyntaxKind.LiteralType;
883890
literal: Expression;
@@ -2624,14 +2631,15 @@ namespace ts {
26242631
Union = 1 << 16, // Union (T | U)
26252632
Intersection = 1 << 17, // Intersection (T & U)
26262633
PropertyName = 1 << 18, // keyof T
2634+
PropertyAccess = 1 << 19, // T[K]
26272635
/* @internal */
2628-
FreshLiteral = 1 << 19, // Fresh literal type
2636+
FreshLiteral = 1 << 20, // Fresh literal type
26292637
/* @internal */
2630-
ContainsWideningType = 1 << 20, // Type is or contains undefined or null widening type
2638+
ContainsWideningType = 1 << 21, // Type is or contains undefined or null widening type
26312639
/* @internal */
2632-
ContainsObjectLiteral = 1 << 21, // Type is or contains object literal type
2640+
ContainsObjectLiteral = 1 << 22, // Type is or contains object literal type
26332641
/* @internal */
2634-
ContainsAnyFunctionType = 1 << 22, // Type is or contains object literal type
2642+
ContainsAnyFunctionType = 1 << 23, // Type is or contains object literal type
26352643

26362644
/* @internal */
26372645
Nullable = Undefined | Null,
@@ -2654,7 +2662,7 @@ namespace ts {
26542662

26552663
// 'Narrowable' types are types where narrowing actually narrows.
26562664
// This *should* be every type other than null, undefined, void, and never
2657-
Narrowable = Any | StructuredType | TypeParameter | StringLike | NumberLike | BooleanLike | ESSymbol,
2665+
Narrowable = Any | StructuredType | TypeParameter | PropertyAccess | StringLike | NumberLike | BooleanLike | ESSymbol,
26582666
NotUnionOrUnit = Any | ESSymbol | Object,
26592667
/* @internal */
26602668
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,
@@ -2819,13 +2827,20 @@ namespace ts {
28192827
/* @internal */
28202828
resolvedPropertyNameType: PropertyNameType;
28212829
/* @internal */
2830+
resolvedPropertyAccessTypes: PropertyAccessType[];
2831+
/* @internal */
28222832
isThisType?: boolean;
28232833
}
28242834

28252835
export interface PropertyNameType extends Type {
28262836
type: TypeParameter;
28272837
}
28282838

2839+
export interface PropertyAccessType extends Type {
2840+
objectType: Type;
2841+
keyType: TypeParameter;
2842+
}
2843+
28292844
export const enum SignatureKind {
28302845
Call,
28312846
Construct,

0 commit comments

Comments
 (0)