@@ -7,17 +7,37 @@ const collections = @import("collections");
77const CIR = @import ("CIR.zig" );
88
99const Ident = base .Ident ;
10+ const Region = base .Region ;
1011
1112const Scope = @This ();
1213
14+ /// Represents a type binding for a type imported from an external module.
15+ /// Contains all necessary information to resolve the type from the imported module.
16+ pub const ExternalTypeBinding = struct {
17+ module_ident : Ident.Idx ,
18+ original_ident : Ident.Idx ,
19+ target_node_idx : ? u16 ,
20+ import_idx : ? CIR.Import.Idx ,
21+ origin_region : Region ,
22+ /// True if the module was attempted to be imported but was not found.
23+ /// This allows us to emit a more specific diagnostic when the type is used.
24+ module_not_found : bool ,
25+ };
26+
27+ /// A unified type binding that can represent either a locally declared type or an externally imported type.
28+ /// This is the single source of truth for all type resolution in a scope.
29+ pub const TypeBinding = union (enum ) {
30+ local_nominal : CIR.Statement.Idx ,
31+ local_alias : CIR.Statement.Idx ,
32+ associated_nominal : CIR.Statement.Idx ,
33+ external_nominal : ExternalTypeBinding ,
34+ };
35+
1336/// Maps an Ident to a Pattern in the Can IR
1437idents : std .AutoHashMapUnmanaged (Ident.Idx , CIR .Pattern .Idx ),
1538aliases : std .AutoHashMapUnmanaged (Ident.Idx , CIR .Pattern .Idx ),
16- /// Maps type names to their type declaration statements
17- type_decls : std .AutoHashMapUnmanaged (Ident.Idx , CIR .Statement .Idx ),
18- /// Maps unqualified type names to their fully qualified Statement.Idx (for associated types)
19- /// Example: within Foo's associated block, "Bar" -> statement for "Foo.Bar"
20- type_aliases : std .AutoHashMapUnmanaged (Ident.Idx , CIR .Statement .Idx ),
39+ /// Canonical bindings for type names (local, auto-imported, and imported types)
40+ type_bindings : std .AutoHashMapUnmanaged (Ident.Idx , TypeBinding ),
2141/// Maps type variables to their type annotation indices
2242type_vars : std .AutoHashMapUnmanaged (Ident.Idx , CIR .TypeAnno .Idx ),
2343/// Maps module alias names to their full module names
@@ -33,8 +53,7 @@ pub fn init(is_function_boundary: bool) Scope {
3353 return Scope {
3454 .idents = std .AutoHashMapUnmanaged (Ident .Idx , CIR .Pattern .Idx ){},
3555 .aliases = std .AutoHashMapUnmanaged (Ident .Idx , CIR .Pattern .Idx ){},
36- .type_decls = std .AutoHashMapUnmanaged (Ident .Idx , CIR .Statement .Idx ){},
37- .type_aliases = std .AutoHashMapUnmanaged (Ident .Idx , CIR .Statement .Idx ){},
56+ .type_bindings = std .AutoHashMapUnmanaged (Ident .Idx , TypeBinding ){},
3857 .type_vars = std .AutoHashMapUnmanaged (Ident .Idx , CIR .TypeAnno .Idx ){},
3958 .module_aliases = std .AutoHashMapUnmanaged (Ident .Idx , Ident .Idx ){},
4059 .exposed_items = std .AutoHashMapUnmanaged (Ident .Idx , ExposedItemInfo ){},
@@ -47,8 +66,7 @@ pub fn init(is_function_boundary: bool) Scope {
4766pub fn deinit (self : * Scope , gpa : std.mem.Allocator ) void {
4867 self .idents .deinit (gpa );
4968 self .aliases .deinit (gpa );
50- self .type_decls .deinit (gpa );
51- self .type_aliases .deinit (gpa );
69+ self .type_bindings .deinit (gpa );
5270 self .type_vars .deinit (gpa );
5371 self .module_aliases .deinit (gpa );
5472 self .exposed_items .deinit (gpa );
@@ -157,20 +175,18 @@ pub const ImportedModuleIntroduceResult = union(enum) {
157175};
158176
159177/// Item kinds in a scope
160- pub const ItemKind = enum { ident , alias , type_decl , type_var , module_alias , exposed_item };
178+ pub const ItemKind = enum { ident , alias , type_var , module_alias , exposed_item };
161179
162180/// Get the appropriate map for the given item kind
163181pub fn items (scope : * Scope , comptime item_kind : ItemKind ) switch (item_kind ) {
164182 .ident , .alias = > * std .AutoHashMapUnmanaged (Ident .Idx , CIR .Pattern .Idx ),
165- .type_decl = > * std .AutoHashMapUnmanaged (Ident .Idx , CIR .Statement .Idx ),
166183 .type_var = > * std .AutoHashMapUnmanaged (Ident .Idx , CIR .TypeAnno .Idx ),
167184 .module_alias = > * std .AutoHashMapUnmanaged (Ident .Idx , Ident .Idx ),
168185 .exposed_item = > * std .AutoHashMapUnmanaged (Ident .Idx , ExposedItemInfo ),
169186} {
170187 return switch (item_kind ) {
171188 .ident = > & scope .idents ,
172189 .alias = > & scope .aliases ,
173- .type_decl = > & scope .type_decls ,
174190 .type_var = > & scope .type_vars ,
175191 .module_alias = > & scope .module_aliases ,
176192 .exposed_item = > & scope .exposed_items ,
@@ -180,15 +196,13 @@ pub fn items(scope: *Scope, comptime item_kind: ItemKind) switch (item_kind) {
180196/// Get the appropriate map for the given item kind (const version)
181197pub fn itemsConst (scope : * const Scope , comptime item_kind : ItemKind ) switch (item_kind ) {
182198 .ident , .alias = > * const std .AutoHashMapUnmanaged (Ident .Idx , CIR .Pattern .Idx ),
183- .type_decl = > * const std .AutoHashMapUnmanaged (Ident .Idx , CIR .Statement .Idx ),
184199 .type_var = > * const std .AutoHashMapUnmanaged (Ident .Idx , CIR .TypeAnno .Idx ),
185200 .module_alias = > * const std .AutoHashMapUnmanaged (Ident .Idx , Ident .Idx ),
186201 .exposed_item = > * const std .AutoHashMapUnmanaged (Ident .Idx , ExposedItemInfo ),
187202} {
188203 return switch (item_kind ) {
189204 .ident = > & scope .idents ,
190205 .alias = > & scope .aliases ,
191- .type_decl = > & scope .type_decls ,
192206 .type_var = > & scope .type_vars ,
193207 .module_alias = > & scope .module_aliases ,
194208 .exposed_item = > & scope .exposed_items ,
@@ -198,7 +212,6 @@ pub fn itemsConst(scope: *const Scope, comptime item_kind: ItemKind) switch (ite
198212/// Put an item in the scope, panics on OOM
199213pub fn put (scope : * Scope , gpa : std.mem.Allocator , comptime item_kind : ItemKind , name : Ident.Idx , value : switch (item_kind ) {
200214 .ident , .alias = > CIR .Pattern .Idx ,
201- .type_decl = > CIR .Statement .Idx ,
202215 .type_var = > CIR .TypeAnno .Idx ,
203216 .module_alias = > Ident .Idx ,
204217 .exposed_item = > ExposedItemInfo ,
@@ -214,13 +227,14 @@ pub fn introduceTypeDecl(
214227 type_decl : CIR.Statement.Idx ,
215228 parent_lookup_fn : ? fn (Ident.Idx ) ? CIR.Statement.Idx ,
216229) std.mem.Allocator.Error ! TypeIntroduceResult {
217- // Check if already exists in current scope by comparing text content
218- var iter = scope .type_decls .iterator ();
219- while (iter .next ()) | entry | {
220- if (name .idx == entry .key_ptr .idx ) {
221- // Type redeclaration is an error, not just a warning
222- return TypeIntroduceResult { .redeclared_error = entry .value_ptr .* };
223- }
230+ // Check if type already exists in this scope
231+ if (scope .type_bindings .getPtr (name )) | existing | {
232+ return switch (existing .* ) {
233+ .local_nominal = > | stmt | TypeIntroduceResult { .redeclared_error = stmt },
234+ .local_alias = > | stmt | TypeIntroduceResult { .type_alias_redeclared = stmt },
235+ .associated_nominal = > | stmt | TypeIntroduceResult { .nominal_type_redeclared = stmt },
236+ .external_nominal = > TypeIntroduceResult { .nominal_type_redeclared = type_decl },
237+ };
224238 }
225239
226240 // Check for shadowing in parent scopes and issue warnings
@@ -229,7 +243,8 @@ pub fn introduceTypeDecl(
229243 shadowed_stmt = lookup_fn (name );
230244 }
231245
232- try scope .put (gpa , .type_decl , name , type_decl );
246+ // Add type binding (single source of truth)
247+ try scope .type_bindings .put (gpa , name , TypeBinding { .local_nominal = type_decl });
233248
234249 if (shadowed_stmt ) | stmt | {
235250 return TypeIntroduceResult { .shadowing_warning = stmt };
@@ -238,26 +253,6 @@ pub fn introduceTypeDecl(
238253 return TypeIntroduceResult { .success = {} };
239254}
240255
241- /// Lookup a type declaration in the scope hierarchy
242- /// TODO: Optimize lookup performance - currently O(n) due to text comparison
243- /// TODO: Consider caching or using a more efficient data structure for type lookup
244- /// TODO: Support for nominal vs structural type distinction (future := operator)
245- pub fn lookupTypeDecl (scope : * const Scope , name : Ident.Idx ) TypeLookupResult {
246- // Search by comparing text content, not identifier index
247- var iter = scope .type_decls .iterator ();
248- while (iter .next ()) | entry | {
249- if (name .idx == entry .key_ptr .idx ) {
250- return TypeLookupResult { .found = entry .value_ptr .* };
251- }
252- }
253- return TypeLookupResult { .not_found = {} };
254- }
255-
256- /// Look up an unqualified type alias (for associated types)
257- pub fn lookupTypeAlias (scope : * const Scope , name : Ident.Idx ) ? CIR.Statement.Idx {
258- return scope .type_aliases .get (name );
259- }
260-
261256/// Introduce an unqualified type alias (for associated types)
262257/// Maps an unqualified name to a fully qualified type declaration
263258pub fn introduceTypeAlias (
@@ -266,7 +261,9 @@ pub fn introduceTypeAlias(
266261 unqualified_name : Ident.Idx ,
267262 qualified_type_decl : CIR.Statement.Idx ,
268263) ! void {
269- try scope .type_aliases .put (gpa , unqualified_name , qualified_type_decl );
264+ try scope .type_bindings .put (gpa , unqualified_name , TypeBinding {
265+ .associated_nominal = qualified_type_decl ,
266+ });
270267}
271268
272269/// Update an existing type declaration in the scope
@@ -278,17 +275,17 @@ pub fn updateTypeDecl(
278275 name : Ident.Idx ,
279276 new_type_decl : CIR.Statement.Idx ,
280277) std.mem.Allocator.Error ! void {
281- // Find the existing entry by comparing text content
282- var iter = scope .type_decls .iterator ();
283- while (iter .next ()) | entry | {
284- if (name .idx == entry .key_ptr .idx ) {
285- // Update the existing entry with the new statement index
286- entry .value_ptr .* = new_type_decl ;
287- return ;
288- }
278+ if (scope .type_bindings .getPtr (name )) | binding_ptr | {
279+ const current = binding_ptr .* ;
280+ binding_ptr .* = switch (current ) {
281+ .local_nominal = > TypeBinding { .local_nominal = new_type_decl },
282+ .local_alias = > TypeBinding { .local_alias = new_type_decl },
283+ .associated_nominal = > TypeBinding { .associated_nominal = new_type_decl },
284+ .external_nominal = > current ,
285+ };
286+ } else {
287+ try scope .type_bindings .put (gpa , name , TypeBinding { .local_nominal = new_type_decl });
289288 }
290- // If not found, add it as a new entry
291- try scope .put (gpa , .type_decl , name , new_type_decl );
292289}
293290
294291/// Introduce a type variable into the scope
0 commit comments