Skip to content

Commit b8f551b

Browse files
Add / and * parameter handling (#340)
Add / and * parameter handling to functions.
1 parent 604f82b commit b8f551b

File tree

3 files changed

+158
-36
lines changed

3 files changed

+158
-36
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: feature
3+
packages:
4+
- "@alloy-js/python"
5+
---
6+
7+
Add / and * parameter handling

packages/python/src/components/CallSignature.tsx

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,16 @@ import { createPythonSymbol } from "../symbol-creation.js";
1414
import { PythonOutputSymbol } from "../symbols/index.js";
1515
import { Atom } from "./Atom.jsx";
1616

17+
export type ParameterMarker = "*" | "/";
18+
19+
function isParameterMarker(
20+
param: string | ParameterDescriptor | undefined,
21+
): param is ParameterMarker {
22+
return typeof param === "string" && (param === "*" || param === "/");
23+
}
24+
1725
export interface CallSignatureParametersProps {
18-
readonly parameters?: (ParameterDescriptor | string)[];
26+
readonly parameters?: (ParameterDescriptor | ParameterMarker | string)[];
1927
readonly args?: boolean;
2028
readonly kwargs?: boolean;
2129
}
@@ -34,13 +42,11 @@ export interface CallSignatureParametersProps {
3442
* ```
3543
*/
3644
export function CallSignatureParameters(props: CallSignatureParametersProps) {
37-
const parameters = normalizeAndDeclareParameters(props.parameters ?? []);
38-
3945
const parameterList = computed(() => {
40-
const params = [];
41-
// Add regular parameters
42-
parameters.forEach((param) => {
43-
params.push(parameter(param));
46+
const params = (props.parameters ?? []).map((p) => {
47+
return isParameterMarker(p) ? p : (
48+
parameter(normalizeAndDeclareParameter(p))
49+
);
4450
});
4551

4652
// Add *args if specified
@@ -88,40 +94,39 @@ interface DeclaredParameterDescriptor
8894
TypeSlot?: SymbolSlot;
8995
}
9096

91-
function normalizeAndDeclareParameters(
92-
parameters: (ParameterDescriptor | string)[],
93-
): DeclaredParameterDescriptor[] {
94-
return parameters.map((param) => {
95-
if (isParameterDescriptor(param)) {
96-
const TypeSlot = createSymbolSlot();
97-
98-
const symbol = createPythonSymbol(
99-
param.name,
100-
{
101-
refkeys: param.refkey,
102-
type: TypeSlot.firstSymbol,
103-
},
104-
"parameter",
105-
);
106-
107-
return {
108-
...param,
109-
symbol,
110-
TypeSlot,
111-
};
112-
} else {
113-
const symbol = createPythonSymbol(param, {}, "parameter");
114-
return { refkeys: symbol.refkeys, symbol };
115-
}
116-
});
97+
function normalizeAndDeclareParameter(
98+
param: ParameterDescriptor | string,
99+
): DeclaredParameterDescriptor {
100+
if (isParameterDescriptor(param)) {
101+
const TypeSlot = createSymbolSlot();
102+
103+
const symbol = createPythonSymbol(
104+
param.name,
105+
{
106+
refkeys: param.refkey,
107+
type: TypeSlot.firstSymbol,
108+
},
109+
"parameter",
110+
);
111+
112+
return {
113+
...param,
114+
symbol,
115+
TypeSlot,
116+
};
117+
} else {
118+
const symbol = createPythonSymbol(param, {}, "parameter");
119+
return { symbol };
120+
}
117121
}
118122

119123
export interface CallSignatureProps {
120124
/**
121-
* The parameters to the call signature. Can be an array of strings (for parameters
122-
* which don't have a type or a default value) or {@link ParameterDescriptor}s.
125+
* The parameters to the call signature. Can be an array of strings (for simple
126+
* parameter names), {@link ParameterDescriptor}s, or special markers ("*" for
127+
* keyword-only, "/" for positional-only).
123128
*/
124-
parameters?: (ParameterDescriptor | string)[];
129+
parameters?: (ParameterDescriptor | ParameterMarker | string)[];
125130

126131
/**
127132
* The type parameters of the call signature, e.g. for a generic function.

packages/python/test/callsignatures.test.tsx

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,35 @@ describe("Call Signature Parameters", () => {
9696
a: int = 5, b: str = "hello"
9797
`);
9898
});
99+
it("renders keyword-only parameters with * marker", () => {
100+
const result = toSourceText([
101+
<py.CallSignatureParameters
102+
parameters={[
103+
{ name: "a", type: "str" },
104+
"*",
105+
{ name: "b", type: "int", default: 10 },
106+
{ name: "c", type: "bool", default: true },
107+
]}
108+
/>,
109+
]);
110+
expect(result).toRenderTo(d`
111+
a: str, *, b: int = 10, c: bool = True
112+
`);
113+
});
114+
it("renders only keyword-only parameters with * marker at start", () => {
115+
const result = toSourceText([
116+
<py.CallSignatureParameters
117+
parameters={[
118+
"*",
119+
{ name: "a", type: "str", default: "hello" },
120+
{ name: "b", type: "int", default: 42 },
121+
]}
122+
/>,
123+
]);
124+
expect(result).toRenderTo(d`
125+
*, a: str = "hello", b: int = 42
126+
`);
127+
});
99128
});
100129

