Preserve literal types in contextual unions (#19966)

* Cherrypick non-comparability related changes from prolific literals PR

* Renames and other style changes

* Accept changes to new tests

* Exclude the domain root from contextual typing literals except for type variables

* Readd simple preservation fix

* Add huge map test

* Revert changes to widening on destructuring initalizers

* Use tristate for subtype-reduction type

* Rename type and argument

* Move longer-running test to user suite
This commit is contained in:
Wesley Wigham
2017-12-11 18:03:38 -05:00
committed by GitHub
parent d01f4d140a
commit eba15b5990
14 changed files with 11007 additions and 49 deletions

View File

@@ -4338,7 +4338,7 @@ namespace ts {
type = getTypeWithFacts(type, TypeFacts.NEUndefined);
}
return declaration.initializer ?
getUnionType([type, checkExpressionCached(declaration.initializer)], /*subtypeReduction*/ true) :
getUnionType([type, checkExpressionCached(declaration.initializer)], UnionReduction.Subtype) :
type;
}
@@ -4506,7 +4506,7 @@ namespace ts {
}
}
const type = jsDocType || getUnionType(types, /*subtypeReduction*/ true);
const type = jsDocType || getUnionType(types, UnionReduction.Subtype);
return getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor));
}
@@ -5339,7 +5339,7 @@ namespace ts {
}
}
if (memberTypeList.length) {
const enumType = getUnionType(memberTypeList, /*subtypeReduction*/ false, symbol, /*aliasTypeArguments*/ undefined);
const enumType = getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined);
if (enumType.flags & TypeFlags.Union) {
enumType.flags |= TypeFlags.EnumLiteral;
enumType.symbol = symbol;
@@ -5902,7 +5902,7 @@ namespace ts {
if (unionSignatures.length > 1) {
let thisParameter = signature.thisParameter;
if (forEach(unionSignatures, sig => sig.thisParameter)) {
const thisType = getUnionType(map(unionSignatures, sig => getTypeOfSymbol(sig.thisParameter) || anyType), /*subtypeReduction*/ true);
const thisType = getUnionType(map(unionSignatures, sig => getTypeOfSymbol(sig.thisParameter) || anyType), UnionReduction.Subtype);
thisParameter = createSymbolWithType(signature.thisParameter, thisType);
}
s = cloneSignature(signature);
@@ -5928,7 +5928,7 @@ namespace ts {
indexTypes.push(indexInfo.type);
isAnyReadonly = isAnyReadonly || indexInfo.isReadonly;
}
return createIndexInfo(getUnionType(indexTypes, /*subtypeReduction*/ true), isAnyReadonly);
return createIndexInfo(getUnionType(indexTypes, UnionReduction.Subtype), isAnyReadonly);
}
function resolveUnionTypeMembers(type: UnionType) {
@@ -6616,7 +6616,7 @@ namespace ts {
}
}
if (propTypes.length) {
return getUnionType(propTypes, /*subtypeReduction*/ true);
return getUnionType(propTypes, UnionReduction.Subtype);
}
}
return undefined;
@@ -7003,7 +7003,7 @@ namespace ts {
type = instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper);
}
else if (signature.unionSignatures) {
type = getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true);
type = getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype);
}
else {
type = getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration);
@@ -7845,7 +7845,7 @@ namespace ts {
// expression constructs such as array literals and the || and ?: operators). Named types can
// circularly reference themselves and therefore cannot be subtype reduced during their declaration.
// For example, "type Item = string | (() => Item" is a named type that circularly references itself.
function getUnionType(types: Type[], subtypeReduction?: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
function getUnionType(types: Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
if (types.length === 0) {
return neverType;
}
@@ -7857,11 +7857,15 @@ namespace ts {
if (typeSet.containsAny) {
return anyType;
}
if (subtypeReduction) {
removeSubtypes(typeSet);
}
else if (typeSet.containsLiteralOrUniqueESSymbol) {
removeRedundantLiteralTypes(typeSet);
switch (unionReduction) {
case UnionReduction.Literal:
if (typeSet.containsLiteralOrUniqueESSymbol) {
removeRedundantLiteralTypes(typeSet);
}
break;
case UnionReduction.Subtype:
removeSubtypes(typeSet);
break;
}
if (typeSet.length === 0) {
return typeSet.containsNull ? typeSet.containsNonWideningType ? nullType : nullWideningType :
@@ -7937,7 +7941,7 @@ namespace ts {
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*subtypeReduction*/ false,
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal,
getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node));
}
return links.resolvedType;
@@ -8012,7 +8016,7 @@ namespace ts {
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
const unionType = <UnionType>typeSet[unionIndex];
return getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))),
/*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments);
UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
}
const id = getTypeListId(typeSet);
let type = intersectionTypes.get(id);
@@ -8851,7 +8855,7 @@ namespace ts {
}
}
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) {
return getUnionType(instantiateTypes((<UnionType>type).types, mapper), /*subtypeReduction*/ false, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
return getUnionType(instantiateTypes((<UnionType>type).types, mapper), UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateTypes((<IntersectionType>type).types, mapper), type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
@@ -10713,7 +10717,7 @@ namespace ts {
const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable));
return primaryTypes.length ?
getNullableType(getSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) :
getUnionType(types, /*subtypeReduction*/ true);
getUnionType(types, UnionReduction.Subtype);
}
// Return the leftmost type for which no type to the right is a subtype.
@@ -11004,7 +11008,7 @@ namespace ts {
// Widening an empty object literal transitions from a highly restrictive type to
// a highly inclusive one. For that reason we perform subtype reduction here if the
// union includes empty object types (e.g. reducing {} | string to just {}).
return getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType));
return getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal);
}
if (isArrayType(type) || isTupleType(type)) {
return createTypeReference((<TypeReference>type).target, sameMap((<TypeReference>type).typeArguments, getWidenedType));
@@ -11251,7 +11255,7 @@ namespace ts {
function inferTargetType(sourceType: Type): Type {
inference.candidates = undefined;
inferTypes(inferences, sourceType, templateType, 0, mappedTypeStack);
return inference.candidates ? getUnionType(inference.candidates, /*subtypeReduction*/ true) : emptyObjectType;
return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : emptyObjectType;
}
}
@@ -11610,7 +11614,7 @@ namespace ts {
if (candidates.length > 1) {
const objectLiterals = filter(candidates, isObjectLiteralType);
if (objectLiterals.length) {
const objectLiteralsType = getWidenedType(getUnionType(objectLiterals, /*subtypeReduction*/ true));
const objectLiteralsType = getWidenedType(getUnionType(objectLiterals, UnionReduction.Subtype));
return concatenate(filter(candidates, t => !isObjectLiteralType(t)), [objectLiteralsType]);
}
}
@@ -11637,7 +11641,7 @@ namespace ts {
// union types were requested or if all inferences were made from the return type position, infer a
// union type. Otherwise, infer a common supertype.
const unwidenedType = inference.priority & InferencePriority.Contravariant ? getCommonSubtype(baseCandidates) :
context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? getUnionType(baseCandidates, /*subtypeReduction*/ true) :
context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? getUnionType(baseCandidates, UnionReduction.Subtype) :
getCommonSupertype(baseCandidates);
inferredType = getWidenedType(unwidenedType);
}
@@ -12211,7 +12215,7 @@ namespace ts {
// Apply a mapping function to a type and return the resulting type. If the source type
// is a union type, the mapping function is applied to each constituent type and a union
// of the resulting types is returned.
function mapType(type: Type, mapper: (t: Type) => Type): Type {
function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type {
if (!(type.flags & TypeFlags.Union)) {
return mapper(type);
}
@@ -12232,7 +12236,7 @@ namespace ts {
}
}
}
return mappedTypes ? getUnionType(mappedTypes) : mappedType;
return mappedTypes ? getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : mappedType;
}
function extractTypesOfKind(type: Type, kind: TypeFlags) {
@@ -12291,7 +12295,7 @@ namespace ts {
return elementType.flags & TypeFlags.Never ?
autoArrayType :
createArrayType(elementType.flags & TypeFlags.Union ?
getUnionType((<UnionType>elementType).types, /*subtypeReduction*/ true) :
getUnionType((<UnionType>elementType).types, UnionReduction.Subtype) :
elementType);
}
@@ -12324,7 +12328,7 @@ namespace ts {
// At flow control branch or loop junctions, if the type along every antecedent code path
// is an evolving array type, we construct a combined evolving array type. Otherwise we
// finalize all evolving array types.
function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: boolean) {
function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: UnionReduction) {
return isEvolvingArrayTypeList(types) ?
getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) :
getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction);
@@ -12616,7 +12620,7 @@ namespace ts {
seenIncomplete = true;
}
}
return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction), seenIncomplete);
return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete);
}
function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
@@ -12645,7 +12649,7 @@ namespace ts {
// path that leads to the top.
for (let i = flowLoopStart; i < flowLoopCount; i++) {
if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) {
return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], /*subtypeReduction*/ false), /*incomplete*/ true);
return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true);
}
}
// Add the flow loop junction and reference to the in-process stack and analyze
@@ -12687,7 +12691,7 @@ namespace ts {
}
// The result is incomplete if the first antecedent (the non-looping control flow path)
// is incomplete.
const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction);
const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal);
if (isIncomplete(firstAntecedentType)) {
return createFlowType(result, /*incomplete*/ true);
}
@@ -14052,11 +14056,11 @@ namespace ts {
return mapType(type, t => {
const prop = t.flags & TypeFlags.StructuredType ? getPropertyOfType(t, name) : undefined;
return prop ? getTypeOfSymbol(prop) : undefined;
});
}, /*noReductions*/ true);
}
function getIndexTypeOfContextualType(type: Type, kind: IndexKind) {
return mapType(type, t => getIndexTypeOfStructuredType(t, kind));
return mapType(type, t => getIndexTypeOfStructuredType(t, kind), /*noReductions*/ true);
}
// Return true if the given contextual type is a tuple-like type
@@ -14463,7 +14467,7 @@ namespace ts {
}
}
return createArrayType(elementTypes.length ?
getUnionType(elementTypes, /*subtypeReduction*/ true) :
getUnionType(elementTypes, UnionReduction.Subtype) :
strictNullChecks ? implicitNeverType : undefinedWideningType);
}
@@ -14542,7 +14546,7 @@ namespace ts {
propTypes.push(getTypeOfSymbol(properties[i]));
}
}
const unionType = propTypes.length ? getUnionType(propTypes, /*subtypeReduction*/ true) : undefinedType;
const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType;
return createIndexInfo(unionType, /*isReadonly*/ false);
}
@@ -14889,7 +14893,7 @@ namespace ts {
const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName);
childrenPropSymbol.type = childrenTypes.length === 1 ?
childrenTypes[0] :
createArrayType(getUnionType(childrenTypes, /*subtypeReduction*/ false));
createArrayType(getUnionType(childrenTypes));
attributesTable.set(jsxChildrenPropertyName, childrenPropSymbol);
}
}
@@ -15025,7 +15029,7 @@ namespace ts {
}
}
return getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true);
return getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype);
}
/**
@@ -15217,7 +15221,7 @@ namespace ts {
const types = (elementType as UnionType).types;
return getUnionType(types.map(type => {
return resolveCustomJsxElementAttributesType(openingLikeElement, shouldIncludeAllStatelessAttributesType, type, elementClassType);
}), /*subtypeReduction*/ true);
}), UnionReduction.Subtype);
}
// If the elemType is a string type, we have to return anyType to prevent an error downstream as we will try to find construct or call signature of the type
@@ -17968,7 +17972,7 @@ namespace ts {
}
// Return a union of the return expression types.
type = getUnionType(types, /*subtypeReduction*/ true);
type = getUnionType(types, UnionReduction.Subtype);
}
if (!contextualSignature) {
@@ -18876,7 +18880,7 @@ namespace ts {
leftType;
case SyntaxKind.BarBarToken:
return getTypeFacts(leftType) & TypeFacts.Falsy ?
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], /*subtypeReduction*/ true) :
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) :
leftType;
case SyntaxKind.EqualsToken:
checkAssignmentOperator(rightType);
@@ -19036,7 +19040,7 @@ namespace ts {
checkExpression(node.condition);
const type1 = checkExpression(node.whenTrue, checkMode);
const type2 = checkExpression(node.whenFalse, checkMode);
return getUnionType([type1, type2], /*subtypeReduction*/ true);
return getUnionType([type1, type2], UnionReduction.Subtype);
}
function checkTemplateExpression(node: TemplateExpression): Type {
@@ -20477,7 +20481,7 @@ namespace ts {
return undefined;
}
return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), /*subtypeReduction*/ true);
return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype);
}
/**
@@ -21908,7 +21912,7 @@ namespace ts {
const arrayTypes = (<UnionType>inputType).types;
const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike));
if (filteredTypes !== arrayTypes) {
arrayType = getUnionType(filteredTypes, /*subtypeReduction*/ true);
arrayType = getUnionType(filteredTypes, UnionReduction.Subtype);
}
}
else if (arrayType.flags & TypeFlags.StringLike) {
@@ -21958,7 +21962,7 @@ namespace ts {
return stringType;
}
return getUnionType([arrayElementType, stringType], /*subtypeReduction*/ true);
return getUnionType([arrayElementType, stringType], UnionReduction.Subtype);
}
return arrayElementType;
@@ -22056,7 +22060,7 @@ namespace ts {
return undefined;
}
const returnType = getUnionType(map(signatures, getReturnTypeOfSignature), /*subtypeReduction*/ true);
const returnType = getUnionType(map(signatures, getReturnTypeOfSignature), UnionReduction.Subtype);
const iteratedType = getIteratedTypeOfIterator(returnType, errorNode, /*isAsyncIterator*/ !!asyncMethodType);
if (checkAssignability && errorNode && iteratedType) {
// If `checkAssignability` was specified, we were called from
@@ -22131,7 +22135,7 @@ namespace ts {
return undefined;
}
let nextResult = getUnionType(map(nextMethodSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true);
let nextResult = getUnionType(map(nextMethodSignatures, getReturnTypeOfSignature), UnionReduction.Subtype);
if (isTypeAny(nextResult)) {
return undefined;
}

View File

@@ -2787,7 +2787,7 @@ namespace ts {
/* @internal */ getNullType(): Type;
/* @internal */ getESSymbolType(): Type;
/* @internal */ getNeverType(): Type;
/* @internal */ getUnionType(types: Type[], subtypeReduction?: boolean): Type;
/* @internal */ getUnionType(types: Type[], subtypeReduction?: UnionReduction): Type;
/* @internal */ createArrayType(elementType: Type): Type;
/* @internal */ createPromiseType(type: Type): Type;
@@ -2842,6 +2842,13 @@ namespace ts {
/* @internal */ getAccessibleSymbolChain(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean): Symbol[] | undefined;
}
/* @internal */
export const enum UnionReduction {
None = 0,
Literal,
Subtype
}
export enum NodeBuilderFlags {
None = 0,
// Options

View File

@@ -305,7 +305,7 @@ namespace ts.codefix {
}
}
if (types.length) {
const type = checker.getWidenedType(checker.getUnionType(types, /*subtypeReduction*/ true));
const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype));
paramTypes[parameterIndex] = isRestParameter ? checker.createArrayType(type) : type;
}
}
@@ -542,12 +542,12 @@ namespace ts.codefix {
return checker.getStringType();
}
else if (usageContext.candidateTypes) {
return checker.getWidenedType(checker.getUnionType(map(usageContext.candidateTypes, t => checker.getBaseTypeOfLiteralType(t)), /*subtypeReduction*/ true));
return checker.getWidenedType(checker.getUnionType(map(usageContext.candidateTypes, t => checker.getBaseTypeOfLiteralType(t)), UnionReduction.Subtype));
}
else if (usageContext.properties && hasCallContext(usageContext.properties.get("then" as __String))) {
const paramType = getParameterTypeFromCallContexts(0, usageContext.properties.get("then" as __String).callContexts, /*isRestParameter*/ false, checker);
const types = paramType.getCallSignatures().map(c => c.getReturnType());
return checker.createPromiseType(types.length ? checker.getUnionType(types, /*subtypeReduction*/ true) : checker.getAnyType());
return checker.createPromiseType(types.length ? checker.getUnionType(types, UnionReduction.Subtype) : checker.getAnyType());
}
else if (usageContext.properties && hasCallContext(usageContext.properties.get("push" as __String))) {
return checker.createArrayType(getParameterTypeFromCallContexts(0, usageContext.properties.get("push" as __String).callContexts, /*isRestParameter*/ false, checker));
@@ -610,7 +610,7 @@ namespace ts.codefix {
}
if (types.length) {
const type = checker.getWidenedType(checker.getUnionType(types, /*subtypeReduction*/ true));
const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype));
return isRestParameter ? checker.createArrayType(type) : type;
}
return undefined;