Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static types for dynamically named properties #11929

Merged
merged 21 commits into from
Nov 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ad88109
Initial implementation of 'keyof T' type operator
ahejlsberg Oct 24, 2016
07478aa
Introduce PropertyNameType
ahejlsberg Oct 25, 2016
c21592e
Initial implementation of 'T[K]' property access types
ahejlsberg Oct 25, 2016
e7cfbfe
Support parametric property access expressions + some renaming
ahejlsberg Oct 26, 2016
4b50ef3
Consider index signatures in type produced by 'keyof T'
ahejlsberg Oct 27, 2016
ece1f19
Refactor getIndexedAccessType to be reusable from checkIndexedAccess
ahejlsberg Oct 27, 2016
8d87971
Minor fixes
ahejlsberg Oct 28, 2016
5515dce
Improved error messages for invalid assignments to identifiers
ahejlsberg Oct 30, 2016
550be9c
Accept new baselines
ahejlsberg Oct 30, 2016
6c20533
Improved error messages for invalid assignments to properties
ahejlsberg Oct 31, 2016
88961cd
Accept new baselines
ahejlsberg Oct 31, 2016
2f34f7c
Improve more error messages
ahejlsberg Oct 31, 2016
f293744
Accept new baselines
ahejlsberg Oct 31, 2016
f9e2085
Unify checking of indexed access expressions and indexed access types
ahejlsberg Oct 31, 2016
03f8403
Accept new baselines
ahejlsberg Oct 31, 2016
8d9356f
Accept additional baselines
ahejlsberg Oct 31, 2016
6b28d21
Merge branch 'master' into keyoftypes
ahejlsberg Oct 31, 2016
41c2054
Improve unification by moving more logic to getIndexedAccessType
ahejlsberg Nov 1, 2016
663985e
Fix 'keyof any' to produce 'string | number'
ahejlsberg Nov 1, 2016
70fc25a
Minor fixes
ahejlsberg Nov 2, 2016
4bbe29a
Adding tests
ahejlsberg Nov 2, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1209,9 +1209,9 @@ namespace ts {
}
else {
forEachChild(node, bind);
if (operator === SyntaxKind.EqualsToken && !isAssignmentTarget(node)) {
if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) {
bindAssignmentTargetFlow(node.left);
if (node.left.kind === SyntaxKind.ElementAccessExpression) {
if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) {
const elementAccess = <ElementAccessExpression>node.left;
if (isNarrowableOperand(elementAccess.expression)) {
currentFlow = createFlowArrayMutation(currentFlow, node);
Expand Down Expand Up @@ -3097,6 +3097,8 @@ namespace ts {
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.ThisType:
case SyntaxKind.TypeOperator:
case SyntaxKind.IndexedAccessType:
case SyntaxKind.LiteralType:
// Types and signatures are TypeScript syntax, and exclude all other facts.
transformFlags = TransformFlags.AssertTypeScript;
Expand Down
436 changes: 244 additions & 192 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,10 @@ namespace ts {
return emitIntersectionType(<IntersectionTypeNode>type);
case SyntaxKind.ParenthesizedType:
return emitParenType(<ParenthesizedTypeNode>type);
case SyntaxKind.TypeOperator:
return emitTypeOperator(<TypeOperatorNode>type);
case SyntaxKind.IndexedAccessType:
return emitPropertyAccessType(<IndexedAccessTypeNode>type);
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
return emitSignatureDeclarationWithJsDocComments(<FunctionOrConstructorTypeNode>type);
Expand Down Expand Up @@ -506,6 +510,19 @@ namespace ts {
write(")");
}

function emitTypeOperator(type: TypeOperatorNode) {
write(tokenToString(type.operator));
write(" ");
emitType(type.type);
}

function emitPropertyAccessType(node: IndexedAccessTypeNode) {
emitType(node.objectType);
write("[");
emitType(node.indexType);
write("]");
}

function emitTypeLiteral(type: TypeLiteralNode) {
write("{");
if (type.members.length) {
Expand Down
54 changes: 33 additions & 21 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,7 @@
"category": "Error",
"code": 2356
},
"The operand of an increment or decrement operator must be a variable, property or indexer.": {
"The operand of an increment or decrement operator must be a variable or a property access.": {
"category": "Error",
"code": 2357
},
Expand Down Expand Up @@ -1099,7 +1099,7 @@
"category": "Error",
"code": 2363
},
"Invalid left-hand side of assignment expression.": {
"The left-hand side of an assignment expression must be a variable or a property access.": {
"category": "Error",
"code": 2364
},
Expand Down Expand Up @@ -1259,7 +1259,7 @@
"category": "Error",
"code": 2405
},
"Invalid left-hand side in 'for...in' statement.": {
"The left-hand side of a 'for...in' statement must be a variable or a property access.": {
"category": "Error",
"code": 2406
},
Expand Down Expand Up @@ -1411,14 +1411,6 @@
"category": "Error",
"code": 2448
},
"The operand of an increment or decrement operator cannot be a constant or a read-only property.": {
"category": "Error",
"code": 2449
},
"Left-hand side of assignment expression cannot be a constant or a read-only property.": {
"category": "Error",
"code": 2450
},
"Cannot redeclare block-scoped variable '{0}'.": {
"category": "Error",
"code": 2451
Expand Down Expand Up @@ -1551,15 +1543,7 @@
"category": "Error",
"code": 2484
},
"The left-hand side of a 'for...of' statement cannot be a constant or a read-only property.": {
"category": "Error",
"code": 2485
},
"The left-hand side of a 'for...in' statement cannot be a constant or a read-only property.": {
"category": "Error",
"code": 2486
},
"Invalid left-hand side in 'for...of' statement.": {
"The left-hand side of a 'for...of' statement must be a variable or a property access.": {
"category": "Error",
"code": 2487
},
Expand Down Expand Up @@ -1747,6 +1731,34 @@
"category": "Error",
"code": 2535
},
"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
},
"Cannot assign to '{0}' because it is not a variable.": {
"category": "Error",
"code": 2539
},
"Cannot assign to '{0}' because it is a constant or a read-only property.": {
"category": "Error",
"code": 2540
},
"The target of an assignment must be a variable or a property access.": {
"category": "Error",
"code": 2541
},
"Index signature in type '{0}' only permits reading.": {
"category": "Error",
"code": 2542
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600
Expand Down Expand Up @@ -2909,7 +2921,7 @@
"category": "Error",
"code": 7016
},
"Index signature of object type implicitly has an 'any' type.": {
"Element implicitly has an 'any' type because type '{0}' has no index signature.": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 this is much better now!

"category": "Error",
"code": 7017
},
Expand Down
17 changes: 17 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,10 @@ const _super = (function (geti, seti) {
return emitExpressionWithTypeArguments(<ExpressionWithTypeArguments>node);
case SyntaxKind.ThisType:
return emitThisType();
case SyntaxKind.TypeOperator:
return emitTypeOperator(<TypeOperatorNode>node);
case SyntaxKind.IndexedAccessType:
return emitPropertyAccessType(<IndexedAccessTypeNode>node);
case SyntaxKind.LiteralType:
return emitLiteralType(<LiteralTypeNode>node);

Expand Down Expand Up @@ -1088,6 +1092,19 @@ const _super = (function (geti, seti) {
write("this");
}

function emitTypeOperator(node: TypeOperatorNode) {
writeTokenText(node.operator);
write(" ");
emit(node.type);
}

function emitPropertyAccessType(node: IndexedAccessTypeNode) {
emit(node.objectType);
write("[");
emit(node.indexType);
write("]");
}

function emitLiteralType(node: LiteralTypeNode) {
emitExpression(node.literal);
}
Expand Down
41 changes: 35 additions & 6 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,11 @@ namespace ts {
case SyntaxKind.IntersectionType:
return visitNodes(cbNodes, (<UnionOrIntersectionTypeNode>node).types);
case SyntaxKind.ParenthesizedType:
return visitNode(cbNode, (<ParenthesizedTypeNode>node).type);
case SyntaxKind.TypeOperator:
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
case SyntaxKind.IndexedAccessType:
return visitNode(cbNode, (<IndexedAccessTypeNode>node).objectType) ||
visitNode(cbNode, (<IndexedAccessTypeNode>node).indexType);
case SyntaxKind.LiteralType:
return visitNode(cbNode, (<LiteralTypeNode>node).literal);
case SyntaxKind.ObjectBindingPattern:
Expand Down Expand Up @@ -2519,14 +2523,39 @@ 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 = <IndexedAccessTypeNode>createNode(SyntaxKind.IndexedAccessType, type.pos);
node.objectType = type;
node.indexType = 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;
}

function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword) {
const node = <TypeOperatorNode>createNode(SyntaxKind.TypeOperator);
parseExpected(operator);
node.operator = operator;
node.type = parseTypeOperatorOrHigher();
return finishNode(node);
}

function parseTypeOperatorOrHigher(): TypeNode {
switch (token()) {
case SyntaxKind.KeyOfKeyword:
return parseTypeOperator(SyntaxKind.KeyOfKeyword);
}
return parseArrayTypeOrHigher();
}

function parseUnionOrIntersectionType(kind: SyntaxKind, parseConstituentType: () => TypeNode, operator: SyntaxKind): TypeNode {
let type = parseConstituentType();
if (token() === operator) {
Expand All @@ -2543,7 +2572,7 @@ namespace ts {
}

function parseIntersectionTypeOrHigher(): TypeNode {
return parseUnionOrIntersectionType(SyntaxKind.IntersectionType, parseArrayTypeOrHigher, SyntaxKind.AmpersandToken);
return parseUnionOrIntersectionType(SyntaxKind.IntersectionType, parseTypeOperatorOrHigher, SyntaxKind.AmpersandToken);
}

function parseUnionTypeOrHigher(): TypeNode {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ namespace ts {
"instanceof": SyntaxKind.InstanceOfKeyword,
"interface": SyntaxKind.InterfaceKeyword,
"is": SyntaxKind.IsKeyword,
"keyof": SyntaxKind.KeyOfKeyword,
"let": SyntaxKind.LetKeyword,
"module": SyntaxKind.ModuleKeyword,
"namespace": SyntaxKind.NamespaceKeyword,
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ namespace ts {
case SyntaxKind.IntersectionType:
case SyntaxKind.ParenthesizedType:
case SyntaxKind.ThisType:
case SyntaxKind.TypeOperator:
case SyntaxKind.IndexedAccessType:
case SyntaxKind.LiteralType:
// TypeScript type nodes are elided.

Expand Down Expand Up @@ -1783,6 +1785,8 @@ namespace ts {
}
// Fallthrough
case SyntaxKind.TypeQuery:
case SyntaxKind.TypeOperator:
case SyntaxKind.IndexedAccessType:
case SyntaxKind.TypeLiteral:
case SyntaxKind.AnyKeyword:
case SyntaxKind.ThisType:
Expand Down
44 changes: 35 additions & 9 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ namespace ts {
DeclareKeyword,
GetKeyword,
IsKeyword,
KeyOfKeyword,
ModuleKeyword,
NamespaceKeyword,
NeverKeyword,
Expand Down Expand Up @@ -216,6 +217,8 @@ namespace ts {
IntersectionType,
ParenthesizedType,
ThisType,
TypeOperator,
IndexedAccessType,
LiteralType,
// Binding patterns
ObjectBindingPattern,
Expand Down Expand Up @@ -882,6 +885,18 @@ namespace ts {
type: TypeNode;
}

export interface TypeOperatorNode extends TypeNode {
kind: SyntaxKind.TypeOperator;
operator: SyntaxKind.KeyOfKeyword;
type: TypeNode;
}

export interface IndexedAccessTypeNode extends TypeNode {
kind: SyntaxKind.IndexedAccessType;
objectType: TypeNode;
indexType: TypeNode;
}

export interface LiteralTypeNode extends TypeNode {
kind: SyntaxKind.LiteralType;
literal: Expression;
Expand Down Expand Up @@ -953,10 +968,6 @@ namespace ts {
operator: PostfixUnaryOperator;
}

export interface PostfixExpression extends UnaryExpression {
_postfixExpressionBrand: any;
}

export interface LeftHandSideExpression extends IncrementExpression {
_leftHandSideExpressionBrand: any;
}
Expand Down Expand Up @@ -2667,14 +2678,16 @@ namespace ts {
Object = 1 << 15, // Object type
Union = 1 << 16, // Union (T | U)
Intersection = 1 << 17, // Intersection (T & U)
Index = 1 << 18, // keyof T
IndexedAccess = 1 << 19, // T[K]
/* @internal */
FreshLiteral = 1 << 18, // Fresh literal type
FreshLiteral = 1 << 20, // Fresh literal type
/* @internal */
ContainsWideningType = 1 << 19, // Type is or contains undefined or null widening type
ContainsWideningType = 1 << 21, // Type is or contains undefined or null widening type
/* @internal */
ContainsObjectLiteral = 1 << 20, // Type is or contains object literal type
ContainsObjectLiteral = 1 << 22, // Type is or contains object literal type
/* @internal */
ContainsAnyFunctionType = 1 << 21, // Type is or contains object literal type
ContainsAnyFunctionType = 1 << 23, // Type is or contains object literal type

/* @internal */
Nullable = Undefined | Null,
Expand All @@ -2697,7 +2710,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 | Index | IndexedAccess | StringLike | NumberLike | BooleanLike | ESSymbol,
NotUnionOrUnit = Any | ESSymbol | Object,
/* @internal */
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,
Expand Down Expand Up @@ -2860,9 +2873,22 @@ namespace ts {
/* @internal */
resolvedApparentType: Type;
/* @internal */
resolvedIndexType: IndexType;
/* @internal */
resolvedIndexedAccessTypes: IndexedAccessType[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why array instead of map? the array is going to be sparse if indexed with type id.

Copy link
Member Author

@ahejlsberg ahejlsberg Nov 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will be sparse, in the same way that our nodeLinks and symbolLinks arrays are sparse. I believe this performs better than a map.

/* @internal */
isThisType?: boolean;
}

export interface IndexType extends Type {
type: TypeParameter;
}

export interface IndexedAccessType extends Type {
objectType: Type;
indexType: TypeParameter;
}

export const enum SignatureKind {
Call,
Construct,
Expand Down
Loading