c-next 0.1.69 → 0.1.70

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 (54) hide show
  1. package/package.json +1 -1
  2. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +240 -204
  3. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +693 -0
  4. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +86 -5
  5. package/src/transpiler/{output/codegen → logic/analysis}/helpers/AssignmentTargetExtractor.ts +1 -1
  6. package/src/transpiler/{output/codegen → logic/analysis}/helpers/ChildStatementCollector.ts +1 -1
  7. package/src/transpiler/{output/codegen → logic/analysis}/helpers/StatementExpressionCollector.ts +1 -1
  8. package/src/transpiler/{output/codegen → logic/analysis}/helpers/__tests__/AssignmentTargetExtractor.test.ts +2 -2
  9. package/src/transpiler/{output/codegen → logic/analysis}/helpers/__tests__/ChildStatementCollector.test.ts +2 -2
  10. package/src/transpiler/{output/codegen → logic/analysis}/helpers/__tests__/StatementExpressionCollector.test.ts +2 -2
  11. package/src/transpiler/output/codegen/CodeGenerator.ts +35 -607
  12. package/src/transpiler/output/codegen/TypeRegistrationUtils.ts +4 -6
  13. package/src/transpiler/output/codegen/TypeResolver.ts +2 -2
  14. package/src/transpiler/output/codegen/TypeValidator.ts +5 -5
  15. package/src/transpiler/output/codegen/__tests__/TypeRegistrationUtils.test.ts +36 -51
  16. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +20 -17
  17. package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +3 -3
  18. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +1 -1
  19. package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +1 -1
  20. package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +1 -1
  21. package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +9 -9
  22. package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +12 -12
  23. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +11 -11
  24. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +23 -17
  25. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +2 -2
  26. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +3 -3
  27. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +3 -3
  28. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +4 -4
  29. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +5 -5
  30. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +23 -25
  31. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +20 -36
  32. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +18 -18
  33. package/src/transpiler/output/codegen/assignment/handlers/__tests__/SpecialHandlers.test.ts +42 -32
  34. package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +5 -4
  35. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +14 -6
  36. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +19 -16
  37. package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +21 -4
  38. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +15 -2
  39. package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +2 -1
  40. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +2 -2
  41. package/src/transpiler/output/codegen/helpers/AssignmentValidator.ts +3 -3
  42. package/src/transpiler/output/codegen/helpers/EnumAssignmentValidator.ts +1 -1
  43. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +2 -2
  44. package/src/transpiler/output/codegen/helpers/__tests__/ArrayInitHelper.test.ts +1 -1
  45. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +7 -7
  46. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +7 -7
  47. package/src/transpiler/output/codegen/helpers/__tests__/EnumAssignmentValidator.test.ts +2 -2
  48. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +4 -4
  49. package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +2 -2
  50. package/src/transpiler/output/codegen/resolution/__tests__/EnumTypeResolver.test.ts +5 -5
  51. package/src/transpiler/state/CodeGenState.ts +122 -4
  52. package/src/transpiler/state/__tests__/CodeGenState.test.ts +269 -1
  53. /package/src/transpiler/{output/codegen → logic/analysis}/helpers/TransitiveModificationPropagator.ts +0 -0
  54. /package/src/transpiler/{output/codegen → logic/analysis}/helpers/__tests__/TransitiveModificationPropagator.test.ts +0 -0
