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
@@ -772,6 +772,55 @@ describe("SymbolTable", () => {
772
772
  expect(symbolTable.isOpaqueType("handle_t")).toBe(true);
773
773
  expect(symbolTable.getAllOpaqueTypes()).toHaveLength(2);
774
774
  });
775
+
776
+ it("clearStructTagHasBody restores opacity for a phantom body (#985)", () => {
777
+ symbolTable.markOpaqueType("obj_t");
778
+ symbolTable.registerStructTagAlias("_obj", "obj_t");
779
+ symbolTable.markStructTagHasBody("_obj"); // phantom body from a blob parse
780
+ expect(symbolTable.isOpaqueType("obj_t")).toBe(false);
781
+
782
+ symbolTable.clearStructTagHasBody("_obj");
783
+ expect(symbolTable.isOpaqueType("obj_t")).toBe(true);
784
+ expect(symbolTable.getAllStructTagsWithBodies()).not.toContain("_obj");
785
+ });
786
+
787
+ it("clearStructTagHasBody is a no-op when the tag has no body", () => {
788
+ // Must not throw or spuriously mutate when the tag was never marked.
789
+ expect(() => symbolTable.clearStructTagHasBody("_never")).not.toThrow();
790
+ expect(symbolTable.getAllStructTagsWithBodies()).not.toContain("_never");
791
+ });
792
+
793
+ it("getStructTagForTypedef returns the aliased tag or undefined", () => {
794
+ symbolTable.registerStructTagAlias("_lv_obj_t", "lv_obj_t");
795
+ expect(symbolTable.getStructTagForTypedef("lv_obj_t")).toBe("_lv_obj_t");
796
+ expect(symbolTable.getStructTagForTypedef("unknown_t")).toBeUndefined();
797
+ });
798
+ });
799
+
800
+ // ========================================================================
801
+ // External Declaration Names (Issue #985 macro recovery)
802
+ // ========================================================================
803
+
804
+ describe("External Declaration Names", () => {
805
+ it("registers and looks up recovered function-like macro names", () => {
806
+ expect(symbolTable.hasExternalDeclaration("pdMS_TO_TICKS")).toBe(false);
807
+ symbolTable.addExternalDeclarationNames(
808
+ new Set(["pdMS_TO_TICKS", "portTICK_PERIOD_MS"]),
809
+ );
810
+ expect(symbolTable.hasExternalDeclaration("pdMS_TO_TICKS")).toBe(true);
811
+ expect(symbolTable.hasExternalDeclaration("portTICK_PERIOD_MS")).toBe(
812
+ true,
813
+ );
814
+ expect(symbolTable.hasExternalDeclaration("not_recovered")).toBe(false);
815
+ });
816
+
817
+ it("accumulates across calls and tolerates an empty set", () => {
818
+ symbolTable.addExternalDeclarationNames(new Set(["A"]));
819
+ symbolTable.addExternalDeclarationNames(new Set());
820
+ symbolTable.addExternalDeclarationNames(new Set(["B"]));
821
+ expect(symbolTable.hasExternalDeclaration("A")).toBe(true);
822
+ expect(symbolTable.hasExternalDeclaration("B")).toBe(true);
823
+ });
775
824
  });
776
825
 
777
826
  // ========================================================================
