c-next 0.2.17 → 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 (81) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +7645 -5941
  3. package/dist/index.js.map +4 -4
  4. package/grammar/CNext.g4 +8 -0
  5. package/package.json +1 -3
  6. package/src/transpiler/Transpiler.ts +286 -26
  7. package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
  8. package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
  9. package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
  10. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +8 -0
  11. package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
  12. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
  13. package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
  14. package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
  15. package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
  16. package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
  17. package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
  18. package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
  19. package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
  20. package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
  21. package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
  22. package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
  23. package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
  24. package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
  25. package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
  26. package/src/transpiler/logic/parser/grammar/CNextParser.ts +1257 -1185
  27. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
  28. package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
  29. package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
  30. package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
  31. package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
  32. package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
  33. package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
  34. package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
  35. package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
  36. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
  37. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
  38. package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
  39. package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
  40. package/src/transpiler/logic/symbols/SymbolTable.ts +45 -0
  41. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
  42. package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
  43. package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
  44. package/src/transpiler/output/codegen/CodeGenerator.ts +45 -4
  45. package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
  46. package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
  47. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +13 -1
  48. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
  49. package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
  50. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +86 -14
  51. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
  52. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
  53. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
  54. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
  55. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
  56. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
  57. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
  58. package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
  59. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
  60. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
  61. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
  62. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
  63. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
  64. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +28 -15
  65. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
  66. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
  67. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
  68. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
  69. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
  70. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +16 -13
  71. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
  72. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
  73. package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
  74. package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
  75. package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
  76. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +65 -24
  77. package/src/transpiler/state/CodeGenState.ts +20 -16
  78. package/src/transpiler/types/ICachedFileEntry.ts +7 -0
  79. package/src/utils/cache/CacheManager.ts +13 -2
  80. package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
  81. package/src/utils/constants/TypeConstants.ts +13 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Issue #579: Shared subscript classifier for array vs bit access
2
+ * Issue #579 / Issue #1100: Shared subscript classifier for array vs bit access
3
3
  *
4
4
  * This utility unifies the classification logic used by:
5
5
  * - AssignmentClassifier (assignment path)
@@ -7,8 +7,28 @@
7
7
  *
8
8
  * The classification rule is:
9
9
  * 1. If isArray or isString -> array access
10
- * 2. If isParameter && !isArray -> array access (parameter becomes pointer in C, ADR-006)
11
- * 3. Otherwise -> bit manipulation
10
+ * 2. Otherwise -> bit manipulation
11
+ *
12
+ * This rule is identical for parameters and local variables. ADR-006 requires
13
+ * array parameters to use explicit array syntax (`u8[8] buf`), which sets
14
+ * isArray on the parameter's type info exactly like a local array declaration
15
+ * does. A scalar-typed parameter (no array brackets) is therefore classified
16
+ * the same way a scalar local variable is: bit manipulation (ADR-007 — "Any
17
+ * integer type can be indexed to access individual bits", with no carve-out
18
+ * for parameters).
19
+ *
20
+ * Issue #1100: A prior version of this classifier treated ANY function
21
+ * parameter without explicit array syntax as array access (`isParameter &&
22
+ * !isArray -> array`), added in #579 to support buffer-style parameters
23
+ * declared without brackets (e.g. `void fillBuffer(u8 buf)`). That blanket
24
+ * rule was a divergent decision path from local-variable classification: it
25
+ * silently broke ADR-007 bit-indexing for scalar parameters (`u32 v; ...
26
+ * v[4]` inside a function became `v[4]` array-subscript C code on a pointer
27
+ * instead of a shift-and-mask bit read), producing invalid/incorrect C
28
+ * for the extremely common embedded pattern of reading a bit out of a
29
+ * hardware-register-shaped scalar parameter. Buffer-style parameters must now
30
+ * use explicit array syntax (`u8[N] buf`), which was already the ADR-006
31
+ * documented and supported spelling — see tests/params/param-array-indexing.test.cnx.
12
32
  */
13
33
  import TSubscriptKind from "./TSubscriptKind";
14
34
  import TTypeInfo from "../types/TTypeInfo";
