c-next 0.1.71 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.71",
3
+ "version": "0.2.0",
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",
@@ -271,8 +271,8 @@ class Transpiler {
271
271
  // Stage 3b: Resolve external const array dimensions
272
272
  CodeGenState.symbolTable.resolveExternalArrayDimensions();
273
273
 
274
- // Stage 4: Check for symbol conflicts (skipped in standalone mode)
275
- if (!input.skipConflictCheck && !this._checkSymbolConflicts(result)) {
274
+ // Stage 4: Check for symbol conflicts
275
+ if (!this._checkSymbolConflicts(result)) {
276
276
  return;
277
277
  }
278
278
 
@@ -581,7 +581,6 @@ class Transpiler {
581
581
  cnextFiles: [...cnextIncludeFiles, mainFile],
582
582
  headerFiles: allHeaders,
583
583
  writeOutputToDisk: false,
584
- skipConflictCheck: true,
585
584
  };
586
585
  }
587
586
 
@@ -1283,9 +1282,14 @@ class Transpiler {
1283
1282
  ? { ...typeInput, symbolTable: CodeGenState.symbolTable }
1284
1283
  : undefined;
1285
1284
 
1286
- // ADR-055 Phase 7: Convert TSymbol to IHeaderSymbol
1287
- // Note: In multi-file run() path, auto-const is already applied during transpilation
1288
- const headerSymbols = HeaderSymbolAdapter.fromTSymbols(exportedSymbols);
1285
+ // ADR-055 Phase 7: Convert TSymbol to IHeaderSymbol with auto-const info
1286
+ // Issue #817: Apply auto-const info same as generateHeaderContent() does
1287
+ const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
1288
+ const headerSymbols = this.convertToHeaderSymbols(
1289
+ exportedSymbols,
1290
+ unmodifiedParams,
1291
+ allKnownEnums,
1292
+ );
1289
1293
 
1290
1294
  const headerContent = this.headerGenerator.generate(
1291
1295
  headerSymbols,
@@ -627,14 +627,30 @@ class SymbolTable {
627
627
  }
628
628
 
629
629
  // Multiple definitions in same language (excluding overloads) = ERROR
630
+ // Issue #817: Group by scope AND kind - symbols in different scopes don't conflict,
631
+ // and symbols with different kinds (variable vs scope) don't conflict either
630
632
  if (cnextDefs.length > 1) {
631
- const locations = cnextDefs.map((s) => `${s.sourceFile}:${s.sourceLine}`);
632
- return {
633
- symbolName: cnextDefs[0].name,
634
- definitions: cnextDefs,
635
- severity: "error",
636
- message: `Symbol conflict: '${cnextDefs[0].name}' is defined multiple times in C-Next:\n ${locations.join("\n ")}`,
637
- };
633
+ const byScopeAndKind = this.groupCNextSymbolsByScopeAndKind(cnextDefs);
634
+
635
+ // Check each scope+kind group for conflicts (multiple symbols in same scope with same kind)
636
+ for (const symbols of byScopeAndKind.values()) {
637
+ if (symbols.length > 1) {
638
+ const locations = symbols.map(
639
+ (s) => `${s.sourceFile}:${s.sourceLine}`,
640
+ );
641
+ const scopeName = symbols[0].scope.name;
642
+ const displayName =
643
+ scopeName === ""
644
+ ? symbols[0].name
645
+ : `${scopeName}.${symbols[0].name}`;
646
+ return {
647
+ symbolName: displayName,
648
+ definitions: symbols,
649
+ severity: "error",
650
+ message: `Symbol conflict: '${displayName}' is defined multiple times in C-Next:\n ${locations.join("\n ")}`,
651
+ };
652
+ }
653
+ }
638
654
  }
639
655
 
640
656
  // Same symbol in C and C++ - typically OK (same symbol)
@@ -645,6 +661,36 @@ class SymbolTable {
645
661
  return null;
646
662
  }
647
663
 
664
+ /**
665
+ * Issue #817: Group C-Next symbols by scope name and kind.
666
+ *
667
+ * Symbols in different scopes don't conflict (Foo.enabled vs Bar.enabled
668
+ * generate Foo_enabled and Bar_enabled). Symbols with different kinds also
669
+ * don't conflict (variable LED vs scope LED are distinct).
670
+ *
671
+ * @param symbols C-Next symbols to group (must all be TSymbol)
672
+ * @returns Map from "scopeName:kind" key to array of symbols
673
+ */
674
+ private groupCNextSymbolsByScopeAndKind(
675
+ symbols: TAnySymbol[],
676
+ ): Map<string, TSymbol[]> {
677
+ const byScopeAndKind = new Map<string, TSymbol[]>();
678
+
679
+ for (const def of symbols) {
680
+ const tSymbol = def as TSymbol;
681
+ const scopeName = tSymbol.scope.name;
682
+ const key = `${scopeName}:${tSymbol.kind}`;
683
+ const existing = byScopeAndKind.get(key);
684
+ if (existing) {
685
+ existing.push(tSymbol);
686
+ } else {
687
+ byScopeAndKind.set(key, [tSymbol]);
688
+ }
689
+ }
690
+
691
+ return byScopeAndKind;
692
+ }
693
+
648
694
  // ========================================================================
649
695
  // Struct Field Information
650
696
  // ========================================================================
@@ -322,6 +322,162 @@ describe("SymbolTable", () => {
322
322
 
323
323
  expect(symbolTable.hasConflict("overloaded")).toBe(false);
324
324
  });
325
+
326
+ // Issue #817: Scope-private members should NOT conflict across scopes
327
+ it("should NOT detect conflict for same-named members in different scopes", () => {
328
+ // Create two different named scopes
329
+ const globalScope = TestScopeUtils.createMockGlobalScope();
330
+ const fooScope = TestScopeUtils.createMockScope("Foo", globalScope);
331
+ const barScope = TestScopeUtils.createMockScope("Bar", globalScope);
332
+
333
+ // Add 'enabled' variable in scope Foo
334
+ symbolTable.addTSymbol({
335
+ kind: "variable",
336
+ name: "enabled",
337
+ sourceFile: "test.cnx",
338
+ sourceLine: 2,
339
+ sourceLanguage: ESourceLanguage.CNext,
340
+ isExported: false,
341
+ type: TTypeUtils.createPrimitive("bool"),
342
+ isArray: false,
343
+ isConst: false,
344
+ isAtomic: false,
345
+ scope: fooScope,
346
+ });
347
+
348
+ // Add 'enabled' variable in scope Bar
349
+ symbolTable.addTSymbol({
350
+ kind: "variable",
351
+ name: "enabled",
352
+ sourceFile: "test.cnx",
353
+ sourceLine: 10,
354
+ sourceLanguage: ESourceLanguage.CNext,
355
+ isExported: false,
356
+ type: TTypeUtils.createPrimitive("bool"),
357
+ isArray: false,
358
+ isConst: false,
359
+ isAtomic: false,
360
+ scope: barScope,
361
+ });
362
+
363
+ // These are NOT conflicts - they generate Foo_enabled and Bar_enabled
364
+ expect(symbolTable.hasConflict("enabled")).toBe(false);
365
+ });
366
+
367
+ // Issue #817: Same-named functions in different scopes are not conflicts
368
+ it("should NOT detect conflict for same-named functions in different scopes", () => {
369
+ const globalScope = TestScopeUtils.createMockGlobalScope();
370
+ const fooScope = TestScopeUtils.createMockScope("Foo", globalScope);
371
+ const barScope = TestScopeUtils.createMockScope("Bar", globalScope);
372
+
373
+ // Add 'initialize' function in scope Foo
374
+ symbolTable.addTSymbol({
375
+ kind: "function",
376
+ name: "initialize",
377
+ sourceFile: "test.cnx",
378
+ sourceLine: 4,
379
+ sourceLanguage: ESourceLanguage.CNext,
380
+ isExported: true,
381
+ returnType: TTypeUtils.createPrimitive("void"),
382
+ parameters: [],
383
+ scope: fooScope,
384
+ visibility: "public",
385
+ body: null,
386
+ } as IFunctionSymbol);
387
+
388
+ // Add 'initialize' function in scope Bar
389
+ symbolTable.addTSymbol({
390
+ kind: "function",
391
+ name: "initialize",
392
+ sourceFile: "test.cnx",
393
+ sourceLine: 12,
394
+ sourceLanguage: ESourceLanguage.CNext,
395
+ isExported: true,
396
+ returnType: TTypeUtils.createPrimitive("void"),
397
+ parameters: [],
398
+ scope: barScope,
399
+ visibility: "public",
400
+ body: null,
401
+ } as IFunctionSymbol);
402
+
403
+ // These generate Foo_initialize and Bar_initialize - no conflict
404
+ expect(symbolTable.hasConflict("initialize")).toBe(false);
405
+ });
406
+
407
+ // True conflicts: same name in same scope should still be detected
408
+ it("should detect conflict for same-named symbols in same scope", () => {
409
+ const globalScope = TestScopeUtils.createMockGlobalScope();
410
+ const fooScope = TestScopeUtils.createMockScope("Foo", globalScope);
411
+
412
+ // Add 'duplicate' variable in scope Foo twice
413
+ symbolTable.addTSymbol({
414
+ kind: "variable",
415
+ name: "duplicate",
416
+ sourceFile: "test.cnx",
417
+ sourceLine: 2,
418
+ sourceLanguage: ESourceLanguage.CNext,
419
+ isExported: false,
420
+ type: TTypeUtils.createPrimitive("bool"),
421
+ isArray: false,
422
+ isConst: false,
423
+ isAtomic: false,
424
+ scope: fooScope,
425
+ });
426
+
427
+ symbolTable.addTSymbol({
428
+ kind: "variable",
429
+ name: "duplicate",
430
+ sourceFile: "test.cnx",
431
+ sourceLine: 5,
432
+ sourceLanguage: ESourceLanguage.CNext,
433
+ isExported: false,
434
+ type: TTypeUtils.createPrimitive("bool"),
435
+ isArray: false,
436
+ isConst: false,
437
+ isAtomic: false,
438
+ scope: fooScope,
439
+ });
440
+
441
+ // Same name in SAME scope IS a conflict
442
+ expect(symbolTable.hasConflict("duplicate")).toBe(true);
443
+ });
444
+
445
+ // Global scope conflicts should still be detected
446
+ it("should detect conflict for same-named globals", () => {
447
+ const globalScope = TestScopeUtils.createMockGlobalScope();
448
+
449
+ // Add two global variables with same name
450
+ symbolTable.addTSymbol({
451
+ kind: "variable",
452
+ name: "globalVar",
453
+ sourceFile: "first.cnx",
454
+ sourceLine: 1,
455
+ sourceLanguage: ESourceLanguage.CNext,
456
+ isExported: true,
457
+ type: TTypeUtils.createPrimitive("u32"),
458
+ isArray: false,
459
+ isConst: false,
460
+ isAtomic: false,
461
+ scope: globalScope,
462
+ });
463
+
464
+ symbolTable.addTSymbol({
465
+ kind: "variable",
466
+ name: "globalVar",
467
+ sourceFile: "second.cnx",
468
+ sourceLine: 1,
469
+ sourceLanguage: ESourceLanguage.CNext,
470
+ isExported: true,
471
+ type: TTypeUtils.createPrimitive("u32"),
472
+ isArray: false,
473
+ isConst: false,
474
+ isAtomic: false,
475
+ scope: globalScope,
476
+ });
477
+
478
+ // Two globals with same name IS a conflict
479
+ expect(symbolTable.hasConflict("globalVar")).toBe(true);
480
+ });
325
481
  });
326
482
 
327
483
  // ========================================================================
@@ -15,9 +15,6 @@ interface IPipelineInput {
15
15
 
16
16
  /** Whether to write generated output to disk */
17
17
  readonly writeOutputToDisk: boolean;
18
-
19
- /** Skip Stage 4 (symbol conflict check) — standalone mode doesn't need it */
20
- readonly skipConflictCheck?: boolean;
21
18
  }
22
19
 
23
20
  export default IPipelineInput;