Propagate variance reliability (#62604)

This commit is contained in:
Anders Hejlsberg 2025-10-31 11:51:01 -07:00 committed by GitHub
parent 9222837872
commit cc05d940a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 141 additions and 21 deletions

View File

@ -23190,29 +23190,35 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be)
related = relation === identityRelation ? isRelatedTo(s, t, RecursionFlags.Both, /*reportErrors*/ false) : compareTypesIdentical(s, t);
}
else if (variance === VarianceFlags.Covariant) {
related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
else if (variance === VarianceFlags.Contravariant) {
related = isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
else if (variance === VarianceFlags.Bivariant) {
// In the bivariant case we first compare contravariantly without reporting
// errors. Then, if that doesn't succeed, we compare covariantly with error
// reporting. Thus, error elaboration will be based on the the covariant check,
// which is generally easier to reason about.
related = isRelatedTo(t, s, RecursionFlags.Both, /*reportErrors*/ false);
if (!related) {
else {
// Propagate unreliable variance flag
if (inVarianceComputation && varianceFlags & VarianceFlags.Unreliable) {
instantiateType(s, reportUnreliableMapper);
}
if (variance === VarianceFlags.Covariant) {
related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
}
else {
// In the invariant case we first compare covariantly, and only when that
// succeeds do we proceed to compare contravariantly. Thus, error elaboration
// will typically be based on the covariant check.
related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
if (related) {
related &= isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
else if (variance === VarianceFlags.Contravariant) {
related = isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
else if (variance === VarianceFlags.Bivariant) {
// In the bivariant case we first compare contravariantly without reporting
// errors. Then, if that doesn't succeed, we compare covariantly with error
// reporting. Thus, error elaboration will be based on the the covariant check,
// which is generally easier to reason about.
related = isRelatedTo(t, s, RecursionFlags.Both, /*reportErrors*/ false);
if (!related) {
related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
}
else {
// In the invariant case we first compare covariantly, and only when that
// succeeds do we proceed to compare contravariantly. Thus, error elaboration
// will typically be based on the covariant check.
related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
if (related) {
related &= isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
}
}
if (!related) {

View File

@ -1,5 +1,8 @@
//// [tests/cases/compiler/circularlySimplifyingConditionalTypesNoCrash.ts] ////
=== Performance Stats ===
Instantiation count: 1,000
=== circularlySimplifyingConditionalTypesNoCrash.ts ===
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
>Omit : Omit<T, K>

View File

@ -0,0 +1,50 @@
//// [tests/cases/compiler/variancePropagation.ts] ////
=== variancePropagation.ts ===
// https://github.com/microsoft/TypeScript/issues/62606
interface DerivedTable<S extends { base: any; new: any }> {
>DerivedTable : Symbol(DerivedTable, Decl(variancePropagation.ts, 0, 0))
>S : Symbol(S, Decl(variancePropagation.ts, 2, 23))
>base : Symbol(base, Decl(variancePropagation.ts, 2, 34))
>new : Symbol(new, Decl(variancePropagation.ts, 2, 45))
// Error disappears when these property declarations are reversed
schema: S["base"] & S["new"]
>schema : Symbol(DerivedTable.schema, Decl(variancePropagation.ts, 2, 59))
>S : Symbol(S, Decl(variancePropagation.ts, 2, 23))
>S : Symbol(S, Decl(variancePropagation.ts, 2, 23))
readonlySchema: Readonly<S["base"] & S["new"]>
>readonlySchema : Symbol(DerivedTable.readonlySchema, Decl(variancePropagation.ts, 4, 32))
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
>S : Symbol(S, Decl(variancePropagation.ts, 2, 23))
>S : Symbol(S, Decl(variancePropagation.ts, 2, 23))
}
interface Base { baseProp: number; }
>Base : Symbol(Base, Decl(variancePropagation.ts, 6, 1))
>baseProp : Symbol(Base.baseProp, Decl(variancePropagation.ts, 8, 16))
interface New { newProp: boolean; }
>New : Symbol(New, Decl(variancePropagation.ts, 8, 36))
>newProp : Symbol(New.newProp, Decl(variancePropagation.ts, 9, 16))
declare const source: DerivedTable<{ base: Base, new: New }>
>source : Symbol(source, Decl(variancePropagation.ts, 11, 13))
>DerivedTable : Symbol(DerivedTable, Decl(variancePropagation.ts, 0, 0))
>base : Symbol(base, Decl(variancePropagation.ts, 11, 36))
>Base : Symbol(Base, Decl(variancePropagation.ts, 6, 1))
>new : Symbol(new, Decl(variancePropagation.ts, 11, 48))
>New : Symbol(New, Decl(variancePropagation.ts, 8, 36))
const destination: DerivedTable<{ base: Base; new: New & Base }> = source; // Error
>destination : Symbol(destination, Decl(variancePropagation.ts, 12, 5))
>DerivedTable : Symbol(DerivedTable, Decl(variancePropagation.ts, 0, 0))
>base : Symbol(base, Decl(variancePropagation.ts, 12, 33))
>Base : Symbol(Base, Decl(variancePropagation.ts, 6, 1))
>new : Symbol(new, Decl(variancePropagation.ts, 12, 45))
>New : Symbol(New, Decl(variancePropagation.ts, 8, 36))
>Base : Symbol(Base, Decl(variancePropagation.ts, 6, 1))
>source : Symbol(source, Decl(variancePropagation.ts, 11, 13))

View File

@ -0,0 +1,45 @@
//// [tests/cases/compiler/variancePropagation.ts] ////
=== variancePropagation.ts ===
// https://github.com/microsoft/TypeScript/issues/62606
interface DerivedTable<S extends { base: any; new: any }> {
>base : any
>new : any
// Error disappears when these property declarations are reversed
schema: S["base"] & S["new"]
>schema : S["base"] & S["new"]
> : ^^^^^^^^^^^^^^^^^^^^
readonlySchema: Readonly<S["base"] & S["new"]>
>readonlySchema : Readonly<S["base"] & S["new"]>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
interface Base { baseProp: number; }
>baseProp : number
> : ^^^^^^
interface New { newProp: boolean; }
>newProp : boolean
> : ^^^^^^^
declare const source: DerivedTable<{ base: Base, new: New }>
>source : DerivedTable<{ base: Base; new: New; }>
> : ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^ ^^^^
>base : Base
> : ^^^^
>new : New
> : ^^^
const destination: DerivedTable<{ base: Base; new: New & Base }> = source; // Error
>destination : DerivedTable<{ base: Base; new: New & Base; }>
> : ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^ ^^^^
>base : Base
> : ^^^^
>new : New & Base
> : ^^^^^^^^^^
>source : DerivedTable<{ base: Base; new: New; }>
> : ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^ ^^^^

View File

@ -0,0 +1,16 @@
// @strict: true
// @noEmit: true
// https://github.com/microsoft/TypeScript/issues/62606
interface DerivedTable<S extends { base: any; new: any }> {
// Error disappears when these property declarations are reversed
schema: S["base"] & S["new"]
readonlySchema: Readonly<S["base"] & S["new"]>
}
interface Base { baseProp: number; }
interface New { newProp: boolean; }
declare const source: DerivedTable<{ base: Base, new: New }>
const destination: DerivedTable<{ base: Base; new: New & Base }> = source; // Error