c-next 0.1.0

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 (72) hide show
  1. package/README.md +726 -0
  2. package/bin/cnext.js +5 -0
  3. package/grammar/C.g4 +1112 -0
  4. package/grammar/CNext.g4 +817 -0
  5. package/grammar/CPP14Lexer.g4 +282 -0
  6. package/grammar/CPP14Parser.g4 +1072 -0
  7. package/package.json +85 -0
  8. package/src/analysis/DivisionByZeroAnalyzer.ts +378 -0
  9. package/src/analysis/FunctionCallAnalyzer.ts +526 -0
  10. package/src/analysis/InitializationAnalyzer.ts +725 -0
  11. package/src/analysis/NullCheckAnalyzer.ts +427 -0
  12. package/src/analysis/types/IDivisionByZeroError.ts +25 -0
  13. package/src/analysis/types/IFunctionCallError.ts +17 -0
  14. package/src/analysis/types/IInitializationError.ts +55 -0
  15. package/src/analysis/types/INullCheckError.ts +25 -0
  16. package/src/codegen/CodeGenerator.ts +7945 -0
  17. package/src/codegen/CommentExtractor.ts +240 -0
  18. package/src/codegen/CommentFormatter.ts +155 -0
  19. package/src/codegen/HeaderGenerator.ts +265 -0
  20. package/src/codegen/TypeResolver.ts +365 -0
  21. package/src/codegen/types/ECommentType.ts +10 -0
  22. package/src/codegen/types/IComment.ts +21 -0
  23. package/src/codegen/types/ICommentError.ts +15 -0
  24. package/src/codegen/types/TOverflowBehavior.ts +6 -0
  25. package/src/codegen/types/TParameterInfo.ts +13 -0
  26. package/src/codegen/types/TTypeConstants.ts +94 -0
  27. package/src/codegen/types/TTypeInfo.ts +22 -0
  28. package/src/index.ts +518 -0
  29. package/src/lib/IncludeDiscovery.ts +131 -0
  30. package/src/lib/InputExpansion.ts +121 -0
  31. package/src/lib/PlatformIODetector.ts +162 -0
  32. package/src/lib/transpiler.ts +439 -0
  33. package/src/lib/types/ITranspileResult.ts +80 -0
  34. package/src/parser/c/grammar/C.interp +338 -0
  35. package/src/parser/c/grammar/C.tokens +229 -0
  36. package/src/parser/c/grammar/CLexer.interp +415 -0
  37. package/src/parser/c/grammar/CLexer.tokens +229 -0
  38. package/src/parser/c/grammar/CLexer.ts +750 -0
  39. package/src/parser/c/grammar/CListener.ts +976 -0
  40. package/src/parser/c/grammar/CParser.ts +9663 -0
  41. package/src/parser/c/grammar/CVisitor.ts +626 -0
  42. package/src/parser/cpp/grammar/CPP14Lexer.interp +478 -0
  43. package/src/parser/cpp/grammar/CPP14Lexer.tokens +264 -0
  44. package/src/parser/cpp/grammar/CPP14Lexer.ts +848 -0
  45. package/src/parser/cpp/grammar/CPP14Parser.interp +492 -0
  46. package/src/parser/cpp/grammar/CPP14Parser.tokens +264 -0
  47. package/src/parser/cpp/grammar/CPP14Parser.ts +19961 -0
  48. package/src/parser/cpp/grammar/CPP14ParserListener.ts +2120 -0
  49. package/src/parser/cpp/grammar/CPP14ParserVisitor.ts +1354 -0
  50. package/src/parser/grammar/CNext.interp +340 -0
  51. package/src/parser/grammar/CNext.tokens +214 -0
  52. package/src/parser/grammar/CNextLexer.interp +374 -0
  53. package/src/parser/grammar/CNextLexer.tokens +214 -0
  54. package/src/parser/grammar/CNextLexer.ts +668 -0
  55. package/src/parser/grammar/CNextListener.ts +1020 -0
  56. package/src/parser/grammar/CNextParser.ts +9239 -0
  57. package/src/parser/grammar/CNextVisitor.ts +654 -0
  58. package/src/preprocessor/Preprocessor.ts +301 -0
  59. package/src/preprocessor/ToolchainDetector.ts +225 -0
  60. package/src/preprocessor/types/IPreprocessResult.ts +39 -0
  61. package/src/preprocessor/types/IToolchain.ts +27 -0
  62. package/src/project/FileDiscovery.ts +236 -0
  63. package/src/project/Project.ts +425 -0
  64. package/src/project/types/IProjectConfig.ts +64 -0
  65. package/src/symbols/CNextSymbolCollector.ts +326 -0
  66. package/src/symbols/CSymbolCollector.ts +457 -0
  67. package/src/symbols/CppSymbolCollector.ts +362 -0
  68. package/src/symbols/SymbolTable.ts +312 -0
  69. package/src/symbols/types/IConflict.ts +20 -0
  70. package/src/types/ESourceLanguage.ts +10 -0
  71. package/src/types/ESymbolKind.ts +20 -0
  72. package/src/types/ISymbol.ts +45 -0
