@alloy-js/python 0.4.0-dev.0 → 0.4.0-dev.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/src/components/ImportStatement.js +2 -2
  2. package/dist/src/components/ImportStatement.js.map +1 -1
  3. package/dist/src/components/MemberExpression.d.ts +1 -1
  4. package/dist/src/components/MemberExpression.d.ts.map +1 -1
  5. package/dist/src/components/MemberExpression.js +98 -180
  6. package/dist/src/components/MemberExpression.js.map +1 -1
  7. package/dist/src/components/MemberScope.d.ts.map +1 -1
  8. package/dist/src/components/MemberScope.js +6 -3
  9. package/dist/src/components/MemberScope.js.map +1 -1
  10. package/dist/src/components/SourceFile.d.ts.map +1 -1
  11. package/dist/src/components/SourceFile.js +2 -2
  12. package/dist/src/components/SourceFile.js.map +1 -1
  13. package/dist/src/create-module.d.ts.map +1 -1
  14. package/dist/src/create-module.js +3 -3
  15. package/dist/src/create-module.js.map +1 -1
  16. package/dist/src/symbol-creation.d.ts.map +1 -1
  17. package/dist/src/symbol-creation.js +9 -5
  18. package/dist/src/symbol-creation.js.map +1 -1
  19. package/dist/src/symbols/python-module-scope.d.ts +1 -0
  20. package/dist/src/symbols/python-module-scope.d.ts.map +1 -1
  21. package/dist/src/symbols/python-module-scope.js +9 -2
  22. package/dist/src/symbols/python-module-scope.js.map +1 -1
  23. package/dist/src/symbols/python-output-symbol.d.ts +1 -0
  24. package/dist/src/symbols/python-output-symbol.d.ts.map +1 -1
  25. package/dist/src/symbols/python-output-symbol.js +12 -3
  26. package/dist/src/symbols/python-output-symbol.js.map +1 -1
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +9 -5
  29. package/src/components/ImportStatement.tsx +1 -1
  30. package/src/components/MemberExpression.tsx +174 -298
  31. package/src/components/MemberScope.tsx +16 -4
  32. package/src/components/SourceFile.tsx +2 -1
  33. package/src/create-module.ts +7 -3
  34. package/src/symbol-creation.ts +10 -5
  35. package/src/symbols/python-module-scope.ts +11 -2
  36. package/src/symbols/python-output-symbol.ts +13 -2
  37. package/temp/api.json +72 -2
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@alloy-js/python",
3
- "version": "0.4.0-dev.0",
3
+ "version": "0.4.0-dev.3",
4
4
  "description": "Python bindings for Alloy",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/alloy-framework/alloy.git"
