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.
Files changed (71) hide show
  1. package/dist/index.js +1019 -267
  2. package/dist/index.js.map +4 -4
  3. package/package.json +1 -1
  4. package/src/transpiler/Transpiler.ts +101 -8
  5. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +170 -0
  6. package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +246 -0
  7. package/src/transpiler/logic/IncludeExtractor.ts +12 -6
  8. package/src/transpiler/logic/__tests__/IncludeExtractor.test.ts +24 -0
  9. package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +12 -1
  10. package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +172 -0
  11. package/src/transpiler/logic/symbols/SymbolTable.ts +84 -0
  12. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +43 -0
  13. package/src/transpiler/logic/symbols/__tests__/TransitiveEnumCollector.test.ts +1 -0
  14. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +98 -1
  15. package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +23 -5
  16. package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +69 -2
  17. package/src/transpiler/logic/symbols/c/index.ts +85 -30
  18. package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +18 -0
  19. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +90 -0
  20. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +40 -39
  21. package/src/transpiler/output/codegen/CodeGenerator.ts +55 -25
  22. package/src/transpiler/output/codegen/TypeResolver.ts +14 -0
  23. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +3 -3
  24. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +35 -30
  25. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +7 -7
  26. package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +1 -0
  27. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +27 -4
  28. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +6 -0
  29. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +73 -0
  30. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +4 -0
  31. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +92 -0
  32. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +5 -2
  33. package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +1 -0
  34. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +14 -0
  35. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +25 -3
  36. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +49 -0
  37. package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +17 -5
  38. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +26 -6
  39. package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +31 -2
  40. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +72 -13
  41. package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +25 -1
  42. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +2 -1
  43. package/src/transpiler/output/codegen/generators/statements/AtomicGenerator.ts +2 -17
  44. package/src/transpiler/output/codegen/generators/support/IncludeGenerator.ts +19 -7
  45. package/src/transpiler/output/codegen/generators/support/__tests__/IncludeGenerator.test.ts +68 -0
  46. package/src/transpiler/output/codegen/helpers/ArgumentGenerator.ts +14 -2
  47. package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +3 -0
  48. package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +3 -5
  49. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +56 -8
  50. package/src/transpiler/output/codegen/helpers/BitRangeHelper.ts +19 -3
  51. package/src/transpiler/output/codegen/helpers/NarrowingCastHelper.ts +191 -0
  52. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +35 -5
  53. package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +131 -1
  54. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +61 -2
  55. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -0
  56. package/src/transpiler/output/codegen/helpers/__tests__/BitRangeHelper.test.ts +53 -2
  57. package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +1 -0
  58. package/src/transpiler/output/codegen/helpers/__tests__/NarrowingCastHelper.test.ts +159 -0
  59. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +1 -0
  60. package/src/transpiler/output/codegen/types/COMPOUND_TO_BINARY.ts +21 -0
  61. package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +2 -0
  62. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +14 -16
  63. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +25 -0
  64. package/src/transpiler/state/CodeGenState.ts +89 -0
  65. package/src/transpiler/state/__tests__/CodeGenState.test.ts +53 -0
  66. package/src/transpiler/state/__tests__/TranspilerState.test.ts +1 -0
  67. package/src/transpiler/types/ICachedFileEntry.ts +2 -0
  68. package/src/transpiler/types/ICodeGenSymbols.ts +8 -0
  69. package/src/utils/BitUtils.ts +33 -4
  70. package/src/utils/cache/CacheManager.ts +9 -1
  71. 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
- return callbacks.generateExpression(ctx);
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
- const value = callbacks.generateExpression(ctx);
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
- const savedExpectedType = CodeGenState.expectedType;
110
- CodeGenState.expectedType = typeName;
111
- const initValue = callbacks.generateExpression(expression);
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 member access - extract identifiers from postfix chain
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
- // If we have member access (multiple identifiers), resolve for member chain
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: Array access or complex patterns - no expected type resolution
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
- return `((${varName}) & ${mask})`;
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
- return `((${varName} >> ${start}) & ${mask})`;
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
- const result = `${decl} = ${callbacks.generateExpression(ctx.expression()!)}`;
537
- CodeGenState.expectedType = savedExpectedType;
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
- return result;
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
  });