Skip to content

Commit

Permalink
Cache propagating variance flags in the relationship cache (#32225)
Browse files Browse the repository at this point in the history
* Cache propagating variance flags in the relationship cache

* Convert base fields in relation comparison result to flags
  • Loading branch information
weswigham committed Sep 23, 2019
1 parent 367b820 commit 00a43d7
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 32 deletions.
58 changes: 29 additions & 29 deletions src/compiler/checker.ts
Expand Up @@ -12561,12 +12561,12 @@ namespace ts {
return true;
}
const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol);
const relation = enumRelation.get(id);
if (relation !== undefined && !(relation === RelationComparisonResult.Failed && errorReporter)) {
return relation === RelationComparisonResult.Succeeded;
const entry = enumRelation.get(id);
if (entry !== undefined && !(!(entry & RelationComparisonResult.Reported) && entry & RelationComparisonResult.Failed && errorReporter)) {
return !!(entry & RelationComparisonResult.Succeeded);
}
if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) {
enumRelation.set(id, RelationComparisonResult.FailedAndReported);
enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported);
return false;
}
const targetEnumType = getTypeOfSymbol(targetSymbol);
Expand All @@ -12577,7 +12577,7 @@ namespace ts {
if (errorReporter) {
errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property),
typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType));
enumRelation.set(id, RelationComparisonResult.FailedAndReported);
enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported);
}
else {
enumRelation.set(id, RelationComparisonResult.Failed);
Expand Down Expand Up @@ -12642,7 +12642,7 @@ namespace ts {
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
const related = relation.get(getRelationKey(source, target, relation));
if (related !== undefined) {
return related === RelationComparisonResult.Succeeded;
return !!(related & RelationComparisonResult.Succeeded);
}
}
if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) {
Expand Down Expand Up @@ -13463,18 +13463,6 @@ namespace ts {
return result;
}

function propagateSidebandVarianceFlags(typeArguments: readonly Type[], variances: VarianceFlags[]) {
for (let i = 0; i < variances.length; i++) {
const v = variances[i];
if (v & VarianceFlags.Unmeasurable) {
instantiateType(typeArguments[i], reportUnmeasurableMarkers);
}
if (v & VarianceFlags.Unreliable) {
instantiateType(typeArguments[i], reportUnreliableMarkers);
}
}
}

// Determine if possibly recursive types are related. First, check if the result is already available in the global cache.
// Second, check if we have already started a comparison of the given two types in which case we assume the result to be true.
// Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are
Expand All @@ -13485,24 +13473,24 @@ namespace ts {
return Ternary.False;
}
const id = getRelationKey(source, target, relation);
const related = relation.get(id);
if (related !== undefined) {
if (reportErrors && related === RelationComparisonResult.Failed) {
const entry = relation.get(id);
if (entry !== undefined) {
if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) {
// We are elaborating errors and the cached result is an unreported failure. The result will be reported
// as a failure, and should be updated as a reported failure by the bottom of this function.
}
else {
if (outofbandVarianceMarkerHandler) {
// We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component
if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
propagateSidebandVarianceFlags(source.aliasTypeArguments, getAliasVariances(source.aliasSymbol));
const saved = entry & RelationComparisonResult.ReportsMask;
if (saved & RelationComparisonResult.ReportsUnmeasurable) {
instantiateType(source, reportUnmeasurableMarkers);
}
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target && length((<TypeReference>source).typeArguments)) {
propagateSidebandVarianceFlags((<TypeReference>source).typeArguments!, getVariances((<TypeReference>source).target));
if (saved & RelationComparisonResult.ReportsUnreliable) {
instantiateType(source, reportUnreliableMarkers);
}
}
return related === RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
}
}
if (!maybeKeys) {
Expand Down Expand Up @@ -13531,22 +13519,34 @@ namespace ts {
const saveExpandingFlags = expandingFlags;
if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= ExpandingFlags.Source;
if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= ExpandingFlags.Target;
let originalHandler: typeof outofbandVarianceMarkerHandler;
let propagatingVarianceFlags: RelationComparisonResult = 0;
if (outofbandVarianceMarkerHandler) {
originalHandler = outofbandVarianceMarkerHandler;
outofbandVarianceMarkerHandler = onlyUnreliable => {
propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable;
return originalHandler!(onlyUnreliable);
};
}
const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, isIntersectionConstituent) : Ternary.Maybe;
if (outofbandVarianceMarkerHandler) {
outofbandVarianceMarkerHandler = originalHandler;
}
expandingFlags = saveExpandingFlags;
depth--;
if (result) {
if (result === Ternary.True || depth === 0) {
// If result is definitely true, record all maybe keys as having succeeded
for (let i = maybeStart; i < maybeCount; i++) {
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded);
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
}
maybeCount = maybeStart;
}
}
else {
// A false result goes straight into global cache (when something is false under
// assumptions it will also be false without assumptions)
relation.set(id, reportErrors ? RelationComparisonResult.FailedAndReported : RelationComparisonResult.Failed);
relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags);
maybeCount = maybeStart;
}
return result;
Expand Down
10 changes: 7 additions & 3 deletions src/compiler/types.ts
Expand Up @@ -614,9 +614,13 @@ namespace ts {

/* @internal */
export const enum RelationComparisonResult {
Succeeded = 1, // Should be truthy
Failed = 2,
FailedAndReported = 3
Succeeded = 1 << 0, // Should be truthy
Failed = 1 << 1,
Reported = 1 << 2,

ReportsUnmeasurable = 1 << 3,
ReportsUnreliable = 1 << 4,
ReportsMask = ReportsUnmeasurable | ReportsUnreliable
}

export interface Node extends TextRange {
Expand Down
@@ -0,0 +1,25 @@
//// [varianceRepeatedlyPropegatesWithUnreliableFlag.ts]
type A = { a: number };
type B = { b: number };
type X<T> = ({ [K in keyof T]: T[K] } & Record<string, void>)[keyof T];
type P1<T> = { data: X<T> };
type P2<T> = { data: X<T> };

interface I<T> {
fn<K extends keyof T>(p1: P1<Pick<T, K>>, p2: P2<Pick<T, K>>): void;
}

const i: I<A & B> = null as any;
const p2: P2<A> = null as any;

// Commenting out the below line will remove the error on the `const _i: I<A> = i;`
i.fn(null as any, p2);

const _i: I<A> = i;

//// [varianceRepeatedlyPropegatesWithUnreliableFlag.js]
var i = null;
var p2 = null;
// Commenting out the below line will remove the error on the `const _i: I<A> = i;`
i.fn(null, p2);
var _i = i;
@@ -0,0 +1,77 @@
=== tests/cases/compiler/varianceRepeatedlyPropegatesWithUnreliableFlag.ts ===
type A = { a: number };
>A : Symbol(A, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 0))
>a : Symbol(a, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 10))

type B = { b: number };
>B : Symbol(B, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 23))
>b : Symbol(b, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 1, 10))

