c-next 0.1.18 → 0.1.20

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.
package/README.md CHANGED
@@ -663,44 +663,45 @@ Decisions are documented in `/docs/decisions/`:
663
663
 
664
664
  ### Implemented
665
665
 
666
- | ADR | Title | Description |
667
- | --------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------ |
668
- | [ADR-001](docs/decisions/adr-001-assignment-operator.md) | Assignment Operator | `<-` for assignment, `=` for comparison |
669
- | [ADR-003](docs/decisions/adr-003-static-allocation.md) | Static Allocation | No dynamic memory after init |
670
- | [ADR-004](docs/decisions/adr-004-register-bindings.md) | Register Bindings | Type-safe hardware access |
671
- | [ADR-006](docs/decisions/adr-006-simplified-references.md) | Simplified References | Pass by reference, no pointer syntax |
672
- | [ADR-007](docs/decisions/adr-007-type-aware-bit-indexing.md) | Type-Aware Bit Indexing | Integers as bit arrays, `.length` property |
673
- | [ADR-010](docs/decisions/adr-010-c-interoperability.md) | C Interoperability | Unified ANTLR parser architecture |
674
- | [ADR-011](docs/decisions/adr-011-vscode-extension.md) | VS Code Extension | Live C preview with syntax highlighting |
675
- | [ADR-012](docs/decisions/adr-012-static-analysis.md) | Static Analysis | cppcheck integration for generated C |
676
- | [ADR-013](docs/decisions/adr-013-const-qualifier.md) | Const Qualifier | Compile-time const enforcement |
677
- | [ADR-014](docs/decisions/adr-014-structs.md) | Structs | Data containers without methods |
678
- | [ADR-015](docs/decisions/adr-015-null-state.md) | Null State | Zero initialization for all variables |
679
- | [ADR-016](docs/decisions/adr-016-scope.md) | Scope | `this.`/`global.` explicit qualification |
680
- | [ADR-017](docs/decisions/adr-017-enums.md) | Enums | Type-safe enums with C-style casting |
681
- | [ADR-030](docs/decisions/adr-030-forward-declarations.md) | Define-Before-Use | Functions must be defined before called |
682
- | [ADR-037](docs/decisions/adr-037-preprocessor.md) | Preprocessor | Flag-only defines, const for values |
683
- | [ADR-043](docs/decisions/adr-043-comments.md) | Comments | Comment preservation with MISRA compliance |
684
- | [ADR-044](docs/decisions/adr-044-primitive-types.md) | Primitive Types | Fixed-width types with `clamp`/`wrap` overflow |
685
- | [ADR-024](docs/decisions/adr-024-type-casting.md) | Type Casting | Widening implicit, narrowing uses bit indexing |
686
- | [ADR-022](docs/decisions/adr-022-conditional-expressions.md) | Conditional Expressions | Ternary with required parens, boolean condition, no nesting |
687
- | [ADR-025](docs/decisions/adr-025-switch-statements.md) | Switch Statements | Safe switch with braces, `\|\|` syntax, counted `default(n)` |
688
- | [ADR-029](docs/decisions/adr-029-function-pointers.md) | Callbacks | Function-as-Type pattern with nominal typing |
689
- | [ADR-045](docs/decisions/adr-045-string-type.md) | Bounded Strings | `string<N>` with compile-time safety |
690
- | [ADR-023](docs/decisions/adr-023-sizeof.md) | Sizeof | Type/value size queries with safety checks |
691
- | [ADR-027](docs/decisions/adr-027-do-while.md) | Do-While | `do { } while ()` with boolean condition (E0701) |
692
- | [ADR-032](docs/decisions/adr-032-nested-structs.md) | Nested Structs | Named nested structs only (no anonymous) |
693
- | [ADR-035](docs/decisions/adr-035-array-initializers.md) | Array Initializers | `[1, 2, 3]` syntax with `[0*]` fill-all |
694
- | [ADR-036](docs/decisions/adr-036-multidimensional-arrays.md) | Multi-dim Arrays | `arr[i][j]` with compile-time bounds enforcement |
695
- | [ADR-040](docs/decisions/adr-040-isr-declaration.md) | ISR Type | Built-in `ISR` type for `void(void)` function pointers |
696
- | [ADR-034](docs/decisions/adr-034-bit-fields.md) | Bitmap Types | `bitmap8`/`bitmap16`/`bitmap32` for portable bit-packed data |
697
- | [ADR-048](docs/decisions/adr-048-cli-executable.md) | CLI Executable | `cnext` command with smart defaults |
698
- | [ADR-049](docs/decisions/adr-049-atomic-types.md) | Atomic Types | `atomic` keyword with LDREX/STREX or PRIMASK fallback |
699
- | [ADR-050](docs/decisions/adr-050-critical-sections.md) | Critical Sections | `critical { }` blocks with PRIMASK save/restore |
700
- | [ADR-108](docs/decisions/adr-108-volatile-keyword.md) | Volatile Variables | `volatile` keyword prevents compiler optimization |
701
- | [ADR-047](docs/decisions/adr-047-nullable-types.md) | NULL for C Interop | `NULL` keyword for C stream function comparisons |
702
- | [ADR-052](docs/decisions/adr-052-safe-numeric-literal-generation.md) | Safe Numeric Literals | `type_MIN`/`type_MAX` constants + safe hex conversion |
703
- | [ADR-053](docs/decisions/adr-053-transpiler-pipeline-architecture.md) | Transpiler Pipeline | Unified multi-pass pipeline with header symbol extraction |
666
+ | ADR | Title | Description |
667
+ | --------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------- |
668
+ | [ADR-001](docs/decisions/adr-001-assignment-operator.md) | Assignment Operator | `<-` for assignment, `=` for comparison |
669
+ | [ADR-003](docs/decisions/adr-003-static-allocation.md) | Static Allocation | No dynamic memory after init |
670
+ | [ADR-004](docs/decisions/adr-004-register-bindings.md) | Register Bindings | Type-safe hardware access |
671
+ | [ADR-006](docs/decisions/adr-006-simplified-references.md) | Simplified References | Pass by reference, no pointer syntax |
672
+ | [ADR-007](docs/decisions/adr-007-type-aware-bit-indexing.md) | Type-Aware Bit Indexing | Integers as bit arrays, `.length` property |
673
+ | [ADR-010](docs/decisions/adr-010-c-interoperability.md) | C Interoperability | Unified ANTLR parser architecture |
674
+ | [ADR-011](docs/decisions/adr-011-vscode-extension.md) | VS Code Extension | Live C preview with syntax highlighting |
675
+ | [ADR-012](docs/decisions/adr-012-static-analysis.md) | Static Analysis | cppcheck integration for generated C |
676
+ | [ADR-013](docs/decisions/adr-013-const-qualifier.md) | Const Qualifier | Compile-time const enforcement |
677
+ | [ADR-014](docs/decisions/adr-014-structs.md) | Structs | Data containers without methods |
678
+ | [ADR-015](docs/decisions/adr-015-null-state.md) | Null State | Zero initialization for all variables |
679
+ | [ADR-016](docs/decisions/adr-016-scope.md) | Scope | `this.`/`global.` explicit qualification |
680
+ | [ADR-017](docs/decisions/adr-017-enums.md) | Enums | Type-safe enums with C-style casting |
681
+ | [ADR-030](docs/decisions/adr-030-forward-declarations.md) | Define-Before-Use | Functions must be defined before called |
682
+ | [ADR-037](docs/decisions/adr-037-preprocessor.md) | Preprocessor | Flag-only defines, const for values |
683
+ | [ADR-043](docs/decisions/adr-043-comments.md) | Comments | Comment preservation with MISRA compliance |
684
+ | [ADR-044](docs/decisions/adr-044-primitive-types.md) | Primitive Types | Fixed-width types with `clamp`/`wrap` overflow |
685
+ | [ADR-024](docs/decisions/adr-024-type-casting.md) | Type Casting | Widening implicit, narrowing uses bit indexing |
686
+ | [ADR-022](docs/decisions/adr-022-conditional-expressions.md) | Conditional Expressions | Ternary with required parens, boolean condition, no nesting |
687
+ | [ADR-025](docs/decisions/adr-025-switch-statements.md) | Switch Statements | Safe switch with braces, `\|\|` syntax, counted `default(n)` |
688
+ | [ADR-029](docs/decisions/adr-029-function-pointers.md) | Callbacks | Function-as-Type pattern with nominal typing |
689
+ | [ADR-045](docs/decisions/adr-045-string-type.md) | Bounded Strings | `string<N>` with compile-time safety |
690
+ | [ADR-023](docs/decisions/adr-023-sizeof.md) | Sizeof | Type/value size queries with safety checks |
691
+ | [ADR-027](docs/decisions/adr-027-do-while.md) | Do-While | `do { } while ()` with boolean condition (E0701) |
692
+ | [ADR-032](docs/decisions/adr-032-nested-structs.md) | Nested Structs | Named nested structs only (no anonymous) |
693
+ | [ADR-035](docs/decisions/adr-035-array-initializers.md) | Array Initializers | `[1, 2, 3]` syntax with `[0*]` fill-all |
694
+ | [ADR-036](docs/decisions/adr-036-multidimensional-arrays.md) | Multi-dim Arrays | `arr[i][j]` with compile-time bounds enforcement |
695
+ | [ADR-040](docs/decisions/adr-040-isr-declaration.md) | ISR Type | Built-in `ISR` type for `void(void)` function pointers |
696
+ | [ADR-034](docs/decisions/adr-034-bit-fields.md) | Bitmap Types | `bitmap8`/`bitmap16`/`bitmap32` for portable bit-packed data |
697
+ | [ADR-048](docs/decisions/adr-048-cli-executable.md) | CLI Executable | `cnext` command with smart defaults |
698
+ | [ADR-049](docs/decisions/adr-049-atomic-types.md) | Atomic Types | `atomic` keyword with LDREX/STREX or PRIMASK fallback |
699
+ | [ADR-050](docs/decisions/adr-050-critical-sections.md) | Critical Sections | `critical { }` blocks with PRIMASK save/restore |
700
+ | [ADR-108](docs/decisions/adr-108-volatile-keyword.md) | Volatile Variables | `volatile` keyword prevents compiler optimization |
701
+ | [ADR-046](docs/decisions/adr-046-nullable-c-interop.md) | Nullable C Interop | `c_` prefix for nullable C pointer types (supersedes ADR-047) |
702
+ | [ADR-047](docs/decisions/adr-047-nullable-types.md) | NULL for C Interop | `NULL` keyword for C stream functions (superseded by ADR-046) |
703
+ | [ADR-052](docs/decisions/adr-052-safe-numeric-literal-generation.md) | Safe Numeric Literals | `type_MIN`/`type_MAX` constants + safe hex conversion |
704
+ | [ADR-053](docs/decisions/adr-053-transpiler-pipeline-architecture.md) | Transpiler Pipeline | Unified multi-pass pipeline with header symbol extraction |
704
705
 
