Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache propagating variance flags in the relationship cache #32225

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 29 additions & 29 deletions src/compiler/checker.ts
Expand Up @@ -12558,12 +12558,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 @@ -12574,7 +12574,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 @@ -12639,7 +12639,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 @@ -13453,18 +13453,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 @@ -13475,24 +13463,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 @@ -13521,22 +13509,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
weswigham marked this conversation as resolved.
Show resolved Hide resolved
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;