Implement type inference in conditional types

This commit is contained in:
Anders Hejlsberg
2018-01-25 12:25:00 -08:00
parent 8e337b5121
commit d608941480
10 changed files with 158 additions and 13 deletions
+32 -1
View File
@@ -101,6 +101,7 @@ namespace ts {
HasLocals = 1 << 5,
IsInterface = 1 << 6,
IsObjectLiteralOrClassExpressionMethod = 1 << 7,
IsInferenceContainer = 1 << 8,
}
const binder = createBinder();
@@ -119,6 +120,7 @@ namespace ts {
let parent: Node;
let container: Node;
let blockScopeContainer: Node;
let inferenceContainer: Node;
let lastContainer: Node;
let seenThisKeyword: boolean;
@@ -186,6 +188,7 @@ namespace ts {
parent = undefined;
container = undefined;
blockScopeContainer = undefined;
inferenceContainer = undefined;
lastContainer = undefined;
seenThisKeyword = false;
currentFlow = undefined;
@@ -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);
}
@@ -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;
@@ -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:
@@ -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 {
@@ -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:
+66 -11
View File
@@ -1169,6 +1169,11 @@ namespace ts {
);
}
}
else if (location.kind === SyntaxKind.ConditionalType) {
// A type parameter declared using 'infer T' in a conditional type is visible only in
// the true branch of the conditional type.
useResult = lastLocation === (<ConditionalTypeNode>location).trueType;
}
if (useResult) {
break loop;
@@ -4628,10 +4633,14 @@ namespace ts {
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.JSDocTemplateTag:
case SyntaxKind.MappedType:
case SyntaxKind.ConditionalType:
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes);
if (node.kind === SyntaxKind.MappedType) {
return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((<MappedTypeNode>node).typeParameter)));
}
else if (node.kind === SyntaxKind.ConditionalType) {
return concatenate(outerTypeParameters, getInferTypeParameters(<ConditionalTypeNode>node));
}
const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(<DeclarationWithTypeParameters>node) || emptyArray);
const thisType = includeThisTypes &&
(node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration) &&
@@ -8078,12 +8087,13 @@ namespace ts {
return type.flags & TypeFlags.Substitution ? (<SubstitutionType>type).typeParameter : type;
}
function createConditionalType(checkType: Type, extendsType: Type, trueType: Type, falseType: Type, target: ConditionalType, mapper: TypeMapper, aliasSymbol: Symbol, aliasTypeArguments: Type[]) {
function createConditionalType(checkType: Type, extendsType: Type, trueType: Type, falseType: Type, inferTypeParameters: TypeParameter[], target: ConditionalType, mapper: TypeMapper, aliasSymbol: Symbol, aliasTypeArguments: Type[]) {
const type = <ConditionalType>createType(TypeFlags.Conditional);
type.checkType = checkType;
type.extendsType = extendsType;
type.trueType = trueType;
type.falseType = falseType;
type.inferTypeParameters = inferTypeParameters;
type.target = target;
type.mapper = mapper;
type.aliasSymbol = aliasSymbol;
@@ -8091,14 +8101,29 @@ namespace ts {
return type;
}
function getConditionalType(checkType: Type, extendsType: Type, baseTrueType: Type, baseFalseType: Type, target: ConditionalType, mapper: TypeMapper, aliasSymbol?: Symbol, baseAliasTypeArguments?: Type[]): Type {
function getConditionalType(checkType: Type, baseExtendsType: Type, baseTrueType: Type, baseFalseType: Type, inferTypeParameters: TypeParameter[], target: ConditionalType, mapper: TypeMapper, aliasSymbol?: Symbol, baseAliasTypeArguments?: Type[]): Type {
// Instantiate extends type without instantiating any 'infer T' type parameters
const extendsType = instantiateType(baseExtendsType, mapper);
let combinedMapper: TypeMapper;
if (inferTypeParameters) {
const inferences = map(inferTypeParameters, createInferenceInfo);
// We don't want inferences from constraints as they may cause us to eagerly resolve the
// conditional type instead of deferring resolution.
inferTypes(inferences, checkType, extendsType, InferencePriority.NoConstraints);
// We infer 'never' when there are no candidates for a type parameter
const inferredTypes = map(inferences, inference => getTypeFromInference(inference) || neverType);
const inferenceMapper = createTypeMapper(inferTypeParameters, inferredTypes);
combinedMapper = mapper ? combineTypeMappers(mapper, inferenceMapper) : inferenceMapper;
}
// Return union of trueType and falseType for any and never since they match anything
if (checkType.flags & (TypeFlags.Any | TypeFlags.Never)) {
return getUnionType([instantiateType(baseTrueType, mapper), instantiateType(baseFalseType, mapper)]);
return getUnionType([instantiateType(baseTrueType, combinedMapper || mapper), instantiateType(baseFalseType, mapper)]);
}
// Instantiate the extends type including inferences for 'infer T' type parameters
const inferredExtendsType = combinedMapper ? instantiateType(baseExtendsType, combinedMapper) : extendsType;
// Return trueType for a definitely true extends check
if (isTypeAssignableTo(checkType, extendsType)) {
return instantiateType(baseTrueType, mapper);
if (isTypeAssignableTo(checkType, inferredExtendsType)) {
return instantiateType(baseTrueType, combinedMapper || mapper);
}
// Return falseType for a definitely false extends check
if (!isTypeAssignableTo(instantiateType(checkType, anyMapper), instantiateType(extendsType, constraintMapper))) {
@@ -8114,25 +8139,45 @@ namespace ts {
return cached;
}
const result = createConditionalType(erasedCheckType, extendsType, trueType, falseType,
target, mapper, aliasSymbol, instantiateTypes(baseAliasTypeArguments, mapper));
inferTypeParameters, target, mapper, aliasSymbol, instantiateTypes(baseAliasTypeArguments, mapper));
if (id) {
conditionalTypes.set(id, result);
}
return result;
}
function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] {
let result: TypeParameter[];
if (node.locals) {
node.locals.forEach(symbol => {
if (symbol.flags & SymbolFlags.TypeParameter) {
result = append(result, getDeclaredTypeOfSymbol(symbol));
}
});
}
return result;
}
function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getConditionalType(
getTypeFromTypeNode(node.checkType), getTypeFromTypeNode(node.extendsType),
getTypeFromTypeNode(node.trueType), getTypeFromTypeNode(node.falseType),
/*target*/ undefined, /*mapper*/ undefined,
getInferTypeParameters(node), /*target*/ undefined, /*mapper*/ undefined,
getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node));
}
return links.resolvedType;
}
function getTypeFromInferTypeNode(node: InferTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter));
}
return links.resolvedType;
}
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
@@ -8423,6 +8468,8 @@ namespace ts {
return getTypeFromMappedTypeNode(<MappedTypeNode>node);
case SyntaxKind.ConditionalType:
return getTypeFromConditionalTypeNode(<ConditionalTypeNode>node);
case SyntaxKind.InferType:
return getTypeFromInferTypeNode(<InferTypeNode>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:
@@ -8714,8 +8761,8 @@ namespace ts {
}
function instantiateConditionalType(type: ConditionalType, mapper: TypeMapper): Type {
return getConditionalType(instantiateType(type.checkType, mapper), instantiateType(type.extendsType, mapper),
type.trueType, type.falseType, type, mapper, type.aliasSymbol, type.aliasTypeArguments);
return getConditionalType(instantiateType(type.checkType, mapper), type.extendsType, type.trueType, type.falseType,
type.inferTypeParameters, type, mapper, type.aliasSymbol, type.aliasTypeArguments);
}
function instantiateType(type: Type, mapper: TypeMapper): Type {
@@ -11206,7 +11253,7 @@ namespace ts {
const templateType = getTemplateTypeFromMappedType(target);
const inference = createInferenceInfo(typeParameter);
inferTypes([inference], sourceType, templateType);
return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : emptyObjectType;
return getTypeFromInference(inference) || emptyObjectType;
}
function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean) {
@@ -11222,6 +11269,12 @@ namespace ts {
return undefined;
}
function getTypeFromInference(inference: InferenceInfo) {
return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) :
inference.contraCandidates ? getCommonSubtype(inference.contraCandidates) :
undefined;
}
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) {
let symbolStack: Symbol[];
let visited: Map<boolean>;
@@ -11381,7 +11434,9 @@ namespace ts {
}
}
else {
source = getApparentType(source);
if (!(priority && InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) {
source = getApparentType(source);
}
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) {
const key = source.id + "," + target.id;
if (visited && visited.get(key)) {
+7
View File
@@ -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:
@@ -557,6 +559,11 @@ namespace ts {
emitType(node.falseType);
}
function emitInferType(node: InferTypeNode) {
write("infer ");
writeTextOfNode(currentText, node.typeParameter.name);
}
function emitParenType(type: ParenthesizedTypeNode) {
write("(");
emitType(type.type);
+7
View File
@@ -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:
@@ -1202,6 +1204,11 @@ namespace ts {
emit(node.falseType);
}
function emitInferType(node: InferTypeNode) {
write("infer ");
emit(node.typeParameter);
}
function emitParenthesizedType(node: ParenthesizedTypeNode) {
writePunctuation("(");
emit(node.type);
+12
View File
@@ -747,6 +747,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;
+14
View File
@@ -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);
@@ -2647,6 +2649,15 @@ 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 parseFunctionOrConstructorType(kind: SyntaxKind): FunctionOrConstructorTypeNode {
const node = <FunctionOrConstructorTypeNode>createNodeWithJSDoc(kind);
if (kind === SyntaxKind.ConstructorType) {
@@ -2733,6 +2744,8 @@ namespace ts {
return parseTupleType();
case SyntaxKind.OpenParenToken:
return parseParenthesizedType();
case SyntaxKind.InferKeyword:
return parseInferType();
default:
return parseTypeReference();
}
@@ -2767,6 +2780,7 @@ namespace ts {
case SyntaxKind.QuestionToken:
case SyntaxKind.ExclamationToken:
case SyntaxKind.DotDotDotToken:
case SyntaxKind.InferKeyword:
return true;
case SyntaxKind.MinusToken:
return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral);
+1
View File
@@ -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,
+11 -1
View File
@@ -215,6 +215,7 @@ namespace ts {
ConstructorKeyword,
DeclareKeyword,
GetKeyword,
InferKeyword,
IsKeyword,
KeyOfKeyword,
ModuleKeyword,
@@ -266,6 +267,7 @@ namespace ts {
UnionType,
IntersectionType,
ConditionalType,
InferType,
ParenthesizedType,
ThisType,
TypeOperator,
@@ -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;
@@ -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;
@@ -3795,6 +3802,8 @@ namespace ts {
trueType: Type;
falseType: Type;
/* @internal */
inferTypeParameters: TypeParameter[];
/* @internal */
target?: ConditionalType;
/* @internal */
mapper?: TypeMapper;
@@ -3870,6 +3879,7 @@ 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
}
export interface InferenceInfo {
+4
View File
@@ -4559,6 +4559,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;
}
+4
View File
@@ -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));