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
@@ -14,7 +14,12 @@
14
14
  * 1. It's a small primitive type (u8, i8, u16, i16, u32, i32, u64, i64, bool)
15
15
  * 2. It's not modified (directly or transitively)
16
16
  * 3. It's not an array, struct, string, or callback
17
- * 4. It's not accessed via subscript (Issue #579)
17
+ *
18
+ * Issue #1100: Subscript access no longer forces pointer semantics on its
19
+ * own. A scalar parameter subscripted with a single index is bit-indexing
20
+ * (ADR-007), not array access, so it stays eligible for pass-by-value.
21
+ * Only genuine array parameters (`isArray`, from explicit `T[N]` syntax,
22
+ * ADR-006) are excluded — via the isArray check below.
18
23
  */
19
24
 
20
25
  import * as Parser from "../parser/grammar/CNextParser";
@@ -173,8 +178,6 @@ class PassByValueAnalyzer {
173
178
 
174
179
  // Initialize modified set
175
180
  CodeGenState.modifiedParameters.set(funcName, new Set());
176
- // Issue #579: Initialize subscript access tracking
177
- CodeGenState.subscriptAccessedParameters.set(funcName, new Set());
178
181
  CodeGenState.functionCallGraph.set(funcName, []);
179
182
 
180
183
  // Walk the function body to find modifications and calls
@@ -225,15 +228,9 @@ class PassByValueAnalyzer {
225
228
  );
226
229
  }
227
230
 
228
- // 2. Walk all expressions in this statement for function calls and subscript access
231
+ // 2. Walk all expressions in this statement for function calls
229
232
  for (const expr of StatementExpressionCollector.collectAll(stmt)) {
230
233
  PassByValueAnalyzer.walkExpressionForCalls(funcName, paramSet, expr);
231
- // Issue #579: Also track subscript read access on parameters
232
- PassByValueAnalyzer.walkExpressionForSubscriptAccess(
233
- funcName,
234
- paramSet,
235
- expr,
236
- );
237
234
  }
238
235
 
239
236
  // 3. Recurse into child statements and blocks
@@ -266,21 +263,11 @@ class PassByValueAnalyzer {
266
263
  const assign = stmt.assignmentStatement()!;
267
264
  const target = assign.assignmentTarget();
268
265
 
269
- const { baseIdentifier, hasSingleIndexSubscript } =
270
- AssignmentTargetExtractor.extract(target);
271
-
272
- // Issue #579: Track subscript access on parameters (for write path)
273
- if (
274
- hasSingleIndexSubscript &&
275
- baseIdentifier &&
276
- paramSet.has(baseIdentifier)
277
- ) {
278
- CodeGenState.subscriptAccessedParameters
279
- .get(funcName)!
280
- .add(baseIdentifier);
281
- }
266
+ const { baseIdentifier } = AssignmentTargetExtractor.extract(target);
282
267
 
283
- // Track as modified parameter
268
+ // Track as modified parameter (covers both `x <- value` and subscripted
269
+ // writes like `x[i] <- value` / `x[4] <- true` — both change x's value,
270
+ // so x must pass by pointer for the caller to observe the change)
284
271
  if (baseIdentifier && paramSet.has(baseIdentifier)) {
285
272
  CodeGenState.modifiedParameters.get(funcName)!.add(baseIdentifier);
286
273
  }
@@ -309,67 +296,9 @@ class PassByValueAnalyzer {
309
296
  }
310
297
  }
311
298
 
