From 8560f3f2d0525c631636b39f5bdcd679bb03ad55 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 26 Sep 2025 16:45:17 +0200 Subject: [PATCH 1/2] Handle intersection with type alias correctly --- type-generation/src/astToIR.ts | 16 +++++++++----- type-generation/tests/a.test.ts | 37 ++++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/type-generation/src/astToIR.ts b/type-generation/src/astToIR.ts index d060bd7..4a90447 100644 --- a/type-generation/src/astToIR.ts +++ b/type-generation/src/astToIR.ts @@ -406,16 +406,22 @@ class SyntheticTypeConverter { .getTypeNodes() .map((ty, idx) => { this.nameContext.push(`Intersection${idx}`); + // In TS it's just fine to use the LHS of a type alias in an + // intersection. However, in Python we can't inherit from a type + // alias. So if the reference target is a type alias, we have to + // unwind it until we hit something we can inherit. + while (Node.isTypeReference(ty)) { + const classified = classifyIdentifier(ty.getTypeName() as Identifier); + if (classified.kind !== "typeAlias") { + break; + } + ty = classified.decl.getTypeNode()!; + } const res = this.typeToIR(ty, modifiers); this.nameContext.pop(); return res; }) .filter((x): x is ReferenceTypeIR => !!x && x.kind === "reference"); - for (const x of types) { - if (!x.name.endsWith("_iface")) { - x.name += "_iface"; - } - } const name = this.nameContext.join("__") + "_iface"; this.converter.extraTopLevels.push( this.converter.interfaceToIR(name, types, [], [], [], []), diff --git a/type-generation/tests/a.test.ts b/type-generation/tests/a.test.ts index a0dad55..bdcea4b 100644 --- a/type-generation/tests/a.test.ts +++ b/type-generation/tests/a.test.ts @@ -1456,19 +1456,46 @@ describe("emit", () => { dedent(` type D = D_iface - type F = F_iface - def f() -> D: ... + class D__Intersection0_iface(Protocol): + a: str = ... + class D__Intersection1_iface(Protocol): @property def id(self, /) -> int | float: ... - class D_iface(F_iface, D__Intersection1_iface, Protocol): + class D_iface(D__Intersection1_iface, D__Intersection0_iface, Protocol): pass + `).trim(), + ); + }); + it("intersection3", () => { + const res = emitFile(` + interface I { + x: T; + } + type F = I; + type D = F & { + id: number; + }; + declare function f(): D; + `); + assert.strictEqual( + removeTypeIgnores(res.slice(1).join("\n\n")), + dedent(` + type D = D_iface - class F_iface(Protocol): - a: str = ... + def f() -> D: ... + + class I_iface[T](Protocol): + x: T = ... + + class D__Intersection1_iface(Protocol): + id: int | float = ... + + class D_iface(D__Intersection1_iface, I_iface[str], Protocol): + pass `).trim(), ); }); From 921403a08d4782d990a1526bcd04fe260aa6dc35 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 14:49:32 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- type-generation/src/astToIR.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/type-generation/src/astToIR.ts b/type-generation/src/astToIR.ts index 4a90447..07dcea2 100644 --- a/type-generation/src/astToIR.ts +++ b/type-generation/src/astToIR.ts @@ -411,7 +411,9 @@ class SyntheticTypeConverter { // alias. So if the reference target is a type alias, we have to // unwind it until we hit something we can inherit. while (Node.isTypeReference(ty)) { - const classified = classifyIdentifier(ty.getTypeName() as Identifier); + const classified = classifyIdentifier( + ty.getTypeName() as Identifier, + ); if (classified.kind !== "typeAlias") { break; }