Skip to content

Compatibility: identical generic addElement methods rejected (Polotno TS2719, matches upstream pin) #25

Open

Description

@jakeleventhal

Summary

Two independently written, identical generic method signatures become incompatible when the parameter intersects a discriminant with a distributive conditional/Extract/Omit type. This is a dependency-free reduction of a Polotno PageType assignment that fails with TS2719 in tsc-rs and the exact upstream pin. The small reduction reports TS2322 while retaining the same addElement parameter comparison failure.

This is a compatibility report against TypeScript 7.0.2, not evidence of a Rust-only bug. The exact upstream version documented by this project, typescript@7.1.0-dev.20260929.1 / 673a5f17d713, produces the same diagnostic. I am filing this to record a real-world upgrade difference in this repository.

Versions and platform

  • tsc-rs npm release: 0.1.0.
  • Baseline: TypeScript 7.0.2.
  • Upstream comparison: TypeScript 7.1.0-dev.20260929.1.
  • macOS: Darwin 25.6.0 arm64 arm.

Standalone reproduction

No application source or external library is needed for this small repro; the package.json only installs comparison compilers.

package.json

{
  "private": true,
  "devDependencies": {
    "tsc7": "npm:typescript@7.0.2",
    "upstream": "npm:typescript@7.1.0-dev.20260929.1",
    "tsc-rs": "0.1.0"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "es2022",
    "module": "preserve",
    "moduleResolution": "bundler",
    "lib": [
      "esnext",
      "dom",
      "dom.iterable"
    ],
    "strict": true,
    "skipLibCheck": true,
    "noEmit": true,
    "types": []
  },
  "files": [
    "case.ts"
  ]
}

case.ts

type ElementType =
	| { type: "text"; text: string }
	| { type: "image"; src: string };
interface Page {
	addElement<T extends ElementType["type"] | (string & {})>(
		attrs: { type: T } & (T extends ElementType["type"]
			? Partial<Omit<Extract<ElementType, { type: T }>, "type">>
			: Record<string, any>),
	): void;
}
declare const returned: {
	addElement<T extends ElementType["type"] | (string & {})>(
		attrs: { type: T } & (T extends ElementType["type"]
			? Partial<Omit<Extract<ElementType, { type: T }>, "type">>
			: Record<string, any>),
	): void;
};
export const page: Page = returned;
bun install --ignore-scripts
bun node_modules/tsc-rs/bin/tsc-rs -p tsconfig.json --pretty false
bun node_modules/tsc7/bin/tsc -p tsconfig.json --pretty false
bun node_modules/upstream/bin/tsc -p tsconfig.json --pretty false

Results

Checker Result
TypeScript 7.0.2 Pass
tsc-rs 0.1.0 Fail, exit 2
TypeScript 7.1.0-dev.20260929.1 (exact upstream pin) Fail, exit 2
Bun 1.4.3-canary.1+43a42a836 Pass

tsc-rs output (upstream reports the same failure):

case.ts(18,14): error TS2322: Type '{ addElement<T extends ElementType["type"] | (string & {})>(attrs: { type: T; } & (T extends "image" | "text" ? Partial<Omit<Extract<...> | Extract<...>, "type">> : Record<...>)): void; }' is not assignable to type 'Page'.
  Types of property 'addElement' are incompatible.
    Type '<T extends ElementType["type"] | (string & {})>(attrs: { type: T; } & (T extends "image" | "text" ? Partial<Omit<Extract<{ type: "text"; text: string; }, { type: T; }> | Extract<{ type: "image"; src: string; }, { ...; }>, "type">> : Record<...>)) => void' is not assignable to type '<T extends ElementType["type"] | (string & {})>(attrs: { type: T; } & (T extends "image" | "text" ? Partial<Omit<Extract<{ type: "text"; text: string; }, { type: T; }> | Extract<{ type: "image"; src: string; }, { ...; }>, "type">> : Record<...>)) => void'. Two different types with this name exist, but they are unrelated.
      Types of parameters 'attrs' and 'attrs' are incompatible.
        Type '{ type: T; } & (T extends "image" | "text" ? Partial<Omit<Extract<{ type: "text"; text: string; }, { type: T; }> | Extract<{ type: "image"; src: string; }, { type: T; }>, "type">> : Record<...>)' is not assignable to type '{ type: T; } & (T extends "image" | "text" ? Partial<Omit<Extract<{ type: "text"; text: string; }, { type: T; }> | Extract<{ type: "image"; src: string; }, { type: T; }>, "type">> : Record<...>)'. Two different types with this name exist, but they are unrelated.
          Type '{ type: T; } & (T extends "image" | "text" ? Partial<Omit<Extract<...> | Extract<...>, "type">> : Record<...>)' is not assignable to type 'T extends "image" | "text" ? Partial<Omit<Extract<{ type: "text"; text: string; }, { ...; }> | Extract<...>, "type">> : Record<...>'.

