c-next 0.1.62 → 0.1.63
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 +86 -63
- package/package.json +1 -1
- package/src/transpiler/Transpiler.ts +3 -2
- package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +1 -1
- package/src/transpiler/__tests__/Transpiler.test.ts +0 -23
- package/src/transpiler/logic/symbols/cnext/collectors/StructCollector.ts +156 -70
- package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +31 -6
- package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +43 -11
- package/src/transpiler/output/codegen/CodeGenState.ts +811 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +817 -1377
- package/src/transpiler/output/codegen/TypeResolver.ts +193 -149
- package/src/transpiler/output/codegen/TypeValidator.ts +148 -370
- package/src/transpiler/output/codegen/__tests__/CodeGenState.test.ts +446 -0
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +326 -60
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +1 -1
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +435 -196
- package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +51 -67
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +495 -471
- package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +39 -43
- package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +52 -55
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +122 -62
- package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +101 -144
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +143 -126
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +287 -320
- package/src/transpiler/output/codegen/generators/GeneratorRegistry.ts +12 -0
- package/src/transpiler/output/codegen/generators/__tests__/GeneratorRegistry.test.ts +28 -1
- package/src/transpiler/output/codegen/generators/declarationGenerators/ArrayDimensionUtils.ts +67 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +121 -51
- package/src/transpiler/output/codegen/generators/declarationGenerators/StructGenerator.ts +100 -23
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ArrayDimensionUtils.test.ts +125 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +157 -4
- package/src/transpiler/output/codegen/generators/support/HelperGenerator.ts +23 -22
- package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +54 -61
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +21 -30
- package/src/transpiler/output/codegen/helpers/AssignmentValidator.ts +56 -53
- package/src/transpiler/output/codegen/helpers/CppModeHelper.ts +22 -30
- package/src/transpiler/output/codegen/helpers/EnumAssignmentValidator.ts +108 -50
- package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +16 -31
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +103 -96
- package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +9 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayInitHelper.test.ts +58 -103
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +97 -40
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +223 -128
- package/src/transpiler/output/codegen/helpers/__tests__/CppModeHelper.test.ts +68 -41
- package/src/transpiler/output/codegen/helpers/__tests__/EnumAssignmentValidator.test.ts +198 -47
- package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +39 -37
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +191 -453
- package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +229 -0
- package/src/transpiler/output/codegen/resolution/ScopeResolver.ts +60 -0
- package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +177 -0
- package/src/transpiler/output/codegen/resolution/__tests__/EnumTypeResolver.test.ts +336 -0
- package/src/transpiler/output/codegen/resolution/__tests__/SizeofResolver.test.ts +201 -0
- package/src/transpiler/output/codegen/types/ITypeResolverDeps.ts +0 -23
- package/src/transpiler/output/codegen/types/ITypeValidatorDeps.ts +0 -53
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TypeValidator - Handles compile-time validation of types, assignments, and control flow
|
|
3
|
-
*
|
|
3
|
+
* Static class using CodeGenState for all state access.
|
|
4
4
|
* Issue #63: Validation logic separated for independent testing
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { dirname, resolve, join } from "node:path";
|
|
8
8
|
import * as Parser from "../../logic/parser/grammar/CNextParser";
|
|
9
|
-
import
|
|
10
|
-
import SymbolTable from "../../logic/symbols/SymbolTable";
|
|
11
|
-
import TTypeInfo from "./types/TTypeInfo";
|
|
12
|
-
import TParameterInfo from "./types/TParameterInfo";
|
|
13
|
-
import ICallbackTypeInfo from "./types/ICallbackTypeInfo";
|
|
14
|
-
import ITypeValidatorDeps from "./types/ITypeValidatorDeps";
|
|
9
|
+
import CodeGenState from "./CodeGenState";
|
|
15
10
|
import TypeResolver from "./TypeResolver";
|
|
16
11
|
import ExpressionUtils from "../../../utils/ExpressionUtils";
|
|
17
12
|
// SonarCloud S3776: Extracted literal parsing to reduce complexity
|
|
@@ -29,39 +24,10 @@ const IMPLEMENTATION_EXTENSIONS = new Set([
|
|
|
29
24
|
]);
|
|
30
25
|
|
|
31
26
|
/**
|
|
32
|
-
* TypeValidator class - validates types, assignments, and control flow at compile time
|
|
27
|
+
* TypeValidator class - validates types, assignments, and control flow at compile time.
|
|
28
|
+
* All methods are static - uses CodeGenState for state access.
|
|
33
29
|
*/
|
|
34
30
|
class TypeValidator {
|
|
35
|
-
private readonly symbols: ICodeGenSymbols | null;
|
|
36
|
-
private readonly symbolTable: SymbolTable | null;
|
|
37
|
-
private readonly typeRegistry: Map<string, TTypeInfo>;
|
|
38
|
-
private readonly typeResolver: TypeResolver;
|
|
39
|
-
private readonly callbackTypes: Map<string, ICallbackTypeInfo>;
|
|
40
|
-
private readonly knownFunctions: Set<string>;
|
|
41
|
-
private readonly knownGlobals: Set<string>;
|
|
42
|
-
private readonly getCurrentScopeFn: () => string | null;
|
|
43
|
-
private readonly getScopeMembersFn: () => Map<string, Set<string>>;
|
|
44
|
-
private readonly getCurrentParametersFn: () => Map<string, TParameterInfo>;
|
|
45
|
-
private readonly getLocalVariablesFn: () => Set<string>;
|
|
46
|
-
private readonly resolveIdentifierFn: (name: string) => string;
|
|
47
|
-
private readonly getExpressionTypeFn: (ctx: unknown) => string | null;
|
|
48
|
-
|
|
49
|
-
constructor(deps: ITypeValidatorDeps) {
|
|
50
|
-
this.symbols = deps.symbols;
|
|
51
|
-
this.symbolTable = deps.symbolTable;
|
|
52
|
-
this.typeRegistry = deps.typeRegistry;
|
|
53
|
-
this.typeResolver = deps.typeResolver;
|
|
54
|
-
this.callbackTypes = deps.callbackTypes;
|
|
55
|
-
this.knownFunctions = deps.knownFunctions;
|
|
56
|
-
this.knownGlobals = deps.knownGlobals;
|
|
57
|
-
this.getCurrentScopeFn = deps.getCurrentScope;
|
|
58
|
-
this.getScopeMembersFn = deps.getScopeMembers;
|
|
59
|
-
this.getCurrentParametersFn = deps.getCurrentParameters;
|
|
60
|
-
this.getLocalVariablesFn = deps.getLocalVariables;
|
|
61
|
-
this.resolveIdentifierFn = deps.resolveIdentifier;
|
|
62
|
-
this.getExpressionTypeFn = deps.getExpressionType;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
31
|
// ========================================================================
|
|
66
32
|
// Include Validation (ADR-010)
|
|
67
33
|
// ========================================================================
|
|
@@ -69,21 +35,18 @@ class TypeValidator {
|
|
|
69
35
|
/**
|
|
70
36
|
* ADR-010: Validate that #include doesn't include implementation files
|
|
71
37
|
*/
|
|
72
|
-
validateIncludeNotImplementationFile(
|
|
38
|
+
static validateIncludeNotImplementationFile(
|
|
73
39
|
includeText: string,
|
|
74
40
|
lineNumber: number,
|
|
75
41
|
): void {
|
|
76
|
-
// Extract the file path from #include directive
|
|
77
|
-
// Match both <file> and "file" forms
|
|
78
42
|
const angleMatch = /#\s*include\s*<([^>]+)>/.exec(includeText);
|
|
79
43
|
const quoteMatch = /#\s*include\s*"([^"]+)"/.exec(includeText);
|
|
80
44
|
|
|
81
45
|
const includePath = angleMatch?.[1] || quoteMatch?.[1];
|
|
82
46
|
if (!includePath) {
|
|
83
|
-
return;
|
|
47
|
+
return;
|
|
84
48
|
}
|
|
85
49
|
|
|
86
|
-
// Get the file extension (lowercase for case-insensitive comparison)
|
|
87
50
|
const ext = includePath
|
|
88
51
|
.substring(includePath.lastIndexOf("."))
|
|
89
52
|
.toLowerCase();
|
|
@@ -98,31 +61,23 @@ class TypeValidator {
|
|
|
98
61
|
|
|
99
62
|
/**
|
|
100
63
|
* E0504: Validate that a .cnx alternative doesn't exist for a .h/.hpp include
|
|
101
|
-
* This helps during codebase migration by alerting developers when they should
|
|
102
|
-
* be using the C-Next version of a file instead of the C header.
|
|
103
|
-
*
|
|
104
|
-
* @param includeText - The full #include directive text
|
|
105
|
-
* @param lineNumber - Line number for error reporting
|
|
106
|
-
* @param sourcePath - Path to the source file (for resolving relative includes)
|
|
107
|
-
* @param includePaths - Array of directories to search for includes
|
|
108
|
-
* @param fileExists - Function to check if a file exists (injectable for testing)
|
|
109
64
|
*/
|
|
110
|
-
validateIncludeNoCnxAlternative(
|
|
65
|
+
static validateIncludeNoCnxAlternative(
|
|
111
66
|
includeText: string,
|
|
112
67
|
lineNumber: number,
|
|
113
68
|
sourcePath: string | null,
|
|
114
69
|
includePaths: string[],
|
|
115
70
|
fileExists: (path: string) => boolean = existsSync,
|
|
116
71
|
): void {
|
|
117
|
-
const parsed =
|
|
72
|
+
const parsed = TypeValidator._parseIncludeDirective(includeText);
|
|
118
73
|
if (!parsed) return;
|
|
119
74
|
if (parsed.path.endsWith(".cnx")) return;
|
|
120
|
-
if (!
|
|
75
|
+
if (!TypeValidator._isHeaderFile(parsed.path)) return;
|
|
121
76
|
|
|
122
77
|
const cnxPath = parsed.path.replace(/\.(h|hpp)$/i, ".cnx");
|
|
123
78
|
|
|
124
79
|
if (parsed.isQuoted) {
|
|
125
|
-
|
|
80
|
+
TypeValidator._checkQuotedIncludeForCnx(
|
|
126
81
|
parsed.path,
|
|
127
82
|
cnxPath,
|
|
128
83
|
sourcePath,
|
|
@@ -130,7 +85,7 @@ class TypeValidator {
|
|
|
130
85
|
fileExists,
|
|
131
86
|
);
|
|
132
87
|
} else {
|
|
133
|
-
|
|
88
|
+
TypeValidator._checkAngleIncludeForCnx(
|
|
134
89
|
parsed.path,
|
|
135
90
|
cnxPath,
|
|
136
91
|
includePaths,
|
|
@@ -140,10 +95,7 @@ class TypeValidator {
|
|
|
140
95
|
}
|
|
141
96
|
}
|
|
142
97
|
|
|
143
|
-
|
|
144
|
-
* Parse an include directive to extract path and type
|
|
145
|
-
*/
|
|
146
|
-
private _parseIncludeDirective(
|
|
98
|
+
private static _parseIncludeDirective(
|
|
147
99
|
includeText: string,
|
|
148
100
|
): { path: string; isQuoted: boolean } | null {
|
|
149
101
|
const angleMatch = /#\s*include\s*<([^>]+)>/.exec(includeText);
|
|
@@ -154,18 +106,12 @@ class TypeValidator {
|
|
|
154
106
|
return null;
|
|
155
107
|
}
|
|
156
108
|
|
|
157
|
-
|
|
158
|
-
* Check if a path is a header file (.h or .hpp)
|
|
159
|
-
*/
|
|
160
|
-
private _isHeaderFile(path: string): boolean {
|
|
109
|
+
private static _isHeaderFile(path: string): boolean {
|
|
161
110
|
const ext = path.substring(path.lastIndexOf(".")).toLowerCase();
|
|
162
111
|
return ext === ".h" || ext === ".hpp";
|
|
163
112
|
}
|
|
164
113
|
|
|
165
|
-
|
|
166
|
-
* Check quoted include for .cnx alternative
|
|
167
|
-
*/
|
|
168
|
-
private _checkQuotedIncludeForCnx(
|
|
114
|
+
private static _checkQuotedIncludeForCnx(
|
|
169
115
|
includePath: string,
|
|
170
116
|
cnxPath: string,
|
|
171
117
|
sourcePath: string | null,
|
|
@@ -184,10 +130,7 @@ class TypeValidator {
|
|
|
184
130
|
}
|
|
185
131
|
}
|
|
186
132
|
|
|
187
|
-
|
|
188
|
-
* Check angle bracket include for .cnx alternative in search paths
|
|
189
|
-
*/
|
|
190
|
-
private _checkAngleIncludeForCnx(
|
|
133
|
+
private static _checkAngleIncludeForCnx(
|
|
191
134
|
includePath: string,
|
|
192
135
|
cnxPath: string,
|
|
193
136
|
includePaths: string[],
|
|
@@ -209,10 +152,7 @@ class TypeValidator {
|
|
|
209
152
|
// Bitmap Field Validation (ADR-034)
|
|
210
153
|
// ========================================================================
|
|
211
154
|
|
|
212
|
-
|
|
213
|
-
* ADR-034: Validate that a literal value fits in a bitmap field
|
|
214
|
-
*/
|
|
215
|
-
validateBitmapFieldLiteral(
|
|
155
|
+
static validateBitmapFieldLiteral(
|
|
216
156
|
expr: Parser.ExpressionContext,
|
|
217
157
|
width: number,
|
|
218
158
|
fieldName: string,
|
|
@@ -220,7 +160,6 @@ class TypeValidator {
|
|
|
220
160
|
const text = expr.getText().trim();
|
|
221
161
|
const maxValue = (1 << width) - 1;
|
|
222
162
|
|
|
223
|
-
// Check for integer literals
|
|
224
163
|
let value: number | null = null;
|
|
225
164
|
|
|
226
165
|
if (/^\d+$/.exec(text)) {
|
|
@@ -242,11 +181,7 @@ class TypeValidator {
|
|
|
242
181
|
// Array Bounds Validation (ADR-036)
|
|
243
182
|
// ========================================================================
|
|
244
183
|
|
|
245
|
-
|
|
246
|
-
* ADR-036: Check array bounds at compile time for constant indices.
|
|
247
|
-
* Throws an error if the constant index is out of bounds.
|
|
248
|
-
*/
|
|
249
|
-
checkArrayBounds(
|
|
184
|
+
static checkArrayBounds(
|
|
250
185
|
arrayName: string,
|
|
251
186
|
dimensions: number[],
|
|
252
187
|
indexExprs: Parser.ExpressionContext[],
|
|
@@ -261,7 +196,6 @@ class TypeValidator {
|
|
|
261
196
|
`Array index out of bounds: ${constValue} is negative for '${arrayName}' dimension ${i + 1} (line ${line})`,
|
|
262
197
|
);
|
|
263
198
|
} else if (dimensions[i] > 0 && constValue >= dimensions[i]) {
|
|
264
|
-
// Issue #547: Skip upper bound check for unsized dimensions (size 0)
|
|
265
199
|
throw new Error(
|
|
266
200
|
`Array index out of bounds: ${constValue} >= ${dimensions[i]} for '${arrayName}' dimension ${i + 1} (line ${line})`,
|
|
267
201
|
);
|
|
@@ -274,12 +208,7 @@ class TypeValidator {
|
|
|
274
208
|
// Callback Assignment Validation (ADR-029)
|
|
275
209
|
// ========================================================================
|
|
276
210
|
|
|
277
|
-
|
|
278
|
-
* ADR-029: Validate callback assignment with nominal typing
|
|
279
|
-
* - If value IS a callback type used as a field type: must match exactly (nominal typing)
|
|
280
|
-
* - If value is just a function (not used as a type): signature must match
|
|
281
|
-
*/
|
|
282
|
-
validateCallbackAssignment(
|
|
211
|
+
static validateCallbackAssignment(
|
|
283
212
|
expectedType: string,
|
|
284
213
|
valueExpr: Parser.ExpressionContext,
|
|
285
214
|
fieldName: string,
|
|
@@ -287,30 +216,23 @@ class TypeValidator {
|
|
|
287
216
|
): void {
|
|
288
217
|
const valueText = valueExpr.getText();
|
|
289
218
|
|
|
290
|
-
|
|
291
|
-
if (!this.knownFunctions.has(valueText)) {
|
|
292
|
-
// Not a function name - could be a variable holding a callback
|
|
293
|
-
// Skip validation for now (C compiler will catch type mismatches)
|
|
219
|
+
if (!CodeGenState.knownFunctions.has(valueText)) {
|
|
294
220
|
return;
|
|
295
221
|
}
|
|
296
222
|
|
|
297
|
-
const expectedInfo =
|
|
298
|
-
const valueInfo =
|
|
223
|
+
const expectedInfo = CodeGenState.callbackTypes.get(expectedType);
|
|
224
|
+
const valueInfo = CodeGenState.callbackTypes.get(valueText);
|
|
299
225
|
|
|
300
226
|
if (!expectedInfo || !valueInfo) {
|
|
301
|
-
// Shouldn't happen, but guard against it
|
|
302
227
|
return;
|
|
303
228
|
}
|
|
304
229
|
|
|
305
|
-
|
|
306
|
-
if (!this.callbackSignaturesMatch(expectedInfo, valueInfo)) {
|
|
230
|
+
if (!TypeValidator.callbackSignaturesMatch(expectedInfo, valueInfo)) {
|
|
307
231
|
throw new Error(
|
|
308
232
|
`Error: Function '${valueText}' signature does not match callback type '${expectedType}'`,
|
|
309
233
|
);
|
|
310
234
|
}
|
|
311
235
|
|
|
312
|
-
// Nominal typing: if the value function is used as a field type somewhere,
|
|
313
|
-
// it can only be assigned to fields of that same type
|
|
314
236
|
if (
|
|
315
237
|
isCallbackTypeUsedAsFieldType(valueText) &&
|
|
316
238
|
valueText !== expectedType
|
|
@@ -322,10 +244,26 @@ class TypeValidator {
|
|
|
322
244
|
}
|
|
323
245
|
}
|
|
324
246
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
247
|
+
static callbackSignaturesMatch(
|
|
248
|
+
a: {
|
|
249
|
+
returnType: string;
|
|
250
|
+
parameters: {
|
|
251
|
+
type: string;
|
|
252
|
+
isConst: boolean;
|
|
253
|
+
isPointer: boolean;
|
|
254
|
+
isArray: boolean;
|
|
255
|
+
}[];
|
|
256
|
+
},
|
|
257
|
+
b: {
|
|
258
|
+
returnType: string;
|
|
259
|
+
parameters: {
|
|
260
|
+
type: string;
|
|
261
|
+
isConst: boolean;
|
|
262
|
+
isPointer: boolean;
|
|
263
|
+
isArray: boolean;
|
|
264
|
+
}[];
|
|
265
|
+
},
|
|
266
|
+
): boolean {
|
|
329
267
|
if (a.returnType !== b.returnType) return false;
|
|
330
268
|
if (a.parameters.length !== b.parameters.length) return false;
|
|
331
269
|
|
|
@@ -345,41 +283,29 @@ class TypeValidator {
|
|
|
345
283
|
// Const Assignment Validation (ADR-013)
|
|
346
284
|
// ========================================================================
|
|
347
285
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
* Returns error message if const, null if mutable.
|
|
351
|
-
*/
|
|
352
|
-
checkConstAssignment(identifier: string): string | null {
|
|
353
|
-
// Check if it's a const parameter
|
|
354
|
-
const paramInfo = this.getCurrentParametersFn().get(identifier);
|
|
286
|
+
static checkConstAssignment(identifier: string): string | null {
|
|
287
|
+
const paramInfo = CodeGenState.currentParameters.get(identifier);
|
|
355
288
|
if (paramInfo?.isConst) {
|
|
356
289
|
return `cannot assign to const parameter '${identifier}'`;
|
|
357
290
|
}
|
|
358
291
|
|
|
359
|
-
|
|
360
|
-
const scopedName = this.resolveIdentifierFn(identifier);
|
|
292
|
+
const scopedName = CodeGenState.resolveIdentifier(identifier);
|
|
361
293
|
|
|
362
|
-
|
|
363
|
-
const typeInfo = this.typeRegistry.get(scopedName);
|
|
294
|
+
const typeInfo = CodeGenState.typeRegistry.get(scopedName);
|
|
364
295
|
if (typeInfo?.isConst) {
|
|
365
296
|
return `cannot assign to const variable '${identifier}'`;
|
|
366
297
|
}
|
|
367
298
|
|
|
368
|
-
return null;
|
|
299
|
+
return null;
|
|
369
300
|
}
|
|
370
301
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
*/
|
|
374
|
-
isConstValue(identifier: string): boolean {
|
|
375
|
-
// Check if it's a const parameter
|
|
376
|
-
const paramInfo = this.getCurrentParametersFn().get(identifier);
|
|
302
|
+
static isConstValue(identifier: string): boolean {
|
|
303
|
+
const paramInfo = CodeGenState.currentParameters.get(identifier);
|
|
377
304
|
if (paramInfo?.isConst) {
|
|
378
305
|
return true;
|
|
379
306
|
}
|
|
380
307
|
|
|
381
|
-
|
|
382
|
-
const typeInfo = this.typeRegistry.get(identifier);
|
|
308
|
+
const typeInfo = CodeGenState.typeRegistry.get(identifier);
|
|
383
309
|
if (typeInfo?.isConst) {
|
|
384
310
|
return true;
|
|
385
311
|
}
|
|
@@ -391,44 +317,36 @@ class TypeValidator {
|
|
|
391
317
|
// Scope Identifier Validation (ADR-016)
|
|
392
318
|
// ========================================================================
|
|
393
319
|
|
|
394
|
-
|
|
395
|
-
* ADR-016: Validate that bare identifiers inside scopes are only used for local variables.
|
|
396
|
-
* Throws an error if a bare identifier references a scope member or global.
|
|
397
|
-
*/
|
|
398
|
-
validateBareIdentifierInScope(
|
|
320
|
+
static validateBareIdentifierInScope(
|
|
399
321
|
identifier: string,
|
|
400
322
|
isLocalVariable: boolean,
|
|
401
323
|
isKnownStruct: (name: string) => boolean,
|
|
402
324
|
): void {
|
|
403
|
-
const currentScope =
|
|
325
|
+
const currentScope = CodeGenState.currentScope;
|
|
404
326
|
|
|
405
|
-
// Only enforce inside scopes
|
|
406
327
|
if (!currentScope) {
|
|
407
328
|
return;
|
|
408
329
|
}
|
|
409
330
|
|
|
410
|
-
// Local variables and parameters are allowed as bare identifiers
|
|
411
331
|
if (isLocalVariable) {
|
|
412
332
|
return;
|
|
413
333
|
}
|
|
414
334
|
|
|
415
|
-
|
|
416
|
-
const scopeMembers = this.getScopeMembersFn().get(currentScope);
|
|
335
|
+
const scopeMembers = CodeGenState.scopeMembers.get(currentScope);
|
|
417
336
|
if (scopeMembers?.has(identifier)) {
|
|
418
337
|
throw new Error(
|
|
419
338
|
`Error: Use 'this.${identifier}' to access scope member '${identifier}' inside scope '${currentScope}'`,
|
|
420
339
|
);
|
|
421
340
|
}
|
|
422
341
|
|
|
423
|
-
|
|
424
|
-
if (this.symbols!.knownRegisters.has(identifier)) {
|
|
342
|
+
if (CodeGenState.symbols!.knownRegisters.has(identifier)) {
|
|
425
343
|
throw new Error(
|
|
426
344
|
`Error: Use 'global.${identifier}' to access register '${identifier}' inside scope '${currentScope}'`,
|
|
427
345
|
);
|
|
428
346
|
}
|
|
429
347
|
|
|
430
348
|
if (
|
|
431
|
-
|
|
349
|
+
CodeGenState.knownFunctions.has(identifier) &&
|
|
432
350
|
!identifier.startsWith(currentScope + "_")
|
|
433
351
|
) {
|
|
434
352
|
throw new Error(
|
|
@@ -436,7 +354,7 @@ class TypeValidator {
|
|
|
436
354
|
);
|
|
437
355
|
}
|
|
438
356
|
|
|
439
|
-
if (
|
|
357
|
+
if (CodeGenState.symbols!.knownEnums.has(identifier)) {
|
|
440
358
|
throw new Error(
|
|
441
359
|
`Error: Use 'global.${identifier}' to access global enum '${identifier}' inside scope '${currentScope}'`,
|
|
442
360
|
);
|
|
@@ -448,9 +366,7 @@ class TypeValidator {
|
|
|
448
366
|
);
|
|
449
367
|
}
|
|
450
368
|
|
|
451
|
-
|
|
452
|
-
// (but not a scoped variable - those would have Scope_ prefix)
|
|
453
|
-
const typeInfo = this.typeRegistry.get(identifier);
|
|
369
|
+
const typeInfo = CodeGenState.typeRegistry.get(identifier);
|
|
454
370
|
if (typeInfo && !identifier.includes("_")) {
|
|
455
371
|
throw new Error(
|
|
456
372
|
`Error: Use 'global.${identifier}' to access global variable '${identifier}' inside scope '${currentScope}'`,
|
|
@@ -458,103 +374,81 @@ class TypeValidator {
|
|
|
458
374
|
}
|
|
459
375
|
}
|
|
460
376
|
|
|
461
|
-
|
|
462
|
-
* Resolve a bare identifier to its qualified name using priority:
|
|
463
|
-
* 1. Local variables/parameters (return null - no transformation needed)
|
|
464
|
-
* 2. Scope members (return Scope_identifier)
|
|
465
|
-
* 3. Global variables/functions (return identifier)
|
|
466
|
-
*
|
|
467
|
-
* Returns null if no transformation needed, the resolved name otherwise.
|
|
468
|
-
* Used by implicit scope resolution feature.
|
|
469
|
-
*/
|
|
470
|
-
resolveBareIdentifier(
|
|
377
|
+
static resolveBareIdentifier(
|
|
471
378
|
identifier: string,
|
|
472
379
|
isLocalVariable: boolean,
|
|
473
380
|
isKnownStruct: (name: string) => boolean,
|
|
474
381
|
): string | null {
|
|
475
|
-
// Priority 1: Local variables and parameters - no transformation
|
|
476
382
|
if (isLocalVariable) {
|
|
477
383
|
return null;
|
|
478
384
|
}
|
|
479
385
|
|
|
480
|
-
const currentScope =
|
|
386
|
+
const currentScope = CodeGenState.currentScope;
|
|
481
387
|
|
|
482
|
-
// Priority 2: If inside a scope, check scope members
|
|
483
388
|
if (currentScope) {
|
|
484
|
-
const scopeResolved =
|
|
389
|
+
const scopeResolved = TypeValidator._resolveScopeMember(
|
|
390
|
+
identifier,
|
|
391
|
+
currentScope,
|
|
392
|
+
);
|
|
485
393
|
if (scopeResolved) return scopeResolved;
|
|
486
394
|
}
|
|
487
395
|
|
|
488
|
-
// Priority 3: Global resolution
|
|
489
396
|
if (
|
|
490
|
-
|
|
397
|
+
TypeValidator._isKnownGlobalIdentifier(
|
|
398
|
+
identifier,
|
|
399
|
+
currentScope,
|
|
400
|
+
isKnownStruct,
|
|
401
|
+
)
|
|
491
402
|
) {
|
|
492
403
|
return currentScope ? identifier : null;
|
|
493
404
|
}
|
|
494
405
|
|
|
495
|
-
// Not found anywhere - let it pass through (may be enum member or error later)
|
|
496
406
|
return null;
|
|
497
407
|
}
|
|
498
408
|
|
|
499
|
-
|
|
500
|
-
* Try to resolve identifier as a scope member or scope function
|
|
501
|
-
*/
|
|
502
|
-
private _resolveScopeMember(
|
|
409
|
+
private static _resolveScopeMember(
|
|
503
410
|
identifier: string,
|
|
504
411
|
currentScope: string,
|
|
505
412
|
): string | null {
|
|
506
|
-
const scopeMembers =
|
|
413
|
+
const scopeMembers = CodeGenState.scopeMembers.get(currentScope);
|
|
507
414
|
if (scopeMembers?.has(identifier)) {
|
|
508
415
|
return `${currentScope}_${identifier}`;
|
|
509
416
|
}
|
|
510
417
|
|
|
511
418
|
const scopedFuncName = `${currentScope}_${identifier}`;
|
|
512
|
-
if (
|
|
419
|
+
if (CodeGenState.knownFunctions.has(scopedFuncName)) {
|
|
513
420
|
return scopedFuncName;
|
|
514
421
|
}
|
|
515
422
|
|
|
516
423
|
return null;
|
|
517
424
|
}
|
|
518
425
|
|
|
519
|
-
|
|
520
|
-
* Check if identifier is a known global (variable, function, enum, struct, register)
|
|
521
|
-
*/
|
|
522
|
-
private _isKnownGlobalIdentifier(
|
|
426
|
+
private static _isKnownGlobalIdentifier(
|
|
523
427
|
identifier: string,
|
|
524
428
|
currentScope: string | null,
|
|
525
429
|
isKnownStruct: (name: string) => boolean,
|
|
526
430
|
): boolean {
|
|
527
|
-
|
|
528
|
-
const typeInfo = this.typeRegistry.get(identifier);
|
|
431
|
+
const typeInfo = CodeGenState.typeRegistry.get(identifier);
|
|
529
432
|
if (typeInfo && !identifier.includes("_")) {
|
|
530
433
|
return true;
|
|
531
434
|
}
|
|
532
435
|
|
|
533
|
-
// Global function (not prefixed with current scope)
|
|
534
436
|
if (
|
|
535
|
-
|
|
437
|
+
CodeGenState.knownFunctions.has(identifier) &&
|
|
536
438
|
!identifier.startsWith(currentScope + "_")
|
|
537
439
|
) {
|
|
538
440
|
return true;
|
|
539
441
|
}
|
|
540
442
|
|
|
541
|
-
// Known types: enums, structs, registers
|
|
542
443
|
return (
|
|
543
|
-
|
|
444
|
+
CodeGenState.symbols!.knownEnums.has(identifier) ||
|
|
544
445
|
isKnownStruct(identifier) ||
|
|
545
|
-
|
|
446
|
+
CodeGenState.symbols!.knownRegisters.has(identifier)
|
|
546
447
|
);
|
|
547
448
|
}
|
|
548
449
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
* Prioritizes scope names for Scope.member() calls.
|
|
552
|
-
*
|
|
553
|
-
* Returns the resolved name or null if not a scope.
|
|
554
|
-
*/
|
|
555
|
-
resolveForMemberAccess(identifier: string): string | null {
|
|
556
|
-
// For member access, check if it's a scope name first
|
|
557
|
-
if (this.symbols!.knownScopes.has(identifier)) {
|
|
450
|
+
static resolveForMemberAccess(identifier: string): string | null {
|
|
451
|
+
if (CodeGenState.symbols!.knownScopes.has(identifier)) {
|
|
558
452
|
return identifier;
|
|
559
453
|
}
|
|
560
454
|
return null;
|
|
@@ -564,44 +458,33 @@ class TypeValidator {
|
|
|
564
458
|
// Critical Section Validation (ADR-050)
|
|
565
459
|
// ========================================================================
|
|
566
460
|
|
|
567
|
-
|
|
568
|
-
* ADR-050: Validate no early exits inside critical block
|
|
569
|
-
* return, break, continue would leave interrupts disabled
|
|
570
|
-
*/
|
|
571
|
-
validateNoEarlyExits(ctx: Parser.BlockContext): void {
|
|
461
|
+
static validateNoEarlyExits(ctx: Parser.BlockContext): void {
|
|
572
462
|
for (const stmt of ctx.statement()) {
|
|
573
|
-
|
|
463
|
+
TypeValidator._validateStatementForEarlyExit(stmt);
|
|
574
464
|
}
|
|
575
465
|
}
|
|
576
466
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
private _validateStatementForEarlyExit(stmt: Parser.StatementContext): void {
|
|
467
|
+
private static _validateStatementForEarlyExit(
|
|
468
|
+
stmt: Parser.StatementContext,
|
|
469
|
+
): void {
|
|
581
470
|
if (stmt.returnStatement()) {
|
|
582
471
|
throw new Error(
|
|
583
472
|
`E0853: Cannot use 'return' inside critical section - would leave interrupts disabled`,
|
|
584
473
|
);
|
|
585
474
|
}
|
|
586
475
|
|
|
587
|
-
// Recursively check nested blocks
|
|
588
476
|
if (stmt.block()) {
|
|
589
|
-
|
|
477
|
+
TypeValidator.validateNoEarlyExits(stmt.block()!);
|
|
590
478
|
}
|
|
591
479
|
|
|
592
|
-
// Check inside if statements
|
|
593
480
|
if (stmt.ifStatement()) {
|
|
594
|
-
|
|
481
|
+
TypeValidator._validateIfStatementForEarlyExit(stmt.ifStatement()!);
|
|
595
482
|
}
|
|
596
483
|
|
|
597
|
-
|
|
598
|
-
this._validateLoopForEarlyExit(stmt);
|
|
484
|
+
TypeValidator._validateLoopForEarlyExit(stmt);
|
|
599
485
|
}
|
|
600
486
|
|
|
601
|
-
|
|
602
|
-
* Validate if statement branches for early exits
|
|
603
|
-
*/
|
|
604
|
-
private _validateIfStatementForEarlyExit(
|
|
487
|
+
private static _validateIfStatementForEarlyExit(
|
|
605
488
|
ifStmt: Parser.IfStatementContext,
|
|
606
489
|
): void {
|
|
607
490
|
for (const innerStmt of ifStmt.statement()) {
|
|
@@ -611,38 +494,35 @@ class TypeValidator {
|
|
|
611
494
|
);
|
|
612
495
|
}
|
|
613
496
|
if (innerStmt.block()) {
|
|
614
|
-
|
|
497
|
+
TypeValidator.validateNoEarlyExits(innerStmt.block()!);
|
|
615
498
|
}
|
|
616
499
|
}
|
|
617
500
|
}
|
|
618
501
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
private _validateLoopForEarlyExit(stmt: Parser.StatementContext): void {
|
|
502
|
+
private static _validateLoopForEarlyExit(
|
|
503
|
+
stmt: Parser.StatementContext,
|
|
504
|
+
): void {
|
|
623
505
|
if (stmt.whileStatement()) {
|
|
624
|
-
|
|
506
|
+
TypeValidator._checkLoopBodyForReturn(stmt.whileStatement()!.statement());
|
|
625
507
|
}
|
|
626
508
|
if (stmt.forStatement()) {
|
|
627
|
-
|
|
509
|
+
TypeValidator._checkLoopBodyForReturn(stmt.forStatement()!.statement());
|
|
628
510
|
}
|
|
629
511
|
if (stmt.doWhileStatement()) {
|
|
630
|
-
|
|
512
|
+
TypeValidator.validateNoEarlyExits(stmt.doWhileStatement()!.block());
|
|
631
513
|
}
|
|
632
514
|
}
|
|
633
515
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
*/
|
|
638
|
-
private _checkLoopBodyForReturn(loopStmt: Parser.StatementContext): void {
|
|
516
|
+
private static _checkLoopBodyForReturn(
|
|
517
|
+
loopStmt: Parser.StatementContext,
|
|
518
|
+
): void {
|
|
639
519
|
if (loopStmt.returnStatement()) {
|
|
640
520
|
throw new Error(
|
|
641
521
|
`E0853: Cannot use 'return' inside critical section - would leave interrupts disabled`,
|
|
642
522
|
);
|
|
643
523
|
}
|
|
644
524
|
if (loopStmt.block()) {
|
|
645
|
-
|
|
525
|
+
TypeValidator.validateNoEarlyExits(loopStmt.block()!);
|
|
646
526
|
}
|
|
647
527
|
}
|
|
648
528
|
|
|
@@ -650,10 +530,7 @@ class TypeValidator {
|
|
|
650
530
|
// Switch Statement Validation (ADR-025)
|
|
651
531
|
// ========================================================================
|
|
652
532
|
|
|
653
|
-
|
|
654
|
-
* ADR-025: Validate switch statement for MISRA compliance
|
|
655
|
-
*/
|
|
656
|
-
validateSwitchStatement(
|
|
533
|
+
static validateSwitchStatement(
|
|
657
534
|
ctx: Parser.SwitchStatementContext,
|
|
658
535
|
switchExpr: Parser.ExpressionContext,
|
|
659
536
|
): void {
|
|
@@ -661,26 +538,23 @@ class TypeValidator {
|
|
|
661
538
|
const defaultCase = ctx.defaultCase();
|
|
662
539
|
const totalClauses = cases.length + (defaultCase ? 1 : 0);
|
|
663
540
|
|
|
664
|
-
|
|
665
|
-
const exprType = this.getExpressionTypeFn(switchExpr);
|
|
541
|
+
const exprType = TypeResolver.getExpressionType(switchExpr);
|
|
666
542
|
if (exprType === "bool") {
|
|
667
543
|
throw new Error(
|
|
668
544
|
"Error: Cannot switch on boolean type (MISRA 16.7). Use if/else instead.",
|
|
669
545
|
);
|
|
670
546
|
}
|
|
671
547
|
|
|
672
|
-
// MISRA 16.6: Minimum 2 clauses required
|
|
673
548
|
if (totalClauses < 2) {
|
|
674
549
|
throw new Error(
|
|
675
550
|
"Error: Switch requires at least 2 clauses (MISRA 16.6). Use if statement for single case.",
|
|
676
551
|
);
|
|
677
552
|
}
|
|
678
553
|
|
|
679
|
-
// Check for duplicate case values
|
|
680
554
|
const seenValues = new Set<string>();
|
|
681
555
|
for (const caseCtx of cases) {
|
|
682
556
|
for (const labelCtx of caseCtx.caseLabel()) {
|
|
683
|
-
const labelValue =
|
|
557
|
+
const labelValue = TypeValidator.getCaseLabelValue(labelCtx);
|
|
684
558
|
if (seenValues.has(labelValue)) {
|
|
685
559
|
throw new Error(
|
|
686
560
|
`Error: Duplicate case value '${labelValue}' in switch statement.`,
|
|
@@ -690,38 +564,36 @@ class TypeValidator {
|
|
|
690
564
|
}
|
|
691
565
|
}
|
|
692
566
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
567
|
+
if (exprType && CodeGenState.symbols!.knownEnums.has(exprType)) {
|
|
568
|
+
TypeValidator.validateEnumExhaustiveness(
|
|
569
|
+
ctx,
|
|
570
|
+
exprType,
|
|
571
|
+
cases,
|
|
572
|
+
defaultCase,
|
|
573
|
+
);
|
|
696
574
|
}
|
|
697
575
|
}
|
|
698
576
|
|
|
699
|
-
|
|
700
|
-
* ADR-025: Validate enum switch exhaustiveness with default(n) counting
|
|
701
|
-
*/
|
|
702
|
-
validateEnumExhaustiveness(
|
|
577
|
+
static validateEnumExhaustiveness(
|
|
703
578
|
ctx: Parser.SwitchStatementContext,
|
|
704
579
|
enumTypeName: string,
|
|
705
580
|
cases: Parser.SwitchCaseContext[],
|
|
706
581
|
defaultCase: Parser.DefaultCaseContext | null,
|
|
707
582
|
): void {
|
|
708
|
-
const enumVariants =
|
|
709
|
-
if (!enumVariants) return;
|
|
583
|
+
const enumVariants = CodeGenState.symbols!.enumMembers.get(enumTypeName);
|
|
584
|
+
if (!enumVariants) return;
|
|
710
585
|
|
|
711
586
|
const totalVariants = enumVariants.size;
|
|
712
587
|
|
|
713
|
-
// Count explicit cases (each || alternative counts as 1)
|
|
714
588
|
let explicitCaseCount = 0;
|
|
715
589
|
for (const caseCtx of cases) {
|
|
716
590
|
explicitCaseCount += caseCtx.caseLabel().length;
|
|
717
591
|
}
|
|
718
592
|
|
|
719
593
|
if (defaultCase) {
|
|
720
|
-
|
|
721
|
-
const defaultCount = this.getDefaultCount(defaultCase);
|
|
594
|
+
const defaultCount = TypeValidator.getDefaultCount(defaultCase);
|
|
722
595
|
|
|
723
596
|
if (defaultCount !== null) {
|
|
724
|
-
// default(n) mode: explicit + n must equal total variants
|
|
725
597
|
const covered = explicitCaseCount + defaultCount;
|
|
726
598
|
if (covered !== totalVariants) {
|
|
727
599
|
throw new Error(
|
|
@@ -731,9 +603,7 @@ class TypeValidator {
|
|
|
731
603
|
);
|
|
732
604
|
}
|
|
733
605
|
}
|
|
734
|
-
// Plain default: no exhaustiveness check needed
|
|
735
606
|
} else if (explicitCaseCount !== totalVariants) {
|
|
736
|
-
// No default: must cover all variants explicitly
|
|
737
607
|
const missing = totalVariants - explicitCaseCount;
|
|
738
608
|
throw new Error(
|
|
739
609
|
`Error: Non-exhaustive switch on ${enumTypeName}: covers ${explicitCaseCount} of ${totalVariants} variants, missing ${missing}.`,
|
|
@@ -741,10 +611,7 @@ class TypeValidator {
|
|
|
741
611
|
}
|
|
742
612
|
}
|
|
743
613
|
|
|
744
|
-
|
|
745
|
-
* Get the count from default(n) syntax, or null for plain default
|
|
746
|
-
*/
|
|
747
|
-
getDefaultCount(ctx: Parser.DefaultCaseContext): number | null {
|
|
614
|
+
static getDefaultCount(ctx: Parser.DefaultCaseContext): number | null {
|
|
748
615
|
const intLiteral = ctx.INTEGER_LITERAL();
|
|
749
616
|
if (intLiteral) {
|
|
750
617
|
return Number.parseInt(intLiteral.getText(), 10);
|
|
@@ -752,10 +619,7 @@ class TypeValidator {
|
|
|
752
619
|
return null;
|
|
753
620
|
}
|
|
754
621
|
|
|
755
|
-
|
|
756
|
-
* Get the string representation of a case label for duplicate checking
|
|
757
|
-
*/
|
|
758
|
-
getCaseLabelValue(ctx: Parser.CaseLabelContext): string {
|
|
622
|
+
static getCaseLabelValue(ctx: Parser.CaseLabelContext): string {
|
|
759
623
|
if (ctx.qualifiedType()) {
|
|
760
624
|
const qt = ctx.qualifiedType()!;
|
|
761
625
|
return qt
|
|
@@ -768,23 +632,19 @@ class TypeValidator {
|
|
|
768
632
|
}
|
|
769
633
|
if (ctx.INTEGER_LITERAL()) {
|
|
770
634
|
const num = ctx.INTEGER_LITERAL()!.getText();
|
|
771
|
-
// Check if minus token exists (first child would be '-')
|
|
772
635
|
const hasNeg = ctx.children && ctx.children[0]?.getText() === "-";
|
|
773
636
|
const value = BigInt(num);
|
|
774
637
|
return String(hasNeg ? -value : value);
|
|
775
638
|
}
|
|
776
639
|
if (ctx.HEX_LITERAL()) {
|
|
777
|
-
// Normalize hex to decimal for comparison
|
|
778
640
|
const hex = ctx.HEX_LITERAL()!.getText();
|
|
779
|
-
// Check if minus token exists (first child would be '-')
|
|
780
641
|
const hasNeg = ctx.children && ctx.children[0]?.getText() === "-";
|
|
781
|
-
const value = BigInt(hex);
|
|
642
|
+
const value = BigInt(hex);
|
|
782
643
|
return String(hasNeg ? -value : value);
|
|
783
644
|
}
|
|
784
645
|
if (ctx.BINARY_LITERAL()) {
|
|
785
|
-
// Normalize binary to decimal for comparison
|
|
786
646
|
const bin = ctx.BINARY_LITERAL()!.getText();
|
|
787
|
-
return String(BigInt(bin));
|
|
647
|
+
return String(BigInt(bin));
|
|
788
648
|
}
|
|
789
649
|
if (ctx.CHAR_LITERAL()) {
|
|
790
650
|
return ctx.CHAR_LITERAL()!.getText();
|
|
@@ -796,17 +656,11 @@ class TypeValidator {
|
|
|
796
656
|
// Ternary Validation (ADR-022)
|
|
797
657
|
// ========================================================================
|
|
798
658
|
|
|
799
|
-
|
|
800
|
-
* ADR-022: Validate that ternary condition is a boolean expression
|
|
801
|
-
* Must be a comparison or logical operation, not just a value
|
|
802
|
-
*/
|
|
803
|
-
validateTernaryCondition(ctx: Parser.OrExpressionContext): void {
|
|
804
|
-
// Check if the condition contains a comparison or logical operator
|
|
659
|
+
static validateTernaryCondition(ctx: Parser.OrExpressionContext): void {
|
|
805
660
|
const text = ctx.getText();
|
|
806
661
|
|
|
807
|
-
// If it has && or ||, it's a logical expression (valid)
|
|
808
662
|
if (ctx.andExpression().length > 1) {
|
|
809
|
-
return;
|
|
663
|
+
return;
|
|
810
664
|
}
|
|
811
665
|
|
|
812
666
|
const andExpr = ctx.andExpression(0);
|
|
@@ -817,7 +671,7 @@ class TypeValidator {
|
|
|
817
671
|
}
|
|
818
672
|
|
|
819
673
|
if (andExpr.equalityExpression().length > 1) {
|
|
820
|
-
return;
|
|
674
|
+
return;
|
|
821
675
|
}
|
|
822
676
|
|
|
823
677
|
const equalityExpr = andExpr.equalityExpression(0);
|
|
@@ -828,7 +682,7 @@ class TypeValidator {
|
|
|
828
682
|
}
|
|
829
683
|
|
|
830
684
|
if (equalityExpr.relationalExpression().length > 1) {
|
|
831
|
-
return;
|
|
685
|
+
return;
|
|
832
686
|
}
|
|
833
687
|
|
|
834
688
|
const relationalExpr = equalityExpr.relationalExpression(0);
|
|
@@ -839,24 +693,19 @@ class TypeValidator {
|
|
|
839
693
|
}
|
|
840
694
|
|
|
841
695
|
if (relationalExpr.bitwiseOrExpression().length > 1) {
|
|
842
|
-
return;
|
|
696
|
+
return;
|
|
843
697
|
}
|
|
844
698
|
|
|
845
|
-
// No comparison or logical operators found - just a value
|
|
846
699
|
throw new Error(
|
|
847
700
|
`Error: Ternary condition must be a boolean expression (comparison or logical operation), not '${text}'`,
|
|
848
701
|
);
|
|
849
702
|
}
|
|
850
703
|
|
|
851
|
-
|
|
852
|
-
* ADR-022: Validate that expression does not contain a nested ternary
|
|
853
|
-
*/
|
|
854
|
-
validateNoNestedTernary(
|
|
704
|
+
static validateNoNestedTernary(
|
|
855
705
|
ctx: Parser.OrExpressionContext,
|
|
856
706
|
branchName: string,
|
|
857
707
|
): void {
|
|
858
708
|
const text = ctx.getText();
|
|
859
|
-
// Check for ternary pattern: something ? something : something
|
|
860
709
|
if (text.includes("?") && text.includes(":")) {
|
|
861
710
|
throw new Error(
|
|
862
711
|
`Error: Nested ternary not allowed in ${branchName}. Use if/else instead.`,
|
|
@@ -868,17 +717,10 @@ class TypeValidator {
|
|
|
868
717
|
// Do-While Validation (ADR-027)
|
|
869
718
|
// ========================================================================
|
|
870
719
|
|
|
871
|
-
|
|
872
|
-
* ADR-027: Validate that do-while condition is a boolean expression (E0701)
|
|
873
|
-
* Must be a comparison, logical operation, or boolean variable - not just a value.
|
|
874
|
-
* This enforces MISRA C:2012 Rule 14.4.
|
|
875
|
-
*/
|
|
876
|
-
validateDoWhileCondition(ctx: Parser.ExpressionContext): void {
|
|
877
|
-
// Unwrap: ExpressionContext -> TernaryExpressionContext -> OrExpressionContext
|
|
720
|
+
static validateDoWhileCondition(ctx: Parser.ExpressionContext): void {
|
|
878
721
|
const ternaryExpr = ctx.ternaryExpression();
|
|
879
722
|
const orExprs = ternaryExpr.orExpression();
|
|
880
723
|
|
|
881
|
-
// For do-while, we expect a non-ternary expression (single orExpression)
|
|
882
724
|
if (orExprs.length !== 1) {
|
|
883
725
|
throw new Error(
|
|
884
726
|
`Error E0701: do-while condition must be a boolean expression, not a ternary (MISRA C:2012 Rule 14.4)`,
|
|
@@ -888,7 +730,6 @@ class TypeValidator {
|
|
|
888
730
|
const orExpr = orExprs[0];
|
|
889
731
|
const text = orExpr.getText();
|
|
890
732
|
|
|
891
|
-
// If it has || operator, it's valid (logical expression)
|
|
892
733
|
if (orExpr.andExpression().length > 1) {
|
|
893
734
|
return;
|
|
894
735
|
}
|
|
@@ -900,7 +741,6 @@ class TypeValidator {
|
|
|
900
741
|
);
|
|
901
742
|
}
|
|
902
743
|
|
|
903
|
-
// If it has && operator, it's valid
|
|
904
744
|
if (andExpr.equalityExpression().length > 1) {
|
|
905
745
|
return;
|
|
906
746
|
}
|
|
@@ -912,7 +752,6 @@ class TypeValidator {
|
|
|
912
752
|
);
|
|
913
753
|
}
|
|
914
754
|
|
|
915
|
-
// If it has = or != operator, it's valid
|
|
916
755
|
if (equalityExpr.relationalExpression().length > 1) {
|
|
917
756
|
return;
|
|
918
757
|
}
|
|
@@ -924,42 +763,34 @@ class TypeValidator {
|
|
|
924
763
|
);
|
|
925
764
|
}
|
|
926
765
|
|
|
927
|
-
// If it has <, >, <=, >= operator, it's valid
|
|
928
766
|
if (relationalExpr.bitwiseOrExpression().length > 1) {
|
|
929
767
|
return;
|
|
930
768
|
}
|
|
931
769
|
|
|
932
|
-
// Check if it's a unary ! (negation) expression - that's valid on booleans
|
|
933
770
|
const bitwiseOrExpr = relationalExpr.bitwiseOrExpression(0);
|
|
934
|
-
if (bitwiseOrExpr &&
|
|
771
|
+
if (bitwiseOrExpr && TypeValidator._isBooleanExpression(bitwiseOrExpr)) {
|
|
935
772
|
return;
|
|
936
773
|
}
|
|
937
774
|
|
|
938
|
-
// No comparison or logical operators found - just a value
|
|
939
775
|
throw new Error(
|
|
940
776
|
`Error E0701: do-while condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)\n help: use explicit comparison: ${text} > 0 or ${text} != 0`,
|
|
941
777
|
);
|
|
942
778
|
}
|
|
943
779
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
*/
|
|
948
|
-
private isBooleanExpression(ctx: Parser.BitwiseOrExpressionContext): boolean {
|
|
780
|
+
private static _isBooleanExpression(
|
|
781
|
+
ctx: Parser.BitwiseOrExpressionContext,
|
|
782
|
+
): boolean {
|
|
949
783
|
const text = ctx.getText();
|
|
950
784
|
|
|
951
|
-
// Check for boolean literals
|
|
952
785
|
if (text === "true" || text === "false") {
|
|
953
786
|
return true;
|
|
954
787
|
}
|
|
955
788
|
|
|
956
|
-
// Check for negation (! operator) - valid for boolean expressions
|
|
957
789
|
if (text.startsWith("!")) {
|
|
958
790
|
return true;
|
|
959
791
|
}
|
|
960
792
|
|
|
961
|
-
|
|
962
|
-
const typeInfo = this.typeRegistry.get(text);
|
|
793
|
+
const typeInfo = CodeGenState.typeRegistry.get(text);
|
|
963
794
|
if (typeInfo?.baseType === "bool") {
|
|
964
795
|
return true;
|
|
965
796
|
}
|
|
@@ -971,13 +802,7 @@ class TypeValidator {
|
|
|
971
802
|
// Function Call in Condition Validation (Issue #254)
|
|
972
803
|
// ========================================================================
|
|
973
804
|
|
|
974
|
-
|
|
975
|
-
* Issue #254: Validate that condition does not contain function calls (E0702)
|
|
976
|
-
* MISRA C:2012 Rule 13.5 forbids function calls in conditions because:
|
|
977
|
-
* - Short-circuit evaluation may skip the function call
|
|
978
|
-
* - Side effects become unpredictable
|
|
979
|
-
*/
|
|
980
|
-
validateConditionNoFunctionCall(
|
|
805
|
+
static validateConditionNoFunctionCall(
|
|
981
806
|
ctx: Parser.ExpressionContext,
|
|
982
807
|
conditionType: string,
|
|
983
808
|
): void {
|
|
@@ -991,11 +816,7 @@ class TypeValidator {
|
|
|
991
816
|
}
|
|
992
817
|
}
|
|
993
818
|
|
|
994
|
-
|
|
995
|
-
* Issue #254: Validate that ternary condition does not contain function calls (E0702)
|
|
996
|
-
* Used for ternary expressions where condition is OrExpressionContext
|
|
997
|
-
*/
|
|
998
|
-
validateTernaryConditionNoFunctionCall(
|
|
819
|
+
static validateTernaryConditionNoFunctionCall(
|
|
999
820
|
ctx: Parser.OrExpressionContext,
|
|
1000
821
|
): void {
|
|
1001
822
|
if (ExpressionUtils.hasFunctionCallInOr(ctx)) {
|
|
@@ -1008,31 +829,22 @@ class TypeValidator {
|
|
|
1008
829
|
}
|
|
1009
830
|
}
|
|
1010
831
|
|
|
1011
|
-
// Function call detection delegated to ExpressionUtils (Issue #254, #366)
|
|
1012
|
-
|
|
1013
832
|
// ========================================================================
|
|
1014
833
|
// Shift Amount Validation (MISRA C:2012 Rule 12.2)
|
|
1015
834
|
// ========================================================================
|
|
1016
835
|
|
|
1017
|
-
|
|
1018
|
-
* Validate shift amount doesn't exceed type width (MISRA C:2012 Rule 12.2).
|
|
1019
|
-
* Shifting by an amount >= type width is undefined behavior.
|
|
1020
|
-
*/
|
|
1021
|
-
validateShiftAmount(
|
|
836
|
+
static validateShiftAmount(
|
|
1022
837
|
leftType: string,
|
|
1023
838
|
rightExpr: Parser.AdditiveExpressionContext,
|
|
1024
839
|
op: string,
|
|
1025
840
|
ctx: Parser.ShiftExpressionContext,
|
|
1026
841
|
): void {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
if (!typeWidth) return; // Unknown type, skip validation
|
|
842
|
+
const typeWidth = TypeValidator._getTypeWidth(leftType);
|
|
843
|
+
if (!typeWidth) return;
|
|
1030
844
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
if (shiftAmount === null) return; // Not a constant, skip validation
|
|
845
|
+
const shiftAmount = TypeValidator._evaluateShiftAmount(rightExpr);
|
|
846
|
+
if (shiftAmount === null) return;
|
|
1034
847
|
|
|
1035
|
-
// Check for negative shift (undefined behavior)
|
|
1036
848
|
if (shiftAmount < 0) {
|
|
1037
849
|
throw new Error(
|
|
1038
850
|
`Error: Negative shift amount (${shiftAmount}) is undefined behavior\n` +
|
|
@@ -1042,7 +854,6 @@ class TypeValidator {
|
|
|
1042
854
|
);
|
|
1043
855
|
}
|
|
1044
856
|
|
|
1045
|
-
// Check if shift amount >= type width (undefined behavior)
|
|
1046
857
|
if (shiftAmount >= typeWidth) {
|
|
1047
858
|
throw new Error(
|
|
1048
859
|
`Error: Shift amount (${shiftAmount}) exceeds type width (${typeWidth} bits) for type '${leftType}'\n` +
|
|
@@ -1053,10 +864,7 @@ class TypeValidator {
|
|
|
1053
864
|
}
|
|
1054
865
|
}
|
|
1055
866
|
|
|
1056
|
-
|
|
1057
|
-
* Get the bit width of a primitive type.
|
|
1058
|
-
*/
|
|
1059
|
-
private getTypeWidth(type: string): number | null {
|
|
867
|
+
private static _getTypeWidth(type: string): number | null {
|
|
1060
868
|
switch (type) {
|
|
1061
869
|
case "u8":
|
|
1062
870
|
case "i8":
|
|
@@ -1071,34 +879,24 @@ class TypeValidator {
|
|
|
1071
879
|
case "i64":
|
|
1072
880
|
return 64;
|
|
1073
881
|
default:
|
|
1074
|
-
return null;
|
|
882
|
+
return null;
|
|
1075
883
|
}
|
|
1076
884
|
}
|
|
1077
885
|
|
|
1078
|
-
|
|
1079
|
-
* Try to evaluate a shift amount expression to get its numeric value.
|
|
1080
|
-
* Returns null if not a constant or cannot be evaluated.
|
|
1081
|
-
*/
|
|
1082
|
-
private evaluateShiftAmount(
|
|
886
|
+
private static _evaluateShiftAmount(
|
|
1083
887
|
ctx: Parser.AdditiveExpressionContext,
|
|
1084
888
|
): number | null {
|
|
1085
|
-
// For now, handle simple literals only
|
|
1086
889
|
const multExprs = ctx.multiplicativeExpression();
|
|
1087
|
-
if (multExprs.length !== 1) return null;
|
|
890
|
+
if (multExprs.length !== 1) return null;
|
|
1088
891
|
|
|
1089
892
|
const multExpr = multExprs[0];
|
|
1090
893
|
const unaryExprs = multExpr.unaryExpression();
|
|
1091
894
|
if (unaryExprs.length !== 1) return null;
|
|
1092
895
|
|
|
1093
|
-
|
|
1094
|
-
return this.evaluateUnaryExpression(unaryExpr);
|
|
896
|
+
return TypeValidator._evaluateUnaryExpression(unaryExprs[0]);
|
|
1095
897
|
}
|
|
1096
898
|
|
|
1097
|
-
|
|
1098
|
-
* Helper to evaluate a unary expression recursively.
|
|
1099
|
-
* SonarCloud S3776: Uses LiteralEvaluator for literal parsing.
|
|
1100
|
-
*/
|
|
1101
|
-
private evaluateUnaryExpression(
|
|
899
|
+
private static _evaluateUnaryExpression(
|
|
1102
900
|
ctx: Parser.UnaryExpressionContext,
|
|
1103
901
|
): number | null {
|
|
1104
902
|
const unaryText = ctx.getText();
|
|
@@ -1106,23 +904,19 @@ class TypeValidator {
|
|
|
1106
904
|
|
|
1107
905
|
const postfixExpr = ctx.postfixExpression();
|
|
1108
906
|
if (postfixExpr) {
|
|
1109
|
-
return
|
|
907
|
+
return TypeValidator._evaluateLiteralFromPostfix(postfixExpr, isNegative);
|
|
1110
908
|
}
|
|
1111
909
|
|
|
1112
|
-
// Recursive unary
|
|
1113
910
|
const nestedUnary = ctx.unaryExpression();
|
|
1114
911
|
if (nestedUnary) {
|
|
1115
|
-
const nestedValue =
|
|
912
|
+
const nestedValue = TypeValidator._evaluateUnaryExpression(nestedUnary);
|
|
1116
913
|
return LiteralEvaluator.applySign(nestedValue, isNegative);
|
|
1117
914
|
}
|
|
1118
915
|
|
|
1119
916
|
return null;
|
|
1120
917
|
}
|
|
1121
918
|
|
|
1122
|
-
|
|
1123
|
-
* Extract and evaluate a literal from a postfix expression.
|
|
1124
|
-
*/
|
|
1125
|
-
private evaluateLiteralFromPostfix(
|
|
919
|
+
private static _evaluateLiteralFromPostfix(
|
|
1126
920
|
postfixExpr: Parser.PostfixExpressionContext,
|
|
1127
921
|
isNegative: boolean,
|
|
1128
922
|
): number | null {
|
|
@@ -1141,46 +935,30 @@ class TypeValidator {
|
|
|
1141
935
|
// Integer Assignment Validation (ADR-024)
|
|
1142
936
|
// ========================================================================
|
|
1143
937
|
|
|
1144
|
-
|
|
1145
|
-
* ADR-024: Validate integer type conversions for assignments.
|
|
1146
|
-
* - For literals: validates the value fits in the target type
|
|
1147
|
-
* - For expressions: validates no narrowing or sign-changing conversions
|
|
1148
|
-
*
|
|
1149
|
-
* @param targetType - The target type (e.g., "u8", "i32")
|
|
1150
|
-
* @param expressionText - The expression text (for literal detection)
|
|
1151
|
-
* @param sourceType - The source type (for non-literal expressions), or null
|
|
1152
|
-
* @param isCompound - Whether this is a compound assignment (skip validation)
|
|
1153
|
-
*/
|
|
1154
|
-
validateIntegerAssignment(
|
|
938
|
+
static validateIntegerAssignment(
|
|
1155
939
|
targetType: string,
|
|
1156
940
|
expressionText: string,
|
|
1157
941
|
sourceType: string | null,
|
|
1158
942
|
isCompound: boolean,
|
|
1159
943
|
): void {
|
|
1160
|
-
// Skip validation for compound assignments (+<-, -<-, etc.)
|
|
1161
|
-
// since the operand doesn't need to fit directly in the target type
|
|
1162
944
|
if (isCompound) {
|
|
1163
945
|
return;
|
|
1164
946
|
}
|
|
1165
947
|
|
|
1166
|
-
|
|
1167
|
-
if (!this.typeResolver.isIntegerType(targetType)) {
|
|
948
|
+
if (!TypeResolver.isIntegerType(targetType)) {
|
|
1168
949
|
return;
|
|
1169
950
|
}
|
|
1170
951
|
|
|
1171
952
|
const trimmed = expressionText.trim();
|
|
1172
953
|
|
|
1173
|
-
// Check if it's a direct literal (decimal, hex, or binary)
|
|
1174
954
|
const isDecimalLiteral = /^-?\d+$/.exec(trimmed);
|
|
1175
955
|
const isHexLiteral = /^0[xX][0-9a-fA-F]+$/.exec(trimmed);
|
|
1176
956
|
const isBinaryLiteral = /^0[bB][01]+$/.exec(trimmed);
|
|
1177
957
|
|
|
1178
958
|
if (isDecimalLiteral || isHexLiteral || isBinaryLiteral) {
|
|
1179
|
-
|
|
1180
|
-
this.typeResolver.validateLiteralFitsType(trimmed, targetType);
|
|
959
|
+
TypeResolver.validateLiteralFitsType(trimmed, targetType);
|
|
1181
960
|
} else {
|
|
1182
|
-
|
|
1183
|
-
this.typeResolver.validateTypeConversion(targetType, sourceType);
|
|
961
|
+
TypeResolver.validateTypeConversion(targetType, sourceType);
|
|
1184
962
|
}
|
|
1185
963
|
}
|
|
1186
964
|
}
|