@@ -0,0 +1,52 @@
1
+ /**
2
+ * MISRA Suppression Utilities
3
+ *
4
+ * Issue #850: Shared helpers for emitting MISRA inline suppression comments.
5
+ * Used by both CodeGenerator (for .c files) and HeaderGeneratorUtils (for .h files).
6
+ */
7
+
8
+ /**
9
+ * Headers that violate MISRA C:2012 rules and need inline suppression.
10
+ * Maps header name to the MISRA rule it violates.
11
+ */
12
+ const MISRA_BANNED_HEADERS: ReadonlyMap<string, string> = new Map([
13
+ // MISRA Rule 21.6: Standard library I/O functions shall not be used
14
+ ["stdio.h", "misra-c2012-21.6"],
15
+ ]);
16
+
17
+ /**
18
+ * Regex to extract header name from angle-bracket includes.
19
+ * Uses possessive matching via atomic group simulation to avoid backtracking.
20
+ * Matches: #include <header.h> -> captures "header.h"
21
+ */
22
+ const ANGLE_BRACKET_INCLUDE_REGEX = /<([^<>]+)>/;
23
+
24
+ /**
25
+ * Check if an include directive needs MISRA suppression.
26
+ * @param includeText The full include directive (e.g., "#include <stdio.h>")
27
+ * @returns true if suppression is needed
28
+ */
29
+ function needsMisraSuppression(includeText: string): boolean {
30
+ const match = ANGLE_BRACKET_INCLUDE_REGEX.exec(includeText);
31
+ if (!match) return false;
32
+ return MISRA_BANNED_HEADERS.has(match[1]);
33
+ }
34
+
35
+ /**
36
+ * Get the MISRA suppression comment for an include directive.
37
+ * @param includeText The full include directive (e.g., "#include <stdio.h>")
38
+ * @returns The suppression comment, or null if not needed
39
+ */
40
+ function getMisraSuppressionComment(includeText: string): string | null {
41
+ const match = ANGLE_BRACKET_INCLUDE_REGEX.exec(includeText);
42
+ if (!match) return null;
43
+ const rule = MISRA_BANNED_HEADERS.get(match[1]);
44
+ return rule ? `// cppcheck-suppress ${rule}` : null;
45
+ }
46
+
47
+ const MisraSuppressionUtils = {
48
+ needsMisraSuppression,
49
+ getMisraSuppressionComment,
50
+ };
51
+
52
+ export default MisraSuppressionUtils;
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import MisraSuppressionUtils from "../MisraSuppressionUtils";
3
+
4
+ describe("MisraSuppressionUtils", () => {
5
+ describe("needsMisraSuppression", () => {
6
+ it("returns true for stdio.h", () => {
7
+ expect(
8
+ MisraSuppressionUtils.needsMisraSuppression("#include <stdio.h>"),
9
+ ).toBe(true);
10
+ });
11
+
12
+ it("returns false for other system headers", () => {
13
+ expect(
14
+ MisraSuppressionUtils.needsMisraSuppression("#include <stdint.h>"),
15
+ ).toBe(false);
16
+ expect(
17
+ MisraSuppressionUtils.needsMisraSuppression("#include <string.h>"),
18
+ ).toBe(false);
19
+ });
20
+
21
+ it("returns false for quote includes", () => {
22
+ expect(
23
+ MisraSuppressionUtils.needsMisraSuppression('#include "stdio.h"'),
24
+ ).toBe(false);
25
+ });
26
+
27
+ it("returns false for non-include text", () => {
28
+ expect(MisraSuppressionUtils.needsMisraSuppression("void foo();")).toBe(
29
+ false,
30
+ );
31
+ });
32
+ });
33
+
34
+ describe("getMisraSuppressionComment", () => {
35
+ it("returns suppression comment for stdio.h", () => {
36
+ expect(
37
+ MisraSuppressionUtils.getMisraSuppressionComment("#include <stdio.h>"),
38
+ ).toBe("// cppcheck-suppress misra-c2012-21.6");
39
+ });
40
+
41
+ it("returns null for other system headers", () => {
42
+ expect(
43
+ MisraSuppressionUtils.getMisraSuppressionComment("#include <stdint.h>"),
44
+ ).toBeNull();
45
+ });
46
+
47
+ it("returns null for quote includes", () => {
48
+ expect(
49
+ MisraSuppressionUtils.getMisraSuppressionComment('#include "stdio.h"'),
50
+ ).toBeNull();
51
+ });
52
+
53
+ it("returns null for non-include text", () => {
54
+ expect(
55
+ MisraSuppressionUtils.getMisraSuppressionComment("void foo();"),
56
+ ).toBeNull();
57
+ });
58
+
59
+ it("handles whitespace in include directives", () => {
60
+ expect(
61
+ MisraSuppressionUtils.getMisraSuppressionComment(
62
+ "#include <stdio.h> ",
63
+ ),
64
+ ).toBe("// cppcheck-suppress misra-c2012-21.6");
65
+ });
66
+ });
67
+ });
@@ -148,6 +148,7 @@ import EnumTypeResolver from "./resolution/EnumTypeResolver";
148
148
  import ScopeResolver from "./resolution/ScopeResolver";
149
149
  // Issue #797: Centralized C-style name generation
150
150
  import QualifiedNameGenerator from "./utils/QualifiedNameGenerator";
151
+ import MisraSuppressionUtils from "../MisraSuppressionUtils";
151
152
 