@@ -0,0 +1,725 @@
1
+ /**
2
+ * Initialization Analyzer
3
+ * Detects use of uninitialized variables (Rust-style E0381 errors)
4
+ *
5
+ * Phases:
6
+ * 1. Linear code tracking
7
+ * 2. Control flow (if/else branches)
8
+ * 3. Loop analysis
9
+ * 4. Scope tracking
10
+ * 5. Function parameters (always initialized)
11
+ * 7. Per-field struct tracking
12
+ */
13
+
14
+ import { ParseTreeWalker } from "antlr4ng";
15
+ import { CNextListener } from "../parser/grammar/CNextListener";
16
+ import * as Parser from "../parser/grammar/CNextParser";
17
+ import {
18
+ IInitializationError,
19
+ IDeclarationInfo,
20
+ } from "./types/IInitializationError";
21
+
22
+ /**
23
+ * Tracks the initialization state of a variable
24
+ */
25
+ interface IVariableState {
26
+ /** Where the variable was declared */
27
+ declaration: IDeclarationInfo;
28
+ /** Whether the variable has been initialized */
29
+ initialized: boolean;
30
+ /** Type of the variable (for struct field tracking) */
31
+ typeName: string | null;
32
+ /** For structs: which fields have been initialized */
33
+ initializedFields: Set<string>;
34
+ /** Is this a struct type? */
35
+ isStruct: boolean;
36
+ }
37
+
38
+ /**
39
+ * Represents a scope (function body, block, etc.)
40
+ */
41
+ interface IScope {
42
+ /** Variables declared in this scope */
43
+ variables: Map<string, IVariableState>;
44
+ /** Parent scope (for nested blocks) */
45
+ parent: IScope | null;
46
+ }
47
+
48
+ /**
49
+ * Listener that walks the parse tree and tracks initialization
50
+ */
51
+ class InitializationListener extends CNextListener {
52
+ private analyzer: InitializationAnalyzer;
53
+
54
+ /** Stack of saved states before each if statement */
55
+ private savedStates: Map<string, IVariableState>[] = [];
56
+
57
+ /** Track when we're inside a function call's argument list */
58
+ private inFunctionCallArgs: number = 0;
59
+
60
+ /** Track nesting depth inside functions/methods (0 = global level) */
61
+ private functionDepth: number = 0;
62
+
63
+ constructor(analyzer: InitializationAnalyzer) {
64
+ super();
65
+ this.analyzer = analyzer;
66
+ }
67
+
68
+ // ========================================================================
69
+ // Scope Entry/Exit
70
+ // ========================================================================
71
+
72
+ override enterFunctionDeclaration = (
73
+ ctx: Parser.FunctionDeclarationContext,
74
+ ): void => {
75
+ this.functionDepth++;
76
+ this.analyzer.enterScope();
77
+
78
+ // Declare parameters as initialized
79
+ const paramList = ctx.parameterList();
80
+ if (paramList) {
81
+ for (const param of paramList.parameter()) {
82
+ const name = param.IDENTIFIER().getText();
83
+ const line = param.start?.line ?? 0;
84
+ const column = param.start?.column ?? 0;
85
+
86
+ // Get type name for struct tracking
87
+ const typeCtx = param.type();
88
+ let typeName: string | null = null;
89
+ if (typeCtx.userType()) {
90
+ typeName = typeCtx.userType()!.IDENTIFIER().getText();
91
+ }
92
+
93
+ this.analyzer.declareParameter(name, line, column, typeName);
94
+ }
95
+ }
96
+ };
97
+
98
+ override exitFunctionDeclaration = (
99
+ _ctx: Parser.FunctionDeclarationContext,
100
+ ): void => {
101
+ this.analyzer.exitScope();
102
+ this.functionDepth--;
103
+ };
104
+
105
+ override enterBlock = (_ctx: Parser.BlockContext): void => {
106
+ this.analyzer.enterScope();
107
+ };
108
+
109
+ override exitBlock = (_ctx: Parser.BlockContext): void => {
110
+ this.analyzer.exitScope();
111
+ };
112
+
113
+ // ========================================================================
114
+ // Variable Declarations
115
+ // ========================================================================
116
+
117
+ override enterVariableDeclaration = (
118
+ ctx: Parser.VariableDeclarationContext,
119
+ ): void => {
120
+ // Skip global variables - they're already handled by createGlobalScope()
121
+ if (this.functionDepth === 0) {
122
+ return;
123
+ }
124
+
125
+ const name = ctx.IDENTIFIER().getText();
126
+ const line = ctx.start?.line ?? 0;
127
+ const column = ctx.start?.column ?? 0;
128
+ const hasInitializer = ctx.expression() !== null;
129
+
130
+ // Get type name for struct tracking
131
+ const typeCtx = ctx.type();
132
+ let typeName: string | null = null;
133
+ if (typeCtx.userType()) {
134
+ typeName = typeCtx.userType()!.IDENTIFIER().getText();
135
+ }
136
+
137
+ this.analyzer.declareVariable(name, line, column, hasInitializer, typeName);
138
+ };
139
+
140
+ // ========================================================================
141
+ // Assignments
142
+ // ========================================================================
143
+
144
+ override enterAssignmentStatement = (
145
+ ctx: Parser.AssignmentStatementContext,
146
+ ): void => {
147
+ const targetCtx = ctx.assignmentTarget();
148
+
149
+ // Simple variable assignment: x <- value
150
+ if (
151
+ targetCtx.IDENTIFIER() &&
152
+ !targetCtx.memberAccess() &&
153
+ !targetCtx.arrayAccess()
154
+ ) {
155
+ const name = targetCtx.IDENTIFIER()!.getText();
156
+ this.analyzer.recordAssignment(name);
157
+ }
158
+
159
+ // Member access: p.x <- value (only first-level field)
160
+ if (targetCtx.memberAccess()) {
161
+ const memberCtx = targetCtx.memberAccess()!;
162
+ const identifiers = memberCtx.IDENTIFIER();
163
+ if (identifiers.length >= 2) {
164
+ const varName = identifiers[0].getText();
165
+ const fieldName = identifiers[1].getText();
166
+ this.analyzer.recordAssignment(varName, fieldName);
167
+ }
168
+ }
169
+
170
+ // Array access: arr[i] <- value
171
+ if (targetCtx.arrayAccess()) {
172
+ const arrayName = targetCtx.arrayAccess()!.IDENTIFIER().getText();
173
+ // Array element assignment initializes that element, but for simplicity
174
+ // we'll consider the array as a whole. More granular tracking would be complex.
175
+ this.analyzer.recordAssignment(arrayName);
176
+ }
177
+ };
178
+
179
+ // ========================================================================
180
+ // Function Call Arguments (ADR-006: pass-by-reference may initialize)
181
+ // ========================================================================
182
+
183
+ override enterArgumentList = (_ctx: Parser.ArgumentListContext): void => {
184
+ // When inside function call arguments, variables passed may be output params
185
+ // We don't error on uninitialized reads, and we mark them as initialized after
186
+ this.inFunctionCallArgs++;
187
+ };
188
+
189
+ override exitArgumentList = (ctx: Parser.ArgumentListContext): void => {
190
+ this.inFunctionCallArgs--;
191
+
192
+ // Mark any simple identifiers passed as arguments as initialized
193
+ // (they might be output parameters that the function writes to)
194
+ for (const expr of ctx.expression()) {
195
+ // Walk down to find simple identifiers
196
+ this.markArgumentsAsInitialized(expr);
197
+ }
198
+ };
199
+
200
+ /**
201
+ * Recursively find and mark simple identifier arguments as initialized
202
+ */
203
+ private markArgumentsAsInitialized(expr: Parser.ExpressionContext): void {
204
+ // Navigate through expression layers to find primary expression
205
+ const ternary = expr.ternaryExpression();
206
+ if (!ternary) return;
207
+ const orExprs = ternary.orExpression();
208
+ if (orExprs.length === 0) return;
209
+ const or = orExprs[0];
210
+ if (!or) return;
211
+ const and = or.andExpression(0);
212
+ if (!and) return;
213
+ const eq = and.equalityExpression(0);
214
+ if (!eq) return;
215
+ const rel = eq.relationalExpression(0);
216
+ if (!rel) return;
217
+ const bor = rel.bitwiseOrExpression(0);
218
+ if (!bor) return;
219
+ const bxor = bor.bitwiseXorExpression(0);
220
+ if (!bxor) return;
221
+ const band = bxor.bitwiseAndExpression(0);
222
+ if (!band) return;
223
+ const shift = band.shiftExpression(0);
224
+ if (!shift) return;
225
+ const add = shift.additiveExpression(0);
226
+ if (!add) return;
227
+ const mult = add.multiplicativeExpression(0);
228
+ if (!mult) return;
229
+ const unary = mult.unaryExpression(0);
230
+ if (!unary) return;
231
+ const postfix = unary.postfixExpression();
232
+ if (!postfix) return;
233
+ const primary = postfix.primaryExpression();
234
+ if (!primary) return;
235
+
236
+ // Found primary expression - check if it's a simple identifier
237
+ if (primary.IDENTIFIER()) {
238
+ const name = primary.IDENTIFIER()!.getText();
239
+ this.analyzer.recordAssignment(name);
240
+ }
241
+ }
242
+
243
+ // ========================================================================
244
+ // Variable Reads (in expressions)
245
+ // ========================================================================
246
+
247
+ override enterPrimaryExpression = (
248
+ ctx: Parser.PrimaryExpressionContext,
249
+ ): void => {
250
+ // Skip if we're in a write context (left side of assignment)
251
+ if (this.analyzer.isInWriteContext()) {
252
+ return;
253
+ }
254
+
255
+ // Skip if we're in function call arguments (might be output param)
256
+ if (this.inFunctionCallArgs > 0) {
257
+ return;
258
+ }
259
+
260
+ // Check for simple identifier
261
+ if (ctx.IDENTIFIER()) {
262
+ const name = ctx.IDENTIFIER()!.getText();
263
+ const line = ctx.start?.line ?? 0;
264
+ const column = ctx.start?.column ?? 0;
265
+
266
+ this.analyzer.checkRead(name, line, column);
267
+ }
268
+ };
269
+
270
+ override enterMemberAccess = (ctx: Parser.MemberAccessContext): void => {
271
+ // Skip if we're in a write context
272
+ if (this.analyzer.isInWriteContext()) {
273
+ return;
274
+ }
275
+
276
+ // Skip if we're in function call arguments
277
+ if (this.inFunctionCallArgs > 0) {
278
+ return;
279
+ }
280
+
281
+ const identifiers = ctx.IDENTIFIER();
282
+ if (identifiers.length >= 2) {
283
+ const varName = identifiers[0].getText();
284
+ const fieldName = identifiers[1].getText();
285
+ const line = ctx.start?.line ?? 0;
286
+ const column = ctx.start?.column ?? 0;
287
+
288
+ // Check if the specific field is initialized
289
+ this.analyzer.checkRead(varName, line, column, fieldName);
290
+ }
291
+ };
292
+
293
+ // ========================================================================
294
+ // Control Flow: If Statements
295
+ // ========================================================================
296
+
297
+ override enterIfStatement = (_ctx: Parser.IfStatementContext): void => {
298
+ // Save current state before entering if
299
+ const stateBefore = this.analyzer.cloneScopeState();
300
+ this.savedStates.push(stateBefore);
301
+ };
302
+
303
+ override exitIfStatement = (ctx: Parser.IfStatementContext): void => {
304
+ const stateBefore = this.savedStates.pop();
305
+ if (!stateBefore) return;
306
+
307
+ // Check if there's an else clause
308
+ const hasElse = ctx.ELSE() !== null;
309
+
310
+ if (hasElse) {
311
+ // With else: the tree walker processes both branches in order.
312
+ // If a variable is initialized in both branches, it ends up initialized.
313
+ // If only one branch initializes it, it may or may not be initialized
314
+ // depending on which branch ran last in the traversal.
315
+ // For now, we'll trust the final state - this is optimistic but
316
+ // often correct for the common pattern where both branches initialize.
317
+ // (A more precise analysis would track both branches separately)
318
+ // Don't restore - keep whatever state the branches produced.
319
+ } else {
320
+ // No else: the if might not execute, so restore to state before if
321
+ // Any initializations inside the if are not guaranteed
322
+ this.analyzer.restoreFromState(stateBefore);
323
+ }
324
+ };
325
+
326
+ // ========================================================================
327
+ // Control Flow: Loops
328
+ // ========================================================================
329
+
330
+ override enterWhileStatement = (_ctx: Parser.WhileStatementContext): void => {
331
+ // Save state before loop - we'll restore after because loop might not run
332
+ this.savedStates.push(this.analyzer.cloneScopeState());
333
+ };
334
+
335
+ override exitWhileStatement = (_ctx: Parser.WhileStatementContext): void => {
336
+ // Loops are conservative: we assume they might not run at all
337
+ // So we restore state to before the loop
338
+ const stateBeforeLoop = this.savedStates.pop();
339
+ if (stateBeforeLoop) {
340
+ this.analyzer.restoreFromState(stateBeforeLoop);
341
+ }
342
+ };
343
+
344
+ override enterForStatement = (_ctx: Parser.ForStatementContext): void => {
345
+ // Save state before loop
346
+ this.savedStates.push(this.analyzer.cloneScopeState());
347
+ };
348
+
349
+ override exitForStatement = (_ctx: Parser.ForStatementContext): void => {
350
+ // Same as while - conservative approach
351
+ const stateBeforeLoop = this.savedStates.pop();
352
+ if (stateBeforeLoop) {
353
+ this.analyzer.restoreFromState(stateBeforeLoop);
354
+ }
355
+ };
356
+ }
357
+
358
+ /**
359
+ * Analyzes C-Next AST for use-before-initialization errors
360
+ */
361
+ export class InitializationAnalyzer {
362
+ private errors: IInitializationError[] = [];
363
+
364
+ private currentScope: IScope | null = null;
365
+
366
+ /** Known struct types and their fields */
367
+ private structFields: Map<string, Set<string>> = new Map();
368
+
369
+ /** Track if we're processing a write target (left side of assignment) */
370
+ private inWriteContext: boolean = false;
371
+
372
+ /**
373
+ * Analyze a parsed program for initialization errors
374
+ * @param tree The parsed program AST
375
+ * @returns Array of initialization errors
376
+ */
377
+ public analyze(tree: Parser.ProgramContext): IInitializationError[] {
378
+ this.errors = [];
379
+ this.currentScope = null;
380
+ this.structFields.clear();
381
+
382
+ // First pass: collect struct definitions
383
+ this.collectStructDefinitions(tree);
384
+
385
+ // Create global scope with all global/namespace variable declarations
386
+ this.createGlobalScope(tree);
387
+
388
+ // Second pass: analyze initialization
389
+ const listener = new InitializationListener(this);
390
+ ParseTreeWalker.DEFAULT.walk(listener, tree);
391
+
392
+ return this.errors;
393
+ }
394
+
395
+ /**
396
+ * Create the global scope with all top-level variable declarations
397
+ * Global variables are considered "initialized" (zero-init by ADR-015)
398
+ */
399
+ private createGlobalScope(tree: Parser.ProgramContext): void {
400
+ this.enterScope(); // Create global scope
401
+
402
+ for (const decl of tree.declaration()) {
403
+ // Global variable declarations
404
+ const varDecl = decl.variableDeclaration();
405
+ if (varDecl) {
406
+ const name = varDecl.IDENTIFIER().getText();
407
+ const line = varDecl.start?.line ?? 0;
408
+ const column = varDecl.start?.column ?? 0;
409
+
410
+ // Get type for struct tracking
411
+ const typeCtx = varDecl.type();
412
+ let typeName: string | null = null;
413
+ if (typeCtx.userType()) {
414
+ typeName = typeCtx.userType()!.IDENTIFIER().getText();
415
+ }
416
+
417
+ // Globals are always initialized (zero-init or explicit)
418
+ this.declareVariable(name, line, column, true, typeName);
419
+ }
420
+
421
+ // Scope member variables (ADR-016: renamed from namespace)
422
+ const scopeDecl = decl.scopeDeclaration();
423
+ if (scopeDecl) {
424
+ const scopeName = scopeDecl.IDENTIFIER().getText();
425
+ for (const member of scopeDecl.scopeMember()) {
426
+ const memberVar = member.variableDeclaration();
427
+ if (memberVar) {
428
+ const varName = memberVar.IDENTIFIER().getText();
429
+ const fullName = `${scopeName}_${varName}`; // Mangled name
430
+ const line = memberVar.start?.line ?? 0;
431
+ const column = memberVar.start?.column ?? 0;
432
+
433
+ const typeCtx = memberVar.type();
434
+ let typeName: string | null = null;
435
+ if (typeCtx.userType()) {
436
+ typeName = typeCtx.userType()!.IDENTIFIER().getText();
437
+ }
438
+
439
+ // Also register with raw name for scope resolution
440
+ this.declareVariable(varName, line, column, true, typeName);
441
+ this.declareVariable(fullName, line, column, true, typeName);
442
+ }
443
+ }
444
+ }
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Collect struct definitions to know their fields
450
+ */
451
+ private collectStructDefinitions(tree: Parser.ProgramContext): void {
452
+ for (const decl of tree.declaration()) {
453
+ const structDecl = decl.structDeclaration();
454
+ if (structDecl) {
455
+ const structName = structDecl.IDENTIFIER().getText();
456
+ const fields = new Set<string>();
457
+
458
+ for (const member of structDecl.structMember()) {
459
+ const fieldName = member.IDENTIFIER().getText();
460
+ fields.add(fieldName);
461
+ }
462
+
463
+ this.structFields.set(structName, fields);
464
+ }
465
+ }
466
+ }
467
+
468
+ // ========================================================================
469
+ // Scope Management
470
+ // ========================================================================
471
+
472
+ /**
473
+ * Enter a new scope (function, block, etc.)
474
+ */
475
+ public enterScope(): void {
476
+ const newScope: IScope = {
477
+ variables: new Map(),
478
+ parent: this.currentScope,
479
+ };
480
+ this.currentScope = newScope;
481
+ }
482
+
483
+ /**
484
+ * Exit the current scope
485
+ */
486
+ public exitScope(): void {
487
+ if (this.currentScope) {
488
+ this.currentScope = this.currentScope.parent;
489
+ }
490
+ }
491
+
492
+ // ========================================================================
493
+ // Variable Tracking
494
+ // ========================================================================
495
+
496
+ /**
497
+ * Declare a variable (may or may not be initialized)
498
+ */
499
+ public declareVariable(
500
+ name: string,
501
+ line: number,
502
+ column: number,
503
+ hasInitializer: boolean,
504
+ typeName: string | null,
505
+ ): void {
506
+ if (!this.currentScope) {
507
+ // Global scope - create implicit scope
508
+ this.enterScope();
509
+ }
510
+
511
+ const isStruct = typeName !== null && this.structFields.has(typeName);
512
+ const fields = isStruct
513
+ ? this.structFields.get(typeName)!
514
+ : new Set<string>();
515
+
516
+ const state: IVariableState = {
517
+ declaration: { name, line, column },
518
+ initialized: hasInitializer,
519
+ typeName,
520
+ isStruct,
521
+ // If initialized with full struct initializer, all fields are initialized
522
+ initializedFields: hasInitializer ? new Set(fields) : new Set(),
523
+ };
524
+
525
+ this.currentScope!.variables.set(name, state);
526
+ }
527
+
528
+ /**
529
+ * Record that a variable (or field) has been assigned
530
+ */
531
+ public recordAssignment(name: string, field?: string): void {
532
+ const state = this.lookupVariable(name);
533
+ if (state) {
534
+ if (field) {
535
+ // Field assignment
536
+ state.initializedFields.add(field);
537
+ // Check if all fields are now initialized
538
+ if (state.isStruct && state.typeName) {
539
+ const allFields = this.structFields.get(state.typeName);
540
+ if (allFields) {
541
+ const allInitialized = [...allFields].every((f) =>
542
+ state.initializedFields.has(f),
543
+ );
544
+ if (allInitialized) {
545
+ state.initialized = true;
546
+ }
547
+ }
548
+ }
549
+ } else {
550
+ // Whole variable assignment
551
+ state.initialized = true;
552
+ // Mark all fields as initialized too
553
+ if (state.isStruct && state.typeName) {
554
+ const fields = this.structFields.get(state.typeName);
555
+ if (fields) {
556
+ state.initializedFields = new Set(fields);
557
+ }
558
+ }
559
+ }
560
+ }
561
+ }
562
+
563
+ /**
564
+ * Check if a variable (or field) is used before initialization
565
+ */
566
+ public checkRead(
567
+ name: string,
568
+ line: number,
569
+ column: number,
570
+ field?: string,
571
+ ): void {
572
+ const state = this.lookupVariable(name);
573
+
574
+ if (!state) {
575
+ // Variable not found in any scope - this would be a different error
576
+ // (undefined variable), not an initialization error
577
+ return;
578
+ }
579
+
580
+ if (field) {
581
+ // Reading a specific field
582
+ if (!state.initializedFields.has(field)) {
583
+ this.addError(
584
+ `${name}.${field}`,
585
+ line,
586
+ column,
587
+ state.declaration,
588
+ false, // Definitely uninitialized
589
+ );
590
+ }
591
+ } else if (!state.initialized) {
592
+ // Reading the whole variable
593
+ this.addError(
594
+ name,
595
+ line,
596
+ column,
597
+ state.declaration,
598
+ false, // Definitely uninitialized
599
+ );
600
+ }
601
+ }
602
+
603
+ /**
604
+ * Look up a variable in the current scope chain
605
+ */
606
+ private lookupVariable(name: string): IVariableState | null {
607
+ let scope = this.currentScope;
608
+ while (scope) {
609
+ if (scope.variables.has(name)) {
610
+ return scope.variables.get(name)!;
611
+ }
612
+ scope = scope.parent;
613
+ }
614
+ return null;
615
+ }
616
+
617
+ // ========================================================================
618
+ // Control Flow
619
+ // ========================================================================
620
+
621
+ /**
622
+ * Clone the current scope state for branch analysis
623
+ */
624
+ public cloneScopeState(): Map<string, IVariableState> {
625
+ const clone = new Map<string, IVariableState>();
626
+ let scope = this.currentScope;
627
+ while (scope) {
628
+ for (const [name, state] of scope.variables) {
629
+ if (!clone.has(name)) {
630
+ clone.set(name, {
631
+ ...state,
632
+ initializedFields: new Set(state.initializedFields),
633
+ });
634
+ }
635
+ }
636
+ scope = scope.parent;
637
+ }
638
+ return clone;
639
+ }
640
+
641
+ /**
642
+ * Restore initialization state from a saved snapshot
643
+ * Used for control flow analysis to "undo" branch changes
644
+ */
645
+ public restoreFromState(savedState: Map<string, IVariableState>): void {
646
+ for (const [name, savedVarState] of savedState) {
647
+ const currentVar = this.lookupVariable(name);
648
+ if (currentVar) {
649
+ // Restore the initialization state from the saved snapshot
650
+ currentVar.initialized = savedVarState.initialized;
651
+ currentVar.initializedFields = new Set(savedVarState.initializedFields);
652
+ }
653
+ }
654
+ }
655
+
656
+ // ========================================================================
657
+ // Error Reporting
658
+ // ========================================================================
659
+
660
+ private addError(
661
+ variable: string,
662
+ line: number,
663
+ column: number,
664
+ declaration: IDeclarationInfo,
665
+ mayBeUninitialized: boolean,
666
+ ): void {
667
+ const certainty = mayBeUninitialized ? "possibly " : "";
668
+ this.errors.push({
669
+ code: "E0381",
670
+ variable,
671
+ line,
672
+ column,
673
+ declaration,
674
+ mayBeUninitialized,
675
+ message: `use of ${certainty}uninitialized variable '${variable}'`,
676
+ });
677
+ }
678
+
679
+ // ========================================================================
680
+ // Write Context Tracking
681
+ // ========================================================================
682
+
683
+ public setWriteContext(inWrite: boolean): void {
684
+ this.inWriteContext = inWrite;
685
+ }
686
+
687
+ public isInWriteContext(): boolean {
688
+ return this.inWriteContext;
689
+ }
690
+
691
+ // ========================================================================
692
+ // Function Parameters
693
+ // ========================================================================
694
+
695
+ /**
696
+ * Declare function parameters (always initialized by caller)
697
+ */
698
+ public declareParameter(
699
+ name: string,
700
+ line: number,
701
+ column: number,
702
+ typeName: string | null,
703
+ ): void {
704
+ if (!this.currentScope) {
705
+ this.enterScope();
706
+ }
707
+
708
+ const isStruct = typeName !== null && this.structFields.has(typeName);
709
+ const fields = isStruct
710
+ ? this.structFields.get(typeName)!
711
+ : new Set<string>();
712
+
713
+ const state: IVariableState = {
714
+ declaration: { name, line, column },
715
+ initialized: true, // Parameters are always initialized by caller
716
+ typeName,
717
+ isStruct,
718
+ initializedFields: new Set(fields), // All fields initialized
719
+ };
720
+
721
+ this.currentScope!.variables.set(name, state);
722
+ }
723
+ }
724
+
725
+ export default InitializationAnalyzer;