Skip to content

Commit b57dc53

Browse files
authored
Add support for Pick type operator (#64)
1 parent 3c91051 commit b57dc53

File tree

2 files changed

+132
-2
lines changed

2 files changed

+132
-2
lines changed

type-generation/src/astToIR.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ type SyntheticTypeRoot =
286286
| {
287287
kind: "omit";
288288
node: TypeReferenceNode;
289+
}
290+
| {
291+
kind: "pick";
292+
node: TypeReferenceNode;
289293
};
290294

291295
function classifySyntheticType(node: TypeNode): SyntheticTypeRoot | undefined {
@@ -303,11 +307,15 @@ function classifySyntheticType(node: TypeNode): SyntheticTypeRoot | undefined {
303307
if (name === "Omit") {
304308
return { kind: "omit", node };
305309
}
310+
if (name === "Pick") {
311+
return { kind: "pick", node };
312+
}
306313
return undefined;
307314
}
308315

309316
type Modifier = {
310317
omitSet?: Set<string>;
318+
pickSet?: Set<string>;
311319
};
312320

313321
class SyntheticTypeConverter {
@@ -322,14 +330,21 @@ class SyntheticTypeConverter {
322330
}
323331

324332
hasWork(base: TypeNode, modifiers: Modifier) {
325-
const { omitSet } = modifiers;
333+
const { omitSet, pickSet } = modifiers;
326334
if (omitSet) {
327335
for (const prop of base.getType().getProperties()) {
328336
if (omitSet.has(prop.getName())) {
329337
return true;
330338
}
331339
}
332340
}
341+
if (pickSet) {
342+
for (const prop of base.getType().getProperties()) {
343+
if (!pickSet.has(prop.getName())) {
344+
return true;
345+
}
346+
}
347+
}
333348
return false;
334349
}
335350

@@ -342,12 +357,17 @@ class SyntheticTypeConverter {
342357
name += "_iface";
343358
}
344359
let members = nodes.flatMap((x) => x.getMembers());
345-
const { omitSet } = modifiers;
360+
const { omitSet, pickSet } = modifiers;
346361
if (omitSet) {
347362
members = members.filter(
348363
(x) => !Node.isPropertyNamed(x) || !omitSet.has(x.getName()),
349364
);
350365
}
366+
if (pickSet) {
367+
members = members.filter(
368+
(x) => !Node.isPropertyNamed(x) || pickSet.has(x.getName()),
369+
);
370+
}
351371
const result = this.converter.interfaceToIR(name, [], members, [], [], []);
352372
this.converter.extraTopLevels.push(result);
353373
return { kind: "reference", name, typeArgs: [] };
@@ -395,6 +415,17 @@ class SyntheticTypeConverter {
395415
this.nameContext.pop();
396416
return result;
397417
}
418+
case "pick": {
419+
const node = typeRoot.node;
420+
const base = node.getTypeArguments()[0]!;
421+
const toPickType = node.getTypeArguments()[1]!;
422+
modifiers = structuredClone(modifiers);
423+
modifiers.pickSet = getLiteralTypeArgSet(toPickType);
424+
this.nameContext.push("Pick");
425+
const res = this.typeToIR(base, modifiers);
426+
this.nameContext.pop();
427+
return res;
428+
}
398429
}
399430
}
400431

type-generation/tests/a.test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,6 +1597,105 @@ describe("emit", () => {
15971597
);
15981598
});
15991599
});
1600+
describe("Pick", () => {
1601+
it("PickLiteral1", () => {
1602+
const res = emitFile(`
1603+
type D = Pick<{ a: string; b: number; }, "b">;
1604+
declare function f(): D;
1605+
`);
1606+
assert.strictEqual(
1607+
removeTypeIgnores(res.slice(1).join("\n\n")),
1608+
dedent(`
1609+
type D = D__Pick_iface
1610+
1611+
def f() -> D: ...
1612+
1613+
class D__Pick_iface(Protocol):
1614+
b: int | float = ...
1615+
`).trim(),
1616+
);
1617+
});
1618+
it("PickLiteral2", () => {
1619+
const res = emitFile(`
1620+
type D = Pick<{ a: string; b: string; c: number; }, "b" | "c">;
1621+
declare function f(): D;
1622+
`);
1623+
assert.strictEqual(
1624+
removeTypeIgnores(res.slice(1).join("\n\n")),
1625+
dedent(`
1626+
type D = D__Pick_iface
1627+
1628+
def f() -> D: ...
1629+
1630+
class D__Pick_iface(Protocol):
1631+
b: str = ...
1632+
c: int | float = ...
1633+
`).trim(),
1634+
);
1635+
});
1636+
it("PickIntersection", () => {
1637+
const res = emitFile(`
1638+
type D = Pick<{ a: string; b: string; } & { c : string; }, "b">;
1639+
declare function f(): D;
1640+
`);
1641+
assert.strictEqual(
1642+
removeTypeIgnores(res.slice(1).join("\n\n")),
1643+
dedent(`
1644+
type D = D__Pick_iface
1645+
1646+
def f() -> D: ...
1647+
1648+
class D__Pick__Intersection0_iface(Protocol):
1649+
b: str = ...
1650+
1651+
class D__Pick__Intersection1_iface(Protocol):
1652+
pass
1653+
1654+
class D__Pick_iface(D__Pick__Intersection1_iface, D__Pick__Intersection0_iface, Protocol):
1655+
pass
1656+
`).trim(),
1657+
);
1658+
});
1659+
it("PickInterface", () => {
1660+
const res = emitFile(`
1661+
interface A { a: string; b: string; }
1662+
type D = Pick<A, "b">;
1663+
declare function f(): D;
1664+
`);
1665+
assert.strictEqual(
1666+
removeTypeIgnores(res.slice(1).join("\n\n")),
1667+
dedent(`
1668+
type D = D__Pick__A_iface
1669+
1670+
def f() -> D: ...
1671+
1672+
class D__Pick__A_iface(Protocol):
1673+
b: str = ...
1674+
`).trim(),
1675+
);
1676+
});
1677+
it("PickAlias", () => {
1678+
const res = emitFile(`
1679+
type A = {
1680+
s?: boolean;
1681+
t?: string;
1682+
};
1683+
type B = Pick<A, 's'>;
1684+
declare function f(): B;
1685+
`);
1686+
assert.strictEqual(
1687+
removeTypeIgnores(res.slice(1).join("\n\n")),
1688+
dedent(`
1689+
type B = B__Pick_iface
1690+
1691+
def f() -> B: ...
1692+
1693+
class B__Pick_iface(Protocol):
1694+
s: bool | None = ...
1695+
`).trim(),
1696+
);
1697+
});
1698+
});
16001699
});
16011700
describe("adjustments", () => {
16021701
it("setTimeout", () => {

0 commit comments

Comments
 (0)