Skip to content

Commit b6c4aef

Browse files
authored
Handle intersection with type alias correctly (#70)
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 from.
1 parent cbab750 commit b6c4aef

File tree

2 files changed

+45
-10
lines changed

2 files changed

+45
-10
lines changed

type-generation/src/astToIR.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -406,16 +406,24 @@ class SyntheticTypeConverter {
406406
.getTypeNodes()
407407
.map((ty, idx) => {
408408
this.nameContext.push(`Intersection${idx}`);
409+
// In TS it's just fine to use the LHS of a type alias in an
410+
// intersection. However, in Python we can't inherit from a type
411+
// alias. So if the reference target is a type alias, we have to
412+
// unwind it until we hit something we can inherit.
413+
while (Node.isTypeReference(ty)) {
414+
const classified = classifyIdentifier(
415+
ty.getTypeName() as Identifier,
416+
);
417+
if (classified.kind !== "typeAlias") {
418+
break;
419+
}
420+
ty = classified.decl.getTypeNode()!;
421+
}
409422
const res = this.typeToIR(ty, modifiers);
410423
this.nameContext.pop();
411424
return res;
412425
})
413426
.filter((x): x is ReferenceTypeIR => !!x && x.kind === "reference");
414-
for (const x of types) {
415-
if (!x.name.endsWith("_iface")) {
416-
x.name += "_iface";
417-
}
418-
}
419427
const name = this.nameContext.join("__") + "_iface";
420428
this.converter.extraTopLevels.push(
421429
this.converter.interfaceToIR(name, types, [], [], [], []),

type-generation/tests/a.test.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,19 +1456,46 @@ describe("emit", () => {
14561456
dedent(`
14571457
type D = D_iface
14581458
1459-
type F = F_iface
1460-
14611459
def f() -> D: ...
14621460
1461+
class D__Intersection0_iface(Protocol):
1462+
a: str = ...
1463+
14631464
class D__Intersection1_iface(Protocol):
14641465
@property
14651466
def id(self, /) -> int | float: ...
14661467
1467-
class D_iface(F_iface, D__Intersection1_iface, Protocol):
1468+
class D_iface(D__Intersection1_iface, D__Intersection0_iface, Protocol):
14681469
pass
1470+
`).trim(),
1471+
);
1472+
});
1473+
it("intersection3", () => {
1474+
const res = emitFile(`
1475+
interface I<T> {
1476+
x: T;
1477+
}
1478+
type F = I<string>;
1479+
type D = F & {
1480+
id: number;
1481+
};
1482+
declare function f(): D;
1483+
`);
1484+
assert.strictEqual(
1485+
removeTypeIgnores(res.slice(1).join("\n\n")),
1486+
dedent(`
1487+
type D = D_iface
14691488
1470-
class F_iface(Protocol):
1471-
a: str = ...
1489+
def f() -> D: ...
1490+
1491+
class I_iface[T](Protocol):
1492+
x: T = ...
1493+
1494+
class D__Intersection1_iface(Protocol):
1495+
id: int | float = ...
1496+
1497+
class D_iface(D__Intersection1_iface, I_iface[str], Protocol):
1498+
pass
14721499
`).trim(),
14731500
);
14741501
});

0 commit comments

Comments
 (0)