c-next 0.2.8 → 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 +1019 -267
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/transpiler/Transpiler.ts +101 -8
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +170 -0
- package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +246 -0
- package/src/transpiler/logic/IncludeExtractor.ts +12 -6
- package/src/transpiler/logic/__tests__/IncludeExtractor.test.ts +24 -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 +55 -25
- package/src/transpiler/output/codegen/TypeResolver.ts +14 -0
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +3 -3
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +35 -30
- 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 +25 -3
- 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 +26 -6
- package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +31 -2
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +72 -13
- package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +25 -1
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +2 -1
- package/src/transpiler/output/codegen/generators/statements/AtomicGenerator.ts +2 -17
- package/src/transpiler/output/codegen/generators/support/IncludeGenerator.ts +19 -7
- package/src/transpiler/output/codegen/generators/support/__tests__/IncludeGenerator.test.ts +68 -0
- package/src/transpiler/output/codegen/helpers/ArgumentGenerator.ts +14 -2
- package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +3 -0
- package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +3 -5
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +56 -8
- 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 -5
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +131 -1
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +61 -2
- 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/output/headers/HeaderGeneratorUtils.ts +14 -16
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +25 -0
- package/src/transpiler/state/CodeGenState.ts +89 -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
|
@@ -66,22 +66,34 @@ class ArgumentGenerator {
|
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* Handle rvalue argument (literals or complex expressions).
|
|
69
|
+
* Issue #872: Sets expectedType for MISRA 7.2 U suffix on unsigned literals.
|
|
69
70
|
*/
|
|
70
71
|
static handleRvalueArg(
|
|
71
72
|
ctx: Parser.ExpressionContext,
|
|
72
73
|
targetParamBaseType: string | undefined,
|
|
73
74
|
callbacks: IArgumentGeneratorCallbacks,
|
|
74
75
|
): string {
|
|
76
|
+
// Issue #872: Early return when no target type - no state management needed
|
|
75
77
|
if (!targetParamBaseType) {
|
|
76
78
|
return callbacks.generateExpression(ctx);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
const cType = TYPE_MAP[targetParamBaseType];
|
|
80
82
|
if (!cType || cType === "void") {
|
|
81
|
-
|
|
83
|
+
// Issue #872: Suppress bare enum resolution in function args (requires ADR to change)
|
|
84
|
+
return CodeGenState.withExpectedType(
|
|
85
|
+
targetParamBaseType,
|
|
86
|
+
() => callbacks.generateExpression(ctx),
|
|
87
|
+
true, // suppressEnumResolution
|
|
88
|
+
);
|
|
82
89
|
}
|
|
83
90
|
|
|
84
|
-
|
|
91
|
+
// Issue #872: Suppress bare enum resolution in function args (requires ADR to change)
|
|
92
|
+
const value = CodeGenState.withExpectedType(
|
|
93
|
+
targetParamBaseType,
|
|
94
|
+
() => callbacks.generateExpression(ctx),
|
|
95
|
+
true, // suppressEnumResolution
|
|
96
|
+
);
|
|
85
97
|
|
|
86
98
|
// C++ mode: rvalues can bind to const T&
|
|
87
99
|
if (CodeGenState.cppMode) {
|
|
@@ -115,6 +115,7 @@ class ArrayAccessHelper {
|
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* Generate integer bit range read: ((value >> start) & mask)
|
|
118
|
+
* Passes sourceType and targetType to BitRangeHelper for MISRA 10.3 casts.
|
|
118
119
|
*/
|
|
119
120
|
static generateIntegerBitRange(
|
|
120
121
|
info: IArrayAccessInfo,
|
|
@@ -125,6 +126,8 @@ class ArrayAccessHelper {
|
|
|
125
126
|
varName: info.resolvedName,
|
|
126
127
|
start: info.startExpr ?? "0",
|
|
127
128
|
mask,
|
|
129
|
+
sourceType: info.typeInfo?.baseType,
|
|
130
|
+
targetType: info.targetType,
|
|
128
131
|
});
|
|
129
132
|
}
|
|
130
133
|
}
|
|
@@ -106,11 +106,9 @@ class ArrayInitHelper {
|
|
|
106
106
|
callbacks: IArrayInitCallbacks,
|
|
107
107
|
): string {
|
|
108
108
|
const typeName = callbacks.getTypeName(typeCtx);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
CodeGenState.expectedType = savedExpectedType;
|
|
113
|
-
return initValue;
|
|
109
|
+
return CodeGenState.withExpectedType(typeName, () =>
|
|
110
|
+
callbacks.generateExpression(expression),
|
|
111
|
+
);
|
|
114
112
|
}
|
|
115
113
|
|
|
116
114
|
/**
|
|
@@ -54,22 +54,36 @@ class AssignmentExpectedTypeResolver {
|
|
|
54
54
|
return AssignmentExpectedTypeResolver.resolveForSimpleIdentifier(baseId);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// Case 2: Has
|
|
57
|
+
// Case 2: Has postfix ops - extract identifiers from chain
|
|
58
58
|
if (baseId && postfixOps.length > 0) {
|
|
59
59
|
const { identifiers, hasSubscript } = analyzePostfixOps(
|
|
60
60
|
baseId,
|
|
61
61
|
postfixOps,
|
|
62
62
|
);
|
|
63
63
|
|
|
64
|
-
//
|
|
64
|
+
// Case 2a: Member access only (no subscript)
|
|
65
65
|
if (identifiers.length >= 2 && !hasSubscript) {
|
|
66
66
|
return AssignmentExpectedTypeResolver.resolveForMemberChain(
|
|
67
67
|
identifiers,
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
// Case 2b: Simple array element access (arr[i] <- value)
|
|
72
|
+
// Issue #872: Resolve element type for MISRA 7.2 U suffix
|
|
73
|
+
if (identifiers.length === 1 && hasSubscript) {
|
|
74
|
+
return AssignmentExpectedTypeResolver.resolveForArrayElement(baseId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Case 2c: Member chain with array access (struct.arr[i] <- value)
|
|
78
|
+
// Issue #872: Walk chain and resolve element type
|
|
79
|
+
if (identifiers.length >= 2 && hasSubscript) {
|
|
80
|
+
return AssignmentExpectedTypeResolver.resolveForMemberArrayElement(
|
|
81
|
+
identifiers,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
70
84
|
}
|
|
71
85
|
|
|
72
|
-
// Case 3:
|
|
86
|
+
// Case 3: Complex patterns we can't resolve
|
|
73
87
|
return { expectedType: null, assignmentContext: null };
|
|
74
88
|
}
|
|
75
89
|
|
|
@@ -98,10 +112,49 @@ class AssignmentExpectedTypeResolver {
|
|
|
98
112
|
*
|
|
99
113
|
* Issue #452: Enables type-aware resolution of unqualified enum members
|
|
100
114
|
* for nested access (e.g., config.nested.field).
|
|
115
|
+
*
|
|
116
|
+
* Delegates to walkMemberChain shared implementation.
|
|
101
117
|
*/
|
|
102
118
|
private static resolveForMemberChain(
|
|
103
119
|
identifiers: string[],
|
|
104
120
|
): IExpectedTypeResult {
|
|
121
|
+
return AssignmentExpectedTypeResolver.walkMemberChain(identifiers);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resolve expected type for array element access.
|
|
126
|
+
* Issue #872: arr[i] <- value needs baseType for MISRA 7.2 U suffix.
|
|
127
|
+
*/
|
|
128
|
+
private static resolveForArrayElement(id: string): IExpectedTypeResult {
|
|
129
|
+
const typeInfo = CodeGenState.getVariableTypeInfo(id);
|
|
130
|
+
if (!typeInfo?.isArray) {
|
|
131
|
+
return { expectedType: null, assignmentContext: null };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Element type is the baseType (e.g., u8[10] -> "u8")
|
|
135
|
+
return { expectedType: typeInfo.baseType, assignmentContext: null };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Resolve expected type for member chain ending with array access.
|
|
140
|
+
* Issue #872: struct.arr[i] <- value needs element type for MISRA 7.2.
|
|
141
|
+
*
|
|
142
|
+
* Delegates to walkMemberChain which handles both member chain and
|
|
143
|
+
* member-array-element patterns identically (both return final field type).
|
|
144
|
+
*/
|
|
145
|
+
private static resolveForMemberArrayElement(
|
|
146
|
+
identifiers: string[],
|
|
147
|
+
): IExpectedTypeResult {
|
|
148
|
+
return AssignmentExpectedTypeResolver.walkMemberChain(identifiers);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Walk a struct member chain to find the final field's type.
|
|
153
|
+
* Shared implementation for both member chain and member-array-element patterns.
|
|
154
|
+
*
|
|
155
|
+
* Issue #831: Uses SymbolTable as single source of truth for struct fields.
|
|
156
|
+
*/
|
|
157
|
+
private static walkMemberChain(identifiers: string[]): IExpectedTypeResult {
|
|
105
158
|
if (identifiers.length < 2) {
|
|
106
159
|
return { expectedType: null, assignmentContext: null };
|
|
107
160
|
}
|
|
@@ -115,8 +168,6 @@ class AssignmentExpectedTypeResolver {
|
|
|
115
168
|
|
|
116
169
|
let currentStructType: string | undefined = rootTypeInfo.baseType;
|
|
117
170
|
|
|
118
|
-
// Walk through each member in the chain to find the final field's type
|
|
119
|
-
// Issue #831: Use SymbolTable as single source of truth for struct fields
|
|
120
171
|
for (let i = 1; i < identifiers.length && currentStructType; i++) {
|
|
121
172
|
const memberName = identifiers[i];
|
|
122
173
|
const memberType = CodeGenState.symbolTable?.getStructFieldType(
|
|
@@ -129,13 +180,10 @@ class AssignmentExpectedTypeResolver {
|
|
|
129
180
|
}
|
|
130
181
|
|
|
131
182
|
if (i === identifiers.length - 1) {
|
|
132
|
-
// Last field in chain - this is the assignment target's type
|
|
133
183
|
return { expectedType: memberType, assignmentContext: null };
|
|
134
184
|
} else if (CodeGenState.isKnownStruct(memberType)) {
|
|
135
|
-
// Intermediate field - continue walking if it's a struct
|
|
136
185
|
currentStructType = memberType;
|
|
137
186
|
} else {
|
|
138
|
-
// Intermediate field is not a struct - can't walk further
|
|
139
187
|
break;
|
|
140
188
|
}
|
|
141
189
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Extracted from CodeGenerator to improve testability.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import NarrowingCastHelper from "./NarrowingCastHelper.js";
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Options for generating float bit read expressions.
|
|
8
10
|
*/
|
|
@@ -21,6 +23,8 @@ interface IIntegerBitReadOptions {
|
|
|
21
23
|
varName: string;
|
|
22
24
|
start: string;
|
|
23
25
|
mask: string;
|
|
26
|
+
sourceType?: string; // Optional: source variable type for cast detection
|
|
27
|
+
targetType?: string; // Optional: target variable type for cast
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
/**
|
|
@@ -51,14 +55,26 @@ class BitRangeHelper {
|
|
|
51
55
|
/**
|
|
52
56
|
* Generate integer bit range read: ((value >> start) & mask)
|
|
53
57
|
* Optimizes away the shift when start is 0.
|
|
58
|
+
*
|
|
59
|
+
* When sourceType and targetType are provided, wraps the expression
|
|
60
|
+
* with MISRA 10.3 compliant cast if needed.
|
|
54
61
|
*/
|
|
55
62
|
static buildIntegerBitReadExpr(options: IIntegerBitReadOptions): string {
|
|
56
|
-
const { varName, start, mask } = options;
|
|
63
|
+
const { varName, start, mask, sourceType, targetType } = options;
|
|
57
64
|
|
|
65
|
+
let expr: string;
|
|
58
66
|
if (start === "0") {
|
|
59
|
-
|
|
67
|
+
expr = `((${varName}) & ${mask})`;
|
|
68
|
+
} else {
|
|
69
|
+
expr = `((${varName} >> ${start}) & ${mask})`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// If target type provided, wrap with MISRA cast if needed
|
|
73
|
+
if (sourceType && targetType) {
|
|
74
|
+
return NarrowingCastHelper.wrap(expr, sourceType, targetType);
|
|
60
75
|
}
|
|
61
|
-
|
|
76
|
+
|
|
77
|
+
return expr;
|
|
62
78
|
}
|
|
63
79
|
|
|
64
80
|
/**
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NarrowingCastHelper - MISRA C:2012 Rule 10.3 compliance
|
|
3
|
+
*
|
|
4
|
+
* Issue #845: Wraps expressions with explicit casts when assigning
|
|
5
|
+
* to narrower types or different essential type categories.
|
|
6
|
+
*
|
|
7
|
+
* C's integer promotion rules mean bit operations on u8/u16 produce int,
|
|
8
|
+
* which MISRA flags when assigned back to narrower types without explicit cast.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import TYPE_WIDTH from "../types/TYPE_WIDTH.js";
|
|
12
|
+
import CppModeHelper from "./CppModeHelper.js";
|
|
13
|
+
import TYPE_MAP from "../types/TYPE_MAP.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extended type widths including C's promoted "int" type.
|
|
17
|
+
* The shared TYPE_WIDTH doesn't include "int" since it's not a C-Next type.
|
|
18
|
+
*/
|
|
19
|
+
const EXTENDED_TYPE_WIDTH: Record<string, number> = {
|
|
20
|
+
...TYPE_WIDTH,
|
|
21
|
+
int: 32, // C's int after promotion
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Types that get promoted to int in C's integer promotion rules.
|
|
26
|
+
* In C, operations on types smaller than int get promoted to int.
|
|
27
|
+
*/
|
|
28
|
+
const PROMOTED_TO_INT = new Set(["u8", "i8", "u16", "i16", "bool"]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Float types for cross-category detection.
|
|
32
|
+
*/
|
|
33
|
+
const FLOAT_TYPES = new Set(["f32", "f64", "float", "double"]);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Integer types for cross-category detection.
|
|
37
|
+
*/
|
|
38
|
+
const INTEGER_TYPES = new Set([
|
|
39
|
+
"u8",
|
|
40
|
+
"u16",
|
|
41
|
+
"u32",
|
|
42
|
+
"u64",
|
|
43
|
+
"i8",
|
|
44
|
+
"i16",
|
|
45
|
+
"i32",
|
|
46
|
+
"i64",
|
|
47
|
+
"uint8_t",
|
|
48
|
+
"uint16_t",
|
|
49
|
+
"uint32_t",
|
|
50
|
+
"uint64_t",
|
|
51
|
+
"int8_t",
|
|
52
|
+
"int16_t",
|
|
53
|
+
"int32_t",
|
|
54
|
+
"int64_t",
|
|
55
|
+
"int",
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Helper for adding MISRA 10.3 compliant casts to generated C code.
|
|
60
|
+
*/
|
|
61
|
+
class NarrowingCastHelper {
|
|
62
|
+
/**
|
|
63
|
+
* Check if a cast is needed for MISRA 10.3 compliance.
|
|
64
|
+
* Returns true if:
|
|
65
|
+
* - Source is wider than target (narrowing)
|
|
66
|
+
* - Source and target are different essential type categories
|
|
67
|
+
*
|
|
68
|
+
* @param sourceType - Type of the expression (C-Next type or "int" for promoted)
|
|
69
|
+
* @param targetType - Type of the target variable (C-Next type)
|
|
70
|
+
*/
|
|
71
|
+
static needsCast(sourceType: string, targetType: string): boolean {
|
|
72
|
+
// Same type never needs cast
|
|
73
|
+
if (sourceType === targetType) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Bool target from non-bool source always needs conversion
|
|
78
|
+
if (targetType === "bool" && sourceType !== "bool") {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const sourceWidth = EXTENDED_TYPE_WIDTH[sourceType];
|
|
83
|
+
const targetWidth = EXTENDED_TYPE_WIDTH[targetType];
|
|
84
|
+
|
|
85
|
+
// Unknown types - be conservative, no cast
|
|
86
|
+
if (sourceWidth === undefined || targetWidth === undefined) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Narrowing: source wider than target
|
|
91
|
+
return sourceWidth > targetWidth;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Wrap expression with cast if needed for MISRA 10.3 compliance.
|
|
96
|
+
*
|
|
97
|
+
* @param expr - The generated C expression
|
|
98
|
+
* @param sourceType - Type of the expression (C-Next type or "int")
|
|
99
|
+
* @param targetType - Type of the assignment target (C-Next type)
|
|
100
|
+
* @returns Expression with cast wrapper if needed, or original expression
|
|
101
|
+
*/
|
|
102
|
+
static wrap(expr: string, sourceType: string, targetType: string): string {
|
|
103
|
+
if (!NarrowingCastHelper.needsCast(sourceType, targetType)) {
|
|
104
|
+
return expr;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Bool target: use comparison instead of cast (MISRA 10.5)
|
|
108
|
+
if (targetType === "bool") {
|
|
109
|
+
return `((${expr}) != 0U)`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Get C type name for the target
|
|
113
|
+
const cType = TYPE_MAP[targetType] ?? targetType;
|
|
114
|
+
return CppModeHelper.cast(cType, expr);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Determine the result type of C integer promotion for a given type.
|
|
119
|
+
*
|
|
120
|
+
* In C, operations on types smaller than int are promoted:
|
|
121
|
+
* - u8, i8, u16, i16, bool -> int (32-bit)
|
|
122
|
+
* - u32, i32, u64, i64 -> no promotion (already >= int width)
|
|
123
|
+
*
|
|
124
|
+
* @param baseType - The C-Next type of the operand
|
|
125
|
+
* @returns "int" for promoted types, or the original type
|
|
126
|
+
*/
|
|
127
|
+
static getPromotedType(baseType: string): string {
|
|
128
|
+
if (PROMOTED_TO_INT.has(baseType)) {
|
|
129
|
+
return "int";
|
|
130
|
+
}
|
|
131
|
+
return baseType;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if a type is a floating-point type.
|
|
136
|
+
*/
|
|
137
|
+
static isFloatType(typeName: string): boolean {
|
|
138
|
+
return FLOAT_TYPES.has(typeName);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check if a type is an integer type.
|
|
143
|
+
*/
|
|
144
|
+
static isIntegerType(typeName: string): boolean {
|
|
145
|
+
return INTEGER_TYPES.has(typeName);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if conversion between source and target is a cross-type-category conversion.
|
|
150
|
+
* MISRA 10.3 requires explicit casts for different essential type categories.
|
|
151
|
+
*/
|
|
152
|
+
static isCrossTypeCategoryConversion(
|
|
153
|
+
sourceType: string,
|
|
154
|
+
targetType: string,
|
|
155
|
+
): boolean {
|
|
156
|
+
const sourceIsFloat = NarrowingCastHelper.isFloatType(sourceType);
|
|
157
|
+
const targetIsFloat = NarrowingCastHelper.isFloatType(targetType);
|
|
158
|
+
const sourceIsInt = NarrowingCastHelper.isIntegerType(sourceType);
|
|
159
|
+
const targetIsInt = NarrowingCastHelper.isIntegerType(targetType);
|
|
160
|
+
|
|
161
|
+
// Float to integer or integer to float
|
|
162
|
+
return (sourceIsFloat && targetIsInt) || (sourceIsInt && targetIsFloat);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get the appropriate C float type for a C-Next type.
|
|
167
|
+
*/
|
|
168
|
+
static getCFloatType(typeName: string): string {
|
|
169
|
+
if (typeName === "f32" || typeName === "float") {
|
|
170
|
+
return "float";
|
|
171
|
+
}
|
|
172
|
+
if (typeName === "f64" || typeName === "double") {
|
|
173
|
+
return "double";
|
|
174
|
+
}
|
|
175
|
+
return "double"; // Default to double
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Wrap int-to-float conversion with explicit cast for MISRA 10.3.
|
|
180
|
+
*
|
|
181
|
+
* @param expr - The integer expression
|
|
182
|
+
* @param targetType - The float target type (f32/f64)
|
|
183
|
+
* @returns Expression with cast
|
|
184
|
+
*/
|
|
185
|
+
static wrapIntToFloat(expr: string, targetType: string): string {
|
|
186
|
+
const floatType = NarrowingCastHelper.getCFloatType(targetType);
|
|
187
|
+
return CppModeHelper.cast(floatType, expr);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export default NarrowingCastHelper;
|
|
@@ -18,10 +18,13 @@ import * as Parser from "../../../logic/parser/grammar/CNextParser.js";
|
|
|
18
18
|
import CodeGenState from "../../../state/CodeGenState.js";
|
|
19
19
|
import TypeResolver from "../TypeResolver.js";
|
|
20
20
|
import ArrayInitHelper from "./ArrayInitHelper.js";
|
|
21
|
+
import CppModeHelper from "./CppModeHelper.js";
|
|
21
22
|
import EnumAssignmentValidator from "./EnumAssignmentValidator.js";
|
|
22
23
|
import IntegerLiteralValidator from "./IntegerLiteralValidator.js";
|
|
24
|
+
import NarrowingCastHelper from "./NarrowingCastHelper.js";
|
|
23
25
|
import StringDeclHelper from "./StringDeclHelper.js";
|
|
24
26
|
import VariableModifierBuilder from "./VariableModifierBuilder.js";
|
|
27
|
+
import TYPE_MAP from "../types/TYPE_MAP.js";
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* Callbacks for integer validation in variable declarations.
|
|
@@ -522,8 +525,6 @@ class VariableDeclHelper {
|
|
|
522
525
|
}
|
|
523
526
|
|
|
524
527
|
const typeName = callbacks.getTypeName(typeCtx);
|
|
525
|
-
const savedExpectedType = CodeGenState.expectedType;
|
|
526
|
-
CodeGenState.expectedType = typeName;
|
|
527
528
|
|
|
528
529
|
// ADR-017: Validate enum type for initialization
|
|
529
530
|
EnumAssignmentValidator.validateEnumAssignment(typeName, ctx.expression()!);
|
|
@@ -533,10 +534,39 @@ class VariableDeclHelper {
|
|
|
533
534
|
getExpressionType: callbacks.getExpressionType,
|
|
534
535
|
});
|
|
535
536
|
|
|
536
|
-
|
|
537
|
-
|
|
537
|
+
// Issue #872: Set expectedType for MISRA 7.2 U suffix compliance
|
|
538
|
+
// MISRA 10.3: Also check for cross-type-category conversions (int <-> float)
|
|
539
|
+
return CodeGenState.withExpectedType(typeName, () => {
|
|
540
|
+
let exprCode = callbacks.generateExpression(ctx.expression()!);
|
|
541
|
+
|
|
542
|
+
// MISRA 10.3: Check for cross-type-category conversions (int <-> float)
|
|
543
|
+
const exprType = callbacks.getExpressionType(ctx.expression()!);
|
|
544
|
+
if (
|
|
545
|
+
exprType &&
|
|
546
|
+
NarrowingCastHelper.isCrossTypeCategoryConversion(exprType, typeName)
|
|
547
|
+
) {
|
|
548
|
+
// Int to float: add explicit cast
|
|
549
|
+
if (
|
|
550
|
+
NarrowingCastHelper.isIntegerType(exprType) &&
|
|
551
|
+
NarrowingCastHelper.isFloatType(typeName)
|
|
552
|
+
) {
|
|
553
|
+
exprCode = NarrowingCastHelper.wrapIntToFloat(exprCode, typeName);
|
|
554
|
+
}
|
|
555
|
+
// Float to int: add explicit cast for MISRA compliance
|
|
556
|
+
// Note: For safety, users should use explicit cast in C-Next source: (i32)float
|
|
557
|
+
// which generates a clamping expression. This implicit cast is just for
|
|
558
|
+
// MISRA 10.3 compliance when user omits explicit cast.
|
|
559
|
+
if (
|
|
560
|
+
NarrowingCastHelper.isFloatType(exprType) &&
|
|
561
|
+
NarrowingCastHelper.isIntegerType(typeName)
|
|
562
|
+
) {
|
|
563
|
+
const cType = TYPE_MAP[typeName] ?? typeName;
|
|
564
|
+
exprCode = CppModeHelper.cast(cType, exprCode);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
538
567
|
|
|
539
|
-
|
|
568
|
+
return `${decl} = ${exprCode}`;
|
|
569
|
+
});
|
|
540
570
|
}
|
|
541
571
|
|
|
542
572
|
// ========================================================================
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* Unit tests for ArrayAccessHelper utility.
|
|
3
3
|
* Tests array access code generation patterns without ANTLR dependencies.
|
|
4
4
|
*/
|
|
5
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
6
6
|
import ArrayAccessHelper from "../ArrayAccessHelper";
|
|
7
7
|
import IArrayAccessInfo from "../../types/IArrayAccessInfo";
|
|
8
8
|
import IArrayAccessDeps from "../../types/IArrayAccessDeps";
|
|
9
|
+
import CodeGenState from "../../../../state/CodeGenState.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Create mock dependencies for testing.
|
|
@@ -478,4 +479,133 @@ describe("ArrayAccessHelper", () => {
|
|
|
478
479
|
expect(mockDeps.generateBitMask).toHaveBeenCalledWith("8", false);
|
|
479
480
|
});
|
|
480
481
|
});
|
|
482
|
+
|
|
483
|
+
describe("generateIntegerBitRange with MISRA casts", () => {
|
|
484
|
+
let mockDeps: IArrayAccessDeps;
|
|
485
|
+
|
|
486
|
+
beforeEach(() => {
|
|
487
|
+
CodeGenState.reset();
|
|
488
|
+
CodeGenState.cppMode = false;
|
|
489
|
+
mockDeps = createMockDeps();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
afterEach(() => {
|
|
493
|
+
CodeGenState.reset();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("adds cast when target type is narrower", () => {
|
|
497
|
+
// Configure mock to return the actual mask for u8 width
|
|
498
|
+
(mockDeps.generateBitMask as ReturnType<typeof vi.fn>).mockReturnValue(
|
|
499
|
+
"0xFFU",
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
const info: IArrayAccessInfo = {
|
|
503
|
+
rawName: "value",
|
|
504
|
+
resolvedName: "value",
|
|
505
|
+
accessType: "bit-range",
|
|
506
|
+
startExpr: "0",
|
|
507
|
+
widthExpr: "8",
|
|
508
|
+
typeInfo: {
|
|
509
|
+
baseType: "u32",
|
|
510
|
+
bitWidth: 32,
|
|
511
|
+
isArray: false,
|
|
512
|
+
isConst: false,
|
|
513
|
+
},
|
|
514
|
+
targetType: "u8",
|
|
515
|
+
line: 1,
|
|
516
|
+
};
|
|
517
|
+
const result = ArrayAccessHelper.generateIntegerBitRange(info, mockDeps);
|
|
518
|
+
expect(result).toBe("(uint8_t)((value) & 0xFFU)");
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it("no cast when target type matches source", () => {
|
|
522
|
+
(mockDeps.generateBitMask as ReturnType<typeof vi.fn>).mockReturnValue(
|
|
523
|
+
"0xFFFFU",
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
const info: IArrayAccessInfo = {
|
|
527
|
+
rawName: "value",
|
|
528
|
+
resolvedName: "value",
|
|
529
|
+
accessType: "bit-range",
|
|
530
|
+
startExpr: "8",
|
|
531
|
+
widthExpr: "16",
|
|
532
|
+
typeInfo: {
|
|
533
|
+
baseType: "u32",
|
|
534
|
+
bitWidth: 32,
|
|
535
|
+
isArray: false,
|
|
536
|
+
isConst: false,
|
|
537
|
+
},
|
|
538
|
+
targetType: "u32",
|
|
539
|
+
line: 1,
|
|
540
|
+
};
|
|
541
|
+
const result = ArrayAccessHelper.generateIntegerBitRange(info, mockDeps);
|
|
542
|
+
expect(result).toBe("((value >> 8) & 0xFFFFU)");
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it("no cast when targetType not provided (backward compatible)", () => {
|
|
546
|
+
(mockDeps.generateBitMask as ReturnType<typeof vi.fn>).mockReturnValue(
|
|
547
|
+
"0xFFU",
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
const info: IArrayAccessInfo = {
|
|
551
|
+
rawName: "value",
|
|
552
|
+
resolvedName: "value",
|
|
553
|
+
accessType: "bit-range",
|
|
554
|
+
startExpr: "0",
|
|
555
|
+
widthExpr: "8",
|
|
556
|
+
typeInfo: {
|
|
557
|
+
baseType: "u32",
|
|
558
|
+
bitWidth: 32,
|
|
559
|
+
isArray: false,
|
|
560
|
+
isConst: false,
|
|
561
|
+
},
|
|
562
|
+
line: 1,
|
|
563
|
+
};
|
|
564
|
+
const result = ArrayAccessHelper.generateIntegerBitRange(info, mockDeps);
|
|
565
|
+
expect(result).toBe("((value) & 0xFFU)");
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it("adds cast for u16 target from u32 source", () => {
|
|
569
|
+
(mockDeps.generateBitMask as ReturnType<typeof vi.fn>).mockReturnValue(
|
|
570
|
+
"0xFFFFU",
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
const info: IArrayAccessInfo = {
|
|
574
|
+
rawName: "data",
|
|
575
|
+
resolvedName: "data",
|
|
576
|
+
accessType: "bit-range",
|
|
577
|
+
startExpr: "16",
|
|
578
|
+
widthExpr: "16",
|
|
579
|
+
typeInfo: {
|
|
580
|
+
baseType: "u32",
|
|
581
|
+
bitWidth: 32,
|
|
582
|
+
isArray: false,
|
|
583
|
+
isConst: false,
|
|
584
|
+
},
|
|
585
|
+
targetType: "u16",
|
|
586
|
+
line: 1,
|
|
587
|
+
};
|
|
588
|
+
const result = ArrayAccessHelper.generateIntegerBitRange(info, mockDeps);
|
|
589
|
+
expect(result).toBe("(uint16_t)((data >> 16) & 0xFFFFU)");
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it("no cast when no typeInfo provided", () => {
|
|
593
|
+
(mockDeps.generateBitMask as ReturnType<typeof vi.fn>).mockReturnValue(
|
|
594
|
+
"0xFFU",
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
const info: IArrayAccessInfo = {
|
|
598
|
+
rawName: "value",
|
|
599
|
+
resolvedName: "value",
|
|
600
|
+
accessType: "bit-range",
|
|
601
|
+
startExpr: "0",
|
|
602
|
+
widthExpr: "8",
|
|
603
|
+
targetType: "u8",
|
|
604
|
+
line: 1,
|
|
605
|
+
};
|
|
606
|
+
const result = ArrayAccessHelper.generateIntegerBitRange(info, mockDeps);
|
|
607
|
+
// No cast because sourceType is undefined
|
|
608
|
+
expect(result).toBe("((value) & 0xFFU)");
|
|
609
|
+
});
|
|
610
|
+
});
|
|
481
611
|
});
|