Expected behavior / attribution

The identical signatures are accepted by TypeScript 7.0.2 and Bun. Pinned upstream rejects them too, so this tracks an inherited generic structural-comparison problem rather than a Rust-only discrepancy.

Controls and upstream context

A strong matching upstream report is microsoft/TypeScript#64673. It concerns identical generic signatures with distributive conditional parameter types. Proposed fix microsoft/TypeScript#64307 is unmerged at the time checked. I have not built that patch or verified it against these cases.

For the original library form, add polotno@4.13.0 and replace case.ts with:

import { createStore } from 'polotno/model/store';
import type { PageType } from 'polotno/model/page-model';
const store = createStore({ key: '', showCredit: false });
export const page: PageType = store.addPage();

I also verified this four-line case in a fresh external install. Polotno 4.13.0 uses mobx-state-tree 8.0.0. TypeScript 7.0.2 and Bun pass; tsc-rs and its upstream pin report TS2719, tracing the incompatibility to addElement. These are types from the same installed library packages, not evidence of duplicate package versions.

Diagnostic controls:

  • Assigning the original store.addPage() result to Omit<PageType, 'addElement'> passes in all compilers. This removes part of the contract and is only an isolation control.
  • Letting the original page type be inferred passes.
  • In the small repro, the return type was simplified to void and the failure remained, localizing it to parameter comparison.

Activity

  1. added
    upstream issueAlso happens in tsgo at the pinned upstream revision; fixed by a later pin bump
    on Oct 8, 2026
  2. t3dotgg commented on Oct 8, 2026

    @t3dotgg
    Member

    Note

    🤖 Claude Opus 5.5 responding on behalf of Theo

    Thanks for the report and the dependency-free reduction, @jakeleventhal.

    Confirmed: tsc-rs and tsc built at the pinned revision, microsoft/TypeScript 673a5f17d713, give the same TS2322 on the reduction, byte for byte, so it is upstream behavior and not a port bug. tsc-rs output follows the pin, and an upstream change reaches tsc-rs with a later pin bump.

    We are labeling this upstream issue and keeping it open while we look for the upstream change and any upstream issue; we will link them here. If you open one in microsoft/TypeScript, please link it too.

  3. jakeleventhal commented on Oct 8, 2026

    @jakeleventhal
    Author

    Thanks for confirming. One existing upstream candidate to check is microsoft/TypeScript#64673: it reports identical generic signatures failing to relate when their parameter types contain distributive conditionals. It links microsoft/TypeScript#64307, which is still open and unmerged; the issue author reports that building that PR fixes their repro.

    I haven't built or tested that patch against our reduced addElement case or the original Polotno assignment, so this is a strong candidate rather than a confirmed duplicate. It may be worth checking that patch against these cases before opening another upstream issue.

  4. t3dotgg commented on Oct 8, 2026

    @t3dotgg
    Member

    Note

    🤖 Claude Opus 5.5 responding on behalf of Theo

    Thanks for the pointer. We checked it, and it is the same bug.

    tsc-rs follows the pin, so the fix reaches tsc-rs with the first pin bump after it merges. We are keeping this open to track #64673. Until then, letting the page type be inferred (your control) avoids the error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    upstream issueAlso happens in tsgo at the pinned upstream revision; fixed by a later pin bump

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions