c-next 0.2.16 → 0.2.18

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 (118) hide show
  1. package/README.md +18 -2
  2. package/dist/index.js +8897 -6260
  3. package/dist/index.js.map +4 -4
  4. package/grammar/CNext.g4 +12 -0
  5. package/package.json +4 -2
  6. package/src/transpiler/Transpiler.ts +376 -48
  7. package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
  8. package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
  9. package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
  10. package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
  11. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +65 -10
  12. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
  13. package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
  14. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
  15. package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
  16. package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
  17. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
  18. package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
  19. package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
  20. package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
  21. package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
  22. package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
  23. package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
  24. package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
  25. package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
  26. package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
  27. package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
  28. package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
  29. package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
  30. package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
  31. package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
  32. package/src/transpiler/logic/parser/grammar/CNextParser.ts +1318 -1178
  33. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
  34. package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
  35. package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
  36. package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
  37. package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
  38. package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
  39. package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
  40. package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
  41. package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
  42. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
  43. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
  44. package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
  45. package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
  46. package/src/transpiler/logic/symbols/SymbolTable.ts +62 -2
  47. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +55 -4
  48. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
  49. package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
  50. package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
  51. package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
  52. package/src/transpiler/output/codegen/CodeGenerator.ts +196 -98
  53. package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
  54. package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
  55. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
  56. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +163 -35
  57. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  58. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
  59. package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
  60. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +97 -14
  61. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
  62. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
  63. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
  64. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
  65. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
  66. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
  67. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
  68. package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
  69. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
  70. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
  71. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
  72. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
  73. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
  74. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
  75. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
  76. package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
  77. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +76 -58
  78. package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
  79. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
  80. package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
  81. package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
  82. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
  83. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
  84. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
  85. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
  86. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
  87. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
  88. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +240 -42
  89. package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
  90. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
  91. package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
  92. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
  93. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
  94. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
  95. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
  96. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +322 -9
  97. package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
  98. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
  99. package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
  100. package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
  101. package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
  102. package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
  103. package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
  104. package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
  105. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
  106. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +140 -24
  107. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
  108. package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
  109. package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
  110. package/src/transpiler/state/CodeGenState.ts +91 -22
  111. package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
  112. package/src/transpiler/types/ICachedFileEntry.ts +7 -0
  113. package/src/utils/LiteralUtils.ts +23 -0
  114. package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
  115. package/src/utils/cache/CacheManager.ts +13 -2
  116. package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
  117. package/src/utils/constants/TypeConstants.ts +13 -0
  118. package/src/utils/types/IParameterSymbol.ts +1 -0
@@ -11,6 +11,52 @@ import CppNamespaceUtils from "../../../../utils/CppNamespaceUtils";
11
11
 
12
12
  const { mapType } = typeUtils;
13
13
 
14
+ /**
15
+ * Resolve the C type for a struct field, checking for callback types first.
16
+ */
17
+ function resolveFieldCType(fieldType: string, input: IHeaderTypeInput): string {
18
+ // ADR-029: Check if field type is a callback type and use typedef name
19
+ const callbackInfo = input.callbackTypes?.get(fieldType);
20
+ if (callbackInfo) {
21
+ return callbackInfo.typedefName;
22
+ }
23
+ // Issue #502/#522: Convert C++ namespace types from _ to :: format
24
+ const convertedType = CppNamespaceUtils.convertToCppNamespace(
25
+ fieldType,
26
+ input.symbolTable,
27
+ );
28
+ return mapType(convertedType);
29
+ }
30
+
31
+ /**
32
+ * Generate the field line for a struct member, handling embedded dimensions.
33
+ *
34
+ * Issue #461: Handle string<N> types which map to char[N+1]
35
+ * The embedded dimension must come after array dimensions in C syntax.
36
+ * Example: string<64> arr[4] -> char arr[4][65], not char[65] arr[4]
37
+ */
38
+ function generateFieldLine(
39
+ fieldName: string,
40
+ cType: string,
41
+ dims: readonly number[] | undefined,
42
+ ): string {
43
+ const dimSuffix =
44
+ dims && dims.length > 0 ? dims.map((d) => `[${d}]`).join("") : "";
45
+ const embeddedMatch = /^(\w+)\[(\d+)\]$/.exec(cType);
46
+
47
+ if (!embeddedMatch) {
48
+ return ` ${cType} ${fieldName}${dimSuffix};`;
49
+ }
50
+
51
+ const baseType = embeddedMatch[1];
52
+ const embeddedDim = embeddedMatch[2];
53
+ // When dims are provided, they already include string capacity from StructCollector
54
+ if (dims && dims.length > 0) {
55
+ return ` ${baseType} ${fieldName}${dimSuffix};`;
56
+ }
57
+ return ` ${baseType} ${fieldName}[${embeddedDim}];`;
58
+ }
59
+
14
60
  /**
15
61
  * Generate a C typedef struct declaration for the given struct name.
16
62
  *
@@ -41,35 +87,9 @@ function generateStructHeader(name: string, input: IHeaderTypeInput): string {
41
87
 
42
88
  // Iterate fields in insertion order (Map preserves order)
43
89
  for (const [fieldName, fieldType] of fields) {
44
- // Issue #502/#522: Convert C++ namespace types from _ to :: format using shared utility
45
- const convertedType = CppNamespaceUtils.convertToCppNamespace(
46
- fieldType,
47
- input.symbolTable,
48
- );
49
- const cType = mapType(convertedType);
90
+ const cType = resolveFieldCType(fieldType, input);
50
91
  const dims = dimensions?.get(fieldName);
51
- const dimSuffix =
52
- dims && dims.length > 0 ? dims.map((d) => `[${d}]`).join("") : "";
53
-
54
- // Issue #461: Handle string<N> types which map to char[N+1]
55
- // The embedded dimension must come after array dimensions in C syntax
56
- // Example: string<64> arr[4] -> char arr[4][65], not char[65] arr[4]
57
- //
58
- // Fix: When dims already include the string capacity (from StructCollector),
59
- // use dimSuffix directly. Only extract embedded dim when no dims provided.
60
- const embeddedMatch = /^(\w+)\[(\d+)\]$/.exec(cType);
61
- if (embeddedMatch) {
62
- const baseType = embeddedMatch[1];
63
- const embeddedDim = embeddedMatch[2];
64
- // dims already includes string capacity, so just use dimSuffix
65
- if (dims && dims.length > 0) {
66
- lines.push(` ${baseType} ${fieldName}${dimSuffix};`);
67
- } else {
68
- lines.push(` ${baseType} ${fieldName}[${embeddedDim}];`);
69
- }
70
- } else {
71
- lines.push(` ${cType} ${fieldName}${dimSuffix};`);
72
- }
92
+ lines.push(generateFieldLine(fieldName, cType, dims));
73
93
  }
74
94
 
75
95
  lines.push(`} ${name};`);
@@ -30,6 +30,7 @@ import ICallbackTypeInfo from "../output/codegen/types/ICallbackTypeInfo";
30
30
  import ITargetCapabilities from "../output/codegen/types/ITargetCapabilities";
31
31
  import TOverflowBehavior from "../output/codegen/types/TOverflowBehavior";
32
32
  import TYPE_WIDTH from "../output/codegen/types/TYPE_WIDTH";
33
+ import type ICodeGenApi from "../output/codegen/types/ICodeGenApi";
33
34
  import TypeResolver from "../../utils/TypeResolver";
34
35
 
35
36
  /**
@@ -73,10 +74,26 @@ export default class CodeGenState {
73
74
  // ===========================================================================
74
75
 
75
76
  /**
76
- * Reference to the CodeGenerator instance for handlers to call methods.
77
- * Typed as unknown to avoid circular dependencies - handlers cast as needed.
77
+ * Reference to the CodeGenerator instance for handlers to call its methods,
78
+ * typed to the method subset they use (ICodeGenApi). `import type` keeps this a
79
+ * compile-time-only edge, and CodeGenState already imports sibling types from
80
+ * output/codegen/types, so this introduces no runtime/circular dependency.
78
81
  */
79
- static generator: unknown = null;
82
+ static generator: ICodeGenApi | null = null;
83
+
84
+ /**
85
+ * The CodeGenerator instance, asserted present. Handlers call this instead of
86
+ * each casting `generator` themselves — one source of truth for the access and
87
+ * null-check (the generator is always set before any handler runs).
88
+ */
89
+ static requireGenerator(): ICodeGenApi {
90
+ if (CodeGenState.generator === null) {
91
+ throw new Error(
92
+ "CodeGenState.generator is not set; codegen accessed before initialization.",
93
+ );
94
+ }
95
+ return CodeGenState.generator;
96
+ }
80
97
 
