c-next 0.2.16 → 0.2.17

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 (56) hide show
  1. package/README.md +16 -0
  2. package/dist/index.js +1347 -414
  3. package/dist/index.js.map +3 -3
  4. package/grammar/CNext.g4 +4 -0
  5. package/package.json +5 -1
  6. package/src/transpiler/Transpiler.ts +90 -22
  7. package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
  8. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +57 -10
  9. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
  10. package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
  11. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
  12. package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
  13. package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
  14. package/src/transpiler/logic/parser/grammar/CNext.interp +1 -1
  15. package/src/transpiler/logic/parser/grammar/CNextParser.ts +154 -86
  16. package/src/transpiler/logic/symbols/SymbolTable.ts +17 -2
  17. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +6 -4
  18. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
  19. package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
  20. package/src/transpiler/output/codegen/CodeGenerator.ts +151 -94
  21. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
  22. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +150 -34
  23. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  24. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +11 -0
  25. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
  26. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
  27. package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
  28. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +48 -43
  29. package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
  30. package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
  31. package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
  32. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
  33. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
  34. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +227 -32
  35. package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
  36. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
  37. package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
  38. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
  39. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
  40. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
  41. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +268 -1
  42. package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
  43. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
  44. package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
  45. package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
  46. package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
  47. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
  48. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +75 -0
  49. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
  50. package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
  51. package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
  52. package/src/transpiler/state/CodeGenState.ts +71 -6
  53. package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
  54. package/src/utils/LiteralUtils.ts +23 -0
  55. package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
  56. package/src/utils/types/IParameterSymbol.ts +1 -0
@@ -26,6 +26,7 @@ import ArrayDimensionUtils from "./ArrayDimensionUtils";
26
26
  import QualifiedNameGenerator from "../../utils/QualifiedNameGenerator";
27
27
  import CodeGenState from "../../../../state/CodeGenState";
28
28
  import ScopeUtils from "../../../../../utils/ScopeUtils";
29
+ import VariableModifierBuilder from "../../helpers/VariableModifierBuilder";
29
30
 
