Skip to content

Commit

Permalink
Merge pull request #21496 from Microsoft/inferTypes
Browse files Browse the repository at this point in the history
Type inference in conditional types
  • Loading branch information
ahejlsberg authored Feb 3, 2018
2 parents c4485bc + 4ae8445 commit 3ef1b56
Show file tree
Hide file tree
Showing 25 changed files with 1,565 additions and 409 deletions.
33 changes: 32 additions & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ namespace ts {
HasLocals = 1 << 5,
IsInterface = 1 << 6,
IsObjectLiteralOrClassExpressionMethod = 1 << 7,
IsInferenceContainer = 1 << 8,
}

const binder = createBinder();
Expand All @@ -119,6 +120,7 @@ namespace ts {
let parent: Node;
let container: Node;
let blockScopeContainer: Node;
let inferenceContainer: Node;
let lastContainer: Node;
let seenThisKeyword: boolean;

Expand Down Expand Up @@ -186,6 +188,7 @@ namespace ts {
parent = undefined;
container = undefined;
blockScopeContainer = undefined;
inferenceContainer = undefined;
lastContainer = undefined;
seenThisKeyword = false;
currentFlow = undefined;
Expand Down Expand Up @@ -561,6 +564,13 @@ namespace ts {
bindChildren(node);
node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis;
}
else if (containerFlags & ContainerFlags.IsInferenceContainer) {
const saveInferenceContainer = inferenceContainer;
inferenceContainer = node;
node.locals = undefined;
bindChildren(node);
inferenceContainer = saveInferenceContainer;
}
else {
bindChildren(node);
}
Expand Down Expand Up @@ -1417,6 +1427,9 @@ namespace ts {
case SyntaxKind.MappedType:
return ContainerFlags.IsContainer | ContainerFlags.HasLocals;

case SyntaxKind.ConditionalType:
return ContainerFlags.IsInferenceContainer;

case SyntaxKind.SourceFile:
return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals;

Expand Down Expand Up @@ -2059,7 +2072,7 @@ namespace ts {
case SyntaxKind.TypePredicate:
return checkTypePredicate(node as TypePredicateNode);
case SyntaxKind.TypeParameter:
return declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
return bindTypeParameter(node as TypeParameterDeclaration);
case SyntaxKind.Parameter:
return bindParameter(<ParameterDeclaration>node);
case SyntaxKind.VariableDeclaration:
Expand Down Expand Up @@ -2576,6 +2589,23 @@ namespace ts {
: declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes);
}

function bindTypeParameter(node: TypeParameterDeclaration) {
if (node.parent.kind === SyntaxKind.InferType) {
if (inferenceContainer) {
if (!inferenceContainer.locals) {
inferenceContainer.locals = createSymbolTable();
}
declareSymbol(inferenceContainer.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
}
else {
bindAnonymousDeclaration(node, SymbolFlags.TypeParameter, getDeclarationName(node));
}
}
else {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
}
}

// reachability checks

function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean {
Expand Down Expand Up @@ -3441,6 +3471,7 @@ namespace ts {
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
case SyntaxKind.ConditionalType:
case SyntaxKind.InferType:
case SyntaxKind.ParenthesizedType:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
Expand Down
153 changes: 115 additions & 38 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,8 @@ namespace ts {
return emitIntersectionType(<IntersectionTypeNode>type);
case SyntaxKind.ConditionalType:
return emitConditionalType(<ConditionalTypeNode>type);
case SyntaxKind.InferType:
return emitInferType(<InferTypeNode>type);
case SyntaxKind.ParenthesizedType:
return emitParenType(<ParenthesizedTypeNode>type);
case SyntaxKind.TypeOperator:
Expand Down Expand Up @@ -552,11 +554,19 @@ namespace ts {
write(" extends ");
emitType(node.extendsType);
write(" ? ");
const prevEnclosingDeclaration = enclosingDeclaration;
enclosingDeclaration = node.trueType;
emitType(node.trueType);
enclosingDeclaration = prevEnclosingDeclaration;
write(" : ");
emitType(node.falseType);
}

function emitInferType(node: InferTypeNode) {
write("infer ");
writeTextOfNode(currentText, node.typeParameter.name);
}

function emitParenType(type: ParenthesizedTypeNode) {
write("(");
emitType(type.type);
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,10 @@
"category": "Error",
"code": 1337
},
"'infer' declarations are only permitted in the 'extends' clause of a conditional type.": {
"category": "Error",
"code": 1338
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
20 changes: 17 additions & 3 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,8 @@ namespace ts {
return emitIntersectionType(<IntersectionTypeNode>node);
case SyntaxKind.ConditionalType:
return emitConditionalType(<ConditionalTypeNode>node);
case SyntaxKind.InferType:
return emitInferType(<InferTypeNode>node);
case SyntaxKind.ParenthesizedType:
return emitParenthesizedType(<ParenthesizedTypeNode>node);
case SyntaxKind.ExpressionWithTypeArguments:
Expand Down Expand Up @@ -1194,14 +1196,26 @@ namespace ts {

function emitConditionalType(node: ConditionalTypeNode) {
emit(node.checkType);
write(" extends ");
writeSpace();
writeKeyword("extends");
writeSpace();
emit(node.extendsType);
write(" ? ");
writeSpace();
writePunctuation("?");
writeSpace();
emit(node.trueType);
write(" : ");
writeSpace();
writePunctuation(":");
writeSpace();
emit(node.falseType);
}

function emitInferType(node: InferTypeNode) {
writeKeyword("infer");
writeSpace();
emit(node.typeParameter);
}

function emitParenthesizedType(node: ParenthesizedTypeNode) {
writePunctuation("(");
emit(node.type);
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,18 @@ namespace ts {
: node;
}

export function createInferTypeNode(typeParameter: TypeParameterDeclaration) {
const node = <InferTypeNode>createSynthesizedNode(SyntaxKind.InferType);
node.typeParameter = typeParameter;
return node;
}

export function updateInferTypeNode(node: InferTypeNode, typeParameter: TypeParameterDeclaration) {
return node.typeParameter !== typeParameter
? updateNode(createInferTypeNode(typeParameter), node)
: node;
}

export function createParenthesizedType(type: TypeNode) {
const node = <ParenthesizedTypeNode>createSynthesizedNode(SyntaxKind.ParenthesizedType);
node.type = type;
Expand Down
14 changes: 14 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ namespace ts {
visitNode(cbNode, (<ConditionalTypeNode>node).extendsType) ||
visitNode(cbNode, (<ConditionalTypeNode>node).trueType) ||
visitNode(cbNode, (<ConditionalTypeNode>node).falseType);
case SyntaxKind.InferType:
return visitNode(cbNode, (<InferTypeNode>node).typeParameter);
case SyntaxKind.ParenthesizedType:
case SyntaxKind.TypeOperator:
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
Expand Down Expand Up @@ -2767,6 +2769,7 @@ namespace ts {
case SyntaxKind.QuestionToken:
case SyntaxKind.ExclamationToken:
case SyntaxKind.DotDotDotToken:
case SyntaxKind.InferKeyword:
return true;
case SyntaxKind.MinusToken:
return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral);
Expand Down Expand Up @@ -2843,12 +2846,23 @@ namespace ts {
return finishNode(node);
}

function parseInferType(): InferTypeNode {
const node = <InferTypeNode>createNode(SyntaxKind.InferType);
parseExpected(SyntaxKind.InferKeyword);
const typeParameter = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
typeParameter.name = parseIdentifier();
node.typeParameter = finishNode(typeParameter);
return finishNode(node);
}

function parseTypeOperatorOrHigher(): TypeNode {
const operator = token();
switch (operator) {
case SyntaxKind.KeyOfKeyword:
case SyntaxKind.UniqueKeyword:
return parseTypeOperator(operator);
case SyntaxKind.InferKeyword:
return parseInferType();
case SyntaxKind.DotDotDotToken: {
const result = createNode(SyntaxKind.JSDocVariadicType) as JSDocVariadicType;
nextToken();
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ namespace ts {
"implements": SyntaxKind.ImplementsKeyword,
"import": SyntaxKind.ImportKeyword,
"in": SyntaxKind.InKeyword,
"infer": SyntaxKind.InferKeyword,
"instanceof": SyntaxKind.InstanceOfKeyword,
"interface": SyntaxKind.InterfaceKeyword,
"is": SyntaxKind.IsKeyword,
Expand Down
15 changes: 14 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ namespace ts {
ConstructorKeyword,
DeclareKeyword,
GetKeyword,
InferKeyword,
IsKeyword,
KeyOfKeyword,
ModuleKeyword,
Expand Down Expand Up @@ -266,6 +267,7 @@ namespace ts {
UnionType,
IntersectionType,
ConditionalType,
InferType,
ParenthesizedType,
ThisType,
TypeOperator,
Expand Down Expand Up @@ -771,7 +773,7 @@ namespace ts {

export interface TypeParameterDeclaration extends NamedDeclaration {
kind: SyntaxKind.TypeParameter;
parent?: DeclarationWithTypeParameters;
parent?: DeclarationWithTypeParameters | InferTypeNode;
name: Identifier;
constraint?: TypeNode;
default?: TypeNode;
Expand Down Expand Up @@ -1125,6 +1127,11 @@ namespace ts {
falseType: TypeNode;
}

export interface InferTypeNode extends TypeNode {
kind: SyntaxKind.InferType;
typeParameter: TypeParameterDeclaration;
}

export interface ParenthesizedTypeNode extends TypeNode {
kind: SyntaxKind.ParenthesizedType;
type: TypeNode;
Expand Down Expand Up @@ -3556,6 +3563,8 @@ namespace ts {
pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any)
aliasSymbol?: Symbol; // Alias associated with type
aliasTypeArguments?: Type[]; // Alias type arguments (if any)
/* @internal */
resolvedAnyInstantiation?: Type; // Instantiation with type parameters mapped to any
}

/* @internal */
Expand Down Expand Up @@ -3801,6 +3810,8 @@ namespace ts {
trueType: Type;
falseType: Type;
/* @internal */
inferTypeParameters: TypeParameter[];
/* @internal */
target?: ConditionalType;
/* @internal */
mapper?: TypeMapper;
Expand Down Expand Up @@ -3876,6 +3887,8 @@ namespace ts {
NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type
MappedType = 1 << 1, // Reverse inference for mapped type
ReturnType = 1 << 2, // Inference made from return type of generic function
NoConstraints = 1 << 3, // Don't infer from constraints of instantiable types
AlwaysStrict = 1 << 4, // Always use strict rules for contravariant inferences
}

export interface InferenceInfo {
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4625,6 +4625,10 @@ namespace ts {
return node.kind === SyntaxKind.ConditionalType;
}

export function isInferTypeNode(node: Node): node is InferTypeNode {
return node.kind === SyntaxKind.InferType;
}

export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode {
return node.kind === SyntaxKind.ParenthesizedType;
}
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,10 @@ namespace ts {
visitNode((<ConditionalTypeNode>node).trueType, visitor, isTypeNode),
visitNode((<ConditionalTypeNode>node).falseType, visitor, isTypeNode));

case SyntaxKind.InferType:
return updateInferTypeNode(<InferTypeNode>node,
visitNode((<InferTypeNode>node).typeParameter, visitor, isTypeParameterDeclaration));

case SyntaxKind.ParenthesizedType:
return updateParenthesizedType(<ParenthesizedTypeNode>node,
visitNode((<ParenthesizedTypeNode>node).type, visitor, isTypeNode));
Expand Down
Loading

0 comments on commit 3ef1b56

Please sign in to comment.