312
- /**
313
- * Issue #579: Walk an expression tree to find subscript access on parameters.
314
- * This tracks read access like `buf[i]` where buf is a parameter.
315
- * Parameters with subscript access must become pointers.
316
- */
317
- private static walkExpressionForSubscriptAccess(
318
- funcName: string,
319
- paramSet: Set<string>,
320
- expr: Parser.ExpressionContext,
321
- ): void {
322
- const ternary = expr.ternaryExpression();
323
- if (ternary) {
324
- for (const orExpr of ternary.orExpression()) {
325
- PassByValueAnalyzer.walkOrExpression(orExpr, (unaryExpr) => {
326
- PassByValueAnalyzer.handleSubscriptAccess(
327
- funcName,
328
- paramSet,
329
- unaryExpr,
330
- );
331
- });
332
- }
333
- }
334
- }
335
-
336
- /**
337
- * Issue #579: Handle subscript access on a unary expression.
338
- * Only tracks single-index subscript access (which could be array access).
339
- * Two-index subscript (e.g., value[start, width]) is always bit extraction,
340
- * so it doesn't require the parameter to become a pointer.
341
- */
342
- private static handleSubscriptAccess(
343
- funcName: string,
344
- paramSet: Set<string>,
345
- unaryExpr: Parser.UnaryExpressionContext,
346
- ): void {
347
- const postfixExpr = unaryExpr.postfixExpression();
348
- if (!postfixExpr) return;
349
-
350
- const primary = postfixExpr.primaryExpression();
351
- const ops = postfixExpr.postfixOp();
352
-
353
- // Check if primary is a parameter and there's subscript access
354
- const primaryId = primary.IDENTIFIER()?.getText();
355
- if (!primaryId || !paramSet.has(primaryId)) {
356
- return;
357
- }
358
-
359
- // Only track SINGLE-index subscript access (potential array access)
360
- // Two-index subscript like value[0, 8] is bit extraction, not array access
361
- const hasSingleIndexSubscript = ops.some(
362
- (op) => op.expression().length === 1,
363
- );
364
- if (hasSingleIndexSubscript) {
365
- CodeGenState.subscriptAccessedParameters.get(funcName)!.add(primaryId);
366
- }
367
- }
368
-
369
299
  /**
370
300
  * Generic walker for orExpression trees.
371
301
  * Walks through the expression hierarchy and calls the handler for each unaryExpression.
372
- * Used by both function call tracking and subscript access tracking.
373
302
  */