@@ -63,9 +83,14 @@ class SubscriptClassifier {
63
83
  * Determine if a type should use array access semantics.
64
84
  *
65
85
  * Array access is used when:
66
- * - Type is explicitly an array (isArray: true)
86
+ * - Type is explicitly an array (isArray: true) — including array
87
+ * parameters declared with explicit syntax, e.g. `u8[8] buf` (ADR-006)
67
88
  * - Type is a string (strings are char arrays)
68
- * - Type is a non-array parameter (becomes pointer in C, ADR-006)
89
+ *
90
+ * Parameters are NOT special-cased here (Issue #1100): whether a subscript
91
+ * is array access or bit access depends solely on the declared type, the
92
+ * same rule used for local variables. A scalar parameter without array
93
+ * syntax is bit-indexable exactly like a scalar local variable (ADR-007).
69
94
  *
70
95
  * @param typeInfo - Type information, or null if unknown
71
96
  * @returns true if subscript should be treated as array access
@@ -82,12 +107,6 @@ class SubscriptClassifier {
82
107
  return true;
83
108
  }
84
109
 
85
- // Issue #579: Non-array parameter becomes pointer in C (ADR-006)
86
- // So buf[i] is array access, not bit access
87
- if (typeInfo.isParameter) {
88
- return true;
89
- }
90
-
91
110
  // Otherwise it's a scalar - use bit access
92
111
  return false;
93
112
  }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  type TSubscriptKind =
8
8
  | "array_element" // Single array element access: arr[i]
9
- | "array_slice" // Array slice/memcpy: arr[offset, length]
9
+ | "array_slice" // Array slice: arr[offset, length] (per-element little-endian writes, ADR-007/#1081)
10
10
  | "bit_single" // Single bit access: flags[3]
11
11
  | "bit_range"; // Bit range extraction: value[start, width]
12
12
 
@@ -74,8 +74,13 @@ describe("SubscriptClassifier", () => {
74
74
  });
75
75
  });
76
76
 
