Skip to content

Commit e0b0655

Browse files
authored
Add support for partial type operator (#65)
1 parent b57dc53 commit e0b0655

File tree

2 files changed

+115
-3
lines changed

2 files changed

+115
-3
lines changed

type-generation/src/astToIR.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@ type SyntheticTypeRoot =
290290
| {
291291
kind: "pick";
292292
node: TypeReferenceNode;
293+
}
294+
| {
295+
kind: "partial";
296+
node: TypeReferenceNode;
293297
};
294298

295299
function classifySyntheticType(node: TypeNode): SyntheticTypeRoot | undefined {
@@ -307,13 +311,17 @@ function classifySyntheticType(node: TypeNode): SyntheticTypeRoot | undefined {
307311
if (name === "Omit") {
308312
return { kind: "omit", node };
309313
}
314+
if (name === "Partial") {
315+
return { kind: "partial", node };
316+
}
310317
if (name === "Pick") {
311318
return { kind: "pick", node };
312319
}
313320
return undefined;
314321
}
315322

316323
type Modifier = {
324+
partial: boolean;
317325
omitSet?: Set<string>;
318326
pickSet?: Set<string>;
319327
};
@@ -330,7 +338,10 @@ class SyntheticTypeConverter {
330338
}
331339

332340
hasWork(base: TypeNode, modifiers: Modifier) {
333-
const { omitSet, pickSet } = modifiers;
341+
const { partial, omitSet, pickSet } = modifiers;
342+
if (partial) {
343+
return true;
344+
}
334345
if (omitSet) {
335346
for (const prop of base.getType().getProperties()) {
336347
if (omitSet.has(prop.getName())) {
@@ -357,7 +368,7 @@ class SyntheticTypeConverter {
357368
name += "_iface";
358369
}
359370
let members = nodes.flatMap((x) => x.getMembers());
360-
const { omitSet, pickSet } = modifiers;
371+
const { omitSet, partial, pickSet } = modifiers;
361372
if (omitSet) {
362373
members = members.filter(
363374
(x) => !Node.isPropertyNamed(x) || !omitSet.has(x.getName()),
@@ -369,6 +380,11 @@ class SyntheticTypeConverter {
369380
);
370381
}
371382
const result = this.converter.interfaceToIR(name, [], members, [], [], []);
383+
if (partial) {
384+
for (const prop of result.properties) {
385+
prop.isOptional = true;
386+
}
387+
}
372388
this.converter.extraTopLevels.push(result);
373389
return { kind: "reference", name, typeArgs: [] };
374390
}
@@ -377,7 +393,7 @@ class SyntheticTypeConverter {
377393
typeRoot: SyntheticTypeRoot,
378394
modifiersArg?: Modifier,
379395
): TypeIR {
380-
let modifiers = modifiersArg ?? {};
396+
let modifiers = modifiersArg ?? { partial: false };
381397
switch (typeRoot.kind) {
382398
case "intersection": {
383399
const node = typeRoot.node;
@@ -426,6 +442,16 @@ class SyntheticTypeConverter {
426442
this.nameContext.pop();
427443
return res;
428444
}
445+
case "partial": {
446+
const node = typeRoot.node;
447+
const base = node.getTypeArguments()[0]!;
448+
modifiers = structuredClone(modifiers);
449+
modifiers.partial = true;
450+
this.nameContext.push("Partial");
451+
const result = this.typeToIR(base, modifiers);
452+
this.nameContext.pop();
453+
return result;
454+
}
429455
}
430456
}
431457

type-generation/tests/a.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,6 +1696,92 @@ describe("emit", () => {
16961696
);
16971697
});
16981698
});
1699+
describe("Partial", () => {
1700+
it("Partial literal type", () => {
1701+
const res = emitFile(`
1702+
type B = Partial<{
1703+
s: boolean;
1704+
t: string;
1705+
}>;
1706+
declare function f(): B;
1707+
def f() -> B: ...
1708+
`);
1709+
assert.strictEqual(
1710+
removeTypeIgnores(res.slice(1).join("\n\n")),
1711+
dedent(`
1712+
type B = B__Partial_iface
1713+
1714+
def f() -> B: ...
1715+
1716+
class B__Partial_iface(Protocol):
1717+
s: bool | None = ...
1718+
t: str | None = ...
1719+
`).trim(),
1720+
);
1721+
});
1722+
it("PartialAlias", () => {
1723+
const res = emitFile(`
1724+
type A = {
1725+
s: boolean;
1726+
t: string;
1727+
};
1728+
type B = Partial<A>;
1729+
declare function f(): B;
1730+
`);
1731+
assert.strictEqual(
1732+
removeTypeIgnores(res.slice(1).join("\n\n")),
1733+
dedent(`
1734+
type B = B__Partial_iface
1735+
1736+
def f() -> B: ...
1737+
1738+
class B__Partial_iface(Protocol):
1739+
s: bool | None = ...
1740+
t: str | None = ...
1741+
`).trim(),
1742+
);
1743+
});
1744+
it("PartialInterface", () => {
1745+
const res = emitFile(`
1746+
interface A { a: string; b: string; }
1747+
type D = Partial<A>;
1748+
declare function f(): D;
1749+
`);
1750+
assert.strictEqual(
1751+
removeTypeIgnores(res.slice(1).join("\n\n")),
1752+
dedent(`
1753+
type D = D__Partial__A_iface
1754+
1755+
def f() -> D: ...
1756+
1757+
class D__Partial__A_iface(Protocol):
1758+
a: str | None = ...
1759+
b: str | None = ...
1760+
`).trim(),
1761+
);
1762+
});
1763+
});
1764+
it("Composed type operators", () => {
1765+
const res = emitFile(`
1766+
interface V {
1767+
i: string;
1768+
v: number[];
1769+
};
1770+
type M = Pick<Partial<V>, "v">
1771+
declare function f(): M;
1772+
`);
1773+
assert.strictEqual(
1774+
removeTypeIgnores(res.slice(1).join("\n\n")),
1775+
dedent(`
1776+
type M = M__Pick__Partial__V_iface
1777+
1778+
def f() -> M: ...
1779+
1780+
class M__Pick__Partial__V_iface(Protocol):
1781+
v: JsArray[int | float] | None = ...
1782+
`).trim(),
1783+
);
1784+
});
16991785
});
17001786
describe("adjustments", () => {
17011787
it("setTimeout", () => {

0 commit comments

Comments
 (0)