101130
describe("Call Signature", () => {
@@ -281,4 +310,85 @@ describe("Call Signature - Parameter Descriptors", () => {
281310
[T, U](a: int, b: str = "default_value") -> int
282311
`);
283312
});
313+
it("renders a call signature with keyword-only parameters using * marker", () => {
314+
const result = toSourceText([
315+
<py.CallSignature
316+
parameters={[
317+
{ name: "id", type: "str" },
318+
"*",
319+
{ name: "locale", type: "str", default: "en-US" },
320+
{ name: "debug", type: "bool", default: false },
321+
]}
322+
returnType="str"
323+
/>,
324+
]);
325+
expect(result).toRenderTo(d`
326+
(id: str, *, locale: str = "en-US", debug: bool = False) -> str
327+
`);
328+
});
329+
it("renders a call signature with only keyword-only parameters", () => {
330+
const result = toSourceText([
331+
<py.CallSignature
332+
parameters={[
333+
"*",
334+
{ name: "name", type: "str", default: "alice" },
335+
{ name: "age", type: "int", default: 30 },
336+
]}
337+
returnType="None"
338+
/>,
339+
]);
340+
expect(result).toRenderTo(d`
341+
(*, name: str = "alice", age: int = 30) -> None
342+
`);
343+
});
344+
it("renders a call signature with positional, keyword-only, and *args/**kwargs", () => {
345+
const result = toSourceText([
346+
<py.CallSignature
347+
parameters={[
348+
{ name: "a", type: "int" },
349+
"*",
350+
{ name: "b", type: "str", default: "hello" },
351+
]}
352+
args
353+
kwargs
354+
returnType="None"
355+
/>,
356+
]);
357+
expect(result).toRenderTo(d`
358+
(a: int, *, b: str = "hello", *args, **kwargs) -> None
359+
`);
360+
});
361+
it("renders a call signature with positional-only parameters using / marker", () => {
362+
const result = toSourceText([
363+
<py.CallSignature
364+
parameters={[
365+
{ name: "a", type: "int" },
366+
{ name: "b", type: "str" },
367+
"/",
368+
{ name: "c", type: "bool" },
369+
]}
370+
returnType="None"
371+
/>,
372+
]);
373+
expect(result).toRenderTo(d`
374+
(a: int, b: str, /, c: bool) -> None
375+
`);
376+
});
377+
it("renders a call signature with positional-only, regular, and keyword-only parameters", () => {
378+
const result = toSourceText([
379+
<py.CallSignature
380+
parameters={[
381+
{ name: "a", type: "int" },
382+
"/",
383+
{ name: "b", type: "str" },
384+
"*",
385+
{ name: "c", type: "bool", default: true },
386+
]}
387+
returnType="None"
388+
/>,
389+
]);
390+
expect(result).toRenderTo(d`
391+
(a: int, /, b: str, *, c: bool = True) -> None
392+
`);
393+
});
284394
});

0 commit comments

Comments
 (0)