Skip to content

Commit cbab750

Browse files
authored
Handle type literals in function signature (#68)
1 parent ed0bf16 commit cbab750

File tree

2 files changed

+45
-11
lines changed

2 files changed

+45
-11
lines changed

type-generation/src/astToIR.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -331,10 +331,15 @@ class SyntheticTypeConverter {
331331
// Currently it has no state (other than recording that nameContext is not
332332
// undefined) and is just factored away from converter to keep things tidier.
333333
converter: Converter;
334-
nameContext: string[];
335-
constructor(converter: Converter, nameContext: string[]) {
334+
constructor(converter: Converter) {
336335
this.converter = converter;
337-
this.nameContext = nameContext;
336+
}
337+
get nameContext(): string[] {
338+
const ctx = this.converter.nameContext;
339+
if (!ctx) {
340+
throw new Error("Should not happen");
341+
}
342+
return ctx;
338343
}
339344

340345
hasWork(base: TypeNode, modifiers: Modifier) {
@@ -657,10 +662,7 @@ export class Converter {
657662
if (!typeRoot) {
658663
return undefined;
659664
}
660-
return new SyntheticTypeConverter(
661-
this,
662-
this.nameContext,
663-
).classifiedTypeToIr(typeRoot);
665+
return new SyntheticTypeConverter(this).classifiedTypeToIr(typeRoot);
664666
}
665667

666668
typeToIR(
@@ -751,13 +753,27 @@ export class Converter {
751753

752754
const pyParams: ParamIR[] = [];
753755
let spreadParam: ParamIR | undefined;
754-
for (const param of decl.getParameters()) {
756+
const params = decl.getParameters();
757+
for (let idx = 0; idx < params.length; idx++) {
758+
const param = params[idx];
755759
const spread = !!param.getDotDotDotToken();
756760
const optional = !!param.hasQuestionToken();
757761
const name = param.getName();
758-
this.pushNameContext(name);
759-
const type = this.typeToIR(param.getTypeNode()!, optional);
760-
this.popNameContext();
762+
const isValidPythonIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_]*$/.test(name);
763+
const oldNameContext = this.nameContext?.slice();
764+
const paramType = param.getTypeNode()!;
765+
const isLast = idx === params.length - 1;
766+
if (isLast && Node.isTypeLiteral(paramType)) {
767+
// If it's the last argument and the type is a type literal, we'll
768+
// destructure it so don't make a type.
769+
this.nameContext = undefined;
770+
} else if (isValidPythonIdentifier) {
771+
this.pushNameContext(name);
772+
} else {
773+
this.nameContext = undefined;
774+
}
775+
const type = this.typeToIR(paramType, optional);
776+
this.nameContext = oldNameContext;
761777
const pyParam: ParamIR = {
762778
name,
763779
type,
@@ -934,7 +950,10 @@ export class Converter {
934950

935951
funcDeclsToIR(name: string, decls: FunctionDeclaration[]): CallableIR {
936952
const astSigs = decls.map((x) => x.getSignature());
953+
const origNameContext = this.nameContext;
954+
this.nameContext ??= [];
937955
const result = this.callableToIR(name, astSigs, false);
956+
this.nameContext = origNameContext;
938957
return result;
939958
}
940959

type-generation/tests/a.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,6 +1798,21 @@ describe("emit", () => {
17981798
);
17991799
});
18001800
describe("type literals", () => {
1801+
it("type literal in function signature", () => {
1802+
const res = emitFile(`
1803+
declare function f(a: {x: string, y: boolean}, b: boolean): void;
1804+
`);
1805+
assert.strictEqual(
1806+
removeTypeIgnores(res.slice(1).join("\n\n")),
1807+
dedent(`
1808+
def f(a: f__Sig0__a_iface, b: bool, /) -> None: ...
1809+
1810+
class f__Sig0__a_iface(Protocol):
1811+
x: str = ...
1812+
y: bool = ...
1813+
`).trim(),
1814+
);
1815+
});
18011816
it("type literal in class property", () => {
18021817
const res = emitFile(`
18031818
declare class T {

0 commit comments

Comments
 (0)