152
153
  const {
153
154
  generateOverflowHelpers: helperGenerateOverflowHelpers,
@@ -299,6 +300,10 @@ export default class CodeGenerator implements IOrchestrator {
299
300
  controlFlowGenerators.generateDoWhile,
300
301
  );
301
302
  this.registry.registerStatement("for", controlFlowGenerators.generateFor);
303
+ this.registry.registerStatement(
304
+ "forever",
305
+ controlFlowGenerators.generateForever,
306
+ );
302
307
  this.registry.registerStatement("switch", switchGenerators.generateSwitch);
303
308
  this.registry.registerStatement("critical", generateCriticalStatement);
304
309
 
@@ -1057,6 +1062,8 @@ export default class CodeGenerator implements IOrchestrator {
1057
1062
  result = this.generateDoWhile(ctx.doWhileStatement()!);
1058
1063
  } else if (ctx.forStatement()) {
1059
1064
  result = this.generateFor(ctx.forStatement()!);
1065
+ } else if (ctx.foreverStatement()) {
1066
+ result = this.generateForever(ctx.foreverStatement()!);
1060
1067
  } else if (ctx.switchStatement()) {
1061
1068
  result = this.generateSwitch(ctx.switchStatement()!);
1062
1069
  } else if (ctx.returnStatement()) {
@@ -1122,6 +1129,14 @@ export default class CodeGenerator implements IOrchestrator {
1122
1129
  TypeValidator.validateConditionIsBoolean(ctx, conditionType);
1123
1130
  }
1124
1131
 
1132
+ /**
1133
+ * ADR-113 / #1075: reject an always-true literal loop condition (E0707).
1134
+ * Part of IOrchestrator interface.
1135
+ */
1136
+ validateLoopConditionNotAlwaysTrue(ctx: Parser.ExpressionContext): void {
1137
+ TypeValidator.validateLoopConditionNotAlwaysTrue(ctx);
1138
+ }
1139
+
1125
1140
  /**
1126
1141
  * Issue #254: Validate no function calls in condition (E0702).
1127
1142
  * Part of IOrchestrator interface (ADR-053 A3).
@@ -1164,8 +1179,6 @@ export default class CodeGenerator implements IOrchestrator {
1164
1179
 
1165
1180
  // Issue #779: Resolve bare scope member identifiers before postfix chain processing
1166
1181
  // This ensures scope members get their prefix even with array/member access.
1167
- // Skip parameters - they don't need scope resolution and shouldn't be dereferenced
1168
- // when used with array indexing (buf[idx] is valid C for pointer params).
1169
1182
  // Also skip known registers - they should be handled by the postfix chain builder
1170
1183
  // to enable proper register validation (requiring global. when shadowed).
1171
1184
  let resolvedIdentifier = identifier ?? "";
@@ -1174,7 +1187,24 @@ export default class CodeGenerator implements IOrchestrator {
1174
1187
  const isLocalVariable = CodeGenState.localVariables.has(identifier);
1175
1188
  const isKnownRegister =
1176
1189
  CodeGenState.symbols?.knownRegisters.has(identifier);
1177
- if (!isParameter && !isLocalVariable && !isKnownRegister) {
1190
+ // Issue #1100: Parameters with postfix ops (array/bit subscript, member
1191
+ // access) must resolve through the same dereference logic as a bare
1192
+ // parameter reference (ParameterDereferenceResolver), not skip it.
1193
+ // For array/struct/string/etc. parameters this is a no-op (they're
1194
+ // already pointer-like, matching the prior behavior verbatim — e.g.
1195
+ // `buf[idx]` stays `buf[idx]`). For a scalar parameter that became a
1196
+ // pointer because it's modified elsewhere in the function, a bit
1197
+ // access (`v[4] <- true`) now correctly dereferences to `(*v)[4]`
1198
+ // (which AssignmentContextBuilder reduces to base identifier `(*v)`)
1199
+ // instead of assigning through the raw pointer.
1200
+ if (isParameter) {
1201
+ const paramInfo = CodeGenState.currentParameters.get(identifier)!;
1202
+ resolvedIdentifier = ParameterDereferenceResolver.resolve(
1203
+ identifier,
1204
+ paramInfo,
1205
+ this._buildParameterDereferenceDeps(),
1206
+ );
1207
+ } else if (!isLocalVariable && !isKnownRegister) {
1178
1208
  const resolved = TypeValidator.resolveBareIdentifier(
1179
1209
  identifier,
1180
1210
  false, // not local
@@ -2362,7 +2392,14 @@ export default class CodeGenerator implements IOrchestrator {
2362
2392
  includePaths,
2363
2393
  );
2364
2394
 
2365
- output.push(this.transformIncludeDirective(includeDir.getText()));
2395
+ // Issue #850: Add MISRA suppression for banned headers
2396
+ const includeText = includeDir.getText();
2397
+ const suppression =
2398
+ MisraSuppressionUtils.getMisraSuppressionComment(includeText);
2399
+ if (suppression) {
2400
+ output.push(suppression);
2401
+ }
2402
+ output.push(this.transformIncludeDirective(includeText));
2366
2403
  }
2367
2404
 
2368
2405
  if (tree.includeDirective().length > 0) {
@@ -4519,6 +4556,10 @@ export default class CodeGenerator implements IOrchestrator {
4519
4556
  return this.invokeStatement("for", ctx);
4520
4557
  }
4521
4558
 
4559
+ private generateForever(ctx: Parser.ForeverStatementContext): string {
4560
+ return this.invokeStatement("forever", ctx);
4561
+ }
4562
+
4522
4563
  private generateReturn(ctx: Parser.ReturnStatementContext): string {
4523
4564
  return this.invokeStatement("return", ctx);
4524
4565
  }
@@ -2,6 +2,7 @@
2
2
  * TypeResolver - Handles type inference, classification, and validation
3
3
  * Static class that reads from CodeGenState directly.
4
4
  */
5
+ import { ParserRuleContext } from "antlr4ng";
5
6
  import * as Parser from "../../logic/parser/grammar/CNextParser";
6
7
  import CodeGenState from "../../state/CodeGenState";
7
8
  import INTEGER_TYPES from "./types/INTEGER_TYPES";
@@ -230,6 +231,153 @@ class TypeResolver {
230
231
  return null;
231
232
  }
232
233
 
234
+ /**
235
+ * Resolve the C-Next integer type of an expression, including composite
236
+ * arithmetic/bitwise expressions that `getExpressionType` leaves unresolved.
237
+ *
238
+ * MISRA C:2012 Rule 10.4 (enforced by MixedTypeCategoryAnalyzer) guarantees a
239
+ * binary operator's operands share an essential type category, so a composite
240
+ * integer expression's category is uniform; its essential width is the widest
241
+ * integer operand. This is what lets slice-assignment serialize an arithmetic
242
+ * source (e.g. `a + b`) MISRA Rule 10.8-clean instead of guessing a width.
243
+ *
244
+ * Returns null when no integer-typed variable leaf can be resolved (e.g. a
245
+ * struct-field or function-call composite — left for a later pass).
246
+ */
247
+ static getIntegerExpressionType(
248
+ ctx: Parser.ExpressionContext,
249
+ ): string | null {
250
+ const direct = TypeResolver.getExpressionType(ctx);
251
+ if (direct !== null) return direct;
252
+ return TypeResolver.resolveCompositeIntegerType(ctx);
253
+ }
254
+
255
+ /**
256
+ * Combine the leaf VALUE operands of a composite expression into a single
257
+ * C-Next type: the (uniform, per Rule 10.4) category at the widest width.
258
+ *
259
+ * Operands are typed by their value (Issue #1085 review) — an array index
260
+ * (`arr[i]`), bit offset (`x[off, w]`) or struct member name is NOT a value
261
+ * operand and must not contribute to the width. A bit-extraction contributes
262
+ * its EXTRACTED width, not the variable's full width (typing `a + b[0, 32]`
263
+ * as u64 would cast the composite to a wider type — MISRA Rule 10.8).
264
+ */
265
+ private static resolveCompositeIntegerType(
266
+ ctx: Parser.ExpressionContext,
267
+ ): string | null {
268
+ let category: "i" | "u" | null = null;
269
+ let width = 0;
270
+
271
+ for (const operand of TypeResolver.collectOperandPostfixes(ctx)) {
272
+ const operandType = TypeResolver.typeOperandPostfix(operand);
273
+ const match = operandType
274
+ ? /^([iu])(8|16|32|64)$/.exec(operandType)
275
+ : null;
276
+ if (!match) continue;
277
+ category ??= match[1] as "i" | "u";
278
+ width = Math.max(width, Number.parseInt(match[2], 10));
279
+ }
280
+
281
+ return category && width > 0 ? `${category}${width}` : null;
282
+ }
283
+
284
+ /**
285
+ * Type one leaf operand of a composite by its VALUE type. A bit-extraction
286
+ * `x[start, width]` yields an unsigned value of the extracted width; a simple
287
+ * function call `name(...)` yields its declared return type; everything else
288
+ * (variable, array element, struct field, member chain) defers to
289
+ * getPostfixExpressionType. Returns null for an operand it cannot classify
290
+ * (e.g. a literal, which is contextually typed).
291
+ */
292
+ private static typeOperandPostfix(
293
+ postfix: Parser.PostfixExpressionContext,
294
+ ): string | null {
295
+ const extractionWidth = TypeResolver.bitExtractionWidth(postfix);
296
+ if (extractionWidth !== null) {
297
+ return TypeResolver.unsignedTypeForBits(extractionWidth);
298
+ }
299
+
300
+ const direct = TypeResolver.getPostfixExpressionType(postfix);
301
+ if (direct !== null) return direct;
302
+
303
+ return TypeResolver.callReturnType(postfix);
304
+ }
305
+
306
+ /**
307
+ * Collect the leaf operand postfix expressions of a composite WITHOUT
308
+ * descending into a postfix's own internals — so an array index (`arr[i]`) or
309
+ * bit offset (`x[off, w]`) variable is never mistaken for a value operand.
310
+ */
311
+ private static collectOperandPostfixes(
312
+ node: ParserRuleContext,
313
+ ): Parser.PostfixExpressionContext[] {
314
+ if (node instanceof Parser.PostfixExpressionContext) return [node];
315
+ const operands: Parser.PostfixExpressionContext[] = [];
316
+ for (let i = 0; i < node.getChildCount(); i += 1) {
317
+ const child = node.getChild(i);
318
+ if (child instanceof ParserRuleContext) {
319
+ operands.push(...TypeResolver.collectOperandPostfixes(child));
320
+ }
321
+ }
322
+ return operands;
323
+ }
324
+
325
+ /**
326
+ * If a postfix expression's terminal suffix is a bit-range extraction
327
+ * `[start, width]` with a compile-time-constant width, return that width in
328
+ * bits; else null.
329
+ */
330
+ private static bitExtractionWidth(
331
+ postfix: Parser.PostfixExpressionContext,
332
+ ): number | null {
333
+ const ops = postfix.postfixOp();
334
+ const last = ops.at(-1);
335
+ if (last?.expression().length !== 2) return null;
336
+ const widthExpr = last.expression()[1];
337
+
338
+ // Resolve the width through the constant evaluator — the same path the slice
339
+ // offset/length use — so a named const or any-base literal width
340
+ // (`b[0, WIDTH]`, `b[0, 0b100000]`) is sized at its real width rather than
341
+ // dropped, which would mis-type a composite slice source (Issue #1085 review).
342
+ const evaluated = CodeGenState.generator?.tryEvaluateConstant(widthExpr);
343
+ if (evaluated !== undefined) {
344
+ return evaluated > 0 ? evaluated : null;
345
+ }
346
+
347
+ // Fallback for contexts with no generator (e.g. isolated unit tests): accept
348
+ // a plain decimal/hex literal width directly.
349
+ const widthText = widthExpr.getText();
350
+ if (!/^(0x[0-9a-fA-F]+|\d+)$/.test(widthText)) return null;
351
+ const value = Number.parseInt(
352
+ widthText,
353
+ widthText.startsWith("0x") ? 16 : 10,
354
+ );
355
+ return Number.isNaN(value) || value <= 0 ? null : value;
356
+ }
357
+
358
+ /** Smallest standard unsigned C-Next type holding `bits` bits, or null if >64. */
359
+ private static unsignedTypeForBits(bits: number): string | null {
360
+ if (bits <= 8) return "u8";
361
+ if (bits <= 16) return "u16";
362
+ if (bits <= 32) return "u32";
363
+ if (bits <= 64) return "u64";
364
+ return null;
365
+ }
366
+
367
+ /**
368
+ * If a postfix expression is a simple function call `name(...)`, return the
369
+ * function's declared return type — a call operand's width comes from its
370
+ * return type, not from being ignored (Issue #1085 review).
371
+ */
372
+ private static callReturnType(
373
+ postfix: Parser.PostfixExpressionContext,
374
+ ): string | null {
375
+ const ops = postfix.postfixOp();
376
+ if (ops.length !== 1 || !ops[0].getText().startsWith("(")) return null;
377
+ const name = postfix.primaryExpression()?.IDENTIFIER()?.getText();
378
+ return name ? (CodeGenState.getFunctionReturnType(name) ?? null) : null;
379
+ }
380
+
233
381
  /**
234
382
  * ADR-024: Get the type of a postfix expression.
235
383
  * Tracks InternalTypeInfo (baseType + isArray) through the suffix chain