Skip to content

Commit 90f857a

Browse files
authored
fix: file scoped aliases (#11)
1 parent 87591c9 commit 90f857a

5 files changed

Lines changed: 164 additions & 25 deletions

File tree

Bond.Parser.Tests/ParserFacadeTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Linq;
12
using System.Threading.Tasks;
23
using Bond.Parser.Parser;
34
using Bond.Parser.Syntax;
@@ -940,5 +941,35 @@ struct User {
940941
result.Success.Should().BeTrue();
941942
}
942943

944+
[Fact]
945+
public async Task Alias_IsFileScoped_WhenImportDefinesSameAlias()
946+
{
947+
var input = """
948+
import "common.bond"
949+
namespace Test
950+
using ID = string;
951+
struct User { 0: required ID id; }
952+
""";
953+
954+
async Task<(string, string)> Resolver(string _, string importPath)
955+
{
956+
var content = """
957+
namespace Test
958+
using ID = int32;
959+
struct Other { 0: required ID id; }
960+
""";
961+
return await Task.FromResult((importPath, content));
962+
}
963+
964+
var result = await Parse(input, Resolver);
965+
966+
result.Success.Should().BeTrue();
967+
var user = result.Ast!.Declarations.OfType<StructDeclaration>().First(d => d.Name == "User");
968+
var fieldType = user.Fields[0].Type as BondType.UserDefined;
969+
fieldType.Should().NotBeNull();
970+
var alias = fieldType!.Declaration.Should().BeOfType<AliasDeclaration>().Subject;
971+
alias.AliasedType.Should().BeOfType<BondType.String>();
972+
}
973+
943974
#endregion
944975
}

Bond.Parser/Parser/SemanticAnalyzer.cs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,30 @@ public SemanticAnalyzer(SymbolTable symbolTable, ImportResolver importResolver,
2929
/// </summary>
3030
public async Task AnalyzeAsync(Syntax.Bond bond)
3131
{
32-
// Process imports first
33-
foreach (var import in bond.Imports)
32+
_symbolTable.PushAliasScope();
33+
try
3434
{
35-
await ProcessImportAsync(import);
36-
}
35+
// Process imports first
36+
foreach (var import in bond.Imports)
37+
{
38+
await ProcessImportAsync(import);
39+
}
3740

38-
// Add all declarations to symbol table
39-
foreach (var declaration in bond.Declarations)
41+
// Add all declarations to symbol table
42+
foreach (var declaration in bond.Declarations)
43+
{
44+
_symbolTable.AddDeclaration(declaration);
45+
}
46+
47+
// Validate declarations after all symbols are registered
48+
foreach (var declaration in bond.Declarations)
49+
{
50+
ValidateDeclaration(declaration);
51+
}
52+
}
53+
finally
4054
{
41-
_symbolTable.AddDeclaration(declaration, bond.Namespaces);
42-
ValidateDeclaration(declaration);
55+
_symbolTable.PopAliasScope();
4356
}
4457
}
4558

Bond.Parser/Parser/SymbolTable.cs

Lines changed: 105 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,24 @@ namespace Bond.Parser.Parser;
1010
/// </summary>
1111
public class SymbolTable
1212
{
13-
private readonly List<Declaration> _declarations = [];
13+
private readonly List<Declaration> _globalDeclarations = [];
14+
private readonly Stack<List<AliasDeclaration>> _aliasScopes = new();
1415
private readonly HashSet<string> _processedImports = [];
1516

1617
/// <summary>
1718
/// Adds a declaration to the symbol table with duplicate checking
1819
/// </summary>
19-
public void AddDeclaration(Declaration declaration, Namespace[] currentNamespaces)
20+
public void AddDeclaration(Declaration declaration)
2021
{
22+
if (declaration is AliasDeclaration alias)
23+
{
24+
AddAliasDeclaration(alias);
25+
return;
26+
}
27+
2128
// Find duplicates in the same namespace
22-
var duplicates = _declarations
23-
.Where(d => d.Name == declaration.Name && d.Namespaces.Any(ns1 => currentNamespaces.Any(ns2 => NamespacesMatch(ns1, ns2))))
29+
var duplicates = _globalDeclarations
30+
.Where(d => d.Name == declaration.Name && d.Namespaces.Any(ns1 => declaration.Namespaces.Any(ns2 => NamespacesMatch(ns1, ns2))))
2431
.ToList();
2532

2633
foreach (var duplicate in duplicates)
@@ -33,18 +40,32 @@ public void AddDeclaration(Declaration declaration, Namespace[] currentNamespace
3340
}
3441
}
3542

36-
_declarations.Add(declaration);
43+
_globalDeclarations.Add(declaration);
3744
}
3845