30
31
  /**
31
32
  * Generate initializer expression for a variable declaration.
@@ -38,10 +39,12 @@ function generateInitializer(
38
39
  ): string {
39
40
  if (varDecl.expression()) {
40
41
  // Issue #872: Set expectedType for MISRA 7.2 U suffix compliance
42
+ // Issue #992: withDeclarationInit suppresses compound literals at file scope (GCC 9-12 compat)
41
43
  const typeName = orchestrator.generateType(varDecl.type());
42
- return CodeGenState.withExpectedType(
43
- typeName,
44
- () => ` = ${orchestrator.generateExpression(varDecl.expression()!)}`,
44
+ return CodeGenState.withExpectedType(typeName, () =>
45
+ CodeGenState.withDeclarationInit(
46
+ () => ` = ${orchestrator.generateExpression(varDecl.expression()!)}`,
47
+ ),
45
48
  );
46
49
  }
47
50
  // ADR-015: Zero initialization for uninitialized scope variables
@@ -204,12 +207,23 @@ function generateRegularVariable(
204
207
  orchestrator.markOpaqueScopeVariable(fullName);
205
208
  }
206
209
 
207
- // Issue #282: Add 'const' modifier for const variables
210
+ // Issue #998: Use VariableModifierBuilder for consistent modifier handling
211
+ // This handles const, atomic, volatile, and validates mutual exclusion
212
+ // Scope variables are always at file scope (not in function body), no initializer for modifier purposes
213
+ const modifiers = VariableModifierBuilder.build(
214
+ varDecl,
215
+ false, // inFunctionBody - scope vars are file scope
216
+ false, // hasInitializer - doesn't affect volatile/atomic handling
217
+ false, // cppMode - doesn't affect volatile/atomic handling
218
+ );
219
+ // For scope variables: static for private, no modifier for public
220
+ // Then add volatile (from atomic or volatile keyword), then const
221
+ const staticPrefix = isPrivate ? "static " : "";
222
+ const volatilePrefix = modifiers.atomic || modifiers.volatile;
208
223
  const constPrefix = isConst ? "const " : "";
209
- const prefix = isPrivate ? "static " : "";
210
224
 
211
225
  // Build declaration with all dimensions
212
- let decl = `${prefix}${constPrefix}${type} ${fullName}`;
226
+ let decl = `${staticPrefix}${volatilePrefix}${constPrefix}${type} ${fullName}`;
213
227
  decl += ArrayDimensionUtils.generateArrayTypeDimension(
214
228
  arrayTypeCtx,
215
229
  orchestrator,
@@ -225,7 +239,12 @@ function generateRegularVariable(
225
239
 
226
240
  // Issue #948: Opaque types use NULL initialization instead of {0}
227
241
  // Issue #958: External typedef struct types also use NULL initialization
228
- if (isOpaque || isExternalStruct) {
242
+ // Issue #996: ...but only for SCALAR handles, which are single pointers. An
243
+ // *array* of opaque handles needs a brace initializer ({0}), not a scalar
244
+ // NULL. Route arrays through generateInitializer, which uses
245
+ // getZeroInitializer(type, isArray) — the single source of truth for
246
+ // zero-initialization (ADR-015).
247
+ if ((isOpaque || isExternalStruct) && !isArray) {
229
248
  decl += " = NULL";
230
249
  } else {
231
250
  decl += generateInitializer(varDecl, isArray, orchestrator);
@@ -126,6 +126,8 @@ function createMockVariableDecl(options: {
126
126
  name: string;
127
127
  type: string;
128
128
  isConst?: boolean;
129
+ isAtomic?: boolean;
130
+ isVolatile?: boolean;
129
131
  initialValue?: string;
130
132
  arrayDims?: string[];
131
133
  hasStringType?: boolean;
@@ -142,6 +144,10 @@ function createMockVariableDecl(options: {
142
144
  options.arrayTypeSize,
143
145
  ),
144
146
  constModifier: () => createMockConstModifier(options.isConst ?? false),
147
+ // Issue #998: atomic and volatile modifiers for scope variables
148
+ // Uses VariableModifierBuilder for consistent handling
149
+ atomicModifier: () => (options.isAtomic ? ({} as unknown) : null),
150
+ volatileModifier: () => (options.isVolatile ? ({} as unknown) : null),
145
151
  expression: () =>
146
152
  options.initialValue ? createMockExpression(options.initialValue) : null,
147
153
  arrayDimension: () =>
@@ -772,6 +778,86 @@ describe("ScopeGenerator", () => {
772
778
  expect(result.code).toContain("static Point Canvas_point = 0;");
773
779
  expect(result.code).not.toContain("Point*");
774
780
  });
781
+
782
+ it("generates atomic variable with volatile modifier (Issue #998)", () => {
783
+ const varDecl = createMockVariableDecl({
784
+ name: "counter",
785
+ type: "u32",
786
+ isAtomic: true,
787
+ initialValue: "0",
788
+ });
789
+ const member = createMockScopeMember({ variableDecl: varDecl });
790
+ const ctx = createMockScopeContext("ISR", [member]);
791
+ const input = createMockInput();
792
+ const state = createMockState();
793
+ const orchestrator = createMockOrchestrator();
794
+
795
+ const result = generateScope(ctx, input, state, orchestrator);
796
+
797
+ expect(result.code).toContain(
798
+ "static volatile uint32_t ISR_counter = 0;",
799
+ );
800
+ });
801
+
802
+ it("generates public atomic variable with volatile but without static (Issue #998)", () => {
803
+ const varDecl = createMockVariableDecl({
804
+ name: "flag",
805
+ type: "bool",
806
+ isAtomic: true,
807
+ initialValue: "false",
808
+ });
809
+ const member = createMockScopeMember({
810
+ visibility: "public",
811
+ variableDecl: varDecl,
812
+ });
813
+ const ctx = createMockScopeContext("Shared", [member]);
814
+ const input = createMockInput();
815
+ const state = createMockState();
816
+ const orchestrator = createMockOrchestrator();
817
+
818
+ const result = generateScope(ctx, input, state, orchestrator);
819
+
820
+ expect(result.code).toContain("volatile bool Shared_flag = false;");
821
+ expect(result.code).not.toContain("static volatile bool Shared_flag");
822
+ });
823
+
824
+ it("generates volatile-keyword variable with volatile modifier (Issue #998)", () => {
825
+ const varDecl = createMockVariableDecl({
826
+ name: "reg",
827
+ type: "u8",
828
+ isVolatile: true,
829
+ initialValue: "0",
830
+ });
831
+ const member = createMockScopeMember({ variableDecl: varDecl });
832
+ const ctx = createMockScopeContext("HW", [member]);
833
+ const input = createMockInput();
834
+ const state = createMockState();
835
+ const orchestrator = createMockOrchestrator();
836
+
837
+ const result = generateScope(ctx, input, state, orchestrator);
838
+
839
+ expect(result.code).toContain("static volatile uint8_t HW_reg = 0;");
840
+ });
841
+
842
+ it("throws error when both atomic and volatile are specified (Issue #998)", () => {
843
+ const varDecl = createMockVariableDecl({
844
+ name: "bad",
845
+ type: "u8",
846
+ isAtomic: true,
847
+ isVolatile: true,
848
+ initialValue: "0",
849
+ startLine: 10,
850
+ });
851
+ const member = createMockScopeMember({ variableDecl: varDecl });
852
+ const ctx = createMockScopeContext("Test", [member]);
853
+ const input = createMockInput();
854
+ const state = createMockState();
855
+ const orchestrator = createMockOrchestrator();
856
+
857
+ expect(() => generateScope(ctx, input, state, orchestrator)).toThrow(
858
+ /Cannot use both 'atomic' and 'volatile' modifiers/,
859
+ );
860
+ });
775
861
  });
776
862
 
777
863
  // ========================================================================
@@ -18,6 +18,7 @@ import IGeneratorInput from "../IGeneratorInput";
18
18
  import IGeneratorState from "../IGeneratorState";
19
19
  import IOrchestrator from "../IOrchestrator";
20
20
  import BinaryExprUtils from "./BinaryExprUtils";
21
+ import CodeGenState from "../../../../state/CodeGenState";
21
22
 
22
23
  /**
23
24
  * Generator context passed to child generators.
@@ -116,6 +117,7 @@ const generateAndExpr = (
116
117
  * ADR-001: = becomes == in C
117
118
  * ADR-017: Enum type safety validation
118
119
  * ADR-045: String comparison via strcmp()
120
+ * Issue #1032: Clear expectedType for comparison operands
119
121
  */
