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.
- package/README.md +2 -2
- package/dist/index.js +7645 -5941
- package/dist/index.js.map +4 -4
- package/grammar/CNext.g4 +8 -0
- package/package.json +1 -3
- package/src/transpiler/Transpiler.ts +286 -26
- package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
- package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
- package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +8 -0
- package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
- package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
- package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
- package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
- package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
- package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
- package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
- package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
- package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
- package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
- package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +1257 -1185
- package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
- package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
- package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
- package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
- package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
- package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
- package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
- package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
- package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
- package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
- package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +45 -0
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
- package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
- package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +45 -4
- package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
- package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +13 -1
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +86 -14
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
- package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
- package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
- package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
- package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
- package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +28 -15
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
- package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +16 -13
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
- package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
- package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
- package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +65 -24
- package/src/transpiler/state/CodeGenState.ts +20 -16
- package/src/transpiler/types/ICachedFileEntry.ts +7 -0
- package/src/utils/cache/CacheManager.ts +13 -2
- package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
- package/src/utils/constants/TypeConstants.ts +13 -0
|
@@ -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;
|
|
@@ -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
|
+
});
|