type X<T> = ({ [K in keyof T]: T[K] } & Record<string, void>)[keyof T];
>X : Symbol(X, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 1, 23))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 7))
>K : Symbol(K, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 16))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 7))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 7))
>K : Symbol(K, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 16))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 7))

type P1<T> = { data: X<T> };
>P1 : Symbol(P1, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 71))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 8))
>data : Symbol(data, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 14))
>X : Symbol(X, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 1, 23))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 8))

type P2<T> = { data: X<T> };
>P2 : Symbol(P2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 28))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 8))
>data : Symbol(data, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 14))
>X : Symbol(X, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 1, 23))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 8))

interface I<T> {
>I : Symbol(I, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 28))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 12))

fn<K extends keyof T>(p1: P1<Pick<T, K>>, p2: P2<Pick<T, K>>): void;
>fn : Symbol(I.fn, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 16))
>K : Symbol(K, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 7, 7))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 12))
>p1 : Symbol(p1, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 7, 26))
>P1 : Symbol(P1, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 71))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 12))
>K : Symbol(K, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 7, 7))
>p2 : Symbol(p2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 7, 45))
>P2 : Symbol(P2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 28))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 12))
>K : Symbol(K, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 7, 7))
}

const i: I<A & B> = null as any;
>i : Symbol(i, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 10, 5))
>I : Symbol(I, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 28))
>A : Symbol(A, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 0))
>B : Symbol(B, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 23))

