Skip to content

Commit

Permalink
Unify substitution type any handling into costruction and instantia…
Browse files Browse the repository at this point in the history
…tion (#30592)

* Unify substitution type `any` handling into costruction and instantiation

* Strengthen supertype reduction check to reduce breakage

* Rename conditional type fields per convention

* Explicitly handle anyish signatures in compareSignaturesRelated so strict variance doesnt kill subtyping

* Allow tuple expansions to an `any` rest to be considered an `any` signature as well
  • Loading branch information
weswigham authored and RyanCavanaugh committed Mar 27, 2019
1 parent 307bf39 commit b7881a2
Show file tree
Hide file tree
Showing 17 changed files with 1,280 additions and 66 deletions.
95 changes: 55 additions & 40 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3492,8 +3492,8 @@ namespace ts {
context.inferTypeParameters = (<ConditionalType>type).root.inferTypeParameters;
const extendsTypeNode = typeToTypeNodeHelper((<ConditionalType>type).extendsType, context);
context.inferTypeParameters = saveInferTypeParameters;
const trueTypeNode = typeToTypeNodeHelper(getTrueTypeFromConditionalType(<ConditionalType>type), context);
const falseTypeNode = typeToTypeNodeHelper(getFalseTypeFromConditionalType(<ConditionalType>type), context);
const trueTypeNode = typeToTypeNodeHelper((<ConditionalType>type).trueType, context);
const falseTypeNode = typeToTypeNodeHelper((<ConditionalType>type).falseType, context);
context.approximateLength += 15;
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
}
Expand Down Expand Up @@ -7477,6 +7477,10 @@ namespace ts {
}

function getConstraintOfIndexedAccess(type: IndexedAccessType) {
return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined;
}

function getConstraintFromIndexedAccess(type: IndexedAccessType) {
const objectType = getConstraintOfType(type.objectType) || type.objectType;
if (objectType !== type.objectType) {
const constraint = getIndexedAccessType(objectType, type.indexType, /*accessNode*/ undefined, errorType);
Expand All @@ -7488,24 +7492,14 @@ namespace ts {
return baseConstraint && baseConstraint !== type ? baseConstraint : undefined;
}

function getDefaultConstraintOfTrueBranchOfConditionalType(root: ConditionalRoot, combinedMapper: TypeMapper | undefined, mapper: TypeMapper | undefined) {
const rootTrueType = root.trueType;
const rootTrueConstraint = !(rootTrueType.flags & TypeFlags.Substitution)
? rootTrueType
: instantiateType(((<SubstitutionType>rootTrueType).substitute), combinedMapper || mapper).flags & TypeFlags.AnyOrUnknown
? (<SubstitutionType>rootTrueType).typeVariable
: getIntersectionType([(<SubstitutionType>rootTrueType).substitute, (<SubstitutionType>rootTrueType).typeVariable]);
return instantiateType(rootTrueConstraint, combinedMapper || mapper);
}

function getDefaultConstraintOfConditionalType(type: ConditionalType) {
if (!type.resolvedDefaultConstraint) {
// An `any` branch of a conditional type would normally be viral - specifically, without special handling here,
// a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to
// just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type,
// in effect treating `any` like `never` rather than `unknown` in this location.
const trueConstraint = getDefaultConstraintOfTrueBranchOfConditionalType(type.root, type.combinedMapper, type.mapper);
const falseConstraint = getFalseTypeFromConditionalType(type);
const trueConstraint = getInferredTrueTypeFromConditionalType(type);
const falseConstraint = type.falseType;
type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]);
}
return type.resolvedDefaultConstraint;
Expand Down Expand Up @@ -7537,10 +7531,14 @@ namespace ts {
return undefined;
}

function getConstraintOfConditionalType(type: ConditionalType) {
function getConstraintFromConditionalType(type: ConditionalType) {
return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type);
}

function getConstraintOfConditionalType(type: ConditionalType) {
return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined;
}

function getUnionConstraintOfIntersection(type: IntersectionType, targetIsUnion: boolean) {
let constraints: Type[] | undefined;
let hasDisjointDomainType = false;
Expand Down Expand Up @@ -7617,7 +7615,7 @@ namespace ts {
if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) {
return circularConstraintType;
}
if (constraintDepth === 50) {
if (constraintDepth >= 50) {
// We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a
// very high likelyhood we're dealing with an infinite generic type that perpetually generates
// new type identities as we descend into it. We stop the recursion here and mark this type
Expand Down Expand Up @@ -7684,8 +7682,11 @@ namespace ts {
return baseIndexedAccess && baseIndexedAccess !== errorType ? getBaseConstraint(baseIndexedAccess) : undefined;
}
if (t.flags & TypeFlags.Conditional) {
const constraint = getConstraintOfConditionalType(<ConditionalType>t);
return constraint && getBaseConstraint(constraint);
const constraint = getConstraintFromConditionalType(<ConditionalType>t);
constraintDepth++; // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward)
const result = constraint && getBaseConstraint(constraint);
constraintDepth--;
return result;
}
if (t.flags & TypeFlags.Substitution) {
return getBaseConstraint((<SubstitutionType>t).substitute);
Expand Down Expand Up @@ -8866,6 +8867,9 @@ namespace ts {
}

function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) {
if (substitute.flags & TypeFlags.AnyOrUnknown) {
return typeVariable;
}
const result = <SubstitutionType>createType(TypeFlags.Substitution);
result.typeVariable = typeVariable;
result.substitute = substitute;
Expand Down Expand Up @@ -10161,7 +10165,7 @@ namespace ts {
// Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`.
if (falseType.flags & TypeFlags.Never && isTypeIdenticalTo(getActualTypeVariable(trueType), getActualTypeVariable(checkType))) {
if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
return getDefaultConstraintOfTrueBranchOfConditionalType(root, /*combinedMapper*/ undefined, mapper);
return trueType;
}
else if (isIntersectionEmpty(checkType, extendsType)) { // Always false
return neverType;
Expand All @@ -10172,7 +10176,7 @@ namespace ts {
return neverType;
}
else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false
return falseType; // TODO: Intersect negated `extends` type here
return falseType;
}
}

Expand Down Expand Up @@ -10227,21 +10231,15 @@ namespace ts {
result.extendsType = extendsType;
result.mapper = mapper;
result.combinedMapper = combinedMapper;
if (!combinedMapper) {
result.resolvedTrueType = trueType;
result.resolvedFalseType = falseType;
}
result.trueType = trueType;
result.falseType = falseType;
result.aliasSymbol = root.aliasSymbol;
result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217
return result;
}

function getTrueTypeFromConditionalType(type: ConditionalType) {
return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(type.root.trueType, type.mapper));
}

function getFalseTypeFromConditionalType(type: ConditionalType) {
return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(type.root.falseType, type.mapper));
function getInferredTrueTypeFromConditionalType(type: ConditionalType) {
return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = instantiateType(type.root.trueType, type.combinedMapper || type.mapper));
}

function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined {
Expand Down Expand Up @@ -11182,7 +11180,11 @@ namespace ts {
return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((<SubstitutionType>type).substitute, mapper));
}
else {
return maybeVariable;
const sub = instantiateType((<SubstitutionType>type).substitute, mapper);
if (sub.flags & TypeFlags.AnyOrUnknown || isTypeSubtypeOf(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) {
return maybeVariable;
}
return sub;
}
}
return type;
Expand Down Expand Up @@ -11727,6 +11729,15 @@ namespace ts {

type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void;

/**
* Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any`
*/
function isAnySignature(s: Signature) {
return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 &&
s.hasRestParameter && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) &&
isTypeAny(getReturnTypeOfSignature(s));
}

/**
* See signatureRelatedTo, compareSignaturesIdentical
*/
Expand All @@ -11742,6 +11753,10 @@ namespace ts {
return Ternary.True;
}

if (isAnySignature(target)) {
return Ternary.True;
}

const targetCount = getParameterCount(target);
if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) {
return Ternary.False;
Expand Down Expand Up @@ -12706,8 +12721,8 @@ namespace ts {
if ((<ConditionalType>source).root.isDistributive === (<ConditionalType>target).root.isDistributive) {
if (result = isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType, /*reportErrors*/ false)) {
if (result &= isRelatedTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType, /*reportErrors*/ false)) {
if (result &= isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), /*reportErrors*/ false)) {
if (result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), /*reportErrors*/ false)) {
if (result &= isRelatedTo((<ConditionalType>source).trueType, (<ConditionalType>target).trueType, /*reportErrors*/ false)) {
if (result &= isRelatedTo((<ConditionalType>source).falseType, (<ConditionalType>target).falseType, /*reportErrors*/ false)) {
return result;
}
}
Expand Down Expand Up @@ -12828,7 +12843,7 @@ namespace ts {
return result;
}
}
const constraint = getConstraintOfType(<TypeParameter>source);
const constraint = getConstraintOfType(<TypeVariable>source);
if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
// A type variable with no constraint is not related to the non-primitive object type.
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) {
Expand Down Expand Up @@ -12860,8 +12875,8 @@ namespace ts {
// and Y1 is related to Y2.
if (isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
(isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType) || isRelatedTo((<ConditionalType>target).checkType, (<ConditionalType>source).checkType))) {
if (result = isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), reportErrors)) {
result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), reportErrors);
if (result = isRelatedTo((<ConditionalType>source).trueType, (<ConditionalType>target).trueType, reportErrors)) {
result &= isRelatedTo((<ConditionalType>source).falseType, (<ConditionalType>target).falseType, reportErrors);
}
if (result) {
errorInfo = saveErrorInfo;
Expand Down Expand Up @@ -14696,12 +14711,12 @@ namespace ts {
else if (source.flags & TypeFlags.Conditional && target.flags & TypeFlags.Conditional) {
inferFromTypes((<ConditionalType>source).checkType, (<ConditionalType>target).checkType);
inferFromTypes((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType);
inferFromTypes(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target));
inferFromTypes(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target));
inferFromTypes((<ConditionalType>source).trueType, (<ConditionalType>target).trueType);
inferFromTypes((<ConditionalType>source).falseType, (<ConditionalType>target).falseType);
}
else if (target.flags & TypeFlags.Conditional && !contravariant) {
inferFromTypes(source, getTrueTypeFromConditionalType(<ConditionalType>target));
inferFromTypes(source, getFalseTypeFromConditionalType(<ConditionalType>target));
inferFromTypes(source, (<ConditionalType>target).trueType);
inferFromTypes(source, (<ConditionalType>target).falseType);
}
else if (target.flags & TypeFlags.UnionOrIntersection) {
for (const t of (<UnionOrIntersectionType>target).types) {
Expand Down
6 changes: 4 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4310,8 +4310,10 @@ namespace ts {
root: ConditionalRoot;
checkType: Type;
extendsType: Type;
resolvedTrueType?: Type;
resolvedFalseType?: Type;
trueType: Type;
falseType: Type;
/* @internal */
resolvedInferredTrueType?: Type; // The `trueType` instantiated with the `combinedMapper`, if present
/* @internal */
resolvedDefaultConstraint?: Type;
/* @internal */
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2382,8 +2382,8 @@ declare namespace ts {
root: ConditionalRoot;
checkType: Type;
extendsType: Type;
resolvedTrueType?: Type;
resolvedFalseType?: Type;
trueType: Type;
falseType: Type;
}
interface SubstitutionType extends InstantiableType {
typeVariable: TypeVariable;
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2382,8 +2382,8 @@ declare namespace ts {
root: ConditionalRoot;
checkType: Type;
extendsType: Type;
resolvedTrueType?: Type;
resolvedFalseType?: Type;
trueType: Type;
falseType: Type;
}
interface SubstitutionType extends InstantiableType {
typeVariable: TypeVariable;
Expand Down
2 changes: 0 additions & 2 deletions tests/baselines/reference/conditionalTypes1.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,8 +509,6 @@ declare function f5<T extends Options, K extends string>(p: K): Extract<T, {
}>;
declare let x0: {
k: "a";
} & {
k: "a";
a: number;
};
declare type OptionsOfKind<K extends Options["k"]> = Extract<Options, {
Expand Down
14 changes: 7 additions & 7 deletions tests/baselines/reference/conditionalTypes1.types
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type T02 = Exclude<string | number | (() => void), Function>; // string | numbe
>T02 : string | number

type T03 = Extract<string | number | (() => void), Function>; // () => void
>T03 : Function & (() => void)
>T03 : () => void

type T04 = NonNullable<string | number | undefined>; // string | number
>T04 : string | number
Expand Down Expand Up @@ -113,7 +113,7 @@ type T10 = Exclude<Options, { k: "a" | "b" }>; // { k: "c", c: boolean }
>k : "a" | "b"

type T11 = Extract<Options, { k: "a" | "b" }>; // { k: "a", a: number } | { k: "b", b: string }
>T11 : ({ k: "a" | "b"; } & { k: "a"; a: number; }) | ({ k: "a" | "b"; } & { k: "b"; b: string; })
>T11 : { k: "a"; a: number; } | { k: "b"; b: string; }
>k : "a" | "b"

type T12 = Exclude<Options, { k: "a" } | { k: "b" }>; // { k: "c", c: boolean }
Expand All @@ -122,7 +122,7 @@ type T12 = Exclude<Options, { k: "a" } | { k: "b" }>; // { k: "c", c: boolean }
>k : "b"

type T13 = Extract<Options, { k: "a" } | { k: "b" }>; // { k: "a", a: number } | { k: "b", b: string }
>T13 : ({ k: "a"; } & { k: "a"; a: number; }) | ({ k: "b"; } & { k: "a"; a: number; }) | ({ k: "a"; } & { k: "b"; b: string; }) | ({ k: "b"; } & { k: "b"; b: string; })
>T13 : { k: "a"; a: number; } | { k: "b"; b: string; }
>k : "a"
>k : "b"

Expand All @@ -140,8 +140,8 @@ declare function f5<T extends Options, K extends string>(p: K): Extract<T, { k:
>k : K

let x0 = f5("a"); // { k: "a", a: number }
>x0 : { k: "a"; } & { k: "a"; a: number; }
>f5("a") : { k: "a"; } & { k: "a"; a: number; }
>x0 : { k: "a"; a: number; }
>f5("a") : { k: "a"; a: number; }
>f5 : <T extends Options, K extends string>(p: K) => Extract<T, { k: K; }>
>"a" : "a"

Expand All @@ -150,13 +150,13 @@ type OptionsOfKind<K extends Options["k"]> = Extract<Options, { k: K }>;
>k : K

type T16 = OptionsOfKind<"a" | "b">; // { k: "a", a: number } | { k: "b", b: string }
>T16 : ({ k: "a" | "b"; } & { k: "a"; a: number; }) | ({ k: "a" | "b"; } & { k: "b"; b: string; })
>T16 : { k: "a"; a: number; } | { k: "b"; b: string; }

type Select<T, K extends keyof T, V extends T[K]> = Extract<T, { [P in K]: V }>;
>Select : Extract<T, { [P in K]: V; }>

type T17 = Select<Options, "k", "a" | "b">; // // { k: "a", a: number } | { k: "b", b: string }
>T17 : ({ k: "a" | "b"; } & { k: "a"; a: number; }) | ({ k: "a" | "b"; } & { k: "b"; b: string; })
>T17 : { k: "a"; a: number; } | { k: "b"; b: string; }

type TypeName<T> =
>TypeName : TypeName<T>
Expand Down
Loading

0 comments on commit b7881a2

Please sign in to comment.