c-next 0.2.9 → 0.2.10
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/dist/index.js +814 -206
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/transpiler/Transpiler.ts +97 -7
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +170 -0
- package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +246 -0
- package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +12 -1
- package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +172 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +84 -0
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +43 -0
- package/src/transpiler/logic/symbols/__tests__/TransitiveEnumCollector.test.ts +1 -0
- package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +98 -1
- package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +23 -5
- package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +69 -2
- package/src/transpiler/logic/symbols/c/index.ts +85 -30
- package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +18 -0
- package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +90 -0
- package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +40 -39
- package/src/transpiler/output/codegen/CodeGenerator.ts +23 -1
- package/src/transpiler/output/codegen/TypeResolver.ts +14 -0
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +19 -14
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +7 -7
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +1 -0
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +27 -4
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +6 -0
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +73 -0
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +4 -0
- package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +92 -0
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +5 -2
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +1 -0
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +14 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +17 -2
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +49 -0
- package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +17 -5
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +5 -0
- package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +16 -0
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +41 -5
- package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +25 -1
- package/src/transpiler/output/codegen/generators/statements/AtomicGenerator.ts +2 -17
- package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +3 -0
- package/src/transpiler/output/codegen/helpers/BitRangeHelper.ts +19 -3
- package/src/transpiler/output/codegen/helpers/NarrowingCastHelper.ts +191 -0
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +35 -4
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +131 -1
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/BitRangeHelper.test.ts +53 -2
- package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/NarrowingCastHelper.test.ts +159 -0
- package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +1 -0
- package/src/transpiler/output/codegen/types/COMPOUND_TO_BINARY.ts +21 -0
- package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +2 -0
- package/src/transpiler/state/CodeGenState.ts +49 -0
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +53 -0
- package/src/transpiler/state/__tests__/TranspilerState.test.ts +1 -0
- package/src/transpiler/types/ICachedFileEntry.ts +2 -0
- package/src/transpiler/types/ICodeGenSymbols.ts +8 -0
- package/src/utils/BitUtils.ts +33 -4
- package/src/utils/cache/CacheManager.ts +9 -1
- package/src/utils/cache/__tests__/CacheManager.test.ts +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for NarrowingCastHelper
|
|
3
|
+
* Issue #845: MISRA C:2012 Rule 10.3 compliance
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
7
|
+
import NarrowingCastHelper from "../NarrowingCastHelper.js";
|
|
8
|
+
import CodeGenState from "../../../../state/CodeGenState.js";
|
|
9
|
+
|
|
10
|
+
describe("NarrowingCastHelper", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
CodeGenState.reset();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("needsCast", () => {
|
|
16
|
+
it("returns false for same type", () => {
|
|
17
|
+
expect(NarrowingCastHelper.needsCast("u32", "u32")).toBe(false);
|
|
18
|
+
expect(NarrowingCastHelper.needsCast("u8", "u8")).toBe(false);
|
|
19
|
+
expect(NarrowingCastHelper.needsCast("bool", "bool")).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns false for widening (u8 -> u32)", () => {
|
|
23
|
+
expect(NarrowingCastHelper.needsCast("u8", "u32")).toBe(false);
|
|
24
|
+
expect(NarrowingCastHelper.needsCast("u16", "u32")).toBe(false);
|
|
25
|
+
expect(NarrowingCastHelper.needsCast("i8", "i32")).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns true for narrowing (u32 -> u8)", () => {
|
|
29
|
+
expect(NarrowingCastHelper.needsCast("u32", "u8")).toBe(true);
|
|
30
|
+
expect(NarrowingCastHelper.needsCast("u32", "u16")).toBe(true);
|
|
31
|
+
expect(NarrowingCastHelper.needsCast("i32", "i8")).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns true for int -> smaller unsigned (C promotion result)", () => {
|
|
35
|
+
expect(NarrowingCastHelper.needsCast("int", "u8")).toBe(true);
|
|
36
|
+
expect(NarrowingCastHelper.needsCast("int", "u16")).toBe(true);
|
|
37
|
+
expect(NarrowingCastHelper.needsCast("int", "i8")).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("returns true for int -> bool (different essential type)", () => {
|
|
41
|
+
expect(NarrowingCastHelper.needsCast("int", "bool")).toBe(true);
|
|
42
|
+
expect(NarrowingCastHelper.needsCast("u32", "bool")).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("returns true for char literal type -> u8", () => {
|
|
46
|
+
expect(NarrowingCastHelper.needsCast("int", "u8")).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("wrap (C mode)", () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
CodeGenState.cppMode = false;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns expression unchanged for same type", () => {
|
|
56
|
+
expect(NarrowingCastHelper.wrap("x", "u32", "u32")).toBe("x");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("returns expression unchanged for widening", () => {
|
|
60
|
+
expect(NarrowingCastHelper.wrap("x", "u8", "u32")).toBe("x");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("adds C cast for narrowing u32 -> u8", () => {
|
|
64
|
+
const expr = "((value >> 0U) & 0xFFU)";
|
|
65
|
+
expect(NarrowingCastHelper.wrap(expr, "u32", "u8")).toBe(
|
|
66
|
+
"(uint8_t)((value >> 0U) & 0xFFU)",
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("adds C cast for narrowing u32 -> u16", () => {
|
|
71
|
+
const expr = "((value >> 0U) & 0xFFFFU)";
|
|
72
|
+
expect(NarrowingCastHelper.wrap(expr, "u32", "u16")).toBe(
|
|
73
|
+
"(uint16_t)((value >> 0U) & 0xFFFFU)",
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("adds C cast for int -> u8 (C promotion result)", () => {
|
|
78
|
+
const expr = "((flags >> 3) & 0x7)";
|
|
79
|
+
expect(NarrowingCastHelper.wrap(expr, "int", "u8")).toBe(
|
|
80
|
+
"(uint8_t)((flags >> 3) & 0x7)",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("uses != 0U comparison for bool target (MISRA 10.5)", () => {
|
|
85
|
+
const expr = "((flags >> 0) & 1)";
|
|
86
|
+
expect(NarrowingCastHelper.wrap(expr, "int", "bool")).toBe(
|
|
87
|
+
"((((flags >> 0) & 1)) != 0U)",
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("adds cast for char literal to u8", () => {
|
|
92
|
+
expect(NarrowingCastHelper.wrap("'A'", "int", "u8")).toBe("(uint8_t)'A'");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("wrap (C++ mode)", () => {
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
CodeGenState.cppMode = true;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("uses static_cast for narrowing", () => {
|
|
102
|
+
const expr = "((value >> 0U) & 0xFFU)";
|
|
103
|
+
expect(NarrowingCastHelper.wrap(expr, "u32", "u8")).toBe(
|
|
104
|
+
"static_cast<uint8_t>(((value >> 0U) & 0xFFU))",
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("uses != 0U for bool (same as C mode)", () => {
|
|
109
|
+
const expr = "((flags >> 0) & 1)";
|
|
110
|
+
expect(NarrowingCastHelper.wrap(expr, "int", "bool")).toBe(
|
|
111
|
+
"((((flags >> 0) & 1)) != 0U)",
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("getPromotedType", () => {
|
|
117
|
+
it("returns 'int' for u8 (promoted)", () => {
|
|
118
|
+
expect(NarrowingCastHelper.getPromotedType("u8")).toBe("int");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("returns 'int' for i8 (promoted)", () => {
|
|
122
|
+
expect(NarrowingCastHelper.getPromotedType("i8")).toBe("int");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("returns 'int' for u16 (promoted)", () => {
|
|
126
|
+
expect(NarrowingCastHelper.getPromotedType("u16")).toBe("int");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns 'int' for i16 (promoted)", () => {
|
|
130
|
+
expect(NarrowingCastHelper.getPromotedType("i16")).toBe("int");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("returns same type for u32 (no promotion)", () => {
|
|
134
|
+
expect(NarrowingCastHelper.getPromotedType("u32")).toBe("u32");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("returns same type for i32 (no promotion)", () => {
|
|
138
|
+
expect(NarrowingCastHelper.getPromotedType("i32")).toBe("i32");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("returns same type for u64 (no promotion)", () => {
|
|
142
|
+
expect(NarrowingCastHelper.getPromotedType("u64")).toBe("u64");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("returns same type for i64 (no promotion)", () => {
|
|
146
|
+
expect(NarrowingCastHelper.getPromotedType("i64")).toBe("i64");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("returns 'int' for bool (promoted)", () => {
|
|
150
|
+
expect(NarrowingCastHelper.getPromotedType("bool")).toBe("int");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("returns same type for unknown types (conservative)", () => {
|
|
154
|
+
expect(NarrowingCastHelper.getPromotedType("custom_type")).toBe(
|
|
155
|
+
"custom_type",
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map compound assignment operator to its binary operator equivalent.
|
|
3
|
+
*
|
|
4
|
+
* Shared constant used by:
|
|
5
|
+
* - SimpleHandler.ts: MISRA 10.3 compound assignment expansion
|
|
6
|
+
* - AtomicGenerator.ts: Atomic compound operation expansion
|
|
7
|
+
*/
|
|
8
|
+
const COMPOUND_TO_BINARY: Record<string, string> = {
|
|
9
|
+
"+=": "+",
|
|
10
|
+
"-=": "-",
|
|
11
|
+
"*=": "*",
|
|
12
|
+
"/=": "/",
|
|
13
|
+
"%=": "%",
|
|
14
|
+
"&=": "&",
|
|
15
|
+
"|=": "|",
|
|
16
|
+
"^=": "^",
|
|
17
|
+
"<<=": "<<",
|
|
18
|
+
">>=": ">>",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default COMPOUND_TO_BINARY;
|
|
@@ -19,6 +19,8 @@ type IArrayAccessInfo = {
|
|
|
19
19
|
widthExpr?: string;
|
|
20
20
|
/** Type info for the variable being accessed */
|
|
21
21
|
typeInfo?: TTypeInfo;
|
|
22
|
+
/** Target type for narrowing cast (e.g., "u8" when extracting to uint8_t) */
|
|
23
|
+
targetType?: string;
|
|
22
24
|
/** Source line for error messages */
|
|
23
25
|
line: number;
|
|
24
26
|
};
|
|
@@ -278,6 +278,18 @@ export default class CodeGenState {
|
|
|
278
278
|
/** Issue #473: IRQ wrappers for critical sections */
|
|
279
279
|
static needsIrqWrappers: boolean = false;
|
|
280
280
|
|
|
281
|
+
// ===========================================================================
|
|
282
|
+
// OPAQUE TYPE SCOPE VARIABLES (Issue #948)
|
|
283
|
+
// ===========================================================================
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Tracks scope variables with opaque (forward-declared) struct types.
|
|
287
|
+
* These are generated as pointers with NULL initialization and should
|
|
288
|
+
* be passed directly (not with &) since they're already pointers.
|
|
289
|
+
* Maps qualified name (e.g., "MyScope_widget") to true.
|
|
290
|
+
*/
|
|
291
|
+
private static opaqueScopeVariables: Set<string> = new Set();
|
|
292
|
+
|
|
281
293
|
// ===========================================================================
|
|
282
294
|
// C++ MODE STATE (Issue #250)
|
|
283
295
|
// ===========================================================================
|
|
@@ -400,6 +412,9 @@ export default class CodeGenState {
|
|
|
400
412
|
this.pendingCppClassAssignments = [];
|
|
401
413
|
this.selfIncludeAdded = false;
|
|
402
414
|
|
|
415
|
+
// Issue #948: Opaque scope variables (reset per-file)
|
|
416
|
+
this.opaqueScopeVariables = new Set();
|
|
417
|
+
|
|
403
418
|
// Source paths
|
|
404
419
|
this.sourcePath = null;
|
|
405
420
|
this.includeDirs = [];
|
|
@@ -504,6 +519,15 @@ export default class CodeGenState {
|
|
|
504
519
|
return this.symbols?.knownRegisters.has(name) ?? false;
|
|
505
520
|
}
|
|
506
521
|
|
|
522
|
+
/**
|
|
523
|
+
* Issue #948: Check if a type name is an opaque (forward-declared) struct type.
|
|
524
|
+
* Opaque types are incomplete types that can only be used as pointers.
|
|
525
|
+
* Example: `typedef struct _widget_t widget_t;` without a body makes `widget_t` opaque.
|
|
526
|
+
*/
|
|
527
|
+
static isOpaqueType(typeName: string): boolean {
|
|
528
|
+
return this.symbols?.opaqueTypes.has(typeName) ?? false;
|
|
529
|
+
}
|
|
530
|
+
|
|
507
531
|
/**
|
|
508
532
|
* Get type info for a variable.
|
|
509
533
|
* Checks local typeRegistry first, then falls back to SymbolTable
|
|
@@ -1046,6 +1070,31 @@ export default class CodeGenState {
|
|
|
1046
1070
|
return this.floatShadowCurrent.has(name);
|
|
1047
1071
|
}
|
|
1048
1072
|
|
|
1073
|
+
// ===========================================================================
|
|
1074
|
+
// OPAQUE SCOPE VARIABLE HELPERS (Issue #948)
|
|
1075
|
+
// ===========================================================================
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Mark a scope variable as having an opaque (forward-declared) struct type.
|
|
1079
|
+
* These are generated as pointers with NULL initialization.
|
|
1080
|
+
*
|
|
1081
|
+
* @param qualifiedName - The fully qualified variable name (e.g., "MyScope_widget")
|
|
1082
|
+
*/
|
|
1083
|
+
static markOpaqueScopeVariable(qualifiedName: string): void {
|
|
1084
|
+
this.opaqueScopeVariables.add(qualifiedName);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Check if a scope variable has an opaque type (and is thus a pointer).
|
|
1089
|
+
* Used during access generation to determine if & prefix is needed.
|
|
1090
|
+
*
|
|
1091
|
+
* @param qualifiedName - The fully qualified variable name (e.g., "MyScope_widget")
|
|
1092
|
+
* @returns true if this is an opaque scope variable (already a pointer)
|
|
1093
|
+
*/
|
|
1094
|
+
static isOpaqueScopeVariable(qualifiedName: string): boolean {
|
|
1095
|
+
return this.opaqueScopeVariables.has(qualifiedName);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1049
1098
|
// ===========================================================================
|
|
1050
1099
|
// C++ MODE HELPERS
|
|
1051
1100
|
// ===========================================================================
|
|
@@ -69,6 +69,7 @@ function createMockSymbols(
|
|
|
69
69
|
structFieldArrays: Map<string, Set<string>>;
|
|
70
70
|
functionReturnTypes: Map<string, string>;
|
|
71
71
|
scopeMemberVisibility: Map<string, Map<string, "public" | "private">>;
|
|
72
|
+
opaqueTypes: Set<string>;
|
|
72
73
|
}> = {},
|
|
73
74
|
): ICodeGenSymbols {
|
|
74
75
|
return {
|
|
@@ -95,6 +96,7 @@ function createMockSymbols(
|
|
|
95
96
|
scopeVariableUsage: new Map(),
|
|
96
97
|
scopePrivateConstValues: new Map(),
|
|
97
98
|
functionReturnTypes: overrides.functionReturnTypes ?? new Map(),
|
|
99
|
+
opaqueTypes: overrides.opaqueTypes ?? new Set(),
|
|
98
100
|
getSingleFunctionForVariable: () => null,
|
|
99
101
|
hasPublicSymbols: () => false,
|
|
100
102
|
};
|
|
@@ -714,6 +716,21 @@ describe("CodeGenState", () => {
|
|
|
714
716
|
expect(CodeGenState.isKnownScope("MyScope")).toBe(true);
|
|
715
717
|
expect(CodeGenState.isKnownScope("UnknownScope")).toBe(false);
|
|
716
718
|
});
|
|
719
|
+
|
|
720
|
+
it("isOpaqueType returns false without symbols", () => {
|
|
721
|
+
CodeGenState.symbols = null;
|
|
722
|
+
expect(CodeGenState.isOpaqueType("widget_t")).toBe(false);
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
it("isOpaqueType returns true for opaque type", () => {
|
|
726
|
+
CodeGenState.symbols = createMockSymbols({
|
|
727
|
+
opaqueTypes: new Set(["widget_t", "display_t"]),
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
expect(CodeGenState.isOpaqueType("widget_t")).toBe(true);
|
|
731
|
+
expect(CodeGenState.isOpaqueType("display_t")).toBe(true);
|
|
732
|
+
expect(CodeGenState.isOpaqueType("Point")).toBe(false);
|
|
733
|
+
});
|
|
717
734
|
});
|
|
718
735
|
|
|
719
736
|
describe("Local Variable Helpers", () => {
|
|
@@ -848,4 +865,40 @@ describe("CodeGenState", () => {
|
|
|
848
865
|
expect(result.get("Simple")?.has("value")).toBe(true);
|
|
849
866
|
});
|
|
850
867
|
});
|
|
868
|
+
|
|
869
|
+
describe("Opaque Scope Variable Helpers (Issue #948)", () => {
|
|
870
|
+
it("markOpaqueScopeVariable adds to opaqueScopeVariables", () => {
|
|
871
|
+
CodeGenState.markOpaqueScopeVariable("MyScope_widget");
|
|
872
|
+
expect(CodeGenState.isOpaqueScopeVariable("MyScope_widget")).toBe(true);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
it("isOpaqueScopeVariable returns false for unknown variable", () => {
|
|
876
|
+
expect(CodeGenState.isOpaqueScopeVariable("Unknown_var")).toBe(false);
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
it("isOpaqueScopeVariable returns true for marked variable", () => {
|
|
880
|
+
CodeGenState.markOpaqueScopeVariable("Gui_display");
|
|
881
|
+
expect(CodeGenState.isOpaqueScopeVariable("Gui_display")).toBe(true);
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
it("reset clears opaqueScopeVariables", () => {
|
|
885
|
+
CodeGenState.markOpaqueScopeVariable("Test_opaque");
|
|
886
|
+
expect(CodeGenState.isOpaqueScopeVariable("Test_opaque")).toBe(true);
|
|
887
|
+
|
|
888
|
+
CodeGenState.reset();
|
|
889
|
+
|
|
890
|
+
expect(CodeGenState.isOpaqueScopeVariable("Test_opaque")).toBe(false);
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
it("handles multiple opaque scope variables", () => {
|
|
894
|
+
CodeGenState.markOpaqueScopeVariable("Scope1_widget");
|
|
895
|
+
CodeGenState.markOpaqueScopeVariable("Scope1_display");
|
|
896
|
+
CodeGenState.markOpaqueScopeVariable("Scope2_handle");
|
|
897
|
+
|
|
898
|
+
expect(CodeGenState.isOpaqueScopeVariable("Scope1_widget")).toBe(true);
|
|
899
|
+
expect(CodeGenState.isOpaqueScopeVariable("Scope1_display")).toBe(true);
|
|
900
|
+
expect(CodeGenState.isOpaqueScopeVariable("Scope2_handle")).toBe(true);
|
|
901
|
+
expect(CodeGenState.isOpaqueScopeVariable("Scope1_other")).toBe(false);
|
|
902
|
+
});
|
|
903
|
+
});
|
|
851
904
|
});
|
|
@@ -18,6 +18,8 @@ interface ICachedFileEntry {
|
|
|
18
18
|
needsStructKeyword?: string[];
|
|
19
19
|
/** Issue #208: Enum bit widths from typed enums (enum name -> bit width) */
|
|
20
20
|
enumBitWidth?: Record<string, number>;
|
|
21
|
+
/** Issue #948: Opaque types (forward-declared struct types) */
|
|
22
|
+
opaqueTypes?: string[];
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export default ICachedFileEntry;
|
|
@@ -113,6 +113,14 @@ interface ICodeGenSymbols {
|
|
|
113
113
|
*/
|
|
114
114
|
readonly functionReturnTypes: ReadonlyMap<string, string>;
|
|
115
115
|
|
|
116
|
+
// === Opaque Types (Issue #948) ===
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Issue #948: Types that are opaque (forward-declared structs).
|
|
120
|
+
* Variables of these types should be generated as pointers.
|
|
121
|
+
*/
|
|
122
|
+
readonly opaqueTypes: ReadonlySet<string>;
|
|
123
|
+
|
|
116
124
|
/**
|
|
117
125
|
* Check if a scope variable is used in only one function.
|
|
118
126
|
* Returns the function name if single-function, null otherwise.
|
package/src/utils/BitUtils.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import TYPE_MAP from "../transpiler/output/codegen/types/TYPE_MAP";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Types that need explicit cast for MISRA 10.3 compliance.
|
|
5
|
+
* These types are narrower than int (32-bit) and need casts after
|
|
6
|
+
* bit manipulation operations which promote to int.
|
|
7
|
+
*/
|
|
8
|
+
const NARROW_TYPES = new Set(["u8", "u16", "i8", "i16"]);
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
11
|
* Bit manipulation utilities for C code generation.
|
|
3
12
|
* Pure functions that generate C code strings for bit operations.
|
|
@@ -102,6 +111,22 @@ class BitUtils {
|
|
|
102
111
|
return `0x${value.toString(16).toUpperCase()}U`;
|
|
103
112
|
}
|
|
104
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Wrap an expression with a cast for MISRA 10.3 compliance on narrow types.
|
|
116
|
+
* Bit manipulation operations promote to int; this casts back to the target type.
|
|
117
|
+
*
|
|
118
|
+
* @param expr - The expression to potentially wrap
|
|
119
|
+
* @param targetType - The C-Next type name (e.g., "u8", "u16")
|
|
120
|
+
* @returns Expression wrapped with cast if narrow, or original expression
|
|
121
|
+
*/
|
|
122
|
+
private static wrapNarrowCast(expr: string, targetType?: string): string {
|
|
123
|
+
if (!targetType || !NARROW_TYPES.has(targetType)) {
|
|
124
|
+
return expr;
|
|
125
|
+
}
|
|
126
|
+
const cType = TYPE_MAP[targetType] ?? targetType;
|
|
127
|
+
return `(${cType})(${expr})`;
|
|
128
|
+
}
|
|
129
|
+
|
|
105
130
|
/**
|
|
106
131
|
* Generate code to read a single bit from a value.
|
|
107
132
|
* Pattern: ((target >> offset) & 1)
|
|
@@ -162,7 +187,8 @@ class BitUtils {
|
|
|
162
187
|
const valueShift = is64Bit
|
|
163
188
|
? `((uint64_t)${intValue} << ${offset})`
|
|
164
189
|
: `(${intValue} << ${offset})`;
|
|
165
|
-
|
|
190
|
+
const rhs = `(${target} & ~(${one} << ${offset})) | ${valueShift}`;
|
|
191
|
+
return `${target} = ${BitUtils.wrapNarrowCast(rhs, targetType)};`;
|
|
166
192
|
}
|
|
167
193
|
|
|
168
194
|
/**
|
|
@@ -184,7 +210,8 @@ class BitUtils {
|
|
|
184
210
|
targetType?: string,
|
|
185
211
|
): string {
|
|
186
212
|
const mask = BitUtils.generateMask(width, targetType);
|
|
187
|
-
|
|
213
|
+
const rhs = `(${target} & ~(${mask} << ${offset})) | ((${value} & ${mask}) << ${offset})`;
|
|
214
|
+
return `${target} = ${BitUtils.wrapNarrowCast(rhs, targetType)};`;
|
|
188
215
|
}
|
|
189
216
|
|
|
190
217
|
/**
|
|
@@ -209,7 +236,8 @@ class BitUtils {
|
|
|
209
236
|
// boolToInt already returns unsigned values (1U/0U) for MISRA 10.1 compliance
|
|
210
237
|
const castPrefix =
|
|
211
238
|
targetType === "u64" || targetType === "i64" ? "(uint64_t)" : "";
|
|
212
|
-
|
|
239
|
+
const rhs = `(${castPrefix}${intValue} << ${offset})`;
|
|
240
|
+
return `${target} = ${BitUtils.wrapNarrowCast(rhs, targetType)};`;
|
|
213
241
|
}
|
|
214
242
|
|
|
215
243
|
/**
|
|
@@ -232,7 +260,8 @@ class BitUtils {
|
|
|
232
260
|
targetType?: string,
|
|
233
261
|
): string {
|
|
234
262
|
const mask = BitUtils.generateMask(width, targetType);
|
|
235
|
-
|
|
263
|
+
const rhs = `((${value} & ${mask}) << ${offset})`;
|
|
264
|
+
return `${target} = ${BitUtils.wrapNarrowCast(rhs, targetType)};`;
|
|
236
265
|
}
|
|
237
266
|
}
|
|
238
267
|
|
|
@@ -32,7 +32,7 @@ import ESourceLanguage from "../types/ESourceLanguage";
|
|
|
32
32
|
const defaultFs = NodeFileSystem.instance;
|
|
33
33
|
|
|
34
34
|
/** Current cache format version - increment when serialization format changes */
|
|
35
|
-
const CACHE_VERSION =
|
|
35
|
+
const CACHE_VERSION = 5; // Issue #948: Add opaqueTypes to cache
|
|
36
36
|
|
|
37
37
|
const TRANSPILER_VERSION = packageJson.version;
|
|
38
38
|
|
|
@@ -134,6 +134,7 @@ class CacheManager {
|
|
|
134
134
|
structFields: Map<string, Map<string, IStructFieldInfo>>;
|
|
135
135
|
needsStructKeyword: string[];
|
|
136
136
|
enumBitWidth: Map<string, number>;
|
|
137
|
+
opaqueTypes: string[];
|
|
137
138
|
} | null {
|
|
138
139
|
if (!this.cache) return null;
|
|
139
140
|
|
|
@@ -173,6 +174,7 @@ class CacheManager {
|
|
|
173
174
|
structFields,
|
|
174
175
|
needsStructKeyword: cachedEntry.needsStructKeyword ?? [],
|
|
175
176
|
enumBitWidth,
|
|
177
|
+
opaqueTypes: cachedEntry.opaqueTypes ?? [],
|
|
176
178
|
};
|
|
177
179
|
}
|
|
178
180
|
|
|
@@ -186,6 +188,7 @@ class CacheManager {
|
|
|
186
188
|
structFields: Map<string, Map<string, IStructFieldInfo>>,
|
|
187
189
|
needsStructKeyword?: string[],
|
|
188
190
|
enumBitWidth?: Map<string, number>,
|
|
191
|
+
opaqueTypes?: string[],
|
|
189
192
|
): void {
|
|
190
193
|
if (!this.cache) return;
|
|
191
194
|
|
|
@@ -229,6 +232,7 @@ class CacheManager {
|
|
|
229
232
|
structFields: serializedFields,
|
|
230
233
|
needsStructKeyword,
|
|
231
234
|
enumBitWidth: serializedEnumBitWidth,
|
|
235
|
+
opaqueTypes,
|
|
232
236
|
};
|
|
233
237
|
|
|
234
238
|
this.cache.setKey(filePath, entry);
|
|
@@ -266,6 +270,9 @@ class CacheManager {
|
|
|
266
270
|
symbolTable,
|
|
267
271
|
);
|
|
268
272
|
|
|
273
|
+
// Issue #948: Extract opaque types (forward-declared structs)
|
|
274
|
+
const opaqueTypes = symbolTable.getAllOpaqueTypes();
|
|
275
|
+
|
|
269
276
|
// Delegate to existing setSymbols method
|
|
270
277
|
this.setSymbols(
|
|
271
278
|
filePath,
|
|
@@ -273,6 +280,7 @@ class CacheManager {
|
|
|
273
280
|
structFields,
|
|
274
281
|
needsStructKeyword,
|
|
275
282
|
enumBitWidth,
|
|
283
|
+
opaqueTypes,
|
|
276
284
|
);
|
|
277
285
|
}
|
|
278
286
|
|
|
@@ -1492,7 +1492,7 @@ describe("CacheManager", () => {
|
|
|
1492
1492
|
const content = mockFs.getWrittenContent("/project/.cnx/config.json");
|
|
1493
1493
|
expect(content).toBeDefined();
|
|
1494
1494
|
const newConfig = JSON.parse(content!);
|
|
1495
|
-
expect(newConfig.version).toBe(
|
|
1495
|
+
expect(newConfig.version).toBe(5); // Current CACHE_VERSION (Issue #948 opaqueTypes)
|
|
1496
1496
|
});
|
|
1497
1497
|
|
|
1498
1498
|
it("should not cache files that do not exist in IFileSystem", async () => {
|