Repository navigation
7.1 regression: declaration order of recursive generic interfaces changes assignability (TS2322) #64891
Description
🔎 Search Terms
variance, circular variance, declaration order, mapped type as clause, key remapping, TS2322, 7.1 regression
🕗 Version & Regression Information
- Last good:
7.1.0-dev.20260805.1(gitHead 12318e599d) - First bad:
7.1.0-dev.20260806.1(gitHead 86cc4767d4) - That range is one commit: Order variance computation by associated type symbol typescript-go#4820, "Order variance computation by associated type symbol".
- Still failing on
7.1.0-betaand7.1.0-dev.20261020.3. - TypeScript 6.0.3 and 7.0.2 accept the code.
⏯ Playground Link
The tested 7.1 builds aren't available in the Playground: requesting 7.1.0-dev.20261020.3 reports that it hasn't been prepared, and Nightly currently loads 6.0.0-dev.20260416. The CLI repro is two files in an empty directory, with no package.json, run with tsc -p .:
tsconfig.json
{ "compilerOptions": { "noEmit": true, "strict": true }, "files": ["repro.ts"] }| Version | tsc -p . |
|---|---|
| 6.0.3 | no errors |
| 7.0.2 | no errors |
| 7.1.0-dev.20260805.1 | no errors |
| 7.1.0-dev.20260806.1 | TS2322 |
| 7.1.0-beta | TS2322 |
| 7.1.0-dev.20261020.3 | TS2322 |
In my runs the result did not change with strict off, or with module/moduleResolution set to nodenext or to esnext/bundler.
💻 Code
repro.ts
type M = { kind: "m" };
type Filtered<T> = { [K in keyof T as T[K] extends M ? K : never]: T[K] };
interface Config<T> { measure: keyof T }
interface Ref<N> { dataset: DS<Record<string, M>, N> }
interface DS<T, N> {
a(config: Config<Filtered<T>>): void;
b: Ref<N>;
}
declare const x: DS<{ total: M }, string>;
export const y: DS<Record<string, M>, string> = x;🙁 Actual behavior
repro.ts(10,14): error TS2322: Type 'DS<{ total: M; }, string>' is not assignable to type 'DS<Record<string, M>, string>'.
Property 'total' is missing in type 'Record<string, M>' but required in type '{ total: M; }'.
The reverse assignment (DS<Record<string, M>, string> to DS<{ total: M }, string>) also fails. This is consistent with T being treated as invariant. The diagnostic reports the missing total property on the type argument rather than an incompatibility in a or b. I haven't traced whether a structural comparison is attempted.
🙂 Expected behavior
No error, as in 6.0.3 and 7.0.2. The assignment is valid member by member. a is a method, so its parameter is checked bivariantly ({ measure: "total" } against { measure: string }). b is the same type on both sides. Replacing the final assignment with this member-by-member version succeeds on both tested 7.1 builds:
export const y: DS<Record<string, M>, string> = { a: x.a, b: x.b };⏯ Additional information about the issue
The result depends on declaration order. Each of these changes, applied on its own to the repro above, makes the assignment pass on both 7.1.0-beta and 7.1.0-dev.20261020.3:
- Declaring
DSbeforeRef. - Adding this comparison before the failing assignment:
declare const r: Ref<"a">; export const r2: Ref<string> = r;
- Writing
a's parameter as{ measure: keyof Filtered<T> }instead ofConfig<Filtered<T>>. - Dropping the
asclause fromFiltered. - Removing
NfromDS,Refand their instantiations, so thatRefis not generic.
DS.b doesn't mention T. Ref<N>.dataset always fixes DS's first argument to Record<string, M> and passes through only N. Even so, swapping the declarations changes whether the assignment succeeds.
Real-world impact: this is reduced from @hypequery/datasets 0.23.1. A dataset created with named total and count measures assigns to the exported AnyDatasetInstance on 6.0.3 and 7.0.2, but fails with TS2322 on the tested 7.1 builds. DatasetInstance.metric() takes BaseMetricConfig<BaseMeasures<TMeasures>>, where BaseMeasures uses key remapping. Its returned references carry the concrete dataset type. Separately, DerivedMetricConfig.uses holds BaseMetricRef<TDatasetName> references whose default dataset type is the wide one.
Activity
[-]7.1 regression: variance of a type parameter depends on declaration order of an unrelated circular pair, giving a false TS2322[/-][+]7.1 regression: declaration order of recursive generic interfaces changes assignability (TS2322)[/+]shahinsamiur commentedon Oct 21, 2026
Hello! I am currently investigating this problem and developing a solution to the variance calculation implementation changes introduced in the referenced commit. I will be updating here when I have any updates or PR ready.