77
- describe("parameter types (Issue #579)", () => {
78
- it("returns array_element for non-array parameter with single index", () => {
77
+ describe("parameter types (Issue #1100)", () => {
78
+ // A parameter's array-ness is determined solely by its declared type
79
+ // (isArray, from explicit `T[N]` syntax, ADR-006) — identical to local
80
+ // variables. `isParameter` alone must NOT force array classification;
81
+ // that was the Issue #579 heuristic reverted by Issue #1100 because it
82
+ // broke ADR-007 bit-indexing for scalar parameters.
83
+ it("returns bit_single for non-array parameter with single index", () => {
79
84
  const typeInfo: TTypeInfo = {
80
85
  baseType: "u8",
81
86
  bitWidth: 8,
@@ -88,10 +93,10 @@ describe("SubscriptClassifier", () => {
88
93
  subscriptCount: 1,
89
94
  isRegisterAccess: false,
90
95
  });
91
- expect(result).toBe("array_element");
96
+ expect(result).toBe("bit_single");
92
97
  });
93
98
 
94
- it("returns array_slice for non-array parameter with two indices", () => {
99
+ it("returns bit_range for non-array parameter with two indices", () => {
95
100
  const typeInfo: TTypeInfo = {
96
101
  baseType: "u8",
97
102
  bitWidth: 8,
@@ -104,7 +109,23 @@ describe("SubscriptClassifier", () => {
104
109
  subscriptCount: 2,
105
110
  isRegisterAccess: false,
106
111
  });
107
- expect(result).toBe("array_slice");
112
+ expect(result).toBe("bit_range");
113
+ });
114
+
115
+ it("returns array_element for array parameter with single index", () => {
116
+ const typeInfo: TTypeInfo = {
117
+ baseType: "u8",
118
+ bitWidth: 8,
119
+ isArray: true,
120
+ isConst: false,
121
+ isParameter: true,
122
+ };
123
+ const result = SubscriptClassifier.classify({
124
+ typeInfo,
125
+ subscriptCount: 1,
126
+ isRegisterAccess: false,
127
+ });
128
+ expect(result).toBe("array_element");
108
129
  });
109
130
  });
110
131
 
@@ -187,7 +208,7 @@ describe("SubscriptClassifier", () => {
187
208
  expect(SubscriptClassifier.isArrayAccess(typeInfo)).toBe(true);
188
209
  });
189
210
 
190
- it("returns true for parameter type", () => {
211
+ it("returns false for non-array parameter type (Issue #1100)", () => {
191
212
  const typeInfo: TTypeInfo = {
192
213
  baseType: "u8",
193
214
  bitWidth: 8,
@@ -195,6 +216,17 @@ describe("SubscriptClassifier", () => {
195
216
  isConst: false,
196
217
  isParameter: true,
197
218
  };
219
+ expect(SubscriptClassifier.isArrayAccess(typeInfo)).toBe(false);
220
+ });
221
+
222
+ it("returns true for array parameter type", () => {
223
+ const typeInfo: TTypeInfo = {
224
+ baseType: "u8",
225
+ bitWidth: 8,
226
+ isArray: true,
227
+ isConst: false,
228
+ isParameter: true,
229
+ };
198
230
  expect(SubscriptClassifier.isArrayAccess(typeInfo)).toBe(true);
199
231
  });
200
232
 
@@ -17,6 +17,7 @@ import generateStructHeader from "./generators/generateStructHeader";
17
17
  import generateBitmapHeader from "./generators/generateBitmapHeader";
18
18
  import VariableDeclarationFormatter from "../codegen/helpers/VariableDeclarationFormatter";
19
19
  import type IVariableFormatInput from "../codegen/types/IVariableFormatInput";
20
+ import MisraSuppressionUtils from "../MisraSuppressionUtils";
20
21
 
21
22
  const { mapType, isBuiltInType } = typeUtils;
22
23
 
@@ -267,6 +268,65 @@ class HeaderGeneratorUtils {
267
268
  ];
268
269
  }
269
270
 
271
+ /**
272
+ * Extract header file stem from include directive for deduplication.
273
+ * E.g., '#include <foo/bar.hpp>' -> 'bar'
274
+ * SonarCloud S8786: Avoid backtracking by using separate patterns.
275
+ */
276
+ private static extractIncludeStem(include: string): string {
277
+ // Try angle brackets first, then quotes - avoids backtracking regex
278
+ const angleMatch = /<([^<>]+)>/.exec(include);
279
+ if (angleMatch) {
280
+ return angleMatch[1].replace(/^.*\//, "").replace(/\.(?:h|hpp)$/, "");
281
+ }
282
+ const quoteMatch = /"([^"]+)"/.exec(include);
283
+ if (quoteMatch) {
284
+ return quoteMatch[1].replace(/^.*\//, "").replace(/\.(?:h|hpp)$/, "");
285
+ }
286
+ return include;
287
+ }
288
+
289
+ /**
290
+ * Add user includes with MISRA suppression comments.
291
+ * SonarCloud S3776: Extracted from generateIncludes.
292
+ */
293
+ private static addUserIncludes(
294
+ lines: string[],
295
+ userIncludes: string[] | undefined,
296
+ ): void {
297
+ if (!userIncludes || userIncludes.length === 0) return;
298
+ for (const include of userIncludes) {
299
+ // Issue #850: Add MISRA suppression for banned headers
300
+ const suppression =
301
+ MisraSuppressionUtils.getMisraSuppressionComment(include);
302
+ if (suppression) {
303
+ lines.push(suppression);
304
+ }
305
+ lines.push(include);
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Add external type headers, deduplicating against user includes.
311
+ * SonarCloud S3776: Extracted from generateIncludes.
312
+ */
313
+ private static addExternalTypeHeaders(
314
+ lines: string[],
315
+ headersToInclude: Set<string>,
316
+ userIncludes: string[] | undefined,
317
+ ): void {
318
+ const userIncludeSet = new Set(userIncludes ?? []);
319
+ const userIncludeStems = new Set(
320
+ (userIncludes ?? []).map(HeaderGeneratorUtils.extractIncludeStem),
321
+ );
322
+ for (const directive of headersToInclude) {
323
+ if (userIncludeSet.has(directive)) continue;
324
+ const stem = HeaderGeneratorUtils.extractIncludeStem(directive);
325
+ if (stem && userIncludeStems.has(stem)) continue;
326
+ lines.push(directive);
327
+ }
328
+ }
329
+
270
330
  /**
271
331
  * Generate all include directives (system, user, and external type headers)
272
332
  */
@@ -282,37 +342,18 @@ class HeaderGeneratorUtils {
282
342
  }
283
343
 
284
344
  // User includes (already have correct extension from IncludeExtractor)
285
- if (options.userIncludes && options.userIncludes.length > 0) {
286
- for (const include of options.userIncludes) {
287
- lines.push(include);
288
- }
289
- }
345
+ HeaderGeneratorUtils.addUserIncludes(lines, options.userIncludes);
290
346
 
291
347
  // External type header includes (skip duplicates of user includes)
292
348
  // Dedup by basename stem to handle:
293
349
  // - Different path styles (e.g., <AppConfig.hpp> vs "../AppConfig.hpp")
294
350
  // - Extension mismatch from timing (.h from IncludeResolver before cppDetected,
295
351
  // .hpp from IncludeExtractor after cppDetected)
296
- const userIncludeSet = new Set(options.userIncludes ?? []);
297
- const extractStem = (inc: string): string => {
298
- const match = /["<]([^">]+)[">]/.exec(inc);
299
- if (!match) return inc;
300
- return match[1].replace(/^.*\//, "").replace(/\.(?:h|hpp)$/, "");
301
- };
302
- const userIncludeStems = new Set(
303
- (options.userIncludes ?? []).map(extractStem),
352
+ HeaderGeneratorUtils.addExternalTypeHeaders(
353
+ lines,
354
+ headersToInclude,
355
+ options.userIncludes,
304
356
  );
305
- for (const directive of headersToInclude) {
306
- if (userIncludeSet.has(directive)) {
307
- continue;
308
- }
309
- // Check if a user include already covers the same file
310
- const stem = extractStem(directive);
311
- if (stem && userIncludeStems.has(stem)) {
312
- continue;
313
- }
314
- lines.push(directive);
315
- }
316
357
 
317
358
  // Add blank line if any includes were added
318
359
  const hasIncludes =
@@ -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
 
@@ -363,7 +377,6 @@ export default class CodeGenState {
363
377
 
364
378
  // Pass-by-value analysis
365
379
  this.modifiedParameters = new Map();
366
- this.subscriptAccessedParameters = new Map();
367
380
  this.passByValueParams = new Map();
368
381
  this.functionCallGraph = new Map();
369
382
  this.functionParamLists = new Map();
@@ -770,15 +783,6 @@ export default class CodeGenState {
770
783
  return this.passByValueParams.get(funcName)?.has(paramName) ?? false;
771
784
  }
772
785
 
773
- /**
774
- * Check if a parameter has subscript access.
775
- */
776
- static hasSubscriptAccess(funcName: string, paramName: string): boolean {
777
- return (
778
- this.subscriptAccessedParameters.get(funcName)?.has(paramName) ?? false
779
- );
780
- }
781
-
782
786
  /**
783
787
  * Get the function signature for a function.
784
788
  */
@@ -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;
@@ -32,7 +32,7 @@ import ESourceLanguage from "../types/ESourceLanguage";
32
32
  const defaultFs = NodeFileSystem.instance;
33
33
 
34
34
  /** Current cache format version - increment when serialization format changes */
35
- const CACHE_VERSION = 7; // Issue #958: Add structTagAliases, structTagsWithBodies to cache
35
+ const CACHE_VERSION = 8; // Issue #985: Add preprocessFailed marker to cache
36
36
 
37
37
  const TRANSPILER_VERSION = packageJson.version;
38
38
 
@@ -138,6 +138,7 @@ class CacheManager {
138
138
  typedefStructTypes: Array<[string, string]>;
139
139
  structTagAliases: Array<[string, string]>;
140
140
  structTagsWithBodies: string[];
141
+ preprocessFailed: boolean;
141
142
  } | null {
142
143
  if (!this.cache) return null;
143
144
 
@@ -181,6 +182,7 @@ class CacheManager {
181
182
  typedefStructTypes: cachedEntry.typedefStructTypes ?? [],
182
183
  structTagAliases: cachedEntry.structTagAliases ?? [],
183
184
  structTagsWithBodies: cachedEntry.structTagsWithBodies ?? [],
185
+ preprocessFailed: cachedEntry.preprocessFailed ?? false,
184
186
  };
185
187
  }
186
188
 
@@ -199,6 +201,7 @@ class CacheManager {
199
201
  typedefStructTypes?: Array<[string, string]>;
200
202
  structTagAliases?: Array<[string, string]>;
201
203
  structTagsWithBodies?: string[];
204
+ preprocessFailed?: boolean;
202
205
  },
203
206
  ): void {
204
207
  if (!this.cache) return;
@@ -247,6 +250,7 @@ class CacheManager {
247
250
  typedefStructTypes: options?.typedefStructTypes,
248
251
  structTagAliases: options?.structTagAliases,
249
252
  structTagsWithBodies: options?.structTagsWithBodies,
253
+ preprocessFailed: options?.preprocessFailed,
250
254
  };
251
255
 
252
256
  this.cache.setKey(filePath, entry);
@@ -263,8 +267,14 @@ class CacheManager {
263
267
  *
264
268
  * @param filePath - Path to the file being cached
265
269
  * @param symbolTable - SymbolTable containing all parsed symbols
270
+ * @param preprocessFailed - Issue #985: header fell back to raw content, so its
271
+ * symbols are degraded and a warm-cache build must re-run recovery
266
272
  */
267
- setSymbolsFromTable(filePath: string, symbolTable: SymbolTable): void {
273
+ setSymbolsFromTable(
274
+ filePath: string,
275
+ symbolTable: SymbolTable,
276
+ preprocessFailed = false,
277
+ ): void {
268
278
  // ADR-055 Phase 7: Serialize TAnySymbol directly to ISerializedSymbol
269
279
  const typedSymbols = symbolTable.getSymbolsByFile(filePath);
270
280
  const symbols = typedSymbols.map((s) => this.serializeTypedSymbol(s));
@@ -302,6 +312,7 @@ class CacheManager {
302
312
  typedefStructTypes,
303
313
  structTagAliases,
304
314
  structTagsWithBodies,
315
+ preprocessFailed,
305
316
  });
306
317
  }
307
318
 
@@ -200,6 +200,23 @@ describe("CacheManager", () => {
200
200
  });
201
201
  });
202
202
 
203
+ it("defaults preprocessFailed to false and round-trips it when set", async () => {
204
+ const cleanFile = join(testDir, "clean.h");
205
+ const failedFile = join(testDir, "failed.h");
206
+ writeFileSync(cleanFile, "// test");
207
+ writeFileSync(failedFile, "// test");
208
+
209
+ // Issue #985: a header that fell back to raw content records that fact so
210
+ // a warm-cache build re-runs external-declaration recovery.
211
+ cacheManager.setSymbols(cleanFile, [], new Map());
212
+ cacheManager.setSymbols(failedFile, [], new Map(), {
213
+ preprocessFailed: true,
214
+ });
215
+
216
+ expect(cacheManager.getSymbols(cleanFile)!.preprocessFailed).toBe(false);
217
+ expect(cacheManager.getSymbols(failedFile)!.preprocessFailed).toBe(true);
218
+ });
219
+
203
220
  it("should preserve optional symbol fields", async () => {
204
221
  const testFile = join(testDir, "test.h");
205
222
  writeFileSync(testFile, "// test");
@@ -1494,7 +1511,7 @@ describe("CacheManager", () => {
1494
1511
  const content = mockFs.getWrittenContent("/project/.cnx/config.json");
1495
1512
  expect(content).toBeDefined();
1496
1513
  const newConfig = JSON.parse(content!);
1497
- expect(newConfig.version).toBe(7); // Current CACHE_VERSION (Issue #958 structTagAliases, structTagsWithBodies)
1514
+ expect(newConfig.version).toBe(8); // Current CACHE_VERSION (Issue #985 preprocessFailed marker)
1498
1515
  });
1499
1516
 
1500
1517
  it("should not cache files that do not exist in IFileSystem", async () => {
@@ -42,6 +42,19 @@ class TypeConstants {
42
42
  * - ArrayIndexTypeAnalyzer: detecting signed integer subscript indexes
43
43
  */
44
44
  static readonly SIGNED_TYPES: readonly string[] = ["i8", "i16", "i32", "i64"];
45
+
46
+ /**
47
+ * Unsigned integer types (excludes `bool`, unlike `UNSIGNED_INDEX_TYPES`).
48
+ *
49
+ * Used by:
50
+ * - MixedTypeCategoryAnalyzer: classifying an operand's essential type category
51
+ */
52
+ static readonly UNSIGNED_INT_TYPES: readonly string[] = [
53
+ "u8",
54
+ "u16",
55
+ "u32",
56
+ "u64",
57
+ ];
45
58
  }
46
59
 
47
60
  export default TypeConstants;