const p2: P2<A> = null as any;
>p2 : Symbol(p2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 11, 5))
>P2 : Symbol(P2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 28))
>A : Symbol(A, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 0))

// Commenting out the below line will remove the error on the `const _i: I<A> = i;`
i.fn(null as any, p2);
>i.fn : Symbol(I.fn, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 16))
>i : Symbol(i, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 10, 5))
>fn : Symbol(I.fn, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 16))
>p2 : Symbol(p2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 11, 5))

const _i: I<A> = i;
>_i : Symbol(_i, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 16, 5))
>I : Symbol(I, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 28))
>A : Symbol(A, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 0))
>i : Symbol(i, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 10, 5))

@@ -0,0 +1,51 @@
=== tests/cases/compiler/varianceRepeatedlyPropegatesWithUnreliableFlag.ts ===
type A = { a: number };
>A : A
>a : number

type B = { b: number };
>B : B
>b : number

type X<T> = ({ [K in keyof T]: T[K] } & Record<string, void>)[keyof T];
>X : ({ [K in keyof T]: T[K]; } & Record<string, void>)[keyof T]

type P1<T> = { data: X<T> };
>P1 : P1<T>
>data : ({ [K in keyof T]: T[K]; } & Record<string, void>)[keyof T]

type P2<T> = { data: X<T> };
>P2 : P2<T>
>data : ({ [K in keyof T]: T[K]; } & Record<string, void>)[keyof T]

interface I<T> {
fn<K extends keyof T>(p1: P1<Pick<T, K>>, p2: P2<Pick<T, K>>): void;
>fn : <K extends keyof T>(p1: P1<Pick<T, K>>, p2: P2<Pick<T, K>>) => void
>p1 : P1<Pick<T, K>>
>p2 : P2<Pick<T, K>>
}

const i: I<A & B> = null as any;
>i : I<A & B>
>null as any : any
>null : null

const p2: P2<A> = null as any;
>p2 : P2<A>
>null as any : any
>null : null

// Commenting out the below line will remove the error on the `const _i: I<A> = i;`
i.fn(null as any, p2);
>i.fn(null as any, p2) : void
>i.fn : <K extends "a" | "b">(p1: P1<Pick<A & B, K>>, p2: P2<Pick<A & B, K>>) => void
>i : I<A & B>
>fn : <K extends "a" | "b">(p1: P1<Pick<A & B, K>>, p2: P2<Pick<A & B, K>>) => void
>null as any : any
>null : null
>p2 : P2<A>

const _i: I<A> = i;
>_i : I<A>
>i : I<A & B>

@@ -0,0 +1,17 @@
type A = { a: number };
type B = { b: number };
type X<T> = ({ [K in keyof T]: T[K] } & Record<string, void>)[keyof T];
type P1<T> = { data: X<T> };
type P2<T> = { data: X<T> };

interface I<T> {
fn<K extends keyof T>(p1: P1<Pick<T, K>>, p2: P2<Pick<T, K>>): void;
}

const i: I<A & B> = null as any;
const p2: P2<A> = null as any;

// Commenting out the below line will remove the error on the `const _i: I<A> = i;`
i.fn(null as any, p2);

const _i: I<A> = i;

0 comments on commit 00a43d7

Please sign in to comment.