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
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Unit tests for MixedTypeCategoryAnalyzer
3
+ * Tests detection of binary operators combining mixed essential type categories
4
+ * (MISRA C:2012 Rule 10.4, ADR-024 / Issue #1091).
5
+ */
6
+ import { describe, it, expect } from "vitest";
7
+ import { CharStream, CommonTokenStream } from "antlr4ng";
8
+ import { CNextLexer } from "../../parser/grammar/CNextLexer";
9
+ import { CNextParser } from "../../parser/grammar/CNextParser";
10
+ import MixedTypeCategoryAnalyzer from "../MixedTypeCategoryAnalyzer";
11
+
12
+ function parse(source: string) {
13
+ const charStream = CharStream.fromString(source);
14
+ const lexer = new CNextLexer(charStream);
15
+ const tokenStream = new CommonTokenStream(lexer);
16
+ const parser = new CNextParser(tokenStream);
17
+ return parser.program();
18
+ }
19
+
20
+ function analyze(source: string) {
21
+ return new MixedTypeCategoryAnalyzer().analyze(parse(source));
22
+ }
23
+
24
+ describe("MixedTypeCategoryAnalyzer", () => {
25
+ describe("mixed-category operands (rejected)", () => {
26
+ it("rejects unsigned + signed (u32 + i32)", () => {
27
+ const errors = analyze(`
28
+ void main() {
29
+ u32 a <- 1;
30
+ i32 b <- 2;
31
+ u32 c <- a + b;
32
+ }
33
+ `);
34
+ expect(errors).toHaveLength(1);
35
+ expect(errors[0].code).toBe("E0810");
36
+ expect(errors[0].message).toContain("essential type categories");
37
+ });
38
+
39
+ it("rejects signed + unsigned (i32 + u32), order-independent", () => {
40
+ const errors = analyze(`
41
+ void main() {
42
+ i32 a <- 1;
43
+ u32 b <- 2;
44
+ i32 c <- a + b;
45
+ }
46
+ `);
47
+ expect(errors).toHaveLength(1);
48
+ });
49
+
50
+ it("rejects a mixed comparison (u32 = i32)", () => {
51
+ const errors = analyze(`
52
+ void main() {
53
+ u32 a <- 1;
54
+ i32 b <- 2;
55
+ bool c <- (a = b);
56
+ }
57
+ `);
58
+ expect(errors).toHaveLength(1);
59
+ });
60
+
61
+ it("rejects a mixed multiplication (u16 * i16)", () => {
62
+ const errors = analyze(`
63
+ void main() {
64
+ u16 a <- 1;
65
+ i16 b <- 2;
66
+ u16 c <- a * b;
67
+ }
68
+ `);
69
+ expect(errors).toHaveLength(1);
70
+ });
71
+
72
+ it("rejects a mixed bitwise-or (u8 | i8)", () => {
73
+ const errors = analyze(`
74
+ void main() {
75
+ u8 a <- 1;
76
+ i8 b <- 2;
77
+ u8 c <- a | b;
78
+ }
79
+ `);
80
+ expect(errors).toHaveLength(1);
81
+ });
82
+
83
+ it("rejects a mixed operand reached through parentheses and negation", () => {
84
+ const errors = analyze(`
85
+ void main() {
86
+ u32 a <- 1;
87
+ i32 b <- 2;
88
+ u32 c <- a + (-b);
89
+ }
90
+ `);
91
+ expect(errors).toHaveLength(1);
92
+ });
93
+
94
+ it("reports the differing pair in a chain (u32 + u32 + i32)", () => {
95
+ const errors = analyze(`
96
+ void main() {
97
+ u32 a <- 1;
98
+ u32 b <- 2;
99
+ i32 c <- 3;
100
+ u32 d <- a + b + c;
101
+ }
102
+ `);
103
+ expect(errors).toHaveLength(1);
104
+ });
105
+ });
106
+
107
+ describe("same-category and exempt operands (accepted)", () => {
108
+ it("accepts same category (u32 + u32)", () => {
109
+ const errors = analyze(`
110
+ void main() {
111
+ u32 a <- 1;
112
+ u32 b <- 2;
113
+ u32 c <- a + b;
114
+ }
115
+ `);
116
+ expect(errors).toHaveLength(0);
117
+ });
118
+
119
+ it("accepts same category, different width (u8 + u32 widening)", () => {
120
+ const errors = analyze(`
121
+ void main() {
122
+ u8 a <- 1;
123
+ u32 b <- 2;
124
+ u32 c <- a + b;
125
+ }
126
+ `);
127
+ expect(errors).toHaveLength(0);
128
+ });
129
+
130
+ it("accepts an unsigned variable plus an integer literal", () => {
131
+ const errors = analyze(`
132
+ void main() {
133
+ u32 a <- 1;
134
+ u32 c <- a + 5;
135
+ }
136
+ `);
137
+ expect(errors).toHaveLength(0);
138
+ });
139
+
140
+ it("accepts the sanctioned bit-indexed reinterpretation (a + b[0, 32])", () => {
141
+ const errors = analyze(`
142
+ void main() {
143
+ u32 a <- 1;
144
+ i32 b <- 2;
145
+ u32 c <- a + b[0, 32];
146
+ }
147
+ `);
148
+ expect(errors).toHaveLength(0);
149
+ });
150
+
151
+ it("accepts a single operand (no binary operator)", () => {
152
+ const errors = analyze(`
153
+ void main() {
154
+ i32 b <- 2;
155
+ i32 c <- b;
156
+ }
157
+ `);
158
+ expect(errors).toHaveLength(0);
159
+ });
160
+ });
161
+
162
+ describe("scope-aware variable typing (Issue #1085 review, Finding A)", () => {
163
+ it("does not flag a valid same-category expression because a same-named variable of a different category exists in another function", () => {
164
+ // main's `value` is u32; other's parameter `value` is i32. A flat,
165
+ // file-wide name->type map (last write wins) made `base + value` in main
166
+ // resolve `value` as i32 and falsely reject valid u32 + u32 code.
167
+ const errors = analyze(`
168
+ u32 main() {
169
+ u32 base <- 1;
170
+ u32 value <- 2;
171
+ u32 result <- base + value;
172
+ return 0;
173
+ }
174
+ i32 other(i32 value) {
175
+ return value;
176
+ }
177
+ `);
178
+ expect(errors).toHaveLength(0);
179
+ });
180
+
181
+ it("flags the genuinely mixed expression in its own function, not masked by a same-named variable elsewhere", () => {
182
+ // main's `value + delta` is u32 + i32 (mixed -> 1 error). other's
183
+ // `value + value` is i32 + i32 (same category -> no error). A flat map
184
+ // would mis-type main's `value` as i32 and MISS the real violation.
185
+ const errors = analyze(`
186
+ u32 main() {
187
+ u32 value <- 1;
188
+ i32 delta <- 2;
189
+ u32 r <- value + delta;
190
+ return 0;
191
+ }
192
+ void other(i32 value) {
193
+ i32 x <- value + value;
194
+ }
195
+ `);
196
+ expect(errors).toHaveLength(1);
197
+ });
198
+
199
+ it("does not flag !a = !b — logical negation yields Boolean, not the operand category (Issue #1085)", () => {
200
+ // `!a` and `!b` are both essentially Boolean regardless of a/b signedness,
201
+ // so the comparison shares a category and Rule 10.4 must NOT fire. The old
202
+ // code recursed through `!` and compared a's (unsigned) vs b's (signed)
203
+ // category, falsely rejecting valid code.
204
+ const errors = analyze(`
205
+ void main() {
206
+ u32 a <- 5;
207
+ i32 b <- 3;
208
+ bool r <- !a = !b;
209
+ }
210
+ `);
211
+ expect(errors).toHaveLength(0);
212
+ });
213
+
214
+ it("still flags a + b when operands differ in signedness (control for the ! fix)", () => {
215
+ const errors = analyze(`
216
+ void main() {
217
+ u32 a <- 5;
218
+ i32 b <- 3;
219
+ i32 r <- a + b;
220
+ }
221
+ `);
222
+ expect(errors).toHaveLength(1);
223
+ });
224
+
225
+ it("isolates variables declared in different named scopes", () => {
226
+ // Each scope's `count` has its own type; neither scope's expression is mixed.
227
+ const errors = analyze(`
228
+ scope A {
229
+ u32 count <- 1;
230
+ u32 total <- count + count;
231
+ }
232
+ scope B {
233
+ i32 count <- 1;
234
+ i32 total <- count + count;
235
+ }
236
+ `);
237
+ expect(errors).toHaveLength(0);
238
+ });
239
+ });
240
+
241
+ describe("shift operators (Rule 10.4 does not govern shifts)", () => {
242
+ it("does not flag a left shift whose count is signed (u32 << i32)", () => {
243
+ // Rule 10.4 only governs operators subject to the usual arithmetic
244
+ // conversions; shifts are not (the count is promoted independently). A
245
+ // signed shift COUNT is a Rule 10.1 concern, not 10.4, so E0810 must not
246
+ // fire here (Issue #1085 review).
247
+ const errors = analyze(`
248
+ void main() {
249
+ u32 value <- 256;
250
+ i32 count <- 2;
251
+ u32 r <- value << count;
252
+ }
253
+ `);
254
+ expect(errors).toHaveLength(0);
255
+ });
256
+
257
+ it("does not flag a right shift whose count is signed (u32 >> i32)", () => {
258
+ const errors = analyze(`
259
+ void main() {
260
+ u32 value <- 256;
261
+ i32 count <- 2;
262
+ u32 r <- value >> count;
263
+ }
264
+ `);
265
+ expect(errors).toHaveLength(0);
266
+ });
267
+ });
268
+
269
+ describe("block-aware variable typing (Issue #1085 review)", () => {
270
+ it("does not flag an outer expression when a different-category variable of the same name is declared in a nested block", () => {
271
+ // The inner `i32 x` shadows only within the if-block. The outer `x + a`
272
+ // (both u32) must not be poisoned by it. A function-wide last-write-wins
273
+ // map mis-typed the outer `x` as i32 and falsely rejected valid code.
274
+ const errors = analyze(`
275
+ void main() {
276
+ u32 x <- 1;
277
+ u32 a <- 2;
278
+ u32 r <- x + a;
279
+ if (r > 0) {
280
+ i32 x <- 5;
281
+ i32 z <- x + x;
282
+ }
283
+ }
284
+ `);
285
+ expect(errors).toHaveLength(0);
286
+ });
287
+
288
+ it("does not flag an outer expression poisoned by a different-category for-loop variable", () => {
289
+ const errors = analyze(`
290
+ void main() {
291
+ u32 i <- 1;
292
+ u32 a <- 2;
293
+ u32 r <- i + a;
294
+ for (i32 i <- 0; i < 4; i <- i + 1) {
295
+ a <- a + 1;
296
+ }
297
+ }
298
+ `);
299
+ expect(errors).toHaveLength(0);
300
+ });
301
+ });
302
+
303
+ describe("no cascade of duplicate errors (Issue #1085 review)", () => {
304
+ it("reports a mixed nested expression once, not again at the enclosing level", () => {
305
+ // `a * b` is i32 * u32 (one violation). Representing the product by its
306
+ // leftmost operand (`a`, signed) let the `+ c` level re-report it against
307
+ // `c` (unsigned) — a second, misleading error. The product's category is
308
+ // ambiguous, so the enclosing level must not re-flag it.
309
+ const errors = analyze(`
310
+ void main() {
311
+ i32 a <- 1;
312
+ u32 b <- 2;
313
+ u32 c <- 3;
314
+ u32 r <- a * b + c;
315
+ }
316
+ `);
317
+ expect(errors).toHaveLength(1);
318
+ });
319
+
320
+ it("reports a mix inside parentheses once, not again at the outer operator", () => {
321
+ const errors = analyze(`
322
+ void main() {
323
+ i32 a <- 1;
324
+ u32 b <- 2;
325
+ u32 c <- 3;
326
+ u32 r <- (a + b) + c;
327
+ }
328
+ `);
329
+ expect(errors).toHaveLength(1);
330
+ });
331
+
332
+ it("still flags a uniform compound operand mixed against an outer operand", () => {
333
+ // (a + b) is u32 + u32 (uniform unsigned); combining it with the signed `c`
334
+ // is a real Rule 10.4 violation that must still be caught at the outer `+`.
335
+ const errors = analyze(`
336
+ void main() {
337
+ u32 a <- 1;
338
+ u32 b <- 2;
339
+ i32 c <- 3;
340
+ i32 r <- (a + b) + c;
341
+ }
342
+ `);
343
+ expect(errors).toHaveLength(1);
344
+ });
345
+ });
346
+ });
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Unit tests for ReturnPathAnalyzer
3
+ * ADR-112 / Issue #1040: non-void functions must return a value on all paths.
4
+ */
5
+ import { describe, it, expect } from "vitest";
6
+ import { CharStream, CommonTokenStream } from "antlr4ng";
7
+ import { CNextLexer } from "../../parser/grammar/CNextLexer";
8
+ import { CNextParser } from "../../parser/grammar/CNextParser";
9
+ import ReturnPathAnalyzer from "../ReturnPathAnalyzer";
10
+
11
+ function parse(source: string) {
12
+ const charStream = CharStream.fromString(source);
13
+ const lexer = new CNextLexer(charStream);
14
+ const tokenStream = new CommonTokenStream(lexer);
15
+ const parser = new CNextParser(tokenStream);
16
+ return parser.program();
17
+ }
18
+
19
+ function analyze(source: string) {
20
+ return new ReturnPathAnalyzer().analyze(parse(source));
21
+ }
22
+
23
+ describe("ReturnPathAnalyzer", () => {
24
+ describe("flags non-void functions that can fall through (E0704)", () => {
25
+ it("flags a function with no return at all", () => {
26
+ const errors = analyze(`
27
+ u8 getValue() {
28
+ u8 x <- 123;
29
+ }
30
+ `);
31
+ expect(errors).toHaveLength(1);
32
+ expect(errors[0].code).toBe("E0704");
33
+ expect(errors[0].functionName).toBe("getValue");
34
+ });
35
+
36
+ it("flags an if without else", () => {
37
+ const errors = analyze(`
38
+ u8 f(bool ready) {
39
+ if (ready = true) { return 1; }
40
+ }
41
+ `);
42
+ expect(errors).toHaveLength(1);
43
+ });
44
+
45
+ it("flags an if/else where one branch does not return", () => {
46
+ const errors = analyze(`
47
+ u8 f(bool ready) {
48
+ if (ready = true) { return 1; } else { u8 y <- 0; }
49
+ }
50
+ `);
51
+ expect(errors).toHaveLength(1);
52
+ });
53
+
54
+ it("flags a switch without a default", () => {
55
+ const errors = analyze(`
56
+ u8 f(u8 code) {
57
+ switch (code) {
58
+ case 1 { return 10; }
59
+ case 2 { return 20; }
60
+ }
61
+ }
62
+ `);
63
+ expect(errors).toHaveLength(1);
64
+ });
65
+
66
+ it("flags a switch where a case does not return", () => {
67
+ const errors = analyze(`
68
+ u8 f(u8 code) {
69
+ switch (code) {
70
+ case 1 { return 10; }
71
+ default { u8 z <- 0; }
72
+ }
73
+ }
74
+ `);
75
+ expect(errors).toHaveLength(1);
76
+ });
77
+
78
+ it("flags a while loop body (may not execute)", () => {
79
+ const errors = analyze(`
80
+ u8 f(u8 start) {
81
+ while (start > 0) { return start; }
82
+ }
83
+ `);
84
+ expect(errors).toHaveLength(1);
85
+ });
86
+
87
+ it("flags a for loop body (may not execute)", () => {
88
+ const errors = analyze(`
89
+ u8 f() {
90
+ for (u8 i <- 0; i < 3; i +<- 1) { return i; }
91
+ }
92
+ `);
93
+ expect(errors).toHaveLength(1);
94
+ });
95
+
96
+ it("flags a bare `return;` in a non-void function", () => {
97
+ const errors = analyze(`
98
+ u8 f(bool done) {
99
+ if (done = true) { return 5; }
100
+ return;
101
+ }
102
+ `);
103
+ expect(errors).toHaveLength(1);
104
+ });
105
+ });
106
+
107
+ describe("accepts functions that return on all paths", () => {
108
+ it("accepts a simple return", () => {
109
+ expect(
110
+ analyze(`
111
+ u8 f() { return 1; }
112
+ `),
113
+ ).toHaveLength(0);
114
+ });
115
+
116
+ it("accepts if/else where both branches return", () => {
117
+ expect(
118
+ analyze(`
119
+ u8 f(bool ready) {
120
+ if (ready = true) { return 1; } else { return 0; }
121
+ }
122
+ `),
123
+ ).toHaveLength(0);
124
+ });
125
+
126
+ it("accepts an else-if chain that returns on every branch", () => {
127
+ expect(
128
+ analyze(`
129
+ u8 f(u8 n) {
130
+ if (n = 0) { return 1; }
131
+ else if (n = 1) { return 2; }
132
+ else { return 3; }
133
+ }
134
+ `),
135
+ ).toHaveLength(0);
136
+ });
137
+
138
+ it("accepts a switch with default where all paths return", () => {
139
+ expect(
140
+ analyze(`
141
+ u8 f(u8 code) {
142
+ switch (code) {
143
+ case 1 { return 10; }
144
+ case 2 { return 20; }
145
+ default { return 0; }
146
+ }
147
+ }
148
+ `),
149
+ ).toHaveLength(0);
150
+ });
151
+
152
+ it("accepts a do-while whose body unconditionally returns", () => {
153
+ expect(
154
+ analyze(`
155
+ u8 f() {
156
+ do { return 7; } while (1 = 1);
157
+ }
158
+ `),
159
+ ).toHaveLength(0);
160
+ });
161
+
162
+ it("accepts a loop followed by a trailing return (the canonical E0704 fix)", () => {
163
+ // The loop never counts as a guaranteed return; the trailing return does.
164
+ // This is the shape users will most often write to satisfy E0704, and it
165
+ // guards against any future drift toward 'last statement must return'.
166
+ expect(
167
+ analyze(`
168
+ u8 firstPositive(u8 n) {
169
+ while (n > 0) { return n; }
170
+ return 0;
171
+ }
172
+ `),
173
+ ).toHaveLength(0);
174
+ });
175
+
176
+ it("accepts a return after other statements (later statements unreachable)", () => {
177
+ expect(
178
+ analyze(`
179
+ u8 f() {
180
+ u8 x <- 1;
181
+ return x;
182
+ }
183
+ `),
184
+ ).toHaveLength(0);
185
+ });
186
+
187
+ it("accepts a return nested in a critical block", () => {
188
+ expect(
189
+ analyze(`
190
+ u8 f() {
191
+ critical { return 1; }
192
+ }
193
+ `),
194
+ ).toHaveLength(0);
195
+ });
196
+
197
+ it("does not flag a void function with no return", () => {
198
+ expect(
199
+ analyze(`
200
+ void f() { u8 x <- 1; }
201
+ `),
202
+ ).toHaveLength(0);
203
+ });
204
+ });
205
+
206
+ describe("treats `forever` as a divergent terminal path (ADR-113, the primitive #849 consumes)", () => {
207
+ // A `forever` loop never exits (C-Next has no break/continue, ADR-026), so a
208
+ // function whose terminal path is `forever` never falls through and must not
209
+ // be flagged E0704. (In real programs `forever` is void-only via E0705 in
210
+ // codegen; this is the shared divergence primitive ADR-114 reuses.)
211
+ it("does not flag a function whose only path is a forever loop", () => {
212
+ expect(
213
+ analyze(`
214
+ u8 f() {
215
+ forever { u8 x <- 1; }
216
+ }
217
+ `),
218
+ ).toHaveLength(0);
219
+ });
220
+
221
+ it("does not flag a forever loop after a non-returning if", () => {
222
+ expect(
223
+ analyze(`
224
+ u8 f(bool ready) {
225
+ if (ready = true) { return 1; }
226
+ forever { u8 y <- 0; }
227
+ }
228
+ `),
229
+ ).toHaveLength(0);
230
+ });
231
+ });
232
+ });