From d864b95bfe63fedbb347d6adf102fd5c1098853c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 15 Oct 2025 10:36:35 -0400 Subject: [PATCH 1/5] Propagate variance reliability in typeArgumentsRelatedTo --- src/compiler/checker.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 48bc0da113816..0b1794efd48b4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23178,11 +23178,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // in the process of computing variance information for recursive types and when // comparing 'this' type arguments. const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant; + const s = sources[i]; + const t = targets[i]; + // Propagate variance reliability flags + if (varianceFlags & (VarianceFlags.Unmeasurable | VarianceFlags.Unreliable)) { + instantiateType(s, varianceFlags & VarianceFlags.Unmeasurable ? reportUnmeasurableMapper : reportUnreliableMapper); + } const variance = varianceFlags & VarianceFlags.VarianceMask; // We ignore arguments for independent type parameters (because they're never witnessed). if (variance !== VarianceFlags.Independent) { - const s = sources[i]; - const t = targets[i]; let related = Ternary.True; if (varianceFlags & VarianceFlags.Unmeasurable) { // Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_. From f5298d6ce294649e6ff47c8359920d025919d117 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 15 Oct 2025 10:36:57 -0400 Subject: [PATCH 2/5] Accept new baselines --- .../circularlySimplifyingConditionalTypesNoCrash.types | 3 +++ tests/baselines/reference/deeplyNestedMappedTypes.types | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/circularlySimplifyingConditionalTypesNoCrash.types b/tests/baselines/reference/circularlySimplifyingConditionalTypesNoCrash.types index 765ca3234d8cb..29b958c768ad3 100644 --- a/tests/baselines/reference/circularlySimplifyingConditionalTypesNoCrash.types +++ b/tests/baselines/reference/circularlySimplifyingConditionalTypesNoCrash.types @@ -1,5 +1,8 @@ //// [tests/cases/compiler/circularlySimplifyingConditionalTypesNoCrash.ts] //// +=== Performance Stats === +Instantiation count: 1,000 + === circularlySimplifyingConditionalTypesNoCrash.ts === type Omit = Pick>; >Omit : Omit diff --git a/tests/baselines/reference/deeplyNestedMappedTypes.types b/tests/baselines/reference/deeplyNestedMappedTypes.types index 68a8048a65774..de7c1c0c17356 100644 --- a/tests/baselines/reference/deeplyNestedMappedTypes.types +++ b/tests/baselines/reference/deeplyNestedMappedTypes.types @@ -2,7 +2,7 @@ === Performance Stats === Type Count: 1,000 -Instantiation count: 5,000 +Instantiation count: 25,000 === deeplyNestedMappedTypes.ts === // Simplified repro from #55535 From 0942f04a5411a9eadc4ba417b53bea7082d43379 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 15 Oct 2025 13:00:54 -0400 Subject: [PATCH 3/5] Only propagate VarianceFlags.Unreliable --- src/compiler/checker.ts | 56 +++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0b1794efd48b4..c9180623e64f0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23178,15 +23178,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // in the process of computing variance information for recursive types and when // comparing 'this' type arguments. const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant; - const s = sources[i]; - const t = targets[i]; - // Propagate variance reliability flags - if (varianceFlags & (VarianceFlags.Unmeasurable | VarianceFlags.Unreliable)) { - instantiateType(s, varianceFlags & VarianceFlags.Unmeasurable ? reportUnmeasurableMapper : reportUnreliableMapper); - } const variance = varianceFlags & VarianceFlags.VarianceMask; // We ignore arguments for independent type parameters (because they're never witnessed). if (variance !== VarianceFlags.Independent) { + const s = sources[i]; + const t = targets[i]; let related = Ternary.True; if (varianceFlags & VarianceFlags.Unmeasurable) { // Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_. @@ -23194,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) { From f2e654ac42f58ce554b265e429d39d67a623ae1c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 15 Oct 2025 13:04:56 -0400 Subject: [PATCH 4/5] Accept new baselines --- tests/baselines/reference/deeplyNestedMappedTypes.types | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/baselines/reference/deeplyNestedMappedTypes.types b/tests/baselines/reference/deeplyNestedMappedTypes.types index de7c1c0c17356..68a8048a65774 100644 --- a/tests/baselines/reference/deeplyNestedMappedTypes.types +++ b/tests/baselines/reference/deeplyNestedMappedTypes.types @@ -2,7 +2,7 @@ === Performance Stats === Type Count: 1,000 -Instantiation count: 25,000 +Instantiation count: 5,000 === deeplyNestedMappedTypes.ts === // Simplified repro from #55535 From be235f278bb1971bc644e4e6af1f6fbe6808b64b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 15 Oct 2025 16:57:25 -0400 Subject: [PATCH 5/5] Add regression test --- .../reference/variancePropagation.symbols | 50 +++++++++++++++++++ .../reference/variancePropagation.types | 45 +++++++++++++++++ tests/cases/compiler/variancePropagation.ts | 16 ++++++ 3 files changed, 111 insertions(+) create mode 100644 tests/baselines/reference/variancePropagation.symbols create mode 100644 tests/baselines/reference/variancePropagation.types create mode 100644 tests/cases/compiler/variancePropagation.ts diff --git a/tests/baselines/reference/variancePropagation.symbols b/tests/baselines/reference/variancePropagation.symbols new file mode 100644 index 0000000000000..1c138b4bcffdf --- /dev/null +++ b/tests/baselines/reference/variancePropagation.symbols @@ -0,0 +1,50 @@ +//// [tests/cases/compiler/variancePropagation.ts] //// + +=== variancePropagation.ts === +// https://github.com/microsoft/TypeScript/issues/62606 + +interface DerivedTable { +>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 +>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)) + diff --git a/tests/baselines/reference/variancePropagation.types b/tests/baselines/reference/variancePropagation.types new file mode 100644 index 0000000000000..c3d93d52b891d --- /dev/null +++ b/tests/baselines/reference/variancePropagation.types @@ -0,0 +1,45 @@ +//// [tests/cases/compiler/variancePropagation.ts] //// + +=== variancePropagation.ts === +// https://github.com/microsoft/TypeScript/issues/62606 + +interface DerivedTable { +>base : any +>new : any + + // Error disappears when these property declarations are reversed + schema: S["base"] & S["new"] +>schema : S["base"] & S["new"] +> : ^^^^^^^^^^^^^^^^^^^^ + + readonlySchema: Readonly +>readonlySchema : Readonly +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +} + +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; }> +> : ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^ ^^^^ + diff --git a/tests/cases/compiler/variancePropagation.ts b/tests/cases/compiler/variancePropagation.ts new file mode 100644 index 0000000000000..ab62d64928003 --- /dev/null +++ b/tests/cases/compiler/variancePropagation.ts @@ -0,0 +1,16 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/62606 + +interface DerivedTable { + // Error disappears when these property declarations are reversed + schema: S["base"] & S["new"] + readonlySchema: Readonly +} + +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