@@ -0,0 +1,693 @@
1
+ /**
2
+ * Pass-By-Value Analyzer
3
+ *
4
+ * Extracted from CodeGenerator.ts (Issue #269, #558, #566, #579)
5
+ *
6
+ * Performs three-phase analysis to determine which function parameters
7
+ * can be passed by value (as opposed to pointer):
8
+ *
9
+ * Phase 1: Collect function parameter lists and direct modifications
10
+ * Phase 2: Transitive modification propagation (via TransitiveModificationPropagator)
11
+ * Phase 3: Determine which parameters can pass by value
12
+ *
13
+ * A parameter can pass by value if:
14
+ * 1. It's a small primitive type (u8, i8, u16, i16, u32, i32, u64, i64, bool)
15
+ * 2. It's not modified (directly or transitively)
16
+ * 3. It's not an array, struct, string, or callback
17
+ * 4. It's not accessed via subscript (Issue #579)
18
+ */
19
+
20
+ import * as Parser from "../parser/grammar/CNextParser";
21
+ import CodeGenState from "../../state/CodeGenState";
22
+ import TransitiveModificationPropagator from "./helpers/TransitiveModificationPropagator";
23
+ import StatementExpressionCollector from "./helpers/StatementExpressionCollector";
24
+ import ChildStatementCollector from "./helpers/ChildStatementCollector";
25
+ import AssignmentTargetExtractor from "./helpers/AssignmentTargetExtractor";
26
+ import ExpressionUtils from "../../../utils/ExpressionUtils";
27
+
28
+ /**
29
+ * Small primitive types that are eligible for pass-by-value optimization.
30
+ */
31
+ const SMALL_PRIMITIVES = new Set([
32
+ "u8",
33
+ "i8",
34
+ "u16",
35
+ "i16",
36
+ "u32",
37
+ "i32",
38
+ "u64",
39
+ "i64",
40
+ "bool",
41
+ ]);
42
+
43
+ /**
44
+ * Static analyzer for determining pass-by-value eligibility.
45
+ * All state is stored in CodeGenState - this class contains pure analysis logic.
46
+ */
47
+ class PassByValueAnalyzer {
48
+ /**
49
+ * Main entry point: Analyze a program tree to determine pass-by-value parameters.
50
+ * Updates CodeGenState with analysis results.
51
+ */
52
+ static analyze(tree: Parser.ProgramContext): void {
53
+ // Reset analysis state
54
+ CodeGenState.modifiedParameters.clear();
55
+ CodeGenState.passByValueParams.clear();
56
+ CodeGenState.functionCallGraph.clear();
57
+ CodeGenState.functionParamLists.clear();
58
+
59
+ // Phase 1: Collect function parameter lists and direct modifications
60
+ PassByValueAnalyzer.collectFunctionParametersAndModifications(tree);
61
+
62
+ // Issue #558: Inject cross-file data before transitive propagation
63
+ PassByValueAnalyzer.injectCrossFileModifications();
64
+ PassByValueAnalyzer.injectCrossFileParamLists();
65
+
66
+ // Phase 2: Fixed-point iteration for transitive modifications
67
+ TransitiveModificationPropagator.propagate(
68
+ CodeGenState.functionCallGraph,
69
+ CodeGenState.functionParamLists,
70
+ CodeGenState.modifiedParameters,
71
+ );
72
+
73
+ // Phase 3: Determine which parameters can pass by value
74
+ PassByValueAnalyzer.computePassByValueParams();
75
+ }
76
+
77
+ /**
78
+ * Inject cross-file modification data into modifiedParameters.
79
+ * SonarCloud S3776: Extracted from analyze().
80
+ */
81
+ private static injectCrossFileModifications(): void {
82
+ if (!CodeGenState.pendingCrossFileModifications) return;
83
+
84
+ for (const [
85
+ funcName,
86
+ params,
87
+ ] of CodeGenState.pendingCrossFileModifications) {
88
+ const existing = CodeGenState.modifiedParameters.get(funcName);
89
+ if (existing) {
90
+ for (const param of params) {
91
+ existing.add(param);
92
+ }
93
+ } else {
94
+ CodeGenState.modifiedParameters.set(funcName, new Set(params));
95
+ }
96
+ }
97
+ CodeGenState.pendingCrossFileModifications = null; // Clear after use
98
+ }
99
+
100
+ /**
101
+ * Inject cross-file parameter lists into functionParamLists.
102
+ * SonarCloud S3776: Extracted from analyze().
103
+ */
104
+ private static injectCrossFileParamLists(): void {
105
+ if (!CodeGenState.pendingCrossFileParamLists) return;
106
+
107
+ for (const [funcName, params] of CodeGenState.pendingCrossFileParamLists) {
108
+ if (!CodeGenState.functionParamLists.has(funcName)) {
109
+ CodeGenState.functionParamLists.set(funcName, [...params]);
110
+ }
111
+ }
112
+ CodeGenState.pendingCrossFileParamLists = null; // Clear after use
113
+ }
114
+
115
+ /**
116
+ * Phase 1: Walk all functions to collect:
117
+ * - Parameter lists (for call graph resolution)
118
+ * - Direct modifications (param <- value)
119
+ * - Function calls where params are passed as arguments
120
+ *
121
+ * Exposed as public for use by CodeGenerator.analyzeModificationsOnly()
122
+ * which needs to run just this phase for cross-file analysis.
123
+ */
124
+ static collectFunctionParametersAndModifications(
125
+ tree: Parser.ProgramContext,
126
+ ): void {
127
+ for (const decl of tree.declaration()) {
128
+ // Handle scope-level functions
129
+ if (decl.scopeDeclaration()) {
130
+ const scopeDecl = decl.scopeDeclaration()!;
131
+ const scopeName = scopeDecl.IDENTIFIER().getText();
132
+
133
+ for (const member of scopeDecl.scopeMember()) {
134
+ if (member.functionDeclaration()) {
135
+ const funcDecl = member.functionDeclaration()!;
136
+ const funcName = funcDecl.IDENTIFIER().getText();
137
+ const fullName = `${scopeName}_${funcName}`;
138
+ PassByValueAnalyzer.analyzeFunctionForModifications(
139
+ fullName,
140
+ funcDecl,
141
+ );
142
+ }
143
+ }
144
+ }
145
+
146
+ // Handle top-level functions
147
+ if (decl.functionDeclaration()) {
148
+ const funcDecl = decl.functionDeclaration()!;
149
+ const name = funcDecl.IDENTIFIER().getText();
150
+ PassByValueAnalyzer.analyzeFunctionForModifications(name, funcDecl);
151
+ }
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Analyze a single function for parameter modifications and call graph edges.
157
+ */
158
+ private static analyzeFunctionForModifications(
159
+ funcName: string,
160
+ funcDecl: Parser.FunctionDeclarationContext,
161
+ ): void {
162
+ // Collect parameter names
163
+ const paramNames: string[] = [];
164
+ const paramList = funcDecl.parameterList();
165
+ if (paramList) {
166
+ for (const param of paramList.parameter()) {
167
+ paramNames.push(param.IDENTIFIER().getText());
168
+ }
169
+ }
170
+ CodeGenState.functionParamLists.set(funcName, paramNames);
171
+
172
+ // Initialize modified set
173
+ CodeGenState.modifiedParameters.set(funcName, new Set());
174
+ // Issue #579: Initialize subscript access tracking
175
+ CodeGenState.subscriptAccessedParameters.set(funcName, new Set());
176
+ CodeGenState.functionCallGraph.set(funcName, []);
177
+
178
+ // Walk the function body to find modifications and calls
179
+ const block = funcDecl.block();
180
+ if (block) {
181
+ PassByValueAnalyzer.walkBlockForModifications(
182
+ funcName,
183
+ paramNames,
184
+ block,
185
+ );
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Walk a block to find parameter modifications and function calls.
191
+ */
192
+ private static walkBlockForModifications(
193
+ funcName: string,
194
+ paramNames: string[],
195
+ block: Parser.BlockContext,
196
+ ): void {
197
+ const paramSet = new Set(paramNames);
198
+
199
+ for (const stmt of block.statement()) {
200
+ PassByValueAnalyzer.walkStatementForModifications(
201
+ funcName,
202
+ paramSet,
203
+ stmt,
204
+ );
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Walk a statement recursively looking for modifications and calls.
210
+ * Issue #566: Refactored to use helper methods for expression and child collection.
211
+ */
212
+ private static walkStatementForModifications(
213
+ funcName: string,
214
+ paramSet: Set<string>,
215
+ stmt: Parser.StatementContext,
216
+ ): void {
217
+ // 1. Check for parameter modifications via assignment targets
218
+ if (stmt.assignmentStatement()) {
219
+ PassByValueAnalyzer.trackAssignmentModifications(
220
+ funcName,
221
+ paramSet,
222
+ stmt,
223
+ );
224
+ }
225
+
226
+ // 2. Walk all expressions in this statement for function calls and subscript access
227
+ for (const expr of StatementExpressionCollector.collectAll(stmt)) {
228
+ PassByValueAnalyzer.walkExpressionForCalls(funcName, paramSet, expr);
229
+ // Issue #579: Also track subscript read access on parameters
230
+ PassByValueAnalyzer.walkExpressionForSubscriptAccess(
231
+ funcName,
232
+ paramSet,
233
+ expr,
234
+ );
235
+ }
236
+
237
+ // 3. Recurse into child statements and blocks
238
+ const { statements, blocks } = ChildStatementCollector.collectAll(stmt);
239
+ for (const childStmt of statements) {
240
+ PassByValueAnalyzer.walkStatementForModifications(
241
+ funcName,
242
+ paramSet,
243
+ childStmt,
244
+ );
245
+ }
246
+ for (const block of blocks) {
247
+ PassByValueAnalyzer.walkBlockForModifications(
248
+ funcName,
249
+ [...paramSet],
250
+ block,
251
+ );
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Track assignment modifications for parameter const inference.
257
+ * SonarCloud S3776: Extracted from walkStatementForModifications().
258
+ */
259
+ private static trackAssignmentModifications(
260
+ funcName: string,
261
+ paramSet: Set<string>,
262
+ stmt: Parser.StatementContext,
263
+ ): void {
264
+ const assign = stmt.assignmentStatement()!;
265
+ const target = assign.assignmentTarget();
266
+
267
+ const { baseIdentifier, hasSingleIndexSubscript } =
268
+ AssignmentTargetExtractor.extract(target);
269
+
270
+ // Issue #579: Track subscript access on parameters (for write path)
271
+ if (
272
+ hasSingleIndexSubscript &&
273
+ baseIdentifier &&
274
+ paramSet.has(baseIdentifier)
275
+ ) {
276
+ CodeGenState.subscriptAccessedParameters
277
+ .get(funcName)!
278
+ .add(baseIdentifier);
279
+ }
280
+
281
+ // Track as modified parameter
282
+ if (baseIdentifier && paramSet.has(baseIdentifier)) {
283
+ CodeGenState.modifiedParameters.get(funcName)!.add(baseIdentifier);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Walk an expression tree to find function calls where parameters are passed.
289
+ * Uses recursive descent through the expression hierarchy.
290
+ */
291
+ private static walkExpressionForCalls(
292
+ funcName: string,
293
+ paramSet: Set<string>,
294
+ expr: Parser.ExpressionContext,
295
+ ): void {
296
+ // Expression -> TernaryExpression -> OrExpression -> ... -> PostfixExpression
297
+ const ternary = expr.ternaryExpression();
298
+ if (ternary) {
299
+ // Walk all orExpression children
300
+ for (const orExpr of ternary.orExpression()) {
301
+ PassByValueAnalyzer.walkOrExpressionForCalls(
302
+ funcName,
303
+ paramSet,
304
+ orExpr,
305
+ );
306
+ }
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Issue #579: Walk an expression tree to find subscript access on parameters.
312
+ * This tracks read access like `buf[i]` where buf is a parameter.
313
+ * Parameters with subscript access must become pointers.
314
+ */
315
+ private static walkExpressionForSubscriptAccess(
316
+ funcName: string,
317
+ paramSet: Set<string>,
318
+ expr: Parser.ExpressionContext,
319
+ ): void {
320
+ const ternary = expr.ternaryExpression();
321
+ if (ternary) {
322
+ for (const orExpr of ternary.orExpression()) {
323
+ PassByValueAnalyzer.walkOrExpression(orExpr, (unaryExpr) => {
324
+ PassByValueAnalyzer.handleSubscriptAccess(
325
+ funcName,
326
+ paramSet,
327
+ unaryExpr,
328
+ );
329
+ });
330
+ }
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Issue #579: Handle subscript access on a unary expression.
336
+ * Only tracks single-index subscript access (which could be array access).
337
+ * Two-index subscript (e.g., value[start, width]) is always bit extraction,
338
+ * so it doesn't require the parameter to become a pointer.
339
+ */
340
+ private static handleSubscriptAccess(
341
+ funcName: string,
342
+ paramSet: Set<string>,
343
+ unaryExpr: Parser.UnaryExpressionContext,
344
+ ): void {
345
+ const postfixExpr = unaryExpr.postfixExpression();
346
+ if (!postfixExpr) return;
347
+
348
+ const primary = postfixExpr.primaryExpression();
349
+ const ops = postfixExpr.postfixOp();
350
+
351
+ // Check if primary is a parameter and there's subscript access
352
+ const primaryId = primary.IDENTIFIER()?.getText();
353
+ if (!primaryId || !paramSet.has(primaryId)) {
354
+ return;
355
+ }
356
+
357
+ // Only track SINGLE-index subscript access (potential array access)
358
+ // Two-index subscript like value[0, 8] is bit extraction, not array access
359
+ const hasSingleIndexSubscript = ops.some(
360
+ (op) => op.expression().length === 1,
361
+ );
362
+ if (hasSingleIndexSubscript) {
363
+ CodeGenState.subscriptAccessedParameters.get(funcName)!.add(primaryId);
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Generic walker for orExpression trees.
369
+ * Walks through the expression hierarchy and calls the handler for each unaryExpression.
370
+ * Used by both function call tracking and subscript access tracking.
371
+ */
372
+ private static walkOrExpression(
373
+ orExpr: Parser.OrExpressionContext,
374
+ handler: (unaryExpr: Parser.UnaryExpressionContext) => void,
375
+ ): void {
376
+ orExpr
377
+ .andExpression()
378
+ .flatMap((and) => and.equalityExpression())
379
+ .flatMap((eq) => eq.relationalExpression())
380
+ .flatMap((rel) => rel.bitwiseOrExpression())
381
+ .flatMap((bor) => bor.bitwiseXorExpression())
382
+ .flatMap((bxor) => bxor.bitwiseAndExpression())
383
+ .flatMap((band) => band.shiftExpression())
384
+ .flatMap((shift) => shift.additiveExpression())
385
+ .flatMap((add) => add.multiplicativeExpression())
386
+ .flatMap((mul) => mul.unaryExpression())
387
+ .forEach(handler);
388
+ }
389
+
390
+ /**
391
+ * Walk an orExpression tree for function calls.
392
+ */
393
+ private static walkOrExpressionForCalls(
394
+ funcName: string,
395
+ paramSet: Set<string>,
396
+ orExpr: Parser.OrExpressionContext,
397
+ ): void {
398
+ PassByValueAnalyzer.walkOrExpression(orExpr, (unaryExpr) => {
399
+ PassByValueAnalyzer.walkUnaryExpressionForCalls(
400
+ funcName,
401
+ paramSet,
402
+ unaryExpr,
403
+ );
404
+ });
405
+ }
406
+
407
+ /**
408
+ * Walk a unaryExpression tree for function calls.
409
+ */
410
+ private static walkUnaryExpressionForCalls(
411
+ funcName: string,
412
+ paramSet: Set<string>,
413
+ unaryExpr: Parser.UnaryExpressionContext,
414
+ ): void {
415
+ // Recurse into nested unary
416
+ if (unaryExpr.unaryExpression()) {
417
+ PassByValueAnalyzer.walkUnaryExpressionForCalls(
418
+ funcName,
419
+ paramSet,
420
+ unaryExpr.unaryExpression()!,
421
+ );
422
+ return;
423
+ }
424
+
425
+ // Check postfix expression
426
+ const postfix = unaryExpr.postfixExpression();
427
+ if (postfix) {
428
+ PassByValueAnalyzer.walkPostfixExpressionForCalls(
429
+ funcName,
430
+ paramSet,
431
+ postfix,
432
+ );
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Walk a postfixExpression for function calls.
438
+ * This is where function calls are found: primaryExpr followed by '(' args ')'
439
+ */
440
+ private static walkPostfixExpressionForCalls(
441
+ funcName: string,
442
+ paramSet: Set<string>,
443
+ postfix: Parser.PostfixExpressionContext,
444
+ ): void {
445
+ const primary = postfix.primaryExpression();
446
+ const postfixOps = postfix.postfixOp();
447
+
448
+ // Handle simple function calls: IDENTIFIER followed by '(' ... ')'
449
+ PassByValueAnalyzer.handleSimpleFunctionCall(
450
+ funcName,
451
+ paramSet,
452
+ primary,
453
+ postfixOps,
454
+ );
455
+
456
+ // Issue #365: Handle scope-qualified calls: Scope.method(...) or global.Scope.method(...)
457
+ PassByValueAnalyzer.handleScopeQualifiedCalls(
458
+ funcName,
459
+ paramSet,
460
+ primary,
461
+ postfixOps,
462
+ );
463
+
464
+ // Recurse into primary expression if it's a parenthesized expression
465
+ if (primary.expression()) {
466
+ PassByValueAnalyzer.walkExpressionForCalls(
467
+ funcName,
468
+ paramSet,
469
+ primary.expression()!,
470
+ );
471
+ }
472
+
473
+ // Walk arguments in any postfix function call ops (for nested calls)
474
+ PassByValueAnalyzer.walkPostfixOpsRecursively(
475
+ funcName,
476
+ paramSet,
477
+ postfixOps,
478
+ );
479
+ }
480
+
481
+ /**
482
+ * Handle simple function calls: IDENTIFIER followed by '(' ... ')'
483
+ */
484
+ private static handleSimpleFunctionCall(
485
+ funcName: string,
486
+ paramSet: Set<string>,
487
+ primary: Parser.PrimaryExpressionContext,
488
+ postfixOps: Parser.PostfixOpContext[],
489
+ ): void {
490
+ if (!primary.IDENTIFIER() || postfixOps.length === 0) return;
491
+
492
+ const firstOp = postfixOps[0];
493
+ if (!firstOp.LPAREN()) return;
494
+
495
+ const calleeName = primary.IDENTIFIER()!.getText();
496
+ PassByValueAnalyzer.recordCallsFromArgList(
497
+ funcName,
498
+ paramSet,
499
+ calleeName,
500
+ firstOp,
501
+ );
502
+ }
503
+
504
+ /**
505
+ * Handle scope-qualified calls: Scope.method(...) or global.Scope.method(...)
506
+ * Track member accesses to build the mangled callee name (e.g., Storage_load)
507
+ */
508
+ private static handleScopeQualifiedCalls(
509
+ funcName: string,
510
+ paramSet: Set<string>,
511
+ primary: Parser.PrimaryExpressionContext,
512
+ postfixOps: Parser.PostfixOpContext[],
513
+ ): void {
514
+ if (postfixOps.length === 0) return;
515
+
516
+ const memberNames = PassByValueAnalyzer.collectInitialMemberNames(
517
+ funcName,
518
+ primary,
519
+ );
520
+
521
+ for (const op of postfixOps) {
522
+ if (op.IDENTIFIER()) {
523
+ memberNames.push(op.IDENTIFIER()!.getText());
524
+ } else if (op.LPAREN() && memberNames.length >= 1) {
525
+ const calleeName = memberNames.join("_");
526
+ PassByValueAnalyzer.recordCallsFromArgList(
527
+ funcName,
528
+ paramSet,
529
+ calleeName,
530
+ op,
531
+ );
532
+ memberNames.length = 0; // Reset for potential chained calls
533
+ } else if (op.expression().length > 0) {
534
+ memberNames.length = 0; // Array subscript breaks scope chain
535
+ }
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Collect initial member names from primary expression for scope resolution.
541
+ * Issue #561: When 'this' is used, resolve to the current scope name from funcName.
542
+ */
543
+ private static collectInitialMemberNames(
544
+ funcName: string,
545
+ primary: Parser.PrimaryExpressionContext,
546
+ ): string[] {
547
+ const memberNames: string[] = [];
548
+ const primaryId = primary.IDENTIFIER()?.getText();
549
+
550
+ if (primaryId && primaryId !== "global") {
551
+ memberNames.push(primaryId);
552
+ } else if (primary.THIS()) {
553
+ const scopeName = funcName.split("_")[0];
554
+ if (scopeName && scopeName !== funcName) {
555
+ memberNames.push(scopeName);
556
+ }
557
+ }
558
+ return memberNames;
559
+ }
560
+
561
+ /**
562
+ * Record function calls to the call graph from an argument list.
563
+ * Also recurses into argument expressions.
564
+ */
565
+ private static recordCallsFromArgList(
566
+ funcName: string,
567
+ paramSet: Set<string>,
568
+ calleeName: string,
569
+ op: Parser.PostfixOpContext,
570
+ ): void {
571
+ const argList = op.argumentList();
572
+ if (!argList) return;
573
+
574
+ const args = argList.expression();
575
+ for (let i = 0; i < args.length; i++) {
576
+ const arg = args[i];
577
+ const argName = ExpressionUtils.extractIdentifier(arg);
578
+ if (argName && paramSet.has(argName)) {
579
+ CodeGenState.functionCallGraph.get(funcName)!.push({
580
+ callee: calleeName,
581
+ paramIndex: i,
582
+ argParamName: argName,
583
+ });
584
+ }
585
+ PassByValueAnalyzer.walkExpressionForCalls(funcName, paramSet, arg);
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Walk postfix ops recursively for nested calls and array subscripts.
591
+ */
592
+ private static walkPostfixOpsRecursively(
593
+ funcName: string,
594
+ paramSet: Set<string>,
595
+ postfixOps: Parser.PostfixOpContext[],
596
+ ): void {
597
+ for (const op of postfixOps) {
598
+ if (op.argumentList()) {
599
+ for (const argExpr of op.argumentList()!.expression()) {
600
+ PassByValueAnalyzer.walkExpressionForCalls(
601
+ funcName,
602
+ paramSet,
603
+ argExpr,
604
+ );
605
+ }
606
+ }
607
+ for (const expr of op.expression()) {
608
+ PassByValueAnalyzer.walkExpressionForCalls(funcName, paramSet, expr);
609
+ }
610
+ }
611
+ }
612
+
613
+ /**
614
+ * Phase 3: Determine which parameters can pass by value.
615
+ * A parameter passes by value if:
616
+ * 1. It's a small primitive type (u8, i8, u16, i16, u32, i32, u64, i64, bool)
617
+ * 2. It's not modified (directly or transitively)
618
+ * 3. It's not an array, struct, string, or callback
619
+ */
620
+ private static computePassByValueParams(): void {
621
+ for (const [funcName, paramNames] of CodeGenState.functionParamLists) {
622
+ const passByValue = new Set<string>();
623
+ const modified =
624
+ CodeGenState.modifiedParameters.get(funcName) ?? new Set();
625
+
626
+ // Get function declaration to check parameter types
627
+ const funcSig = CodeGenState.functionSignatures.get(funcName);
628
+ if (funcSig) {
629
+ for (let i = 0; i < paramNames.length; i++) {
630
+ const paramName = paramNames[i];
631
+ const paramSig = funcSig.parameters[i];
632
+
633
+ if (!paramSig) continue;
634
+
635
+ // Check if eligible for pass-by-value:
636
+ // - Is a small primitive type
637
+ // - Not an array
638
+ // - Not modified
639
+ // - Not accessed via subscript (Issue #579)
640
+ const isSmallPrimitive = SMALL_PRIMITIVES.has(paramSig.baseType);
641
+ const isArray = paramSig.isArray ?? false;
642
+ const isModified = modified.has(paramName);
643
+ // Issue #579: Parameters with subscript access must become pointers
644
+ const hasSubscriptAccess =
645
+ CodeGenState.subscriptAccessedParameters
646
+ .get(funcName)
647
+ ?.has(paramName) ?? false;
648
+
649
+ if (
650
+ isSmallPrimitive &&
651
+ !isArray &&
652
+ !isModified &&
653
+ !hasSubscriptAccess
654
+ ) {
655
+ passByValue.add(paramName);
656
+ }
657
+ }
658
+ }
659
+
660
+ CodeGenState.passByValueParams.set(funcName, passByValue);
661
+ }
662
+ }
663
+
664
+ /**
665
+ * Check if a parameter should be passed by value (by name).
666
+ * Used internally during code generation.
667
+ */
668
+ static isParameterPassByValueByName(
669
+ funcName: string,
670
+ paramName: string,
671
+ ): boolean {
672
+ const passByValue = CodeGenState.passByValueParams.get(funcName);
673
+ return passByValue?.has(paramName) ?? false;
674
+ }
675
+
676
+ /**
677
+ * Issue #269: Check if a parameter should be passed by value (by index).
678
+ * Part of IOrchestrator interface - used by CallExprGenerator.
679
+ */
680
+ static isParameterPassByValue(funcName: string, paramIndex: number): boolean {
681
+ const paramList = CodeGenState.functionParamLists.get(funcName);
682
+ if (!paramList || paramIndex < 0 || paramIndex >= paramList.length) {
683
+ return false;
684
+ }
685
+ const paramName = paramList[paramIndex];
686
+ return PassByValueAnalyzer.isParameterPassByValueByName(
687
+ funcName,
688
+ paramName,
689
+ );
690
+ }
691
+ }
692
+
693
+ export default PassByValueAnalyzer;