81
98
  // ===========================================================================
82
99
  // SYMBOL DATA (read-only after initialization)
@@ -142,9 +159,6 @@ export default class CodeGenState {
142
159
  /** Tracks which parameters are modified (directly or transitively) */
143
160
  static modifiedParameters: Map<string, Set<string>> = new Map();
144
161
 
145
- /** Issue #579: Parameters with subscript access (must become pointers) */
146
- static subscriptAccessedParameters: Map<string, Set<string>> = new Map();
147
-
148
162
  /** Parameters that should pass by value (small, unmodified primitives) */
149
163
  static passByValueParams: Map<string, Set<string>> = new Map();
150
164
 
@@ -217,6 +231,12 @@ export default class CodeGenState {
217
231
  /** Whether we're inside a function body */
218
232
  static inFunctionBody: boolean = false;
219
233
 
234
+ /** Whether we're generating the RHS of a variable declaration initializer.
235
+ * When true, struct literals use { .field = value } instead of (Type){ .field = value }
236
+ * because plain designated initializers are valid C99 at any scope, while compound
237
+ * literals are not constant expressions and fail at file scope on GCC < 13. */
238
+ static inDeclarationInit: boolean = false;
239
+
220
240
  /** Expected type for struct initializers and enum inference */
221
241
  static expectedType: string | null = null;
222
242
 
@@ -357,7 +377,6 @@ export default class CodeGenState {
357
377
 
358
378
  // Pass-by-value analysis
359
379
  this.modifiedParameters = new Map();
360
- this.subscriptAccessedParameters = new Map();
361
380
  this.passByValueParams = new Map();
362
381
  this.functionCallGraph = new Map();
363
382
  this.functionParamLists = new Map();
@@ -381,6 +400,7 @@ export default class CodeGenState {
381
400
  // Generation state
382
401
  this.indentLevel = 0;
383
402
  this.inFunctionBody = false;
403
+ this.inDeclarationInit = false;
384
404
  this.expectedType = null;
385
405
  this.suppressBareEnumResolution = false;
386
406
  this.mainArgsName = null;
@@ -476,6 +496,49 @@ export default class CodeGenState {
476
496
  }
477
497
  }
478
498
 
499
+ /** Execute fn with inDeclarationInit=true, restoring prior value on exit. */
500
+ static withDeclarationInit<T>(fn: () => T): T {
501
+ const saved = this.inDeclarationInit;
502
+ this.inDeclarationInit = true;
503
+ try {
504
+ return fn();
505
+ } finally {
506
+ this.inDeclarationInit = saved;
507
+ }
508
+ }
509
+
510
+ /** Execute fn with inDeclarationInit=false, restoring prior value on exit.
511
+ * Used in sub-expression contexts (function args, ternary arms) where
512
+ * plain designated initializers are not valid C. */
513
+ static withoutDeclarationInit<T>(fn: () => T): T {
514
+ const saved = this.inDeclarationInit;
515
+ this.inDeclarationInit = false;
516
+ try {
517
+ return fn();
518
+ } finally {
519
+ this.inDeclarationInit = saved;
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Execute fn with expectedType=null, restoring prior value on exit.
525
+ * Issue #1032: Used in comparison contexts (relational/equality expressions)
526
+ * where MISRA 7.2 U suffix should NOT be applied - comparing `i32 < 0`
527
+ * should not generate `signedIdx < 0U` which changes comparison semantics.
528
+ */
529
+ static withoutExpectedType<T>(fn: () => T): T {
530
+ const savedType = this.expectedType;
531
+ const savedSuppress = this.suppressBareEnumResolution;
532
+ this.expectedType = null;
533
+ this.suppressBareEnumResolution = false;
534
+ try {
535
+ return fn();
536
+ } finally {
537
+ this.expectedType = savedType;
538
+ this.suppressBareEnumResolution = savedSuppress;
539
+ }
540
+ }
541
+
479
542
  // ===========================================================================
480
543
  // CONVENIENCE LOOKUP METHODS
481
544
  // ===========================================================================
@@ -720,15 +783,6 @@ export default class CodeGenState {
720
783
  return this.passByValueParams.get(funcName)?.has(paramName) ?? false;
721
784
  }
722
785
 
723
- /**
724
- * Check if a parameter has subscript access.
725
- */
726
- static hasSubscriptAccess(funcName: string, paramName: string): boolean {
727
- return (
728
- this.subscriptAccessedParameters.get(funcName)?.has(paramName) ?? false
729
- );
730
- }
731
-
732
786
  /**
733
787
  * Get the function signature for a function.
734
788
  */
@@ -1136,14 +1190,29 @@ export default class CodeGenState {
1136
1190
  }
1137
1191
 
1138
1192
  /**
1139
- * Check if a scope variable has an opaque type (and is thus a pointer).
1140
- * Used during access generation to determine if & prefix is needed.
1193
+ * Check if generated code accesses an opaque scope variable (and is thus
1194
+ * already a pointer). Used during argument generation to decide whether an
1195
+ * address-of (&) prefix is needed.
1141
1196
  *
1142
- * @param qualifiedName - The fully qualified variable name (e.g., "MyScope_widget")
1143
- * @returns true if this is an opaque scope variable (already a pointer)
1197
+ * Handles two forms:
1198
+ * - Direct access: "MyScope_widget" → the handle itself (pointer)
1199
+ * - Array-element access: "MyScope_widgets[i]" → an element of an opaque
1200
+ * handle array, which is itself a pointer (Issue #996)
1201
+ *
1202
+ * @param generatedCode - The generated access expression (e.g. "UI_widgets[i]")
1203
+ * @returns true if this resolves to an opaque scope variable (already a pointer)
1144
1204
  */
1145
- static isOpaqueScopeVariable(qualifiedName: string): boolean {
1146
- return this.opaqueScopeVariables.has(qualifiedName);
1205
+ static isOpaqueScopeVariableAccess(generatedCode: string): boolean {
1206
+ if (this.opaqueScopeVariables.has(generatedCode)) {
1207
+ return true;
1208
+ }
1209
+ // Issue #996: An element of an opaque-handle array is already a pointer.
1210
+ // Match on the base array name that precedes the subscript.
1211
+ const bracketIndex = generatedCode.indexOf("[");
1212
+ if (bracketIndex === -1) {
1213
+ return false;
1214
+ }
1215
+ return this.opaqueScopeVariables.has(generatedCode.slice(0, bracketIndex));
1147
1216
  }
1148
1217
 
1149
1218
  // ===========================================================================
@@ -376,6 +376,7 @@ describe("CodeGenState", () => {
376
376
  isArray: false,
377
377
  isConst: false,
378
378
  isPointer: false,
379
+ isStruct: false,
379
380
  arrayDims: "",
380
381
  },
381
382
  ],
@@ -1011,25 +1012,35 @@ describe("CodeGenState", () => {
1011
1012
  describe("Opaque Scope Variable Helpers (Issue #948)", () => {
1012
1013
  it("markOpaqueScopeVariable adds to opaqueScopeVariables", () => {
1013
1014
  CodeGenState.markOpaqueScopeVariable("MyScope_widget");
1014
- expect(CodeGenState.isOpaqueScopeVariable("MyScope_widget")).toBe(true);
1015
+ expect(CodeGenState.isOpaqueScopeVariableAccess("MyScope_widget")).toBe(
1016
+ true,
1017
+ );
1015
1018
  });
1016
1019
 
1017
- it("isOpaqueScopeVariable returns false for unknown variable", () => {
1018
- expect(CodeGenState.isOpaqueScopeVariable("Unknown_var")).toBe(false);
1020
+ it("isOpaqueScopeVariableAccess returns false for unknown variable", () => {
1021
+ expect(CodeGenState.isOpaqueScopeVariableAccess("Unknown_var")).toBe(
1022
+ false,
1023
+ );
1019
1024
  });
1020
1025
 
1021
- it("isOpaqueScopeVariable returns true for marked variable", () => {
1026
+ it("isOpaqueScopeVariableAccess returns true for marked variable", () => {
1022
1027
  CodeGenState.markOpaqueScopeVariable("Gui_display");
1023
- expect(CodeGenState.isOpaqueScopeVariable("Gui_display")).toBe(true);
1028
+ expect(CodeGenState.isOpaqueScopeVariableAccess("Gui_display")).toBe(
1029
+ true,
1030
+ );
1024
1031
  });
1025
1032
 
1026
1033
  it("reset clears opaqueScopeVariables", () => {
1027
1034
  CodeGenState.markOpaqueScopeVariable("Test_opaque");
1028
- expect(CodeGenState.isOpaqueScopeVariable("Test_opaque")).toBe(true);
1035
+ expect(CodeGenState.isOpaqueScopeVariableAccess("Test_opaque")).toBe(
1036
+ true,
1037
+ );
1029
1038
 
1030
1039
  CodeGenState.reset();
1031
1040
 
1032
- expect(CodeGenState.isOpaqueScopeVariable("Test_opaque")).toBe(false);
1041
+ expect(CodeGenState.isOpaqueScopeVariableAccess("Test_opaque")).toBe(
1042
+ false,
1043
+ );
1033
1044
  });
1034
1045
 
1035
1046
  it("handles multiple opaque scope variables", () => {
@@ -1037,10 +1048,241 @@ describe("CodeGenState", () => {
1037
1048
  CodeGenState.markOpaqueScopeVariable("Scope1_display");
1038
1049
  CodeGenState.markOpaqueScopeVariable("Scope2_handle");
1039
1050
 
1040
- expect(CodeGenState.isOpaqueScopeVariable("Scope1_widget")).toBe(true);
1041
- expect(CodeGenState.isOpaqueScopeVariable("Scope1_display")).toBe(true);
1042
- expect(CodeGenState.isOpaqueScopeVariable("Scope2_handle")).toBe(true);
1043
- expect(CodeGenState.isOpaqueScopeVariable("Scope1_other")).toBe(false);
1051
+ expect(CodeGenState.isOpaqueScopeVariableAccess("Scope1_widget")).toBe(
1052
+ true,
1053
+ );
1054
+ expect(CodeGenState.isOpaqueScopeVariableAccess("Scope1_display")).toBe(
1055
+ true,
1056
+ );
1057
+ expect(CodeGenState.isOpaqueScopeVariableAccess("Scope2_handle")).toBe(
1058
+ true,
1059
+ );
1060
+ expect(CodeGenState.isOpaqueScopeVariableAccess("Scope1_other")).toBe(
1061
+ false,
1062
+ );
1063
+ });
1064
+
1065
+ // Issue #996: An element of an opaque-handle array is itself a pointer.
1066
+ it("isOpaqueScopeVariableAccess matches array-element access of an opaque array", () => {
1067
+ CodeGenState.markOpaqueScopeVariable("UI_widgets");
1068
+
1069
+ expect(CodeGenState.isOpaqueScopeVariableAccess("UI_widgets[i]")).toBe(
1070
+ true,
1071
+ );
1072
+ expect(CodeGenState.isOpaqueScopeVariableAccess("UI_widgets[0]")).toBe(
1073
+ true,
1074
+ );
1075
+ });
1076
+
1077
+ it("isOpaqueScopeVariableAccess does not match subscript of a non-opaque array", () => {
1078
+ // Base array name was never marked opaque.
1079
+ expect(CodeGenState.isOpaqueScopeVariableAccess("UI_counts[i]")).toBe(
1080
+ false,
1081
+ );
1082
+ });
1083
+
1084
+ it("isOpaqueScopeVariableAccess does not match a different array that shares a prefix", () => {
1085
+ CodeGenState.markOpaqueScopeVariable("UI_widgets");
1086
+
1087
+ // "UI_widgetsExtra" is a distinct variable, not a subscript of UI_widgets.
1088
+ expect(CodeGenState.isOpaqueScopeVariableAccess("UI_widgetsExtra")).toBe(
1089
+ false,
1090
+ );
1091
+ });
1092
+ });
1093
+
1094
+ describe("withDeclarationInit()", () => {
1095
+ it("sets inDeclarationInit to true during callback", () => {
1096
+ CodeGenState.inDeclarationInit = false;
1097
+ let valueInside = false;
1098
+
1099
+ CodeGenState.withDeclarationInit(() => {
1100
+ valueInside = CodeGenState.inDeclarationInit;
1101
+ });
1102
+
1103
+ expect(valueInside).toBe(true);
1104
+ });
1105
+
1106
+ it("restores prior value after callback", () => {
1107
+ CodeGenState.inDeclarationInit = false;
1108
+
1109
+ CodeGenState.withDeclarationInit(() => {
1110
+ // inside: true
1111
+ });
1112
+
1113
+ expect(CodeGenState.inDeclarationInit).toBe(false);
1114
+ });
1115
+
1116
+ it("restores prior value even when already true", () => {
1117
+ CodeGenState.inDeclarationInit = true;
1118
+
1119
+ CodeGenState.withDeclarationInit(() => {
1120
+ expect(CodeGenState.inDeclarationInit).toBe(true);
1121
+ });
1122
+
1123
+ expect(CodeGenState.inDeclarationInit).toBe(true);
1124
+ });
1125
+
1126
+ it("returns the callback result", () => {
1127
+ const result = CodeGenState.withDeclarationInit(() => "hello");
1128
+ expect(result).toBe("hello");
1129
+ });
1130
+
1131
+ it("restores prior value on exception", () => {
1132
+ CodeGenState.inDeclarationInit = false;
1133
+
1134
+ expect(() =>
1135
+ CodeGenState.withDeclarationInit(() => {
1136
+ throw new Error("test error");
1137
+ }),
1138
+ ).toThrow("test error");
1139
+
1140
+ expect(CodeGenState.inDeclarationInit).toBe(false);
1141
+ });
1142
+ });
1143
+
1144
+ describe("withoutDeclarationInit()", () => {
1145
+ it("sets inDeclarationInit to false during callback", () => {
1146
+ CodeGenState.inDeclarationInit = true;
1147
+ let valueInside = true;
1148
+
1149
+ CodeGenState.withoutDeclarationInit(() => {
1150
+ valueInside = CodeGenState.inDeclarationInit;
1151
+ });
1152
+
1153
+ expect(valueInside).toBe(false);
1154
+ });
1155
+
1156
+ it("restores prior value after callback", () => {
1157
+ CodeGenState.inDeclarationInit = true;
1158
+
1159
+ CodeGenState.withoutDeclarationInit(() => {
1160
+ // inside: false
1161
+ });
1162
+
1163
+ expect(CodeGenState.inDeclarationInit).toBe(true);
1164
+ });
1165
+
1166
+ it("restores prior value even when already false", () => {
1167
+ CodeGenState.inDeclarationInit = false;
1168
+
1169
+ CodeGenState.withoutDeclarationInit(() => {
1170
+ expect(CodeGenState.inDeclarationInit).toBe(false);
1171
+ });
1172
+
1173
+ expect(CodeGenState.inDeclarationInit).toBe(false);
1174
+ });
1175
+
1176
+ it("returns the callback result", () => {
1177
+ const result = CodeGenState.withoutDeclarationInit(() => 42);
1178
+ expect(result).toBe(42);
1179
+ });
1180
+
1181
+ it("restores prior value on exception", () => {
1182
+ CodeGenState.inDeclarationInit = true;
1183
+
1184
+ expect(() =>
1185
+ CodeGenState.withoutDeclarationInit(() => {
1186
+ throw new Error("test error");
1187
+ }),
1188
+ ).toThrow("test error");
1189
+
1190
+ expect(CodeGenState.inDeclarationInit).toBe(true);
1191
+ });
1192
+
1193
+ it("nests correctly with withDeclarationInit", () => {
1194
+ CodeGenState.inDeclarationInit = false;
1195
+
1196
+ CodeGenState.withDeclarationInit(() => {
1197
+ expect(CodeGenState.inDeclarationInit).toBe(true);
1198
+
1199
+ CodeGenState.withoutDeclarationInit(() => {
1200
+ expect(CodeGenState.inDeclarationInit).toBe(false);
1201
+ });
1202
+
1203
+ expect(CodeGenState.inDeclarationInit).toBe(true);
1204
+ });
1205
+
1206
+ expect(CodeGenState.inDeclarationInit).toBe(false);
1207
+ });
1208
+ });
1209
+
1210
+ describe("withoutExpectedType()", () => {
1211
+ it("clears expectedType during callback", () => {
1212
+ CodeGenState.expectedType = "u32";
1213
+ let typeInside: string | null = "notCleared";
1214
+
1215
+ CodeGenState.withoutExpectedType(() => {
1216
+ typeInside = CodeGenState.expectedType;
1217
+ });
1218
+
1219
+ expect(typeInside).toBeNull();
1220
+ });
1221
+
1222
+ it("clears suppressBareEnumResolution during callback", () => {
1223
+ CodeGenState.suppressBareEnumResolution = true;
1224
+ let suppressInside = true;
1225
+
1226
+ CodeGenState.withoutExpectedType(() => {
1227
+ suppressInside = CodeGenState.suppressBareEnumResolution;
1228
+ });
1229
+
1230
+ expect(suppressInside).toBe(false);
1231
+ });
1232
+
1233
+ it("restores expectedType after callback", () => {
1234
+ CodeGenState.expectedType = "i32";
1235
+
1236
+ CodeGenState.withoutExpectedType(() => {
1237
+ // inside: null
1238
+ });
1239
+
1240
+ expect(CodeGenState.expectedType).toBe("i32");
1241
+ });
1242
+
1243
+ it("restores suppressBareEnumResolution after callback", () => {
1244
+ CodeGenState.suppressBareEnumResolution = true;
1245
+
1246
+ CodeGenState.withoutExpectedType(() => {
1247
+ // inside: false
1248
+ });
1249
+
1250
+ expect(CodeGenState.suppressBareEnumResolution).toBe(true);
1251
+ });
1252
+
1253
+ it("returns the callback result", () => {
1254
+ const result = CodeGenState.withoutExpectedType(() => 123);
1255
+ expect(result).toBe(123);
1256
+ });
1257
+
1258
+ it("restores values on exception", () => {
1259
+ CodeGenState.expectedType = "bool";
1260
+ CodeGenState.suppressBareEnumResolution = true;
1261
+
1262
+ expect(() =>
1263
+ CodeGenState.withoutExpectedType(() => {
1264
+ throw new Error("test error");
1265
+ }),
1266
+ ).toThrow("test error");
1267
+
1268
+ expect(CodeGenState.expectedType).toBe("bool");
1269
+ expect(CodeGenState.suppressBareEnumResolution).toBe(true);
1270
+ });
1271
+
1272
+ it("handles null expectedType correctly", () => {
1273
+ CodeGenState.expectedType = null;
1274
+ CodeGenState.suppressBareEnumResolution = false;
1275
+
1276
+ let executed = false;
1277
+ CodeGenState.withoutExpectedType(() => {
1278
+ executed = true;
1279
+ expect(CodeGenState.expectedType).toBeNull();
1280
+ expect(CodeGenState.suppressBareEnumResolution).toBe(false);
1281
+ });
1282
+
1283
+ expect(executed).toBe(true);
1284
+ expect(CodeGenState.expectedType).toBeNull();
1285
+ expect(CodeGenState.suppressBareEnumResolution).toBe(false);
1044
1286
  });
1045
1287
  });
1046
1288
  });
@@ -26,6 +26,13 @@ interface ICachedFileEntry {
26
26
  structTagAliases?: Array<[string, string]>;
27
27
  /** Issue #958: Struct tags that have full definitions (bodies) */
28
28
  structTagsWithBodies?: string[];
29
+ /**
30
+ * Issue #985: This header could not be preprocessed standalone and fell back
31
+ * to raw content, so its cached symbols are degraded (phantom struct bodies,
32
+ * missing/by-value framework signatures). Persisted so a warm-cache build
33
+ * still triggers the external-declaration recovery pass and re-applies the fix.
34
+ */
35
+ preprocessFailed?: boolean;
29
36
  }
30
37
 
31
38
  export default ICachedFileEntry;
@@ -68,9 +68,32 @@ class LiteralUtils {
68
68
  );
69
69
  }
70
70
 
71
+ // Issue #1010: Float literals (0.0, 0.0f, .0, etc.)
72
+ if (ctx.FLOAT_LITERAL()) {
73
+ return LiteralUtils.isFloatZero(text);
74
+ }
75
+
71
76
  return false;
72
77
  }
73
78
 
79
+ /**
80
+ * Check if a float literal string represents zero.
81
+ * Issue #1010: Detect float zero for division-by-zero checking.
82
+ *
83
+ * Handles: 0.0, .0, 0., 0.0f, 0.0F, 0.0e0, 0.0E0, etc.
84
+ *
85
+ * @param text - The float literal text
86
+ * @returns true if the float is zero
87
+ */
88
+ static isFloatZero(text: string): boolean {
89
+ // Remove optional float suffix (f, F)
90
+ const withoutSuffix = text.replace(/[fF]$/, "");
91
+
92
+ // Parse to number and check if zero
93
+ const value = Number.parseFloat(withoutSuffix);
94
+ return value === 0;
95
+ }
96
+
74
97
  /**
75
98
  * Check if a literal is a floating-point number.
76
99
  *