3946
/// <summary>
4047
/// Finds a symbol by qualified name in the given namespaces
4148
/// </summary>
4249
public Declaration? FindSymbol(string[] qualifiedName, Namespace[] currentNamespaces)
50+
{
51+
var alias = FindAlias(qualifiedName, currentNamespaces);
52+
if (alias != null)
53+
{
54+
return alias;
55+
}
56+
57+
return FindGlobalSymbol(qualifiedName, currentNamespaces);
58+
}
59+
60+
/// <summary>
61+
/// Finds a symbol by qualified name in the given namespaces from global declarations
62+
/// </summary>
63+
private Declaration? FindGlobalSymbol(string[] qualifiedName, Namespace[] currentNamespaces)
4364
{
4465
if (qualifiedName.Length == 1)
4566
{
4667
// Unqualified name - search in current namespaces
47-
return _declarations.FirstOrDefault(d =>
68+
return _globalDeclarations.FirstOrDefault(d =>
4869
d.Name == qualifiedName[0] &&
4970
d.Namespaces.Any(ns1 => currentNamespaces.Any(ns2 => NamespacesMatch(ns1, ns2))));
5071
}
@@ -54,7 +75,7 @@ public void AddDeclaration(Declaration declaration, Namespace[] currentNamespace
5475
var namespacePart = qualifiedName[..^1];
5576
var namePart = qualifiedName[^1];
5677

57-
return _declarations.FirstOrDefault(d =>
78+
return _globalDeclarations.FirstOrDefault(d =>
5879
d.Name == namePart &&
5980
d.Namespaces.Any(ns => ns.Name.SequenceEqual(namespacePart)));
6081
}
@@ -65,7 +86,7 @@ public void AddDeclaration(Declaration declaration, Namespace[] currentNamespace
6586
/// </summary>
6687
public StructDeclaration? FindStruct(string[] qualifiedName, Namespace[] currentNamespaces)
6788
{
68-
var symbol = FindSymbol(qualifiedName, currentNamespaces);
89+
var symbol = FindGlobalSymbol(qualifiedName, currentNamespaces);
6990
return symbol as StructDeclaration;
7091
}
7192

@@ -88,15 +109,87 @@ public void MarkImportProcessed(string canonicalPath)
88109
/// <summary>
89110
/// Gets all declarations
90111
/// </summary>
91-
public IReadOnlyList<Declaration> Declarations => _declarations.AsReadOnly();
112+
public IReadOnlyList<Declaration> GlobalDeclarations => _globalDeclarations.AsReadOnly();
113+
114+
/// <summary>
115+
/// Pushes a new alias scope for a file.
116+
/// </summary>
117+
public void PushAliasScope()
118+
{
119+
_aliasScopes.Push([]);
120+
}
121+
122+
/// <summary>
123+
/// Pops the current alias scope.
124+
/// </summary>
125+
public void PopAliasScope()
126+
{
127+
if (_aliasScopes.Count == 0)
128+
{
129+
throw new InvalidOperationException("Alias scope stack is empty");
130+
}
131+
132+
_aliasScopes.Pop();
133+
}
92134

93135
/// <summary>
94136
/// Clears all declarations from the symbol table
95137
/// </summary>
96-
public void Clear()
138+
public void ClearGlobalDeclarations()
97139
{
98-
_declarations.Clear();
99-
// Don't clear processed imports as those are still valid
140+
_globalDeclarations.Clear();
141+
}
142+
143+
/// <summary>
144+
/// Clears all alias scopes
145+
/// </summary>
146+
public void ClearAliasScopes()
147+
{
148+
_aliasScopes.Clear();
149+
}
150+
151+
private void AddAliasDeclaration(AliasDeclaration alias)
152+
{
153+
if (_aliasScopes.Count == 0)
154+
{
155+
throw new InvalidOperationException("Alias scope is not initialized");
156+
}
157+
158+
var scope = _aliasScopes.Peek();
159+
var duplicates = scope
160+
.Where(d => d.Name == alias.Name && d.Namespaces.Any(ns1 => alias.Namespaces.Any(ns2 => NamespacesMatch(ns1, ns2))))
161+
.ToList();
162+
163+
if (duplicates.Count > 0)
164+
{
165+
throw new InvalidOperationException(
166+
$"Duplicate declaration: alias '{alias.Name}' was already declared as alias");
167+
}
168+
169+
scope.Add(alias);
170+
}
171+
172+
private AliasDeclaration? FindAlias(string[] qualifiedName, Namespace[] currentNamespaces)
173+
{
174+
if (_aliasScopes.Count == 0)
175+
{
176+
return null;
177+
}
178+
179+
var scope = _aliasScopes.Peek();
180+
181+
if (qualifiedName.Length == 1)
182+
{
183+
return scope.FirstOrDefault(d =>
184+
d.Name == qualifiedName[0] &&
185+
d.Namespaces.Any(ns1 => currentNamespaces.Any(ns2 => NamespacesMatch(ns1, ns2))));
186+
}
187+
188+
var namespacePart = qualifiedName[..^1];
189+
var namePart = qualifiedName[^1];
190+
return scope.FirstOrDefault(d =>
191+
d.Name == namePart &&
192+
d.Namespaces.Any(ns => ns.Name.SequenceEqual(namespacePart)));
100193
}
101194

102195
private static bool NamespacesMatch(Namespace ns1, Namespace ns2)

Bond.Parser/Parser/TypeResolver.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,23 @@ public Syntax.Bond ResolveTypes(Syntax.Bond ast)
2020
const int maxPasses = 10; // Prevent infinite loops
2121

2222
// Preserve declarations that came from imports so we can re-add them each pass
23-
var importedDeclarations = symbolTable.Declarations
23+
var importedDeclarations = symbolTable.GlobalDeclarations
2424
.Where(d => !ast.Declarations.Contains(d))
2525
.ToArray();
2626

2727
for (int pass = 0; pass < maxPasses; pass++)
2828
{
2929
// Update symbol table with current declarations
30-
symbolTable.Clear();
30+
symbolTable.ClearGlobalDeclarations();
31+
symbolTable.ClearAliasScopes();
32+
symbolTable.PushAliasScope();
3133
foreach (var importDecl in importedDeclarations)
3234
{
33-
symbolTable.AddDeclaration(importDecl, currentAst.Namespaces);
35+
symbolTable.AddDeclaration(importDecl);
3436
}
3537
foreach (var decl in currentAst.Declarations)
3638
{
37-
symbolTable.AddDeclaration(decl, currentAst.Namespaces);
39+
symbolTable.AddDeclaration(decl);
3840
}
3941

4042
// Resolve all declarations

version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.4
1+
0.2.5

0 commit comments

Comments
 (0)