374
303
  private static walkOrExpression(
375
304
  orExpr: Parser.OrExpressionContext,
@@ -671,24 +600,14 @@ class PassByValueAnalyzer {
671
600
 
672
601
  // Check if eligible for pass-by-value:
673
602
  // - Is a small primitive type
674
- // - Not an array
675
- // - Not modified
676
- // - Not accessed via subscript (Issue #579)
603
+ // - Not an array (array parameters always decay to pointers, ADR-006)
604
+ // - Not modified (a subscripted bit-write, e.g. `x[4] <- true`, counts
605
+ // as a modification and is already tracked in `modified` above)
677
606
  const isSmallPrimitive = SMALL_PRIMITIVES.has(paramSig.baseType);
678
607
  const isArray = paramSig.isArray ?? false;
679
608
  const isModified = modified.has(paramName);
680
- // Issue #579: Parameters with subscript access must become pointers
681
- const hasSubscriptAccess =
682
- CodeGenState.subscriptAccessedParameters
683
- .get(funcName)
684
- ?.has(paramName) ?? false;
685
-
686
- if (
687
- isSmallPrimitive &&
688
- !isArray &&
689
- !isModified &&
690
- !hasSubscriptAccess
691
- ) {
609
+
610
+ if (isSmallPrimitive && !isArray && !isModified) {
692
611
  passByValue.add(paramName);
693
612
  }
694
613
  }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Return-Path Analyzer
3
+ * ADR-112 / Issue #1040: reject a non-void function that can reach the end of
4
+ * its body without returning a value (undefined behavior in C).
5
+ *
6
+ * The analysis is intentionally strict and conservative (sound): it never
7
+ * accepts a function that might fall through, and it does not attempt to prove
8
+ * that loops are infinite or that a switch over an enum is exhaustive. Where it
9
+ * cannot prove a return, an explicit `return <value>` is required.
10
+ *
11
+ * C-Next has no break/continue (ADR-026), so loop bodies have no early
12
+ * structural exits to reason about.
13
+ */
14
+
15
+ import { ParseTreeWalker } from "antlr4ng";
16
+ import { CNextListener } from "../parser/grammar/CNextListener";
17
+ import * as Parser from "../parser/grammar/CNextParser";
18
+ import IReturnPathError from "./types/IReturnPathError";
19
+
20
+ /**
21
+ * Does executing this statement guarantee that the enclosing function returns a
22
+ * value before control passes beyond it?
23
+ */
24
+ function statementDefinitelyReturns(ctx: Parser.StatementContext): boolean {
25
+ const returnStmt = ctx.returnStatement();
26
+ if (returnStmt) {
27
+ // A bare `return;` (no expression) returns no value, so it does not satisfy
28
+ // a non-void function.
29
+ return returnStmt.expression() !== null;
30
+ }
31
+
32
+ const ifStmt = ctx.ifStatement();
33
+ if (ifStmt) {
34
+ return ifDefinitelyReturns(ifStmt);
35
+ }
36
+
37
+ const switchStmt = ctx.switchStatement();
38
+ if (switchStmt) {
39
+ return switchDefinitelyReturns(switchStmt);
40
+ }
41
+
42
+ const doWhileStmt = ctx.doWhileStatement();
43
+ if (doWhileStmt) {
44
+ // The body always executes at least once.
45
+ return blockDefinitelyReturns(doWhileStmt.block());
46
+ }
47
+
48
+ if (ctx.foreverStatement()) {
49
+ // ADR-113: a `forever` loop is divergent — C-Next has no break/continue
50
+ // (ADR-026), so control never passes beyond it. It is therefore a terminal
51
+ // path, like an unconditional return, and the function never falls through
52
+ // here. This is the shared "divergence" primitive ADR-114 (#849) reuses.
53
+ //
54
+ // `forever` is void-only (E0705, enforced in codegen). Marking it terminal
55
+ // here keeps a non-void function containing a `forever` loop from emitting a
56
+ // misleading E0704 ("must return a value") instead of the precise E0705.
57
+ return true;
58
+ }
59
+
60
+ const block = ctx.block();
61
+ if (block) {
62
+ return blockDefinitelyReturns(block);
63
+ }
64
+
65
+ const criticalStmt = ctx.criticalStatement();
66
+ if (criticalStmt) {
67
+ return blockDefinitelyReturns(criticalStmt.block());
68
+ }
69
+
70
+ // while / for (the body may not execute), variable declarations, assignments,
71
+ // and expression statements never guarantee a return on their own.
72
+ return false;
73
+ }
74
+
75
+ /**
76
+ * A block guarantees a return iff any of its statements does: statements after
77
+ * an unconditional return are unreachable.
78
+ */
79
+ function blockDefinitelyReturns(ctx: Parser.BlockContext): boolean {
80
+ return ctx.statement().some(statementDefinitelyReturns);
81
+ }
82
+
83
+ /**
84
+ * An if guarantees a return only when an `else` is present and both branches
85
+ * guarantee a return. `else if` chains recurse through the else branch.
86
+ */
87
+ function ifDefinitelyReturns(ctx: Parser.IfStatementContext): boolean {
88
+ const branches = ctx.statement();
89
+ if (branches.length < 2) {
90
+ return false;
91
+ }
92
+ return (
93
+ statementDefinitelyReturns(branches[0]) &&
94
+ statementDefinitelyReturns(branches[1])
95
+ );
96
+ }
97
+
98
+ /**
99
+ * A switch guarantees a return only when a `default` is present and every case
100
+ * block and the default block guarantee a return.
101
+ */
102
+ function switchDefinitelyReturns(ctx: Parser.SwitchStatementContext): boolean {
103
+ const defaultCase = ctx.defaultCase();
104
+ if (!defaultCase) {
105
+ return false;
106
+ }
107
+ const everyCaseReturns = ctx
108
+ .switchCase()
109
+ .every((switchCase) => blockDefinitelyReturns(switchCase.block()));
110
+ return everyCaseReturns && blockDefinitelyReturns(defaultCase.block());
111
+ }
112
+
113
+ /**
114
+ * Listener that flags non-void functions whose body can fall through.
115
+ */
116
+ class ReturnPathListener extends CNextListener {
117
+ private readonly analyzer: ReturnPathAnalyzer;
118
+
119
+ constructor(analyzer: ReturnPathAnalyzer) {
120
+ super();
121
+ this.analyzer = analyzer;
122
+ }
123
+
124
+ override enterFunctionDeclaration = (
125
+ ctx: Parser.FunctionDeclarationContext,
126
+ ): void => {
127
+ // Void functions are allowed to fall off the end.
128
+ if (ctx.type().getText() === "void") {
129
+ return;
130
+ }
131
+
132
+ if (blockDefinitelyReturns(ctx.block())) {
133
+ return;
134
+ }
135
+
136
+ const identifier = ctx.IDENTIFIER();
137
+ this.analyzer.addError(
138
+ identifier.getText(),
139
+ identifier.symbol.line,
140
+ identifier.symbol.column,
141
+ );
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Analyzer for missing return paths (ADR-112).
147
+ */
148
+ class ReturnPathAnalyzer {
149
+ private errors: IReturnPathError[] = [];
150
+
151
+ public analyze(tree: Parser.ProgramContext): IReturnPathError[] {
152
+ this.errors = [];
153
+ const listener = new ReturnPathListener(this);
154
+ ParseTreeWalker.DEFAULT.walk(listener, tree);
155
+ return this.errors;
156
+ }
157
+
158
+ public addError(functionName: string, line: number, column: number): void {
159
+ this.errors.push({
160
+ code: "E0704",
161
+ functionName,
162
+ line,
163
+ column,
164
+ message: `Non-void function '${functionName}' must return a value on all paths`,
165
+ helpText:
166
+ "Add an explicit 'return <value>;' so every control-flow path returns a value",
167
+ });
168
+ }
169
+ }
170
+
171
+ export default ReturnPathAnalyzer;
@@ -21,6 +21,7 @@ import ISignedShiftError from "./types/ISignedShiftError";
21
21
  import ParserUtils from "../../../utils/ParserUtils";
22
22
  import TypeConstants from "../../../utils/constants/TypeConstants";
23
23
  import ExpressionUtils from "../../../utils/ExpressionUtils";
24
+ import CodeGenState from "../../state/CodeGenState";
24
25
 
25
26
  /**
26
27
  * First pass: Collect variable declarations with their types
@@ -28,25 +29,36 @@ import ExpressionUtils from "../../../utils/ExpressionUtils";
28
29
  class SignedVariableCollector extends CNextListener {
29
30
  private readonly signedVars: Set<string> = new Set();
30
31
 
32
+ // Track all variable types (for resolving struct member chains)
33
+ private readonly varTypes: Map<string, string> = new Map();
34
+
31
35
  public getSignedVars(): Set<string> {
32
36
  return this.signedVars;
33
37
  }
34
38
 
39
+ public getVarTypes(): Map<string, string> {
40
+ return this.varTypes;
41
+ }
42
+
35
43
  /**
36
- * Track a typed identifier if it has a signed type
44
+ * Track a typed identifier - add to signedVars if signed, always track type
37
45
  */
38
- private trackIfSigned(
46
+ private trackType(
39
47
  typeCtx: Parser.TypeContext | null,
40
48
  identifier: { getText(): string } | null,
41
49
  ): void {
42
- if (!typeCtx) return;
50
+ if (!typeCtx || !identifier) return;
43
51
 
44
52
  const typeName = typeCtx.getText();
45
- if (!TypeConstants.SIGNED_TYPES.includes(typeName)) return;
53
+ const varName = identifier.getText();
46
54
 
47
- if (!identifier) return;
55
+ // Always track the variable's type for member chain resolution
56
+ this.varTypes.set(varName, typeName);
48
57
 
49
- this.signedVars.add(identifier.getText());
58
+ // Also track in signedVars if it's a signed type
59
+ if (TypeConstants.SIGNED_TYPES.includes(typeName)) {
60
+ this.signedVars.add(varName);
61
+ }
50
62
  }
51
63
 
52
64
  /**
@@ -55,21 +67,21 @@ class SignedVariableCollector extends CNextListener {
55
67
  override enterVariableDeclaration = (
56
68
  ctx: Parser.VariableDeclarationContext,
57
69
  ): void => {
58
- this.trackIfSigned(ctx.type(), ctx.IDENTIFIER());
70
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
59
71
  };
60
72
 
61
73
  /**
62
74
  * Track function parameters with signed types
63
75
  */
64
76
  override enterParameter = (ctx: Parser.ParameterContext): void => {
65
- this.trackIfSigned(ctx.type(), ctx.IDENTIFIER());
77
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
66
78
  };
67
79
 
68
80
  /**
69
81
  * Track for-loop variable declarations with signed types
70
82
  */
71
83
  override enterForVarDecl = (ctx: Parser.ForVarDeclContext): void => {
72
- this.trackIfSigned(ctx.type(), ctx.IDENTIFIER());
84
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
73
85
  };
74
86
  }
75
87
 
@@ -82,10 +94,18 @@ class SignedShiftListener extends CNextListener {
82
94
  // eslint-disable-next-line @typescript-eslint/lines-between-class-members
83
95
  private readonly signedVars: Set<string>;
84
96
 
85
- constructor(analyzer: SignedShiftAnalyzer, signedVars: Set<string>) {
97
+ // eslint-disable-next-line @typescript-eslint/lines-between-class-members
98
+ private readonly varTypes: Map<string, string>;
99
+
100
+ constructor(
101
+ analyzer: SignedShiftAnalyzer,
102
+ signedVars: Set<string>,
103
+ varTypes: Map<string, string>,
104
+ ) {
86
105
  super();
87
106
  this.analyzer = analyzer;
88
107
  this.signedVars = signedVars;
108
+ this.varTypes = varTypes;
89
109
  }
90
110
 
91
111
  /**
@@ -116,6 +136,97 @@ class SignedShiftListener extends CNextListener {
116
136
  }
117
137
  };
118
138
 
139
+ /**
140
+ * Check compound shift-assign statements for signed targets
141
+ * assignmentStatement: assignmentTarget assignmentOperator expression ';'
142
+ * Issue #1008: <<<- and >><- must also be rejected on signed types
143
+ *
144
+ * Handles both simple identifiers (x <<<- 2) and member chains (s.x <<<- 2)
145
+ */
146
+ override enterAssignmentStatement = (
147
+ ctx: Parser.AssignmentStatementContext,
148
+ ): void => {
149
+ const opCtx = ctx.assignmentOperator();
150
+ if (!opCtx) return;
151
+
152
+ const isLeftShiftAssign = opCtx.LSHIFT_ASSIGN() !== null;
153
+ const isRightShiftAssign = opCtx.RSHIFT_ASSIGN() !== null;
154
+ if (!isLeftShiftAssign && !isRightShiftAssign) return;
155
+
156
+ const target = ctx.assignmentTarget();
157
+ if (!target) return;
158
+
159
+ // Get the base identifier from the assignment target
160
+ const identifier = target.IDENTIFIER();
161
+ if (!identifier) return;
162
+
163
+ const baseName = identifier.getText();
164
+ const postfixOps = target.postfixTargetOp();
165
+
166
+ // Check if the final target type is signed
167
+ if (this.isSignedTarget(baseName, postfixOps)) {
168
+ const operator = isLeftShiftAssign ? "<<<-" : ">><-";
169
+ const { line, column } = ParserUtils.getPosition(target);
170
+ this.analyzer.addError(line, column, operator);
171
+ }
172
+ };
173
+
174
+ /**
175
+ * Resolve the final type of an assignment target, handling member chains.
176
+ * Returns true if the final target is a signed type.
177
+ *
178
+ * Examples:
179
+ * - "x" with no postfix ops → check if x is signed
180
+ * - "s" with postfixOps [".x"] → check if s.x field is signed
181
+ * - "arr" with postfixOps ["[0]", ".field"] → check if field is signed
182
+ */
183
+ private isSignedTarget(
184
+ baseName: string,
185
+ postfixOps: Parser.PostfixTargetOpContext[],
186
+ ): boolean {
187
+ // Simple case: no member access, just a variable
188
+ if (postfixOps.length === 0) {
189
+ return this.signedVars.has(baseName);
190
+ }
191
+
192
+ // Member chain case: resolve through the chain
193
+ let currentType = this.varTypes.get(baseName);
194
+ if (!currentType) {
195
+ // Unknown base type - can't resolve, skip
196
+ return false;
197
+ }
198
+
199
+ // Walk through the postfix operations
200
+ for (const op of postfixOps) {
201
+ const memberIdent = op.IDENTIFIER();
202
+ if (memberIdent) {
203
+ // Member access: .fieldName
204
+ const fieldName = memberIdent.getText();
205
+ const fieldType = CodeGenState.getStructFieldType(
206
+ currentType,
207
+ fieldName,
208
+ );
209
+ if (!fieldType) {
210
+ // Unknown field - can't resolve, skip
211
+ return false;
212
+ }
213
+ currentType = fieldType;
214
+ } else {
215
+ // Array subscript: [expr] - doesn't change the base type for primitives
216
+ // For arrays like u8[4], after [i] we still have u8
217
+ // Strip array dimensions if present
218
+ const bracketIndex = currentType.indexOf("[");
219
+ if (bracketIndex !== -1) {
220
+ currentType = currentType.substring(0, bracketIndex);
221
+ }
222
+ // Otherwise keep the type as-is (e.g., bit indexing on u8)
223
+ }
224
+ }
225
+
226
+ // Check if the final resolved type is signed
227
+ return TypeConstants.SIGNED_TYPES.includes(currentType);
228
+ }
229
+
119
230
  /**
120
231
  * Check if an additive expression contains a signed type operand
121
232
  */
@@ -202,13 +313,14 @@ class SignedShiftAnalyzer {
202
313
  public analyze(tree: Parser.ProgramContext): ISignedShiftError[] {
203
314
  this.errors = [];
204
315
 
205
- // First pass: collect signed variables
316
+ // First pass: collect signed variables and all variable types
206
317
  const collector = new SignedVariableCollector();
207
318
  ParseTreeWalker.DEFAULT.walk(collector, tree);
208
319
  const signedVars = collector.getSignedVars();
320
+ const varTypes = collector.getVarTypes();
209
321
 
210
322
  // Second pass: detect shift with signed operands
211
- const listener = new SignedShiftListener(this, signedVars);
323
+ const listener = new SignedShiftListener(this, signedVars, varTypes);
212
324
  ParseTreeWalker.DEFAULT.walk(listener, tree);
213
325
 
214
326
  return this.errors;