120
122
  const generateEqualityExpr = (
121
123
  node: Parser.EqualityExpressionContext,
@@ -151,18 +153,14 @@ const generateEqualityExpr = (
151
153
  // Generate strcmp for string comparison - needs string.h
152
154
  effects.push({ type: "include", header: "string" });
153
155
 
154
- const leftResult = generateRelationalExpr(
155
- exprs[0],
156
- input,
157
- state,
158
- orchestrator,
159
- );
160
- const rightResult = generateRelationalExpr(
161
- exprs[1],
162
- input,
163
- state,
164
- orchestrator,
165
- );
156
+ // Issue #1032: Clear expectedType for equality comparisons.
157
+ // Use CodeGenState.withoutExpectedType() to clear the global state that
158
+ // generators read via getState(). The passed state is not used for
159
+ // expectedType lookup - generators read from CodeGenState directly.
160
+ const [leftResult, rightResult] = CodeGenState.withoutExpectedType(() => [
161
+ generateRelationalExpr(exprs[0], input, state, orchestrator),
162
+ generateRelationalExpr(exprs[1], input, state, orchestrator),
163
+ ]);
166
164
  effects.push(...leftResult.effects, ...rightResult.effects);
167
165
 
168
166
  const fullText = node.getText();
@@ -183,18 +181,29 @@ const generateEqualityExpr = (
183
181
  // Issue #152: Extract operators in order from parse tree children
184
182
  // ADR-001: C-Next uses = for equality, transpile to ==
185
183
  const operators = orchestrator.getOperatorsFromChildren(node);
186
- return accumulateBinaryExprs(
187
- exprs,
188
- operators,
189
- "=",
190
- generateRelationalExpr,
191
- { input, state, orchestrator },
192
- BinaryExprUtils.mapEqualityOperator,
184
+
185
+ // Issue #1032: Clear expectedType for equality comparisons.
186
+ // The U suffix for MISRA 7.2 compliance applies to assignments, not comparisons.
187
+ // Use CodeGenState.withoutExpectedType() to clear the global state that
188
+ // generators read via getState(). The passed state is not used for
189
+ // expectedType lookup - generators read from CodeGenState directly.
190
+ return CodeGenState.withoutExpectedType(() =>
191
+ accumulateBinaryExprs(
192
+ exprs,
193
+ operators,
194
+ "=",
195
+ generateRelationalExpr,
196
+ { input, state, orchestrator },
197
+ BinaryExprUtils.mapEqualityOperator,
198
+ ),
193
199
  );
194
200
  };
195
201
 
196
202
  /**
197
203
  * Generate C code for a relational expression.
204
+ * Issue #1032: Clear expectedType for comparison operands - MISRA 7.2 suffix
205
+ * should not apply to comparisons, only to assignments. This prevents
206
+ * `i32 < 0` from becoming `signedIdx < 0U` which changes comparison semantics.
198
207
  */
199
208
  const generateRelationalExpr = (
200
209
  node: Parser.RelationalExpressionContext,
@@ -210,11 +219,21 @@ const generateRelationalExpr = (
210
219
 
211
220
  // Issue #152: Extract operators in order from parse tree children
212
221
  const operators = orchestrator.getOperatorsFromChildren(node);
213
- return accumulateBinaryExprs(exprs, operators, "<", generateBitwiseOrExpr, {
214
- input,
215
- state,
216
- orchestrator,
217
- });
222
+
223
+ // Issue #1032: Clear expectedType for relational comparisons.
224
+ // The U suffix for MISRA 7.2 compliance applies to assignments, not comparisons.
225
+ // Comparing `i32 < 0` should NOT generate `signedIdx < 0U` because that
226
+ // changes semantics due to C's integer promotion rules.
227
+ // Use CodeGenState.withoutExpectedType() to clear the global state that
228
+ // generators read via getState(). The passed state is not used for
229
+ // expectedType lookup - generators read from CodeGenState directly.
230
+ return CodeGenState.withoutExpectedType(() =>
231
+ accumulateBinaryExprs(exprs, operators, "<", generateBitwiseOrExpr, {
232
+ input,
233
+ state,
234
+ orchestrator,
235
+ }),
236
+ );
218
237
  };
219
238
 
220
239
  /**
@@ -166,7 +166,8 @@ const _generateCFunctionArg = (
166
166
  }
167
167
 
168
168
  // Issue #948: Check if argument is an opaque scope variable (already a pointer)
169
- const isOpaqueScopeVar = CodeGenState.isOpaqueScopeVariable(argCode);
169
+ // Issue #996: ...including an element of an opaque-handle array (arr[i])
170
+ const isOpaqueScopeVar = CodeGenState.isOpaqueScopeVariableAccess(argCode);
170
171
 
171
172
  // Add & if argument needs address-of to match parameter type.
172
173
  // Issue #322: struct types passed to pointer params.
@@ -284,50 +285,54 @@ const generateFunctionCall = (
284
285
  // Get function signature once for all arguments
285
286
  const sig = input.functionSignatures.get(funcExpr);
286
287
 
287
- const args = argExprs
288
- .map((e, idx) => {
289
- // Get parameter type info from local signature or cross-file SymbolTable
290
- const resolved = CallExprUtils.resolveTargetParam(
291
- sig,
292
- idx,
293
- funcExpr,
294
- input.symbolTable,
295
- );
296
- const targetParam = resolved.param;
297
-
298
- // C/C++ function: use pass-by-value semantics
299
- if (!isCNextFunc) {
300
- return _generateCFunctionArg(e, targetParam, input, orchestrator);
301
- }
302
-
303
- // C-Next function: check if target parameter should be passed by value
304
- if (
305
- _shouldPassByValue(
306
- funcExpr,
288
+ // Issue #992: Clear inDeclarationInit for function call arguments — struct
289
+ // initializers inside function args need compound literals, not plain designated initializers.
290
+ const args = CodeGenState.withoutDeclarationInit(() =>
291
+ argExprs
292
+ .map((e, idx) => {
293
+ // Get parameter type info from local signature or cross-file SymbolTable
294
+ const resolved = CallExprUtils.resolveTargetParam(
295
+ sig,
307
296
  idx,
308
- targetParam,
309
- resolved.isCrossFile,
310
- orchestrator,
311
- )
312
- ) {
313
- // Issue #872: Set expectedType for MISRA 7.2 compliance, but suppress bare enum resolution
314
- const argCode = CodeGenState.withExpectedType(
315
- targetParam?.baseType,
316
- () => orchestrator.generateExpression(e),
317
- true, // suppressEnumResolution
318
- );
319
- return wrapWithCppEnumCast(
320
- argCode,
321
- e,
322
- targetParam?.baseType,
323
- orchestrator,
297
+ funcExpr,
298
+ input.symbolTable,
324
299
  );
325
- }
326
-
327
- // Target parameter is pass-by-reference: use & logic
328
- return orchestrator.generateFunctionArg(e, targetParam?.baseType);
329
- })
330
- .join(", ");
300
+ const targetParam = resolved.param;
301
+
302
+ // C/C++ function: use pass-by-value semantics
303
+ if (!isCNextFunc) {
304
+ return _generateCFunctionArg(e, targetParam, input, orchestrator);
305
+ }
306
+
307
+ // C-Next function: check if target parameter should be passed by value
308
+ if (
309
+ _shouldPassByValue(
310
+ funcExpr,
311
+ idx,
312
+ targetParam,
313
+ resolved.isCrossFile,
314
+ orchestrator,
315
+ )
316
+ ) {
317
+ // Issue #872: Set expectedType for MISRA 7.2 compliance, but suppress bare enum resolution
318
+ const argCode = CodeGenState.withExpectedType(
319
+ targetParam?.baseType,
320
+ () => orchestrator.generateExpression(e),
321
+ true, // suppressEnumResolution
322
+ );
323
+ return wrapWithCppEnumCast(
324
+ argCode,
325
+ e,
326
+ targetParam?.baseType,
327
+ orchestrator,
328
+ );
329
+ }
330
+
331
+ // Target parameter is pass-by-reference: use & logic
332
+ return orchestrator.generateFunctionArg(e, targetParam?.baseType);
333
+ })
334
+ .join(", "),
335
+ );
331
336
 
332
337
  return { code: `${funcExpr}(${args})`, effects };
333
338
  };
@@ -15,6 +15,7 @@ import TGeneratorEffect from "../TGeneratorEffect";
15
15
  import IGeneratorInput from "../IGeneratorInput";
16
16
  import IGeneratorState from "../IGeneratorState";
17
17
  import IOrchestrator from "../IOrchestrator";
18
+ import CodeGenState from "../../../../state/CodeGenState";
18
19
 
19
20
  /**
20
21
  * Generate C code for an expression (entry point).
@@ -74,9 +75,15 @@ const generateTernaryExpr = (
74
75
  orchestrator.validateTernaryConditionNoFunctionCall(condition);
75
76
 
76
77
  // Generate C output - parentheses already present from grammar
78
+ // Issue #992: Clear inDeclarationInit in ternary arms — struct initializers
79
+ // inside ternary branches need compound literals, not plain designated initializers.
77
80
  const condCode = orchestrator.generateOrExpr(condition);
78
- const trueCode = orchestrator.generateOrExpr(trueExpr);
79
- const falseCode = orchestrator.generateOrExpr(falseExpr);
81
+ const trueCode = CodeGenState.withoutDeclarationInit(() =>
82
+ orchestrator.generateOrExpr(trueExpr),
83
+ );
84
+ const falseCode = CodeGenState.withoutDeclarationInit(() =>
85
+ orchestrator.generateOrExpr(falseExpr),
86
+ );
80
87
 
81
88
  return { code: `(${condCode}) ? ${trueCode} : ${falseCode}`, effects };
82
89
  };
@@ -1477,4 +1477,48 @@ describe("CallExprGenerator", () => {
1477
1477
  expect(result.code).toBe("use_handle(my_handle)");
1478
1478
  });
1479
1479
  });
1480
+
1481
+ describe("inDeclarationInit clearing (Issue #992)", () => {
1482
+ it("clears inDeclarationInit during function argument generation", () => {
1483
+ CodeGenState.inDeclarationInit = true;
1484
+
1485
+ const argExprs = [createMockExpressionContext("myArg")];
1486
+ const argCtx = createMockArgListContext(argExprs);
1487
+ const input = createMockInput();
1488
+ const state = createMockState();
1489
+
1490
+ let flagDuringArg = true;
1491
+ const orchestrator = createMockOrchestrator({
1492
+ isCNextFunction: vi.fn(() => false),
1493
+ generateExpression: vi.fn((ctx: Parser.ExpressionContext) => {
1494
+ flagDuringArg = CodeGenState.inDeclarationInit;
1495
+ return ctx.getText();
1496
+ }),
1497
+ });
1498
+
1499
+ generateFunctionCall("myFunc", argCtx, input, state, orchestrator);
1500
+
1501
+ expect(flagDuringArg).toBe(false);
1502
+ expect(CodeGenState.inDeclarationInit).toBe(true);
1503
+ });
1504
+
1505
+ it("restores inDeclarationInit after argument generation", () => {
1506
+ CodeGenState.inDeclarationInit = true;
1507
+
1508
+ const argExprs = [
1509
+ createMockExpressionContext("a"),
1510
+ createMockExpressionContext("b"),
1511
+ ];
1512
+ const argCtx = createMockArgListContext(argExprs);
1513
+ const input = createMockInput();
1514
+ const state = createMockState();
1515
+ const orchestrator = createMockOrchestrator({
1516
+ isCNextFunction: vi.fn(() => false),
1517
+ });
1518
+
1519
+ generateFunctionCall("myFunc", argCtx, input, state, orchestrator);
1520
+
1521
+ expect(CodeGenState.inDeclarationInit).toBe(true);
1522
+ });
1523
+ });
1480
1524
  });
@@ -1,9 +1,10 @@
1
- import { describe, it, expect, vi } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import expressionGenerators from "../ExpressionGenerator";
3
3
  import IGeneratorInput from "../../IGeneratorInput";
4
4
  import IGeneratorState from "../../IGeneratorState";
5
5
  import IOrchestrator from "../../IOrchestrator";
6
6
  import * as Parser from "../../../../../logic/parser/grammar/CNextParser";
7
+ import CodeGenState from "../../../../../state/CodeGenState";
7
8
 
8
9
  // ========================================================================
9
10
  // Test Helpers
@@ -455,6 +456,86 @@ describe("ExpressionGenerator", () => {
455
456
  });
456
457
  });
457
458
 
459
+ describe("inDeclarationInit clearing (Issue #992)", () => {
460
+ beforeEach(() => {
461
+ CodeGenState.reset();
462
+ });
463
+
464
+ it("clears inDeclarationInit in ternary true and false arms", () => {
465
+ CodeGenState.inDeclarationInit = true;
466
+
467
+ const condition = createMockOrExpr("x > 0");
468
+ const trueExpr = createMockOrExpr("a");
469
+ const falseExpr = createMockOrExpr("b");
470
+ const ctx = createMockTernaryContext([condition, trueExpr, falseExpr]);
471
+
472
+ const input = createMockInput();
473
+ const state = createMockState();
474
+
475
+ const flagDuringTrue: boolean[] = [];
476
+ const flagDuringFalse: boolean[] = [];
477
+
478
+ const orchestrator = createMockOrchestrator();
479
+ let callCount = 0;
480
+ (
481
+ orchestrator.generateOrExpr as ReturnType<typeof vi.fn>
482
+ ).mockImplementation((ctx: Parser.OrExpressionContext) => {
483
+ callCount++;
484
+ if (callCount === 2) {
485
+ flagDuringTrue.push(CodeGenState.inDeclarationInit);
486
+ } else if (callCount === 3) {
487
+ flagDuringFalse.push(CodeGenState.inDeclarationInit);
488
+ }
489
+ return ctx.getText();
490
+ });
491
+
492
+ expressionGenerators.generateTernaryExpr(
493
+ ctx,
494
+ input,
495
+ state,
496
+ orchestrator,
497
+ );
498
+
499
+ expect(flagDuringTrue).toEqual([false]);
500
+ expect(flagDuringFalse).toEqual([false]);
501
+ expect(CodeGenState.inDeclarationInit).toBe(true);
502
+ });
503
+
504
+ it("does not affect condition generation", () => {
505
+ CodeGenState.inDeclarationInit = true;
506
+
507
+ const condition = createMockOrExpr("x > 0");
508
+ const trueExpr = createMockOrExpr("a");
509
+ const falseExpr = createMockOrExpr("b");
510
+ const ctx = createMockTernaryContext([condition, trueExpr, falseExpr]);
511
+
512
+ const input = createMockInput();
513
+ const state = createMockState();
514
+
515
+ let flagDuringCondition = false;
516
+ const orchestrator = createMockOrchestrator();
517
+ let callCount = 0;
518
+ (
519
+ orchestrator.generateOrExpr as ReturnType<typeof vi.fn>
520
+ ).mockImplementation((ctx: Parser.OrExpressionContext) => {
521
+ callCount++;
522
+ if (callCount === 1) {
523
+ flagDuringCondition = CodeGenState.inDeclarationInit;
524
+ }
525
+ return ctx.getText();
526
+ });
527
+
528
+ expressionGenerators.generateTernaryExpr(
529
+ ctx,
530
+ input,
531
+ state,
532
+ orchestrator,
533
+ );
534
+
535
+ expect(flagDuringCondition).toBe(true);
536
+ });
537
+ });
538
+
458
539
  describe("effects", () => {
459
540
  it("returns empty effects array for non-ternary", () => {
460
541
  const orExpr = createMockOrExpr("value");
@@ -60,6 +60,13 @@ interface IFromASTDeps {
60
60
  * When the C typedef has `const T*`, this preserves const on the generated param.
61
61
  */
62
62
  forceConst?: boolean;
63
+
64
+ /**
65
+ * Issue #995: Check if a type is an opaque handle (incomplete struct typedef).
66
+ * Opaque handles should not get auto-const because they must be passed to
67
+ * C APIs that expect non-const pointers.
68
+ */
69
+ isOpaqueType?: (typeName: string) => boolean;
63
70
  }
64
71
 
65
72
  /**
@@ -142,6 +149,8 @@ class ParameterInputAdapter {
142
149
  const isKnownPrimitive = !!deps.typeMap[typeName];
143
150
  // Issue #958: C-header typedef struct types need pointer semantics
144
151
  const isTypedefStruct = deps.isTypedefStructType(typeName);
152
+ // Issue #995: Detect opaque handles — rule applied in ParameterSignatureBuilder
153
+ const isOpaque = deps.isOpaqueType?.(typeName) ?? false;
145
154
  // Issue #895: Don't add auto-const for callback-compatible functions
146
155
  // because it would change the signature and break typedef compatibility
147
156
  const isAutoConst =
@@ -171,6 +180,8 @@ class ParameterInputAdapter {
171
180
  deps.forcePassByReference || isTypedefStruct || undefined,
172
181
  // Issue #895: Preserve const from callback typedef signature
173
182
  forceConst: deps.forceConst,
183
+ // Issue #995: Pass through opaque handle detection — rule applied in builder
184
+ isOpaqueHandle: isOpaque || undefined,
174
185
  };
175
186
  }
176
187
 
@@ -236,6 +247,8 @@ class ParameterInputAdapter {
236
247
  isPassByReference: isCallbackPointer ? true : !deps.isPassByValue,
237
248
  forcePointerSyntax: isCallbackPointer || undefined,
238
249
  forceConst: param.isCallbackConst || undefined,
250
+ // Issue #995: Pass through opaque handle detection — rule applied in builder
251
+ isOpaqueHandle: param.isOpaqueHandle || undefined,
239
252
  };
240
253
  }
241
254
 
@@ -296,14 +309,15 @@ class ParameterInputAdapter {
296
309
  }
297
310
  }
298
311
 
299
- const isAutoConst = !deps.isModified && !isConst;
300
-
312
+ // ADR-006: Arrays are pass-by-reference and mutable by default.
313
+ // Never apply auto-const to arrays - only explicit const from source code.
314
+ // Auto-const would break compatibility with C APIs expecting mutable pointers.
301
315
  return {
302
316
  name,
303
317
  baseType: typeName,
304
318
  mappedType,
305
319
  isConst,
306
- isAutoConst,
320
+ isAutoConst: false,
307
321
  isArray: true,
308
322
  arrayDimensions: dims,
309
323
  isCallback: false,
@@ -47,6 +47,11 @@ class ParameterSignatureBuilder {
47
47
  return this._buildStringParam(param);
48
48
  }
49
49
 
50
+ // Issue #995: Opaque handles are always pass-by-reference with pointer syntax
51
+ if (param.isOpaqueHandle) {
52
+ return this._buildRefParam(param, refSuffix);
53
+ }
54
+
50
55
  // Known struct or known primitive: pass by reference
51
56
  if (param.isPassByReference) {
52
57
  return this._buildRefParam(param, refSuffix);
@@ -103,18 +108,21 @@ class ParameterSignatureBuilder {
103
108
  /**
104
109
  * Build pass-by-reference parameter signature.
105
110
  * C mode: const Point* p
106
- * C++ mode: const Point& p (unless forcePointerSyntax)
111
+ * C++ mode: const Point& p (unless forcePointerSyntax or isOpaqueHandle)
107
112
  *
108
113
  * Issue #895: When forcePointerSyntax is set, always use pointer syntax
109
114
  * because C callback typedefs expect pointers, not C++ references.
115
+ * Issue #995: Opaque handles must use pointer syntax because C APIs
116
+ * expect pointers to incomplete struct types (e.g., widget_t*).
110
117
  */
111
118
  private static _buildRefParam(
112
119
  param: IParameterInput,
113
120
  refSuffix: string,
114
121
  ): string {
115
122
  const constPrefix = this._getConstPrefix(param);
116
- // Issue #895: Override refSuffix for callback-compatible functions
117
- const actualSuffix = param.forcePointerSyntax ? "*" : refSuffix;
123
+ // Issue #895/#995: Override refSuffix for callback-compatible or opaque handle params
124
+ const actualSuffix =
125
+ param.forcePointerSyntax || param.isOpaqueHandle ? "*" : refSuffix;
118
126
  return `${constPrefix}${param.mappedType}${actualSuffix} ${param.name}`;
119
127
  }
120
128
 
@@ -130,10 +138,15 @@ class ParameterSignatureBuilder {
130
138
  * Get const prefix combining explicit const, auto-const, and forced const.
131
139
  * Priority: forceConst > isConst > isAutoConst
132
140
  * Issue #895: forceConst preserves const from callback typedef signature.
141
+ * Issue #995: Opaque handles suppress auto-const (they must be passed to
142
+ * C APIs that expect non-const pointers).
133
143
  */
134
144
  private static _getConstPrefix(param: IParameterInput): string {
145
+ // Issue #995: Opaque handles never get auto-const — they're passed to C APIs
146
+ // expecting mutable pointers (e.g., LVGL's lv_obj_t)
147
+ const effectiveAutoConst = param.isOpaqueHandle ? false : param.isAutoConst;
135
148
  // Any source of const results in "const " prefix
136
- if (param.forceConst || param.isConst || param.isAutoConst) {
149
+ if (param.forceConst || param.isConst || effectiveAutoConst) {
137
150
  return "const ";
138
151
  }
139
152
  return "";