705
706
  ### Research (v1 Roadmap)
706
707
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -10,6 +10,7 @@ import ESourceLanguage from "../types/ESourceLanguage";
10
10
  import ESymbolKind from "../types/ESymbolKind";
11
11
  import CommentExtractor from "./CommentExtractor";
12
12
  import CommentFormatter from "./CommentFormatter";
13
+ import IncludeDiscovery from "../lib/IncludeDiscovery";
13
14
  import IComment from "./types/IComment";
14
15
  import TYPE_WIDTH from "./types/TYPE_WIDTH";
15
16
  import C_TYPE_WIDTH from "./types/C_TYPE_WIDTH";
@@ -1444,6 +1445,10 @@ export default class CodeGenerator implements IOrchestrator {
1444
1445
  // C-Next does NOT hardcode any libraries - all includes must be explicit
1445
1446
  // ADR-043: Comments before first include become file-level comments
1446
1447
  // ADR-010: Transform .cnx includes to .h, reject implementation files
1448
+ // E0504: Cache include paths for performance (computed once, used for all includes)
1449
+ const includePaths = this.sourcePath
1450
+ ? IncludeDiscovery.discoverIncludePaths(this.sourcePath)
1451
+ : [];
1447
1452
  for (const includeDir of tree.includeDirective()) {
1448
1453
  const leadingComments = this.getLeadingComments(includeDir);
1449
1454
  output.push(...this.formatLeadingComments(leadingComments));
@@ -1455,6 +1460,14 @@ export default class CodeGenerator implements IOrchestrator {
1455
1460
  lineNumber,
1456
1461
  );
1457
1462
 
1463
+ // E0504: Check if a .cnx alternative exists for .h/.hpp includes
1464
+ this.typeValidator!.validateIncludeNoCnxAlternative(
1465
+ includeDir.getText(),
1466
+ lineNumber,
1467
+ this.sourcePath,
1468
+ includePaths,
1469
+ );
1470
+
1458
1471
  const transformedInclude = this.transformIncludeDirective(
1459
1472
  includeDir.getText(),
1460
1473
  );
@@ -4012,6 +4025,64 @@ export default class CodeGenerator implements IOrchestrator {
4012
4025
  return false;
4013
4026
  }
4014
4027
 
4028
+ /**
4029
+ * Issue #308: Check if a member access expression is accessing an array member.
4030
+ * For example, result.data where data is a u8[6] array member.
4031
+ * When passing such expressions to functions, the array should naturally decay
4032
+ * to a pointer, so we should NOT add & operator.
4033
+ *
4034
+ * Note: Currently handles single-level member access only (e.g., result.data).
4035
+ * Nested access like outer.inner.data would require traversing the postfix chain
4036
+ * to resolve intermediate struct types. This is acceptable since issue #308
4037
+ * involves single-level access patterns.
4038
+ *
4039
+ * @param ctx - The expression context
4040
+ * @returns true if the expression is a member access to an array field
4041
+ */
4042
+ private isMemberAccessToArray(ctx: Parser.ExpressionContext): boolean {
4043
+ const postfix = this.getPostfixExpression(ctx);
4044
+ if (!postfix) return false;
4045
+
4046
+ const ops = postfix.postfixOp();
4047
+ if (ops.length === 0) return false;
4048
+
4049
+ // Last operator must be member access (.identifier)
4050
+ const lastOp = ops[ops.length - 1];
4051
+ const memberName = lastOp.IDENTIFIER()?.getText();
4052
+ if (!memberName) return false;
4053
+
4054
+ // Get the base identifier to find the struct type
4055
+ const primary = postfix.primaryExpression();
4056
+ if (!primary) return false;
4057
+ const baseId = primary.IDENTIFIER()?.getText();
4058
+ if (!baseId) return false;
4059
+
4060
+ // Look up the struct type from either:
4061
+ // 1. Local variable: typeRegistry.get(baseId).baseType
4062
+ // 2. Parameter: currentParameters.get(baseId).baseType
4063
+ let structType: string | undefined;
4064
+
4065
+ const typeInfo = this.context.typeRegistry.get(baseId);
4066
+ if (typeInfo) {
4067
+ structType = typeInfo.baseType;
4068
+ } else {
4069
+ const paramInfo = this.context.currentParameters.get(baseId);
4070
+ if (paramInfo) {
4071
+ structType = paramInfo.baseType;
4072
+ }
4073
+ }
4074
+
4075
+ if (!structType) return false;
4076
+
4077
+ // Check if this struct member is an array
4078
+ const memberInfo = this.getMemberTypeInfo(structType, memberName);
4079
+ if (memberInfo?.isArray) {
4080
+ return true;
4081
+ }
4082
+
4083
+ return false;
4084
+ }
4085
+
4015
4086
  /**
4016
4087
  * Check if an expression is a simple literal (number, bool, etc.)
4017
4088
  * Navigates: expression -> ternaryExpression -> orExpression -> ... -> primaryExpression -> literal
@@ -4172,6 +4243,13 @@ export default class CodeGenerator implements IOrchestrator {
4172
4243
  // Check if it's a member access or array access (lvalue) - needs &
4173
4244
  const lvalueType = this.getLvalueType(ctx);
4174
4245
  if (lvalueType) {
4246
+ // Issue #308: If member access to an array, don't add & - arrays decay to pointers
4247
+ // For example: result.data where data is u8[6] should pass as result.data (decays to uint8_t*)
4248
+ // NOT &result.data (which gives uint8_t (*)[6] - wrong type)
4249
+ if (lvalueType === "member" && this.isMemberAccessToArray(ctx)) {
4250
+ return this._generateExpression(ctx);
4251
+ }
4252
+
4175
4253
  // Issue #251/#252: In C++ mode, struct member access may need temp variable
4176
4254
  if (
4177
4255
  lvalueType === "member" &&
@@ -5497,6 +5575,14 @@ export default class CodeGenerator implements IOrchestrator {
5497
5575
  return "{}";
5498
5576
  }
5499
5577
 
5578
+ // Issue #309: In C++ mode, unknown user types (external libraries)
5579
+ // should use {} instead of {0} because they may have non-trivial
5580
+ // constructors. Known structs (C-Next or C headers) are POD types
5581
+ // where {0} works fine.
5582
+ if (this.cppMode && !this._isKnownStruct(typeName)) {
5583
+ return "{}";
5584
+ }
5585
+
5500
5586
  return "{0}";
5501
5587
  }
5502
5588
 
@@ -7913,7 +7999,10 @@ export default class CodeGenerator implements IOrchestrator {
7913
7999
  currentIdentifier = memberName; // Track for .length lookups
7914
8000
  isGlobalAccess = true; // Mark that we're in a global access chain
7915
8001
  // Issue #304: Check if this is a C++ scope symbol (namespace, class, enum)
7916
- if (this.isCppScopeSymbol(memberName)) {
8002
+ // Issue #314: In C++ mode, global.X.method() should always use :: syntax
8003
+ // because the programmer is explicitly requesting C++ static method access,
8004
+ // even for undeclared external symbols (e.g., Arduino's Serial class)
8005
+ if (this.isCppScopeSymbol(memberName) || this.cppMode) {
7917
8006
  isCppAccessChain = true;
7918
8007
  }
7919
8008
  // Check if this first identifier is a register
@@ -8498,9 +8587,13 @@ export default class CodeGenerator implements IOrchestrator {
8498
8587
  }
8499
8588
  // Issue #304: C++ class/namespace static member access uses :: (e.g., CommandHandler::execute)
8500
8589
  // Also handles nested namespaces (hw::nested::configure) by checking if result already contains ::
8590
+ // Issue #314: Also use :: for global.X.method() in C++ mode with undeclared external classes
8591
+ // (e.g., Arduino's Serial class) - the global. prefix explicitly requests C++ static access
8501
8592
  else if (
8502
8593
  isCppAccessChain &&
8503
- (this.isCppScopeSymbol(result) || result.includes("::"))
8594
+ (this.isCppScopeSymbol(result) ||
8595
+ result.includes("::") ||
8596
+ isGlobalAccess)
8504
8597
  ) {
8505
8598
  result = `${result}::${memberName}`;
8506
8599
  } else {
@@ -3,6 +3,7 @@
3
3
  * Extracted from CodeGenerator for better separation of concerns
4
4
  * Issue #63: Validation logic separated for independent testing
5
5
  */
6
+ import { dirname, resolve, join } from "path";
6
7
  import * as Parser from "../parser/grammar/CNextParser";
7
8
  import SymbolCollector from "./SymbolCollector";
8
9
  import SymbolTable from "../symbols/SymbolTable";
@@ -85,6 +86,75 @@ class TypeValidator {
85
86
  }
86
87
  }
87
88
 
89
+ /**
90
+ * E0504: Validate that a .cnx alternative doesn't exist for a .h/.hpp include
91
+ * This helps during codebase migration by alerting developers when they should
92
+ * be using the C-Next version of a file instead of the C header.
93
+ *
94
+ * @param includeText - The full #include directive text
95
+ * @param lineNumber - Line number for error reporting
96
+ * @param sourcePath - Path to the source file (for resolving relative includes)
97
+ * @param includePaths - Array of directories to search for includes
98
+ * @param fileExists - Function to check if a file exists (injectable for testing)
99
+ */
100
+ validateIncludeNoCnxAlternative(
101
+ includeText: string,
102
+ lineNumber: number,
103
+ sourcePath: string | null,
104
+ includePaths: string[],
105
+ fileExists: (path: string) => boolean = (p) => require("fs").existsSync(p),
106
+ ): void {
107
+ // Extract the file path from #include directive
108
+ const angleMatch = includeText.match(/#\s*include\s*<([^>]+)>/);
109
+ const quoteMatch = includeText.match(/#\s*include\s*"([^"]+)"/);
110
+
111
+ const includePath = angleMatch?.[1] || quoteMatch?.[1];
112
+ if (!includePath) {
113
+ return; // Malformed include, let other validation handle it
114
+ }
115
+
116
+ // Skip if already a .cnx include
117
+ if (includePath.endsWith(".cnx")) {
118
+ return;
119
+ }
120
+
121
+ // Only check .h and .hpp files
122
+ const ext = includePath
123
+ .substring(includePath.lastIndexOf("."))
124
+ .toLowerCase();
125
+ if (ext !== ".h" && ext !== ".hpp") {
126
+ return;
127
+ }
128
+
129
+ // Build the .cnx alternative path
130
+ const cnxPath = includePath.replace(/\.(h|hpp)$/i, ".cnx");
131
+
132
+ if (quoteMatch) {
133
+ // Quoted include: resolve relative to source file's directory
134
+ if (sourcePath) {
135
+ const sourceDir = dirname(sourcePath);
136
+ const fullCnxPath = resolve(sourceDir, cnxPath);
137
+ if (fileExists(fullCnxPath)) {
138
+ throw new Error(
139
+ `E0504: Found #include "${includePath}" but '${cnxPath}' exists at the same location.\n` +
140
+ ` Use #include "${cnxPath}" instead to use the C-Next version. Line ${lineNumber}`,
141
+ );
142
+ }
143
+ }
144
+ } else if (angleMatch) {
145
+ // Angle bracket include: search through include paths
146
+ for (const searchDir of includePaths) {
147
+ const fullCnxPath = join(searchDir, cnxPath);
148
+ if (fileExists(fullCnxPath)) {
149
+ throw new Error(
150
+ `E0504: Found #include <${includePath}> but '${cnxPath}' exists at the same location.\n` +
151
+ ` Use #include <${cnxPath}> instead to use the C-Next version. Line ${lineNumber}`,
152
+ );
153
+ }
154
+ }
155
+ }
156
+ }
157
+
88
158
  // ========================================================================
89
159
  // Bitmap Field Validation (ADR-034)
90
160
  // ========================================================================
@@ -30,15 +30,6 @@ import generateScopedRegister from "./ScopedRegisterGenerator";
30
30
  * - Visibility control (private -> static, public -> extern)
31
31
  * - Organization without runtime overhead
32
32
  */
33
- /**
34
- * Issue #232: Information about a single-function variable
35
- */
36
- interface ISingleFunctionVar {
37
- varDecl: Parser.VariableDeclarationContext;
38
- targetFunction: string;
39
- isPrivate: boolean;
40
- }
41
-
42
33
  const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
43
34
  node: Parser.ScopeDeclarationContext,
44
35
  input: IGeneratorInput,
@@ -53,10 +44,6 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
53
44
  const lines: string[] = [];
54
45
  lines.push(`/* Scope: ${name} */`);
55
46
 
56
- // Issue #232: First pass - identify single-function variables
57
- // Maps functionName -> list of variable declarations that are local to it
58
- const localVarsForFunction = new Map<string, ISingleFunctionVar[]>();
59
-
60
47
  for (const member of node.scopeMember()) {
61
48
  const visibility = member.visibilityModifier()?.getText() || "private";
62
49
  const isPrivate = visibility === "private";
@@ -66,39 +53,16 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
66
53
  const varDecl = member.variableDeclaration()!;
67
54
  const varName = varDecl.IDENTIFIER().getText();
68
55
 
69
- // Issue #282: Check if this is a const variable - const values should be inlined,
70
- // not injected as local variables
56
+ // Issue #282: Check if this is a const variable - const values should be inlined
71
57
  const isConst = varDecl.constModifier() !== null;
72
58
 
73
- // Issue #232: Check if this PRIVATE variable is used in only one function
74
- // PUBLIC variables must stay file-scope because they can be accessed from outside
75
- // Issue #282: Skip const variables - they should be inlined, not made local
76
- const targetFunc =
77
- isPrivate && !isConst
78
- ? input.symbols?.getSingleFunctionForVariable(name, varName)
79
- : null;
80
-
81
- if (targetFunc) {
82
- // Single-function private variable - emit as local, not file-scope
83
- if (!localVarsForFunction.has(targetFunc)) {
84
- localVarsForFunction.set(targetFunc, []);
85
- }
86
- localVarsForFunction.get(targetFunc)!.push({
87
- varDecl,
88
- targetFunction: targetFunc,
89
- isPrivate,
90
- });
91
- // Don't emit file-scope declaration
92
- continue;
93
- }
94
-
95
59
  // Issue #282: Private const variables should be inlined, not emitted at file scope
96
60
  // The inlining happens in CodeGenerator when resolving this.CONST_NAME
97
61
  if (isPrivate && isConst) {
98
62
  continue;
99
63
  }
100
64
 
101
- // Multi-function or unused variable - emit at file scope (original behavior)
65
+ // ADR-016: All scope variables are emitted at file scope (static-like persistence)
102
66
  const type = orchestrator.generateType(varDecl.type());
103
67
  const fullName = `${name}_${varName}`;
104
68
  // Issue #282: Add 'const' modifier for const variables
@@ -149,7 +113,7 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
149
113
 
150
114
  // Issue #281: Generate body FIRST to track parameter modifications,
151
115
  // then generate parameter list using that tracking info
152
- let body = orchestrator.generateBlock(funcDecl.block());
116
+ const body = orchestrator.generateBlock(funcDecl.block());
153
117
 
154
118
  // Issue #281: Update symbol's parameter info with auto-const before generating params
155
119
  orchestrator.updateFunctionParamsAutoConst(fullName);
@@ -159,52 +123,6 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
159
123
  ? orchestrator.generateParameterList(funcDecl.parameterList()!)
160
124
  : "void";
161
125
 
162
- // Issue #232: Inject local variable declarations for single-function vars
163
- const localVars = localVarsForFunction.get(funcName);
164
- if (localVars && localVars.length > 0) {
165
- // Generate local variable declarations
166
- const localDecls: string[] = [];
167
- for (const { varDecl } of localVars) {
168
- const varType = orchestrator.generateType(varDecl.type());
169
- const varName = varDecl.IDENTIFIER().getText();
170
- const varFullName = `${name}_${varName}`;
171
-
172
- const arrayDims = varDecl.arrayDimension();
173
- const isArray = arrayDims.length > 0;
174
- let decl = ` ${varType} ${varFullName}`;
175
- if (isArray) {
176
- decl += orchestrator.generateArrayDimensions(arrayDims);
177
- }
178
- // ADR-045: Add string capacity dimension for string arrays
179
- if (varDecl.type().stringType()) {
180
- const stringCtx = varDecl.type().stringType()!;
181
- const intLiteral = stringCtx.INTEGER_LITERAL();
182
- if (intLiteral) {
183
- const capacity = parseInt(intLiteral.getText(), 10);
184
- decl += `[${capacity + 1}]`;
185
- }
186
- }
187
- if (varDecl.expression()) {
188
- decl += ` = ${orchestrator.generateExpression(varDecl.expression()!)}`;
189
- } else {
190
- decl += ` = ${orchestrator.getZeroInitializer(varDecl.type(), isArray)}`;
191
- }
192
- localDecls.push(decl + ";");
193
- }
194
-
195
- // Inject after opening brace: { -> {\n local_var = init;
196
- // Body format is typically "{\n ...statements...\n}"
197
- const openBraceIdx = body.indexOf("{");
198
- if (openBraceIdx !== -1) {
199
- const afterBrace = body.substring(openBraceIdx + 1);
200
- body =
201
- body.substring(0, openBraceIdx + 1) +
202
- "\n" +
203
- localDecls.join("\n") +
204
- afterBrace;
205
- }
206
- }
207
-
208
126
  // ADR-016: Exit function body context
209
127
  orchestrator.exitFunctionBody();
210
128
  orchestrator.setCurrentFunctionName(null); // Issue #269: Clear function name
@@ -16,6 +16,7 @@ import TGeneratorEffect from "../TGeneratorEffect";
16
16
  import IGeneratorInput from "../IGeneratorInput";
17
17
  import IGeneratorState from "../IGeneratorState";
18
18
  import IOrchestrator from "../IOrchestrator";
19
+ import ESymbolKind from "../../../types/ESymbolKind";
19
20
 
20
21
  /**
21
22
  * Issue #304: Map C-Next type to C type for static_cast.
@@ -38,6 +39,14 @@ const mapTypeToCType = (cnxType: string): string => {
38
39
  return TYPE_MAP[cnxType] || cnxType;
39
40
  };
40
41
 
42
+ /**
43
+ * Issue #315: Small primitive types that are always passed by value.
44
+ * These match the types used in Issue #269 for pass-by-value optimization.
45
+ * For cross-file function calls, we use these types directly since we can't
46
+ * know if the parameter is modified (that info is only in the source file).
47
+ */
48
+ const SMALL_PRIMITIVE_TYPES = new Set(["u8", "u16", "i8", "i16", "bool"]);
49
+
41
50
  /**
42
51
  * Issue #304: Wrap argument with static_cast if it's a C++ enum class
43
52
  * being passed to an integer parameter.
@@ -115,7 +124,27 @@ const generateFunctionCall = (
115
124
  .map((e, idx) => {
116
125
  // Get function signature for parameter type info
117
126
  const sig = input.functionSignatures.get(funcExpr);
118
- const targetParam = sig?.parameters[idx];
127
+ let targetParam = sig?.parameters[idx];
128
+ // Issue #315: Track if we got param info from SymbolTable (cross-file function)
129
+ let isCrossFileFunction = false;
130
+
131
+ // Issue #315: If no local signature, try SymbolTable for cross-file functions
132
+ if (!targetParam && input.symbolTable) {
133
+ const symbols = input.symbolTable.getOverloads(funcExpr);
134
+ for (const sym of symbols) {
135
+ if (sym.kind === ESymbolKind.Function && sym.parameters?.[idx]) {
136
+ // Map symbol parameter to targetParam format (IFunctionSignature.parameters)
137
+ targetParam = {
138
+ name: sym.parameters[idx].name,
139
+ baseType: sym.parameters[idx].type,
140
+ isConst: sym.parameters[idx].isConst,
141
+ isArray: sym.parameters[idx].isArray,
142
+ };
143
+ isCrossFileFunction = true;
144
+ break;
145
+ }
146
+ }
147
+ }
119
148
 
120
149
  if (!isCNextFunc) {
121
150
  // C function: pass-by-value, just generate the expression
@@ -134,13 +163,26 @@ const generateFunctionCall = (
134
163
  targetParam && orchestrator.isFloatType(targetParam.baseType);
135
164
  const isEnumParam =
136
165
  targetParam && orchestrator.getKnownEnums().has(targetParam.baseType);
137
- // Issue #269: Check if small unmodified primitive
166
+ // Issue #269: Check if small unmodified primitive (for local functions)
138
167
  const isPrimitivePassByValue = orchestrator.isParameterPassByValue(
139
168
  funcExpr,
140
169
  idx,
141
170
  );
171
+ // Issue #315: For cross-file functions ONLY, check if it's a small primitive type
172
+ // that should always be passed by value (u8, u16, i8, i16, bool).
173
+ // We only do this for cross-file functions because for local functions,
174
+ // isPrimitivePassByValue correctly considers whether the parameter is modified.
175
+ const isSmallPrimitive =
176
+ isCrossFileFunction &&
177
+ targetParam &&
178
+ SMALL_PRIMITIVE_TYPES.has(targetParam.baseType);
142
179
 
143
- if (isFloatParam || isEnumParam || isPrimitivePassByValue) {
180
+ if (
181
+ isFloatParam ||
182
+ isEnumParam ||
183
+ isPrimitivePassByValue ||
184
+ isSmallPrimitive
185
+ ) {
144
186
  // Target parameter is pass-by-value: pass value directly
145
187
  const argCode = orchestrator.generateExpression(e);
146
188
  // Issue #304: Wrap with static_cast if C++ enum class → integer