8
+ },
5
9
  "exports": {
6
10
  ".": {
7
11
  "import": "./dist/src/index.js"
@@ -14,18 +18,18 @@
14
18
  "author": "",
15
19
  "license": "MIT",
16
20
  "dependencies": {
17
- "@alloy-js/core": "~0.22.0 || >= 0.23.0-dev.0",
21
+ "@alloy-js/core": "~0.22.0 || >= 0.23.0-dev.10",
18
22
  "change-case": "^5.4.4",
19
23
  "pathe": "^2.0.3"
20
24
  },
21
25
  "devDependencies": {
22
- "@alloy-js/cli": "~0.22.0 || >= 0.23.0-dev.0",
23
- "@alloy-js/rollup-plugin": "~0.1.0 || >= 0.1.1-dev.0",
26
+ "@alloy-js/cli": "~0.22.0 || >= 0.23.0-dev.3",
27
+ "@alloy-js/rollup-plugin": "~0.1.0 || >= 0.1.1-dev.1",
24
28
  "@microsoft/api-extractor": "~7.52.8",
25
29
  "@rollup/plugin-typescript": "^12.1.2",
26
30
  "concurrently": "^9.2.0",
27
31
  "typescript": "^5.8.3",
28
- "vitest": "^3.2.4"
32
+ "vitest": "3.2.4"
29
33
  },
30
34
  "type": "module",
31
35
  "scripts": {
@@ -45,7 +45,7 @@ export function ImportStatements(props: ImportStatementsProps) {
45
45
  return sortedSymbols.map((symbol, idx, arr) => (
46
46
  <>
47
47
  <ImportStatement path={targetPath} symbols={new Set([symbol])} />
48
- {idx < arr.length - 1 && <hbr />}
48
+ {/*@once*/ idx < arr.length - 1 && <hbr />}
49
49
  </>
50
50
  ));
51
51
  }
@@ -1,57 +1,126 @@
1
1
  import {
2
2
  Children,
3
- childrenArray,
4
3
  computed,
4
+ createAccessExpression,
5
5
  For,
6
- isComponentCreator,
7
6
  List,
8
7
  OutputSymbol,
9
- reactive,
10
- ref,
11
8
  Refkey,
12
9
  Show,
13
- takeSymbols,
14
- ToRefs,
15
- useBinder,
16
10
  } from "@alloy-js/core";
17
11
 
18
12
  export interface MemberExpressionProps {
19
13
  children: Children;
20
14
  }
21
15
 
22
- const MEMBER_ACCESS_TYPES = {
23
- ATTRIBUTE: "attribute",
24
- SUBSCRIPTION: "subscription",
25
- CALL: "call",
26
- } as const;
16
+ type PartDescriptor = {
17
+ type: "call" | "subscription" | "attribute";
18
+ name: Children | undefined;
19
+ expression: Children | undefined;
20
+ quoted: boolean;
21
+ args: Children[] | undefined;
22
+ };
27
23
 
28
- interface AttributeDescriptor extends PartDescriptorBase {
29
- type: typeof MEMBER_ACCESS_TYPES.ATTRIBUTE;
30
- name: Children;
31
- }
24
+ const exclusiveParts: (keyof MemberExpressionPartProps)[] = [
25
+ "args",
26
+ "refkey",
27
+ "symbol",
28
+ "id",
29
+ "key",
30
+ "keys",
31
+ "slice",
32
+ ];
32
33
 
33
- interface SubscriptionDescriptor extends PartDescriptorBase {
34
- type: typeof MEMBER_ACCESS_TYPES.SUBSCRIPTION;
35
- expression: Children;
36
- quoted: boolean;
37
- }
34
+ const { Expression, Part, registerOuterComponent } = createAccessExpression<
35
+ MemberExpressionPartProps,
36
+ PartDescriptor
37
+ >({
38
+ createDescriptor(partProps, sym, first) {
39
+ const foundProps = exclusiveParts.filter((key) => key in partProps);
40
+ if (foundProps.length > 1) {
41
+ throw new Error(
42
+ `Only one of ${foundProps.join(", ")} can be used for a MemberExpression part at a time`,
43
+ );
44
+ }
38
45
 
39
- interface CallDescriptor extends PartDescriptorBase {
40
- type: typeof MEMBER_ACCESS_TYPES.CALL;
41
- args: Children[];
42
- }
46
+ // Validate slice syntax
47
+ if (partProps.slice) {
48
+ const { start, stop, step } = partProps.slice;
49
+ if (!start && !stop && !step) {
50
+ throw new Error("MemberExpression.Part: slice object cannot be empty");
51
+ }
52
+ }
43
53
 
44
- interface PartDescriptorBase {
45
- type:
46
- | typeof MEMBER_ACCESS_TYPES.CALL
47
- | typeof MEMBER_ACCESS_TYPES.SUBSCRIPTION
48
- | typeof MEMBER_ACCESS_TYPES.ATTRIBUTE;
49
- }
54
+ // Validate keys array
55
+ if (partProps.keys?.length === 0) {
56
+ throw new Error("MemberExpression.Part: keys array cannot be empty");
57
+ }
58
+
59
+ if (partProps.args !== undefined) {
60
+ return {
61
+ type: "call" as const,
62
+ name: undefined,
63
+ expression: undefined,
64
+ quoted: false,
65
+ args: partProps.args === true ? [] : (partProps.args as Children[]),
66
+ };
67
+ } else if (
68
+ partProps.key !== undefined ||
69
+ partProps.keys !== undefined ||
70
+ partProps.slice !== undefined
71
+ ) {
72
+ return {
73
+ type: "subscription" as const,
74
+ name: undefined,
75
+ expression: getSubscriptionValue(partProps),
76
+ quoted:
77
+ partProps.key !== undefined && typeof partProps.key === "string",
78
+ args: undefined,
79
+ };
80
+ } else {
81
+ let name: Children;
82
+ if (first && partProps.refkey) {
83
+ name = partProps.refkey;
84
+ } else if (partProps.id !== undefined) {
85
+ if (!isValidIdentifier(partProps.id)) {
86
+ throw new Error(`Invalid identifier: ${partProps.id}`);
87
+ }
88
+ name = partProps.id;
89
+ } else if (sym) {
90
+ name = sym.name;
91
+ } else {
92
+ name = "<unresolved symbol>";
93
+ }
50
94
 
51
- type PartDescriptor =
52
- | AttributeDescriptor
53
- | SubscriptionDescriptor
54
- | CallDescriptor;
95
+ return {
96
+ type: "attribute" as const,
97
+ name,
98
+ expression: undefined,
99
+ quoted: false,
100
+ args: undefined,
101
+ };
102
+ }
103
+ },
104
+
105
+ getBase(part) {
106
+ if (part.type !== "attribute") {
107
+ throw new Error(
108
+ "The first part of a MemberExpression must be an id or refkey",
109
+ );
110
+ }
111
+ return part.name;
112
+ },
113
+
114
+ formatPart(part, _prevPart, _inCallChain) {
115
+ if (part.type === "call") {
116
+ return formatCallOutput(part);
117
+ } else if (part.type === "attribute") {
118
+ return formatAttributeOutput(part);
119
+ } else {
120
+ return formatSubscriptionOutput(part);
121
+ }
122
+ },
123
+ });
55
124
 
56
125
  /**
57
126
  * Create a member expression from parts. Each part can provide one of
@@ -83,237 +152,20 @@ type PartDescriptor =
83
152
  * ```
84
153
  */
85
154
  export function MemberExpression(props: MemberExpressionProps): Children {
86
- const children = childrenArray(() => props.children);
87
- const parts = childrenToPartDescriptors(children);
88
- // any symbols emitted from the children won't be relevant to
89
- // parent scopes. TODO: emit the proper symbol if we know it?
90
- takeSymbols();
91
-
92
- if (parts.length === 0) {
93
- return <></>;
94
- }
95
-
96
- return computed(() => {
97
- return formatChain(parts);
98
- });
155
+ return Expression(props);
99
156
  }
100
157
 
101
158
  /**
102
- * Build part descriptors from the children of a MemberExpression.
103
- */
104
- function childrenToPartDescriptors(children: Children[]): PartDescriptor[] {
105
- const parts: PartDescriptor[] = [];
106
- for (const child of children) {
107
- if (!isComponentCreator(child, MemberExpression.Part)) {
108
- // we ignore non-parts
109
- continue;
110
- }
111
-
112
- parts.push(
113
- createPartDescriptorFromProps(child.props, child === children[0]),
114
- );
115
- }
116
-
117
- return parts;
118
- }
119
-
120
- const exclusiveParts: (keyof MemberExpressionPartProps)[] = [
121
- "args",
122
- "refkey",
123
- "symbol",
124
- "id",
125
- "key",
126
- "keys",
127
- "slice",
128
- ];
129
- /**
130
- * Creates a reactive part descriptor from the given part props.
159
+ * A part of a member expression. Each part can provide one of the following
160
+ * props:
131
161
  *
132
- * @param partProps The props for the part.
133
- * @param first Whether this is the first part in the expression. Refkeys are
134
- * handled specially for the first part.
135
- */
136
- function createPartDescriptorFromProps(
137
- partProps: MemberExpressionPartProps,
138
- first: boolean,
139
- ) {
140
- const foundProps = exclusiveParts.filter((key) => {
141
- return key in partProps;
142
- });
143
-
144
- if (foundProps.length > 1) {
145
- throw new Error(
146
- `Only one of ${foundProps.join(", ")} can be used for a MemberExpression part at a time`,
147
- );
148
- }
149
-
150
- // Validate slice syntax
151
- if (partProps.slice) {
152
- const { start, stop, step } = partProps.slice;
153
- if (!start && !stop && !step) {
154
- throw new Error("MemberExpression.Part: slice object cannot be empty");
155
- }
156
- }
157
-
158
- // Validate keys array
159
- if (partProps.keys?.length === 0) {
160
- throw new Error("MemberExpression.Part: keys array cannot be empty");
161
- }
162
-
163
- const symbolSource = computed(() => {
164
- if (partProps.refkey) {
165
- return getSymbolForRefkey(partProps.refkey).value;
166
- } else if (partProps.symbol) {
167
- return partProps.symbol;
168
- } else {
169
- return undefined;
170
- }
171
- });
172
-
173
- // Return different descriptor types based on what props are provided
174
- let part: ToRefs<PartDescriptor>;
175
- if (partProps.args !== undefined) {
176
- // CallDescriptor
177
- part = {
178
- type: computed(() => {
179
- return "call" as const;
180
- }),
181
- args: ref<any>(partProps.args === true ? [] : partProps.args),
182
- };
183
- } else if (
184
- partProps.key !== undefined ||
185
- partProps.keys !== undefined ||
186
- partProps.slice !== undefined
187
- ) {
188
- // SubscriptionDescriptor
189
- part = {
190
- type: computed(() => {
191
- return "subscription" as const;
192
- }),
193
- expression: computed(() => {
194
- return getSubscriptionValue(partProps);
195
- }),
196
- quoted: computed(() => {
197
- return partProps.key !== undefined && typeof partProps.key === "string";
198
- }),
199
- };
200
- } else {
201
- // IdentifierDescriptor
202
- part = {
203
- type: computed(() => {
204
- return "attribute" as const;
205
- }),
206
- name: computed(() => {
207
- if (first && partProps.refkey) {
208
- return partProps.refkey;
209
- } else if (partProps.id !== undefined) {
210
- if (!isValidIdentifier(partProps.id)) {
211
- throw new Error(`Invalid identifier: ${partProps.id}`);
212
- }
213
- return partProps.id;
214
- } else if (symbolSource.value) {
215
- return symbolSource.value.name;
216
- } else {
217
- return "<unresolved symbol>";
218
- }
219
- }),
220
- };
221
- }
222
- return reactive(part);
223
- }
224
-
225
- /**
226
- * Convert a refkey to a symbol ref using the current binder.
227
- */
228
- function getSymbolForRefkey(refkey: Refkey) {
229
- const binder = useBinder();
230
- return binder!.getSymbolForRefkey(refkey);
231
- }
232
-
233
- /**
234
- * Format a chain of parts into a MemberExpression.
235
- */
236
- function formatChain(parts: PartDescriptor[]): Children {
237
- return computed(() => {
238
- const expression: Children[] = [];
239
-
240
- for (let i = 0; i < parts.length; i++) {
241
- const part = parts[i];
242
- if (i === 0) {
243
- if (!isAttributeDescriptor(part)) {
244
- throw new Error(
245
- "The first part of a MemberExpression must be an id or refkey",
246
- );
247
- }
248
- expression.push(part.name);
249
- } else {
250
- if (isCallDescriptor(part)) {
251
- expression.push(formatCallOutput(part));
252
- } else if (isAttributeDescriptor(part)) {
253
- expression.push(formatAttributeOutput(part));
254
- } else if (isSubscriptionDescriptor(part)) {
255
- expression.push(formatSubscriptionOutput(part));
256
- }
257
- }
258
- }
259
-
260
- return expression;
261
- });
262
- }
263
-
264
- /**
265
- * Format a part of a member expression that is an array access.
266
- * This is used for parts like `foo[0]` or `foo["bar"]`.
162
+ * * **id**: The identifier for the member expression part
163
+ * * **refkey**: A refkey for a symbol whose name becomes the identifier
164
+ * * **symbol**: a symbol whose name becomes the identifier part
165
+ * * **args**: create a method call with the given args
267
166
  */
268
- function formatSubscriptionOutput(part: SubscriptionDescriptor) {
269
- return (
270
- <group>
271
- {""}[
272
- <indent>
273
- <sbr />
274
- {part.quoted && '"'}
275
- {part.expression}
276
- {part.quoted && '"'}
277
- </indent>
278
- <sbr />]
279
- </group>
280
- );
281
- }
282
-
283
- function formatAttributeOutput(part: AttributeDescriptor) {
284
- return (
285
- <group>
286
- <indent>
287
- <ifBreak> \</ifBreak>
288
- <sbr />
289
- {"."}
290
- {part.name}
291
- </indent>
292
- </group>
293
- );
294
- }
295
-
296
- function formatCallOutput(part: CallDescriptor) {
297
- const args = computed(() => {
298
- return typeof part.args === "boolean" ? [] : (part.args ?? []);
299
- });
300
-
301
- return (
302
- <group>
303
- {""}(<Show when={args.value.length <= 1}>{args.value[0]}</Show>
304
- <Show when={args.value.length > 1}>
305
- <indent>
306
- <sbr />
307
- <For each={args} comma line>
308
- {(arg) => arg}
309
- </For>
310
- </indent>
311
- <sbr />
312
- </Show>
313
- )
314
- </group>
315
- );
316
- }
167
+ MemberExpression.Part = Part;
168
+ registerOuterComponent(MemberExpression);
317
169
 
318
170
  export interface MemberExpressionPartProps {
319
171
  /**
@@ -359,43 +211,6 @@ export interface MemberExpressionPartProps {
359
211
  */
360
212
  args?: Children[] | boolean;
361
213
  }
362
- /**
363
- * A part of a member expression. Each part can provide one of the following
364
- * props:
365
- *
366
- * * **id**: The identifier for the member expression part
367
- * * **refkey**: A refkey for a symbol whose name becomes the identifier
368
- * * **symbol**: a symbol whose name becomes the identifier part
369
- * * **args**: create a method call with the given args
370
- */
371
- MemberExpression.Part = function (props: MemberExpressionPartProps) {
372
- /**
373
- * This component does nothing except hold props which are retrieved by
374
- * the `MemberExpression` component.
375
- */
376
- };
377
-
378
- function isValidIdentifier(id: Children) {
379
- if (typeof id === "string" && id.includes('"')) {
380
- return false;
381
- }
382
- return true;
383
- }
384
- function isCallDescriptor(part: PartDescriptor): part is CallDescriptor {
385
- return "type" in part && part.type === MEMBER_ACCESS_TYPES.CALL;
386
- }
387
-
388
- function isAttributeDescriptor(
389
- part: PartDescriptor,
390
- ): part is AttributeDescriptor {
391
- return "type" in part && part.type === MEMBER_ACCESS_TYPES.ATTRIBUTE;
392
- }
393
-
394
- function isSubscriptionDescriptor(
395
- part: PartDescriptor,
396
- ): part is SubscriptionDescriptor {
397
- return "type" in part && part.type === MEMBER_ACCESS_TYPES.SUBSCRIPTION;
398
- }
399
214
 
400
215
  export interface SubscriptionProps {
401
216
  /**
@@ -421,6 +236,67 @@ export interface SubscriptionProps {
421
236
  };
422
237
  }
423
238
 
239
+ // --- Formatting helpers ---
240
+
241
+ function formatSubscriptionOutput(part: PartDescriptor) {
242
+ return (
243
+ <group>
244
+ {""}[
245
+ <indent>
246
+ <sbr />
247
+ {part.quoted && '"'}
248
+ {part.expression}
249
+ {part.quoted && '"'}
250
+ </indent>
251
+ <sbr />]
252
+ </group>
253
+ );
254
+ }
255
+
256
+ function formatAttributeOutput(part: PartDescriptor) {
257
+ return (
258
+ <group>
259
+ <indent>
260
+ <ifBreak> \</ifBreak>
261
+ <sbr />
262
+ {"."}
263
+ {part.name}
264
+ </indent>
265
+ </group>
266
+ );
267
+ }
268
+
269
+ function formatCallOutput(part: PartDescriptor) {
270
+ const args = computed(() => {
271
+ return typeof part.args === "boolean" ? [] : (part.args ?? []);
272
+ });
273
+
274
+ return (
275
+ <group>
276
+ {""}(<Show when={args.value.length <= 1}>{args.value[0]}</Show>
277
+ <Show when={args.value.length > 1}>
278
+ <indent>
279
+ <sbr />
280
+ <For each={args} comma line>
281
+ {(arg) => arg}
282
+ </For>
283
+ </indent>
284
+ <sbr />
285
+ </Show>
286
+ )
287
+ </group>
288
+ );
289
+ }
290
+
291
+ // --- Utilities ---
292
+
293
+ function isValidIdentifier(id: Children) {
294
+ if (typeof id === "string" && id.includes('"')) {
295
+ return false;
296
+ }
297
+ return true;
298
+ }
299
+
424
300
  function getSubscriptionValue(partProps: SubscriptionProps): Children {
425
301
  // Handle tuple keys: obj[a, b] → (a, b)
426
302
  if (partProps.keys?.length) {
@@ -1,4 +1,8 @@
1
- import { Children, MemberScope as CoreMemberScope } from "@alloy-js/core";
1
+ import {
2
+ Children,
3
+ MemberScope as CoreMemberScope,
4
+ createScope,
5
+ } from "@alloy-js/core";
2
6
  import { PythonMemberScope } from "../symbols/python-member-scope.js";
3
7
  import { PythonOutputSymbol } from "../symbols/python-output-symbol.js";
4
8
  import { usePythonScope } from "../symbols/scopes.js";
@@ -8,9 +12,17 @@ export interface MemberScopeProps {
8
12
  }
9
13
 
10
14
  export function MemberScope(props: MemberScopeProps) {
11
- const memberScope = new PythonMemberScope("member-scope", usePythonScope(), {
12
- ownerSymbol: props.ownerSymbol,
13
- });
15
+ const parentScope = usePythonScope();
16
+ const binder = props.ownerSymbol.binder ?? parentScope?.binder;
17
+ const memberScope = createScope(
18
+ PythonMemberScope,
19
+ "member-scope",
20
+ parentScope,
21
+ {
22
+ ownerSymbol: props.ownerSymbol,
23
+ binder,
24
+ },
25
+ );
14
26
 
15
27
  return (
16
28
  <CoreMemberScope value={memberScope}>{props.children}</CoreMemberScope>
@@ -3,6 +3,7 @@ import {
3
3
  ComponentContext,
4
4
  SourceFile as CoreSourceFile,
5
5
  createNamedContext,
6
+ createScope,
6
7
  isComponentCreator,
7
8
  List,
8
9
  Scope,
@@ -145,7 +146,7 @@ export function SourceFile(props: SourceFileProps) {
145
146
  const path: string = join(currentDir, props.path)
146
147
  .replace(/\.py$/, "")
147
148
  .replace(/\//g, ".");
148
- const scope = new PythonModuleScope(path, undefined);
149
+ const scope = createScope(PythonModuleScope, path, undefined);
149
150
  const sfContext: PythonSourceFileContext = {
150
151
  scope: scope,
151
152
  module: path,
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type Binder,
3
+ createScope,
3
4
  getSymbolCreatorSymbol,
4
5
  type Refkey,
5
6
  refkey,
@@ -36,9 +37,12 @@ function createSymbols(
36
37
  // Otherwise, we append the path to the module name
37
38
  const fullModuleScopeName = props.name + (path === "." ? "" : `.${path}`);
38
39
  const keys = refkeys[path];
39
- const moduleScope = new PythonModuleScope(fullModuleScopeName, undefined, {
40
- binder: binder,
41
- });
40
+ const moduleScope = createScope(
41
+ PythonModuleScope,
42
+ fullModuleScopeName,
43
+ undefined,
44
+ { binder },
45
+ );
42
46
 
43
47
  // Create a symbol for each exported name
44
48
  for (const exportedName of symbols ?? []) {
@@ -2,7 +2,8 @@ import {
2
2
  Namekey,
3
3
  OutputScopeOptions,
4
4
  OutputSpace,
5
- useBinder,
5
+ createScope,
6
+ createSymbol,
6
7
  useContext,
7
8
  } from "@alloy-js/core";
8
9
  import { PythonSourceFileContext } from "./components/SourceFile.js";
@@ -62,10 +63,10 @@ export function createPythonSymbol(
62
63
  }
63
64
  }
64
65
 
65
- const binder = options.binder ?? currentScope?.binder ?? useBinder();
66
+ const binder = options.binder ?? currentScope?.binder;
66
67
 
67
- return new PythonOutputSymbol(name, targetSpace, {
68
- binder: binder,
68
+ return createSymbol(PythonOutputSymbol, name, targetSpace, {
69
+ binder,
69
70
  aliasTarget: options.aliasTarget,
70
71
  refkeys: options.refkeys,
71
72
  metadata: options.metadata,
@@ -81,6 +82,10 @@ export function createLexicalScope(
81
82
  options: OutputScopeOptions = {},
82
83
  ) {
83
84
  const parent = usePythonScope();
85
+ const binder = options.binder ?? parent?.binder;
84
86
 
85
- return new PythonLexicalScope(name, parent, options);
87
+ return createScope(PythonLexicalScope, name, parent, {
88
+ ...options,
89
+ binder,
90
+ });
86
91
  }
@@ -1,4 +1,4 @@
1
- import { reactive, shallowReactive } from "@alloy-js/core";
1
+ import { createSymbol, reactive, shallowReactive } from "@alloy-js/core";
2
2
  import { PythonLexicalScope } from "./python-lexical-scope.js";
3
3
  import { PythonOutputSymbol } from "./python-output-symbol.js";
4
4
 
@@ -46,7 +46,8 @@ export class PythonModuleScope extends PythonLexicalScope {
46
46
  });
47
47
  }
48
48
 
49
- const localSymbol = new PythonOutputSymbol(
49
+ const localSymbol = createSymbol(
50
+ PythonOutputSymbol,
50
51
  targetSymbol.name,
51
52
  this.symbols,
52
53
  { binder: this.binder, aliasTarget: targetSymbol },
@@ -60,4 +61,12 @@ export class PythonModuleScope extends PythonLexicalScope {
60
61
 
61
62
  return localSymbol;
62
63
  }
64
+
65
+ override get debugInfo(): Record<string, unknown> {
66
+ return {
67
+ ...super.debugInfo,
68
+ importedSymbolCount: this.#importedSymbols.size,
69
+ importedModuleCount: this.#importedModules.size,
70
+ };
71
+ }
63
72
  }