c-next 0.2.16 → 0.2.17
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 +16 -0
- package/dist/index.js +1347 -414
- package/dist/index.js.map +3 -3
- package/grammar/CNext.g4 +4 -0
- package/package.json +5 -1
- package/src/transpiler/Transpiler.ts +90 -22
- package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +57 -10
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
- package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
- package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
- package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
- package/src/transpiler/logic/parser/grammar/CNext.interp +1 -1
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +154 -86
- package/src/transpiler/logic/symbols/SymbolTable.ts +17 -2
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +6 -4
- package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
- package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
- package/src/transpiler/output/codegen/CodeGenerator.ts +151 -94
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +150 -34
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +11 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
- package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +48 -43
- package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
- package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
- package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
- package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
- package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +227 -32
- package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
- package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
- package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +268 -1
- package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
- package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
- package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
- package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
- package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
- package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +75 -0
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
- package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
- package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
- package/src/transpiler/state/CodeGenState.ts +71 -6
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
- package/src/utils/LiteralUtils.ts +23 -0
- package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
- package/src/utils/types/IParameterSymbol.ts +1 -0
package/grammar/CNext.g4
CHANGED
|
@@ -538,10 +538,14 @@ stringType
|
|
|
538
538
|
|
|
539
539
|
// C-Next style array type: dimensions in type position
|
|
540
540
|
// Supports: u8[8], u8[4][4], u8[] (unbounded), string<32>[5]
|
|
541
|
+
// Also supports scoped/qualified types: this.Type[4], Scope.Type[4], global.Type[4]
|
|
541
542
|
arrayType
|
|
542
543
|
: primitiveType arrayTypeDimension+
|
|
543
544
|
| userType arrayTypeDimension+
|
|
544
545
|
| stringType arrayTypeDimension+
|
|
546
|
+
| scopedType arrayTypeDimension+ // this.Type[4] - scoped type array
|
|
547
|
+
| qualifiedType arrayTypeDimension+ // Scope.Type[4] - qualified type array
|
|
548
|
+
| globalType arrayTypeDimension+ // global.Type[4] - global type array
|
|
545
549
|
;
|
|
546
550
|
|
|
547
551
|
arrayTypeDimension
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c-next",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
|
|
5
5
|
"packageManager": "npm@11.9.0",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"test:update": "tsx scripts/test.ts --update",
|
|
25
25
|
"integration:transpile": "tsx scripts/test.ts --transpile-only",
|
|
26
26
|
"integration:execute": "tsx scripts/test.ts --execute-only",
|
|
27
|
+
"test:bugs": "tsx scripts/test.ts -- bugs",
|
|
28
|
+
"test:bugs:q": "tsx scripts/test.ts -- bugs -q",
|
|
29
|
+
"integration:bugs:transpile": "tsx scripts/test.ts -- bugs --transpile-only",
|
|
30
|
+
"integration:bugs:execute": "tsx scripts/test.ts -- bugs --execute-only",
|
|
27
31
|
"analyze": "./scripts/static-analysis.sh",
|
|
28
32
|
"clean": "rm -rf dist src/transpiler/logic/parser/grammar src/transpiler/logic/parser/c/grammar src/transpiler/logic/parser/cpp/grammar",
|
|
29
33
|
"prettier:check": "prettier --check .",
|
|
@@ -1410,8 +1410,15 @@ class Transpiler {
|
|
|
1410
1410
|
CodeGenState.symbolTable,
|
|
1411
1411
|
);
|
|
1412
1412
|
|
|
1413
|
+
// ADR-029: Convert callback types to header format
|
|
1414
|
+
const callbackTypesForHeader = this._buildCallbackTypesForHeader();
|
|
1415
|
+
|
|
1413
1416
|
const typeInputWithSymbolTable = typeInput
|
|
1414
|
-
? {
|
|
1417
|
+
? {
|
|
1418
|
+
...typeInput,
|
|
1419
|
+
symbolTable: CodeGenState.symbolTable,
|
|
1420
|
+
callbackTypes: callbackTypesForHeader,
|
|
1421
|
+
}
|
|
1415
1422
|
: undefined;
|
|
1416
1423
|
|
|
1417
1424
|
const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
|
|
@@ -1437,6 +1444,52 @@ class Transpiler {
|
|
|
1437
1444
|
);
|
|
1438
1445
|
}
|
|
1439
1446
|
|
|
1447
|
+
/**
|
|
1448
|
+
* ADR-029: Build callback types for header generation.
|
|
1449
|
+
* Only includes callbacks that are actually used as struct field types.
|
|
1450
|
+
* Converts CodeGenState.callbackTypes to the format expected by IHeaderTypeInput.
|
|
1451
|
+
*/
|
|
1452
|
+
private _buildCallbackTypesForHeader(): ReadonlyMap<
|
|
1453
|
+
string,
|
|
1454
|
+
{
|
|
1455
|
+
typedefName: string;
|
|
1456
|
+
returnType: string;
|
|
1457
|
+
parameters: ReadonlyArray<{ type: string; isStruct: boolean }>;
|
|
1458
|
+
}
|
|
1459
|
+
> {
|
|
1460
|
+
const result = new Map<
|
|
1461
|
+
string,
|
|
1462
|
+
{
|
|
1463
|
+
typedefName: string;
|
|
1464
|
+
returnType: string;
|
|
1465
|
+
parameters: ReadonlyArray<{ type: string; isStruct: boolean }>;
|
|
1466
|
+
}
|
|
1467
|
+
>();
|
|
1468
|
+
|
|
1469
|
+
// Collect callback function names that are actually used as struct field types
|
|
1470
|
+
const usedCallbackTypes = new Set<string>();
|
|
1471
|
+
for (const [, funcName] of CodeGenState.callbackFieldTypes) {
|
|
1472
|
+
usedCallbackTypes.add(funcName);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// Only include callbacks that are used as struct field types
|
|
1476
|
+
for (const funcName of usedCallbackTypes) {
|
|
1477
|
+
const cbInfo = CodeGenState.callbackTypes.get(funcName);
|
|
1478
|
+
if (cbInfo) {
|
|
1479
|
+
result.set(funcName, {
|
|
1480
|
+
typedefName: cbInfo.typedefName,
|
|
1481
|
+
returnType: cbInfo.returnType,
|
|
1482
|
+
parameters: cbInfo.parameters.map((p) => ({
|
|
1483
|
+
type: p.type,
|
|
1484
|
+
isStruct: p.isStruct,
|
|
1485
|
+
})),
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
return result;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1440
1493
|
/**
|
|
1441
1494
|
* Collect external enum sources from included C-Next files.
|
|
1442
1495
|
*/
|
|
@@ -1509,6 +1562,9 @@ class Transpiler {
|
|
|
1509
1562
|
|
|
1510
1563
|
// Issue #914: For callback-compatible functions, bake pointer/const overrides
|
|
1511
1564
|
// onto each parameter. Skip auto-const (matches CodeGenerator path).
|
|
1565
|
+
// Note: isOpaqueHandle is not set here because callback params get their
|
|
1566
|
+
// pointer/const semantics from the typedef signature via isCallbackPointer/
|
|
1567
|
+
// isCallbackConst, which take precedence over opaque handling in the builder.
|
|
1512
1568
|
if (callbackTypedefType) {
|
|
1513
1569
|
const updatedParams = TypedefParamParser.resolveCallbackParams(
|
|
1514
1570
|
headerSymbol.parameters,
|
|
@@ -1517,29 +1573,41 @@ class Transpiler {
|
|
|
1517
1573
|
return { ...headerSymbol, parameters: updatedParams };
|
|
1518
1574
|
}
|
|
1519
1575
|
|
|
1520
|
-
// Apply auto-const
|
|
1576
|
+
// Apply auto-const and resolve opaque type info for non-callback function parameters
|
|
1521
1577
|
const unmodified = unmodifiedParams.get(headerSymbol.name);
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1578
|
+
const updatedParams = headerSymbol.parameters.map((param) => {
|
|
1579
|
+
// Issue #995: Resolve opaque type info ONCE onto the symbol.
|
|
1580
|
+
// This is the single source of truth for both body (.c/.cpp) and header (.h/.hpp).
|
|
1581
|
+
const isOpaque = CodeGenState.isOpaqueType(param.type ?? "");
|
|
1582
|
+
|
|
1583
|
+
// ADR-006: Only non-array pointer params get auto-const.
|
|
1584
|
+
// Arrays are pass-by-reference and mutable by default - auto-const would
|
|
1585
|
+
// break compatibility with C APIs expecting mutable pointers (issue #986).
|
|
1586
|
+
// Note: isAutoConst may be set here, but ParameterSignatureBuilder will
|
|
1587
|
+
// suppress it for opaque handles (Issue #995) — single source of truth.
|
|
1588
|
+
const isPointerParam =
|
|
1589
|
+
!param.isConst &&
|
|
1590
|
+
!param.isArray &&
|
|
1591
|
+
param.type !== "f32" &&
|
|
1592
|
+
param.type !== "f64" &&
|
|
1593
|
+
param.type !== "ISR" &&
|
|
1594
|
+
!knownEnums.has(param.type ?? "");
|
|
1595
|
+
|
|
1596
|
+
const shouldAutoConst =
|
|
1597
|
+
unmodified && isPointerParam && unmodified.has(param.name);
|
|
1598
|
+
|
|
1599
|
+
// Return updated param with resolved flags
|
|
1600
|
+
if (shouldAutoConst || isOpaque) {
|
|
1601
|
+
return {
|
|
1602
|
+
...param,
|
|
1603
|
+
isAutoConst: shouldAutoConst || undefined,
|
|
1604
|
+
isOpaqueHandle: isOpaque || undefined,
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
return param;
|
|
1608
|
+
});
|
|
1541
1609
|
|
|
1542
|
-
return headerSymbol;
|
|
1610
|
+
return { ...headerSymbol, parameters: updatedParams };
|
|
1543
1611
|
});
|
|
1544
1612
|
}
|
|
1545
1613
|
|
|
@@ -315,7 +315,10 @@ class FunctionCallListener extends CNextListener {
|
|
|
315
315
|
if (!baseName) return;
|
|
316
316
|
|
|
317
317
|
// Walk through postfix ops to find the call and resolve the name
|
|
318
|
-
const { resolvedName, foundCall } = this.resolveCallTarget(
|
|
318
|
+
const { resolvedName, foundCall, isGlobalCall } = this.resolveCallTarget(
|
|
319
|
+
ops,
|
|
320
|
+
baseName,
|
|
321
|
+
);
|
|
319
322
|
if (!foundCall) return;
|
|
320
323
|
|
|
321
324
|
// Check if the function is defined
|
|
@@ -325,6 +328,7 @@ class FunctionCallListener extends CNextListener {
|
|
|
325
328
|
line,
|
|
326
329
|
column,
|
|
327
330
|
this.currentScope,
|
|
331
|
+
isGlobalCall,
|
|
328
332
|
);
|
|
329
333
|
};
|
|
330
334
|
|
|
@@ -340,6 +344,9 @@ class FunctionCallListener extends CNextListener {
|
|
|
340
344
|
if (primary.THIS()) {
|
|
341
345
|
return "this";
|
|
342
346
|
}
|
|
347
|
+
if (primary.GLOBAL()) {
|
|
348
|
+
return "global";
|
|
349
|
+
}
|
|
343
350
|
return null;
|
|
344
351
|
}
|
|
345
352
|
|
|
@@ -351,15 +358,21 @@ class FunctionCallListener extends CNextListener {
|
|
|
351
358
|
private resolveCallTarget(
|
|
352
359
|
ops: Parser.PostfixOpContext[],
|
|
353
360
|
baseName: string,
|
|
354
|
-
): { resolvedName: string; foundCall: boolean } {
|
|
361
|
+
): { resolvedName: string; foundCall: boolean; isGlobalCall: boolean } {
|
|
355
362
|
let resolvedName = baseName;
|
|
363
|
+
let isGlobalCall = baseName === "global";
|
|
356
364
|
|
|
357
365
|
for (const op of ops) {
|
|
358
366
|
// Member access: check if it's Scope.member or this.member pattern
|
|
359
367
|
if (op.IDENTIFIER()) {
|
|
360
368
|
const resolved = this.resolveMemberAccess(resolvedName, op);
|
|
361
369
|
if (resolved === null) {
|
|
362
|
-
return { resolvedName, foundCall: false };
|
|
370
|
+
return { resolvedName, foundCall: false, isGlobalCall };
|
|
371
|
+
}
|
|
372
|
+
// If resolution went through a known scope, this is a scope
|
|
373
|
+
// method call, not a global function lookup
|
|
374
|
+
if (isGlobalCall && this.analyzer.isScope(resolvedName)) {
|
|
375
|
+
isGlobalCall = false;
|
|
363
376
|
}
|
|
364
377
|
resolvedName = resolved;
|
|
365
378
|
continue;
|
|
@@ -369,12 +382,12 @@ class FunctionCallListener extends CNextListener {
|
|
|
369
382
|
if (op.argumentList() || op.getChildCount() === 2) {
|
|
370
383
|
const text = op.getText();
|
|
371
384
|
if (text.startsWith("(")) {
|
|
372
|
-
return { resolvedName, foundCall: true };
|
|
385
|
+
return { resolvedName, foundCall: true, isGlobalCall };
|
|
373
386
|
}
|
|
374
387
|
}
|
|
375
388
|
}
|
|
376
389
|
|
|
377
|
-
return { resolvedName, foundCall: false };
|
|
390
|
+
return { resolvedName, foundCall: false, isGlobalCall };
|
|
378
391
|
}
|
|
379
392
|
|
|
380
393
|
/**
|
|
@@ -391,6 +404,11 @@ class FunctionCallListener extends CNextListener {
|
|
|
391
404
|
return `${this.currentScope}_${memberName}`;
|
|
392
405
|
}
|
|
393
406
|
|
|
407
|
+
// Issue #985: Handle global.member -> member (strip global prefix)
|
|
408
|
+
if (resolvedName === "global") {
|
|
409
|
+
return memberName;
|
|
410
|
+
}
|
|
411
|
+
|
|
394
412
|
// Check if base is a known scope
|
|
395
413
|
if (this.analyzer.isScope(resolvedName)) {
|
|
396
414
|
return `${resolvedName}_${memberName}`;
|
|
@@ -934,12 +952,14 @@ class FunctionCallAnalyzer {
|
|
|
934
952
|
* @param line Source line number
|
|
935
953
|
* @param column Source column number
|
|
936
954
|
* @param currentScope The current scope name (if inside a scope)
|
|
955
|
+
* @param isGlobalCall Whether the call used global. prefix
|
|
937
956
|
*/
|
|
938
957
|
public checkFunctionCall(
|
|
939
958
|
name: string,
|
|
940
959
|
line: number,
|
|
941
960
|
column: number,
|
|
942
961
|
currentScope: string | null,
|
|
962
|
+
isGlobalCall: boolean = false,
|
|
943
963
|
): void {
|
|
944
964
|
// Check for self-recursion (MISRA C:2012 Rule 17.2)
|
|
945
965
|
if (this.currentFunctionName && name === this.currentFunctionName) {
|
|
@@ -981,7 +1001,8 @@ class FunctionCallAnalyzer {
|
|
|
981
1001
|
// ADR-057: Allow implicit scope function calls without this. prefix
|
|
982
1002
|
// Check if this is an unqualified call to a scope function
|
|
983
1003
|
// e.g., calling helper() instead of this.helper() inside a scope
|
|
984
|
-
|
|
1004
|
+
// Skip for global. calls — global. explicitly means global scope
|
|
1005
|
+
if (currentScope && !isGlobalCall) {
|
|
985
1006
|
const qualifiedName = `${currentScope}_${name}`;
|
|
986
1007
|
if (this.definedFunctions.has(qualifiedName)) {
|
|
987
1008
|
return; // OK - implicit resolution will handle it
|
|
@@ -989,11 +1010,15 @@ class FunctionCallAnalyzer {
|
|
|
989
1010
|
}
|
|
990
1011
|
|
|
991
1012
|
// Not defined - report error with optional hint
|
|
1013
|
+
// If the function is local (defined later in this file), it's a
|
|
1014
|
+
// define-before-use error regardless of global. prefix
|
|
1015
|
+
const isLocalFunction = this.allLocalFunctions.has(name);
|
|
992
1016
|
const header = this.findStdlibHeader(name);
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1017
|
+
const message = this.buildUndefinedFunctionMessage(
|
|
1018
|
+
name,
|
|
1019
|
+
header,
|
|
1020
|
+
isGlobalCall && !isLocalFunction,
|
|
1021
|
+
);
|
|
997
1022
|
|
|
998
1023
|
this.errors.push({
|
|
999
1024
|
code: "E0422",
|
|
@@ -1004,6 +1029,28 @@ class FunctionCallAnalyzer {
|
|
|
1004
1029
|
});
|
|
1005
1030
|
}
|
|
1006
1031
|
|
|
1032
|
+
/**
|
|
1033
|
+
* Issue #985: Build error message for undefined function calls.
|
|
1034
|
+
* Adjusts hint based on whether the call used global. prefix.
|
|
1035
|
+
*/
|
|
1036
|
+
private buildUndefinedFunctionMessage(
|
|
1037
|
+
name: string,
|
|
1038
|
+
header: string | null,
|
|
1039
|
+
isGlobalCall: boolean,
|
|
1040
|
+
): string {
|
|
1041
|
+
if (isGlobalCall && header) {
|
|
1042
|
+
return `'${name}' is not declared in any included header; add #include <${header}>`;
|
|
1043
|
+
}
|
|
1044
|
+
if (isGlobalCall) {
|
|
1045
|
+
return `'${name}' is not declared in any included header`;
|
|
1046
|
+
}
|
|
1047
|
+
let message = `function '${name}' called before definition`;
|
|
1048
|
+
if (header) {
|
|
1049
|
+
message += `; hint: '${name}' is available from ${header} — try global.${name}()`;
|
|
1050
|
+
}
|
|
1051
|
+
return message;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1007
1054
|
/**
|
|
1008
1055
|
* Check if a function is defined externally (from included files)
|
|
1009
1056
|
* This includes C/C++ headers AND C-Next includes.
|
|
@@ -157,10 +157,32 @@ class InitializationListener extends CNextListener {
|
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
//
|
|
160
|
+
// Resolve the assignment target ONCE (single source of truth)
|
|
161
|
+
const target = this._resolveAssignmentTarget(baseId, postfixOps);
|
|
162
|
+
|
|
163
|
+
// Issue #1012: Compound assignment reads the LHS before writing.
|
|
164
|
+
// Check if the assignment operator is a compound operator (not simple `<-`).
|
|
165
|
+
const isCompoundAssignment = ctx.assignmentOperator().ASSIGN() === null;
|
|
166
|
+
if (isCompoundAssignment) {
|
|
167
|
+
const { line, column } = ParserUtils.getPosition(ctx);
|
|
168
|
+
this.analyzer.checkRead(target.varName, line, column, target.fieldName);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Record the assignment to the resolved target
|
|
172
|
+
this.analyzer.recordAssignment(target.varName, target.fieldName);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Resolve an assignment target to its variable name and optional field name.
|
|
177
|
+
* Single classification path used by both read checks and assignment recording.
|
|
178
|
+
*/
|
|
179
|
+
private _resolveAssignmentTarget(
|
|
180
|
+
baseId: string,
|
|
181
|
+
postfixOps: Parser.PostfixTargetOpContext[],
|
|
182
|
+
): { varName: string; fieldName?: string } {
|
|
183
|
+
// Simple variable: x <- value (no postfix ops)
|
|
161
184
|
if (postfixOps.length === 0) {
|
|
162
|
-
|
|
163
|
-
return;
|
|
185
|
+
return { varName: baseId };
|
|
164
186
|
}
|
|
165
187
|
|
|
166
188
|
// Analyze postfix operations
|
|
@@ -168,15 +190,13 @@ class InitializationListener extends CNextListener {
|
|
|
168
190
|
|
|
169
191
|
// Member access: p.x <- value (struct field)
|
|
170
192
|
if (identifiers.length >= 2 && !hasSubscript) {
|
|
171
|
-
|
|
172
|
-
const fieldName = identifiers[1];
|
|
173
|
-
this.analyzer.recordAssignment(varName, fieldName);
|
|
174
|
-
} else {
|
|
175
|
-
// Array access or mixed: arr[i] <- value or arr[i].field <- value
|
|
176
|
-
// Consider the array/base as a whole initialized
|
|
177
|
-
this.analyzer.recordAssignment(baseId);
|
|
193
|
+
return { varName: identifiers[0], fieldName: identifiers[1] };
|
|
178
194
|
}
|
|
179
|
-
|
|
195
|
+
|
|
196
|
+
// Array access or mixed: arr[i] <- value or arr[i].field <- value
|
|
197
|
+
// Consider the array/base as a whole
|
|
198
|
+
return { varName: baseId };
|
|
199
|
+
}
|
|
180
200
|
|
|
181
201
|
// ========================================================================
|
|
182
202
|
// Function Call Arguments (ADR-006: pass-by-reference may initialize)
|
|
@@ -526,14 +546,159 @@ class InitializationAnalyzer {
|
|
|
526
546
|
|
|
527
547
|
/**
|
|
528
548
|
* Process scope member variable declarations (ADR-016)
|
|
549
|
+
* Issue #1019: Scope members require explicit initialization like locals
|
|
529
550
|
*/
|
|
530
551
|
private _processScopeMembers(decl: Parser.DeclarationContext): void {
|
|
531
552
|
const scopeDecl = decl.scopeDeclaration();
|
|
532
553
|
if (!scopeDecl) return;
|
|
533
554
|
|
|
534
555
|
const scopeName = scopeDecl.IDENTIFIER().getText();
|
|
556
|
+
|
|
557
|
+
// Phase 1: Find all members assigned in any scope function
|
|
558
|
+
const assignedMembers = this._findAssignedScopeMembers(scopeDecl);
|
|
559
|
+
|
|
560
|
+
// Phase 2: Process each member with assignment info
|
|
561
|
+
for (const member of scopeDecl.scopeMember()) {
|
|
562
|
+
this._processScopeMemberVariable(member, scopeName, assignedMembers);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Scan all functions in a scope to find which members are assigned.
|
|
568
|
+
* Issue #1019: A member assigned in ANY function is considered initialized
|
|
569
|
+
* for reads in other functions within the same scope.
|
|
570
|
+
*/
|
|
571
|
+
private _findAssignedScopeMembers(
|
|
572
|
+
scopeDecl: Parser.ScopeDeclarationContext,
|
|
573
|
+
): Set<string> {
|
|
574
|
+
const assigned = new Set<string>();
|
|
575
|
+
|
|
535
576
|
for (const member of scopeDecl.scopeMember()) {
|
|
536
|
-
|
|
577
|
+
const funcDecl = member.functionDeclaration();
|
|
578
|
+
if (!funcDecl) continue;
|
|
579
|
+
|
|
580
|
+
const body = funcDecl.block();
|
|
581
|
+
if (!body) continue;
|
|
582
|
+
|
|
583
|
+
// Scan the function body for assignments to scope members
|
|
584
|
+
this._collectAssignmentsInBlock(body, assigned);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return assigned;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Recursively collect variable names that are assigned in a block.
|
|
592
|
+
* Looks for assignment statements targeting bare identifiers or this.member.
|
|
593
|
+
*/
|
|
594
|
+
private _collectAssignmentsInBlock(
|
|
595
|
+
block: Parser.BlockContext,
|
|
596
|
+
assigned: Set<string>,
|
|
597
|
+
): void {
|
|
598
|
+
for (const stmt of block.statement()) {
|
|
599
|
+
this._collectAssignmentsInStatement(stmt, assigned);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Collect assignments from a single statement, recursing into nested blocks.
|
|
605
|
+
*/
|
|
606
|
+
private _collectAssignmentsInStatement(
|
|
607
|
+
stmt: Parser.StatementContext,
|
|
608
|
+
assigned: Set<string>,
|
|
609
|
+
): void {
|
|
610
|
+
this._collectDirectAssignment(stmt, assigned);
|
|
611
|
+
this._collectFromControlFlow(stmt, assigned);
|
|
612
|
+
this._collectFromSwitch(stmt, assigned);
|
|
613
|
+
this._collectFromBlock(stmt, assigned);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Collect assignment from the statement itself (if it's an assignment).
|
|
618
|
+
*/
|
|
619
|
+
private _collectDirectAssignment(
|
|
620
|
+
stmt: Parser.StatementContext,
|
|
621
|
+
assigned: Set<string>,
|
|
622
|
+
): void {
|
|
623
|
+
const assignStmt = stmt.assignmentStatement();
|
|
624
|
+
if (!assignStmt) return;
|
|
625
|
+
|
|
626
|
+
const target = assignStmt.assignmentTarget();
|
|
627
|
+
if (!target) return;
|
|
628
|
+
|
|
629
|
+
// Both bare identifier and this.member use the same IDENTIFIER token
|
|
630
|
+
const id = target.IDENTIFIER()?.getText();
|
|
631
|
+
if (id) {
|
|
632
|
+
assigned.add(id);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Recurse into control flow statements (if, while, do-while, for).
|
|
638
|
+
*/
|
|
639
|
+
private _collectFromControlFlow(
|
|
640
|
+
stmt: Parser.StatementContext,
|
|
641
|
+
assigned: Set<string>,
|
|
642
|
+
): void {
|
|
643
|
+
// if statement
|
|
644
|
+
const ifStmt = stmt.ifStatement();
|
|
645
|
+
if (ifStmt) {
|
|
646
|
+
for (const childStmt of ifStmt.statement()) {
|
|
647
|
+
this._collectAssignmentsInStatement(childStmt, assigned);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// while statement
|
|
652
|
+
const whileBody = stmt.whileStatement()?.statement();
|
|
653
|
+
if (whileBody) {
|
|
654
|
+
this._collectAssignmentsInStatement(whileBody, assigned);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// do-while statement (Issue #1019 review feedback)
|
|
658
|
+
const doWhileBody = stmt.doWhileStatement()?.block();
|
|
659
|
+
if (doWhileBody) {
|
|
660
|
+
this._collectAssignmentsInBlock(doWhileBody, assigned);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// for statement
|
|
664
|
+
const forBody = stmt.forStatement()?.statement();
|
|
665
|
+
if (forBody) {
|
|
666
|
+
this._collectAssignmentsInStatement(forBody, assigned);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Recurse into switch statement cases.
|
|
672
|
+
*/
|
|
673
|
+
private _collectFromSwitch(
|
|
674
|
+
stmt: Parser.StatementContext,
|
|
675
|
+
assigned: Set<string>,
|
|
676
|
+
): void {
|
|
677
|
+
const switchStmt = stmt.switchStatement();
|
|
678
|
+
if (!switchStmt) return;
|
|
679
|
+
|
|
680
|
+
for (const switchCase of switchStmt.switchCase()) {
|
|
681
|
+
const caseBlock = switchCase.block();
|
|
682
|
+
if (caseBlock) {
|
|
683
|
+
this._collectAssignmentsInBlock(caseBlock, assigned);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const defaultBlock = switchStmt.defaultCase()?.block();
|
|
687
|
+
if (defaultBlock) {
|
|
688
|
+
this._collectAssignmentsInBlock(defaultBlock, assigned);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Recurse into standalone block statement.
|
|
694
|
+
*/
|
|
695
|
+
private _collectFromBlock(
|
|
696
|
+
stmt: Parser.StatementContext,
|
|
697
|
+
assigned: Set<string>,
|
|
698
|
+
): void {
|
|
699
|
+
const block = stmt.block();
|
|
700
|
+
if (block) {
|
|
701
|
+
this._collectAssignmentsInBlock(block, assigned);
|
|
537
702
|
}
|
|
538
703
|
}
|
|
539
704
|
|
|
@@ -543,6 +708,7 @@ class InitializationAnalyzer {
|
|
|
543
708
|
private _processScopeMemberVariable(
|
|
544
709
|
member: Parser.ScopeMemberContext,
|
|
545
710
|
scopeName: string,
|
|
711
|
+
assignedMembers: Set<string>,
|
|
546
712
|
): void {
|
|
547
713
|
const memberVar = member.variableDeclaration();
|
|
548
714
|
if (!memberVar) return;
|
|
@@ -552,9 +718,15 @@ class InitializationAnalyzer {
|
|
|
552
718
|
const { line, column } = ParserUtils.getPosition(memberVar);
|
|
553
719
|
const typeName = this._extractUserTypeName(memberVar.type());
|
|
554
720
|
|
|
721
|
+
// Issue #1019: Scope members are initialized if they have an inline
|
|
722
|
+
// initializer OR are assigned in any function within the scope
|
|
723
|
+
const hasInlineInit = memberVar.expression() !== null;
|
|
724
|
+
const isAssignedInScope = assignedMembers.has(varName);
|
|
725
|
+
const hasInitializer = hasInlineInit || isAssignedInScope;
|
|
726
|
+
|
|
555
727
|
// Register with both raw name and transpiled C name for scope resolution
|
|
556
|
-
this.declareVariable(varName, line, column,
|
|
557
|
-
this.declareVariable(fullName, line, column,
|
|
728
|
+
this.declareVariable(varName, line, column, hasInitializer, typeName);
|
|
729
|
+
this.declareVariable(fullName, line, column, hasInitializer, typeName);
|
|
558
730
|
}
|
|
559
731
|
|
|
560
732
|
/**
|