mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
More fixes
This commit is contained in:
parent
486183800c
commit
d5e5e2bd7d
7 changed files with 204 additions and 261 deletions
|
|
@ -46,6 +46,7 @@ exposed_ident_texts: std.StringHashMapUnmanaged(Region) = .{},
|
|||
/// Track exposed types by text to handle changing indices
|
||||
exposed_type_texts: std.StringHashMapUnmanaged(Region) = .{},
|
||||
/// Track which identifiers in the current scope are placeholders (not yet replaced with real definitions)
|
||||
/// This is empty for 99% of files; only used during multi-phase canonicalization (mainly Builtin.roc)
|
||||
placeholder_idents: std.AutoHashMapUnmanaged(Ident.Idx, void) = .{},
|
||||
/// Stack of function regions for tracking var reassignment across function boundaries
|
||||
function_regions: std.array_list.Managed(Region),
|
||||
|
|
@ -561,154 +562,172 @@ fn processAssociatedBlock(
|
|||
type_name: Ident.Idx,
|
||||
assoc: anytype,
|
||||
) std.mem.Allocator.Error!void {
|
||||
// First, introduce placeholder patterns for all associated items
|
||||
try self.processAssociatedItemsFirstPass(qualified_name_idx, assoc.statements);
|
||||
// First, introduce placeholder patterns for all associated items
|
||||
try self.processAssociatedItemsFirstPass(qualified_name_idx, assoc.statements);
|
||||
|
||||
// Now enter a new scope for the associated block where both qualified and unqualified names work
|
||||
try self.scopeEnter(self.env.gpa, false); // false = not a function boundary
|
||||
defer self.scopeExit(self.env.gpa) catch unreachable;
|
||||
// Now enter a new scope for the associated block where both qualified and unqualified names work
|
||||
try self.scopeEnter(self.env.gpa, false); // false = not a function boundary
|
||||
defer self.scopeExit(self.env.gpa) catch unreachable;
|
||||
|
||||
// Introduce the parent type itself into this scope so it can be referenced by its unqualified name
|
||||
// For example, if we're processing MyBool's associated items, we need "MyBool" to resolve to "Test.MyBool"
|
||||
if (self.scopeLookupTypeDecl(qualified_name_idx)) |parent_type_decl_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
try current_scope.introduceTypeAlias(self.env.gpa, type_name, parent_type_decl_idx);
|
||||
}
|
||||
// Introduce the parent type itself into this scope so it can be referenced by its unqualified name
|
||||
// For example, if we're processing MyBool's associated items, we need "MyBool" to resolve to "Test.MyBool"
|
||||
if (self.scopeLookupTypeDecl(qualified_name_idx)) |parent_type_decl_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
try current_scope.introduceTypeAlias(self.env.gpa, type_name, parent_type_decl_idx);
|
||||
}
|
||||
|
||||
// Note: Sibling types and ancestor types are accessible via parent scope lookup.
|
||||
// When nested types were introduced in processTypeDeclFirstPass, unqualified aliases
|
||||
// were added in their declaration scope, making them visible to all child scopes.
|
||||
// Note: Sibling types and ancestor types are accessible via parent scope lookup.
|
||||
// When nested types were introduced in processTypeDeclFirstPass, unqualified aliases
|
||||
// were added in their declaration scope, making them visible to all child scopes.
|
||||
|
||||
// Introduce aliases into this scope so associated items can reference each other
|
||||
// We only add unqualified and type-qualified names; fully qualified names are
|
||||
// already in the parent scope and accessible via scope nesting
|
||||
const parent_type_text = self.env.getIdent(type_name);
|
||||
for (self.parse_ir.store.statementSlice(assoc.statements)) |assoc_stmt_idx| {
|
||||
const assoc_stmt = self.parse_ir.store.getStatement(assoc_stmt_idx);
|
||||
switch (assoc_stmt) {
|
||||
.type_decl => |nested_type_decl| {
|
||||
const nested_header = self.parse_ir.store.getTypeHeader(nested_type_decl.header) catch continue;
|
||||
const unqualified_ident = self.parse_ir.tokens.resolveIdentifier(nested_header.name) orelse continue;
|
||||
// Introduce aliases into this scope so associated items can reference each other
|
||||
// We only add unqualified and type-qualified names; fully qualified names are
|
||||
// already in the parent scope and accessible via scope nesting
|
||||
const parent_type_text = self.env.getIdent(type_name);
|
||||
for (self.parse_ir.store.statementSlice(assoc.statements)) |assoc_stmt_idx| {
|
||||
const assoc_stmt = self.parse_ir.store.getStatement(assoc_stmt_idx);
|
||||
switch (assoc_stmt) {
|
||||
.type_decl => |nested_type_decl| {
|
||||
const nested_header = self.parse_ir.store.getTypeHeader(nested_type_decl.header) catch continue;
|
||||
const unqualified_ident = self.parse_ir.tokens.resolveIdentifier(nested_header.name) orelse continue;
|
||||
|
||||
// Build fully qualified name (e.g., "Test.MyBool")
|
||||
const parent_text = self.env.getIdent(qualified_name_idx);
|
||||
const nested_type_text = self.env.getIdent(unqualified_ident);
|
||||
const qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, nested_type_text);
|
||||
// Build fully qualified name (e.g., "Test.MyBool")
|
||||
const parent_text = self.env.getIdent(qualified_name_idx);
|
||||
const nested_type_text = self.env.getIdent(unqualified_ident);
|
||||
const qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, nested_type_text);
|
||||
|
||||
// Introduce unqualified type alias (fully qualified is already in parent scope)
|
||||
if (self.scopeLookupTypeDecl(qualified_ident_idx)) |qualified_type_decl_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
try current_scope.introduceTypeAlias(self.env.gpa, unqualified_ident, qualified_type_decl_idx);
|
||||
}
|
||||
// Introduce unqualified type alias (fully qualified is already in parent scope)
|
||||
if (self.scopeLookupTypeDecl(qualified_ident_idx)) |qualified_type_decl_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
try current_scope.introduceTypeAlias(self.env.gpa, unqualified_ident, qualified_type_decl_idx);
|
||||
}
|
||||
|
||||
// Introduce associated items of nested types
|
||||
if (nested_type_decl.associated) |nested_assoc| {
|
||||
for (self.parse_ir.store.statementSlice(nested_assoc.statements)) |nested_assoc_stmt_idx| {
|
||||
const nested_assoc_stmt = self.parse_ir.store.getStatement(nested_assoc_stmt_idx);
|
||||
if (nested_assoc_stmt == .decl) {
|
||||
const nested_decl = nested_assoc_stmt.decl;
|
||||
const nested_pattern = self.parse_ir.store.getPattern(nested_decl.pattern);
|
||||
if (nested_pattern == .ident) {
|
||||
const nested_pattern_ident_tok = nested_pattern.ident.ident_tok;
|
||||
if (self.parse_ir.tokens.resolveIdentifier(nested_pattern_ident_tok)) |nested_decl_ident| {
|
||||
// Build fully qualified name (e.g., "Test.MyBool.my_not")
|
||||
const qualified_text = self.env.getIdent(qualified_ident_idx);
|
||||
const nested_decl_text = self.env.getIdent(nested_decl_ident);
|
||||
const full_qualified_ident_idx = try self.env.insertQualifiedIdent(qualified_text, nested_decl_text);
|
||||
// Introduce associated items of nested types
|
||||
if (nested_type_decl.associated) |nested_assoc| {
|
||||
for (self.parse_ir.store.statementSlice(nested_assoc.statements)) |nested_assoc_stmt_idx| {
|
||||
const nested_assoc_stmt = self.parse_ir.store.getStatement(nested_assoc_stmt_idx);
|
||||
if (nested_assoc_stmt == .decl) {
|
||||
const nested_decl = nested_assoc_stmt.decl;
|
||||
const nested_pattern = self.parse_ir.store.getPattern(nested_decl.pattern);
|
||||
if (nested_pattern == .ident) {
|
||||
const nested_pattern_ident_tok = nested_pattern.ident.ident_tok;
|
||||
if (self.parse_ir.tokens.resolveIdentifier(nested_pattern_ident_tok)) |nested_decl_ident| {
|
||||
// Build fully qualified name (e.g., "Test.MyBool.my_not")
|
||||
const qualified_text = self.env.getIdent(qualified_ident_idx);
|
||||
const nested_decl_text = self.env.getIdent(nested_decl_ident);
|
||||
const full_qualified_ident_idx = try self.env.insertQualifiedIdent(qualified_text, nested_decl_text);
|
||||
|
||||
// Look up the fully qualified pattern (from parent scope via nesting)
|
||||
switch (self.scopeLookup(.ident, full_qualified_ident_idx)) {
|
||||
.found => |pattern_idx| {
|
||||
const scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
// Look up the fully qualified pattern (from parent scope via nesting)
|
||||
switch (self.scopeLookup(.ident, full_qualified_ident_idx)) {
|
||||
.found => |pattern_idx| {
|
||||
const scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
|
||||
// Add unqualified name (e.g., "my_not")
|
||||
try scope.idents.put(self.env.gpa, nested_decl_ident, pattern_idx);
|
||||
// Check if this is a placeholder
|
||||
const is_placeholder = self.isPlaceholder(full_qualified_ident_idx);
|
||||
|
||||
// Add type-qualified name (e.g., "MyBool.my_not")
|
||||
const type_qualified_ident_idx = try self.env.insertQualifiedIdent(nested_type_text, nested_decl_text);
|
||||
try scope.idents.put(self.env.gpa, type_qualified_ident_idx, pattern_idx);
|
||||
},
|
||||
.not_found => {},
|
||||
}
|
||||
// Add unqualified name (e.g., "my_not")
|
||||
try scope.idents.put(self.env.gpa, nested_decl_ident, pattern_idx);
|
||||
if (is_placeholder) {
|
||||
try self.placeholder_idents.put(self.env.gpa, nested_decl_ident, {});
|
||||
}
|
||||
|
||||
// Add type-qualified name (e.g., "MyBool.my_not")
|
||||
const type_qualified_ident_idx = try self.env.insertQualifiedIdent(nested_type_text, nested_decl_text);
|
||||
try scope.idents.put(self.env.gpa, type_qualified_ident_idx, pattern_idx);
|
||||
if (is_placeholder) {
|
||||
try self.placeholder_idents.put(self.env.gpa, type_qualified_ident_idx, {});
|
||||
}
|
||||
},
|
||||
.not_found => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.decl => |decl| {
|
||||
const pattern = self.parse_ir.store.getPattern(decl.pattern);
|
||||
if (pattern == .ident) {
|
||||
const pattern_ident_tok = pattern.ident.ident_tok;
|
||||
if (self.parse_ir.tokens.resolveIdentifier(pattern_ident_tok)) |decl_ident| {
|
||||
// Build fully qualified name (e.g., "Test.MyBool.my_not")
|
||||
const parent_text = self.env.getIdent(qualified_name_idx);
|
||||
const decl_text = self.env.getIdent(decl_ident);
|
||||
const fully_qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, decl_text);
|
||||
|
||||
// Look up the fully qualified pattern (from parent scope via nesting)
|
||||
switch (self.scopeLookup(.ident, fully_qualified_ident_idx)) {
|
||||
.found => |pattern_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
|
||||
// Add unqualified name (e.g., "my_not")
|
||||
try current_scope.idents.put(self.env.gpa, decl_ident, pattern_idx);
|
||||
|
||||
// Add type-qualified name (e.g., "MyBool.my_not")
|
||||
const type_qualified_ident_idx = try self.env.insertQualifiedIdent(parent_type_text, decl_text);
|
||||
try current_scope.idents.put(self.env.gpa, type_qualified_ident_idx, pattern_idx);
|
||||
},
|
||||
.not_found => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// Note: .type_anno is not handled here because anno-only patterns
|
||||
// are created during processAssociatedItemsSecondPass, so they need
|
||||
// to be re-introduced AFTER that call completes
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Process the associated items (canonicalize their bodies)
|
||||
try self.processAssociatedItemsSecondPass(qualified_name_idx, type_name, assoc.statements);
|
||||
|
||||
// After processing, introduce anno-only defs into the associated block scope
|
||||
// (They were just created by processAssociatedItemsSecondPass)
|
||||
// We only add unqualified and type-qualified names; fully qualified is in parent scope
|
||||
for (self.parse_ir.store.statementSlice(assoc.statements)) |anno_stmt_idx| {
|
||||
const anno_stmt = self.parse_ir.store.getStatement(anno_stmt_idx);
|
||||
switch (anno_stmt) {
|
||||
.type_anno => |type_anno| {
|
||||
if (self.parse_ir.tokens.resolveIdentifier(type_anno.name)) |anno_ident| {
|
||||
// Build fully qualified name (e.g., "Test.MyBool.len")
|
||||
}
|
||||
},
|
||||
.decl => |decl| {
|
||||
const pattern = self.parse_ir.store.getPattern(decl.pattern);
|
||||
if (pattern == .ident) {
|
||||
const pattern_ident_tok = pattern.ident.ident_tok;
|
||||
if (self.parse_ir.tokens.resolveIdentifier(pattern_ident_tok)) |decl_ident| {
|
||||
// Build fully qualified name (e.g., "Test.MyBool.my_not")
|
||||
const parent_text = self.env.getIdent(qualified_name_idx);
|
||||
const anno_text = self.env.getIdent(anno_ident);
|
||||
const fully_qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, anno_text);
|
||||
const decl_text = self.env.getIdent(decl_ident);
|
||||
const fully_qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, decl_text);
|
||||
|
||||
// Look up the fully qualified pattern (from parent scope via nesting)
|
||||
switch (self.scopeLookup(.ident, fully_qualified_ident_idx)) {
|
||||
.found => |pattern_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
|
||||
// Add unqualified name (e.g., "len")
|
||||
try current_scope.idents.put(self.env.gpa, anno_ident, pattern_idx);
|
||||
// Check if this is a placeholder by checking if the identifier is tracked
|
||||
const is_placeholder = self.isPlaceholder(fully_qualified_ident_idx);
|
||||
|
||||
// Add type-qualified name (e.g., "List.len")
|
||||
const type_qualified_ident_idx = try self.env.insertQualifiedIdent(parent_type_text, anno_text);
|
||||
// Add unqualified name (e.g., "my_not")
|
||||
try current_scope.idents.put(self.env.gpa, decl_ident, pattern_idx);
|
||||
if (is_placeholder) {
|
||||
try self.placeholder_idents.put(self.env.gpa, decl_ident, {});
|
||||
}
|
||||
|
||||
// Add type-qualified name (e.g., "MyBool.my_not")
|
||||
const type_qualified_ident_idx = try self.env.insertQualifiedIdent(parent_type_text, decl_text);
|
||||
try current_scope.idents.put(self.env.gpa, type_qualified_ident_idx, pattern_idx);
|
||||
if (is_placeholder) {
|
||||
try self.placeholder_idents.put(self.env.gpa, type_qualified_ident_idx, {});
|
||||
}
|
||||
},
|
||||
.not_found => {
|
||||
// This can happen if the type_anno was followed by a matching decl
|
||||
// (in which case it's not an anno-only def)
|
||||
},
|
||||
.not_found => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// Note: .type_anno is not handled here because anno-only patterns
|
||||
// are created during processAssociatedItemsSecondPass, so they need
|
||||
// to be re-introduced AFTER that call completes
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Process the associated items (canonicalize their bodies)
|
||||
try self.processAssociatedItemsSecondPass(qualified_name_idx, type_name, assoc.statements);
|
||||
|
||||
// After processing, introduce anno-only defs into the associated block scope
|
||||
// (They were just created by processAssociatedItemsSecondPass)
|
||||
// We only add unqualified and type-qualified names; fully qualified is in parent scope
|
||||
for (self.parse_ir.store.statementSlice(assoc.statements)) |anno_stmt_idx| {
|
||||
const anno_stmt = self.parse_ir.store.getStatement(anno_stmt_idx);
|
||||
switch (anno_stmt) {
|
||||
.type_anno => |type_anno| {
|
||||
if (self.parse_ir.tokens.resolveIdentifier(type_anno.name)) |anno_ident| {
|
||||
// Build fully qualified name (e.g., "Test.MyBool.len")
|
||||
const parent_text = self.env.getIdent(qualified_name_idx);
|
||||
const anno_text = self.env.getIdent(anno_ident);
|
||||
const fully_qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, anno_text);
|
||||
|
||||
// Look up the fully qualified pattern (from parent scope via nesting)
|
||||
switch (self.scopeLookup(.ident, fully_qualified_ident_idx)) {
|
||||
.found => |pattern_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
|
||||
// Add unqualified name (e.g., "len")
|
||||
try current_scope.idents.put(self.env.gpa, anno_ident, pattern_idx);
|
||||
|
||||
// Add type-qualified name (e.g., "List.len")
|
||||
const type_qualified_ident_idx = try self.env.insertQualifiedIdent(parent_type_text, anno_text);
|
||||
try current_scope.idents.put(self.env.gpa, type_qualified_ident_idx, pattern_idx);
|
||||
},
|
||||
.not_found => {
|
||||
// This can happen if the type_anno was followed by a matching decl
|
||||
// (in which case it's not an anno-only def)
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Canonicalize an associated item declaration with a qualified name
|
||||
|
|
@ -796,7 +815,7 @@ fn processAssociatedItemsSecondPass(
|
|||
parent_name: Ident.Idx,
|
||||
parent_type_name: Ident.Idx,
|
||||
statements: AST.Statement.Span,
|
||||
) std.mem.Allocator.Error!void {
|
||||
) std.mem.Allocator.Error!void {
|
||||
const stmt_idxs = self.parse_ir.store.statementSlice(statements);
|
||||
var i: usize = 0;
|
||||
while (i < stmt_idxs.len) : (i += 1) {
|
||||
|
|
@ -888,10 +907,14 @@ fn processAssociatedItemsSecondPass(
|
|||
|
||||
// Update type-qualified name (e.g., "MyBool.my_not")
|
||||
const type_qualified_idx = try self.env.insertQualifiedIdent(self.env.getIdent(parent_type_name), decl_text);
|
||||
try self.updatePlaceholder(current_scope, type_qualified_idx, pattern_idx);
|
||||
if (type_qualified_idx.idx != decl_ident.idx) {
|
||||
try self.updatePlaceholder(current_scope, type_qualified_idx, pattern_idx);
|
||||
}
|
||||
|
||||
// Update fully qualified name (e.g., "Test.MyBool.my_not")
|
||||
try self.updatePlaceholder(current_scope, qualified_idx, pattern_idx);
|
||||
if (qualified_idx.idx != type_qualified_idx.idx and qualified_idx.idx != decl_ident.idx) {
|
||||
try self.updatePlaceholder(current_scope, qualified_idx, pattern_idx);
|
||||
}
|
||||
|
||||
break :blk true; // Found and processed matching decl
|
||||
}
|
||||
|
|
@ -927,10 +950,14 @@ fn processAssociatedItemsSecondPass(
|
|||
|
||||
// Update type-qualified name (e.g., "List.is_empty")
|
||||
const type_qualified_idx = try self.env.insertQualifiedIdent(self.env.getIdent(parent_type_name), name_text);
|
||||
try self.updatePlaceholder(current_scope, type_qualified_idx, pattern_idx);
|
||||
if (type_qualified_idx.idx != name_ident.idx) {
|
||||
try self.updatePlaceholder(current_scope, type_qualified_idx, pattern_idx);
|
||||
}
|
||||
|
||||
// Update fully qualified name (e.g., "Builtin.List.is_empty")
|
||||
try self.updatePlaceholder(current_scope, qualified_idx, pattern_idx);
|
||||
if (qualified_idx.idx != type_qualified_idx.idx and qualified_idx.idx != name_ident.idx) {
|
||||
try self.updatePlaceholder(current_scope, qualified_idx, pattern_idx);
|
||||
}
|
||||
|
||||
try self.env.store.addScratchDef(def_idx);
|
||||
}
|
||||
|
|
@ -1031,6 +1058,10 @@ fn processAssociatedItemsFirstPass(
|
|||
};
|
||||
const placeholder_pattern_idx = try self.env.addPattern(placeholder_pattern, region);
|
||||
|
||||
// Track both identifiers as placeholders
|
||||
try self.placeholder_idents.put(self.env.gpa, qualified_idx, {});
|
||||
try self.placeholder_idents.put(self.env.gpa, decl_ident, {});
|
||||
|
||||
// Directly put placeholder in scope (no conflict checking)
|
||||
// updatePlaceholder will verify it's replacing a placeholder later
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
|
|
@ -1047,7 +1078,6 @@ fn processAssociatedItemsFirstPass(
|
|||
const anno_text = self.env.getIdent(anno_ident);
|
||||
const qualified_idx = try self.env.insertQualifiedIdent(parent_text, anno_text);
|
||||
|
||||
|
||||
const region = self.parse_ir.tokenizedRegionToRegion(type_anno.region);
|
||||
const placeholder_pattern = Pattern{
|
||||
.assign = .{
|
||||
|
|
@ -1056,6 +1086,10 @@ fn processAssociatedItemsFirstPass(
|
|||
};
|
||||
const placeholder_pattern_idx = try self.env.addPattern(placeholder_pattern, region);
|
||||
|
||||
// Track both identifiers as placeholders
|
||||
try self.placeholder_idents.put(self.env.gpa, qualified_idx, {});
|
||||
try self.placeholder_idents.put(self.env.gpa, anno_ident, {});
|
||||
|
||||
// Directly put placeholder in scope (no conflict checking)
|
||||
// updatePlaceholder will verify it's replacing a placeholder later
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
|
|
@ -1165,13 +1199,15 @@ pub fn canonicalizeFile(
|
|||
const scratch_defs_start = self.env.store.scratchDefTop();
|
||||
const scratch_statements_start = self.env.store.scratch.?.statements.top();
|
||||
|
||||
// First pass: Process all type declarations to introduce them into scope
|
||||
// Defer associated blocks until after we've created placeholders for top-level items
|
||||
// First pass (1a): Process type declarations WITH associated blocks to introduce them into scope
|
||||
// Defer associated blocks themselves until after we've created placeholders for top-level items
|
||||
for (self.parse_ir.store.statementSlice(file.statements)) |stmt_id| {
|
||||
const stmt = self.parse_ir.store.getStatement(stmt_id);
|
||||
switch (stmt) {
|
||||
.type_decl => |type_decl| {
|
||||
try self.processTypeDeclFirstPass(type_decl, null, true); // defer associated blocks
|
||||
if (type_decl.associated) |_| {
|
||||
try self.processTypeDeclFirstPass(type_decl, null, true); // defer associated blocks
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// Skip non-type-declaration statements in first pass
|
||||
|
|
@ -1179,59 +1215,9 @@ pub fn canonicalizeFile(
|
|||
}
|
||||
}
|
||||
|
||||
// Phase 1.5: Create placeholders for top-level declarations and type annotations
|
||||
// This ensures sibling items (like list_get_unsafe) are available during associated block processing
|
||||
for (self.parse_ir.store.statementSlice(file.statements)) |stmt_id| {
|
||||
const stmt = self.parse_ir.store.getStatement(stmt_id);
|
||||
switch (stmt) {
|
||||
.decl => |decl| {
|
||||
const pattern = self.parse_ir.store.getPattern(decl.pattern);
|
||||
if (pattern == .ident) {
|
||||
const pattern_ident_tok = pattern.ident.ident_tok;
|
||||
if (self.parse_ir.tokens.resolveIdentifier(pattern_ident_tok)) |decl_ident| {
|
||||
const region = self.parse_ir.tokenizedRegionToRegion(decl.region);
|
||||
const placeholder_pattern = Pattern{
|
||||
.assign = .{
|
||||
.ident = decl_ident,
|
||||
},
|
||||
};
|
||||
const placeholder_pattern_idx = try self.env.addPattern(placeholder_pattern, region);
|
||||
|
||||
|
||||
// Directly put placeholder in current scope
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
try current_scope.idents.put(self.env.gpa, decl_ident, placeholder_pattern_idx);
|
||||
// Track that this is a placeholder
|
||||
try self.placeholder_idents.put(self.env.gpa, decl_ident, {});
|
||||
}
|
||||
}
|
||||
},
|
||||
.type_anno => |type_anno| {
|
||||
if (self.parse_ir.tokens.resolveIdentifier(type_anno.name)) |anno_ident| {
|
||||
const region = self.parse_ir.tokenizedRegionToRegion(type_anno.region);
|
||||
const placeholder_pattern = Pattern{
|
||||
.assign = .{
|
||||
.ident = anno_ident,
|
||||
},
|
||||
};
|
||||
const placeholder_pattern_idx = try self.env.addPattern(placeholder_pattern, region);
|
||||
|
||||
|
||||
// Directly put placeholder in current scope
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
try current_scope.idents.put(self.env.gpa, anno_ident, placeholder_pattern_idx);
|
||||
// Track that this is a placeholder
|
||||
try self.placeholder_idents.put(self.env.gpa, anno_ident, {});
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// Skip other statement types
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// For type modules, expose the main type and all associated items BEFORE processing associated blocks
|
||||
// This ensures unused variable checking (which happens during scope exit) doesn't flag exposed items
|
||||
// For type modules, mark the main type and all associated items as exposed
|
||||
// This must happen BEFORE processing to avoid unused variable warnings
|
||||
// Note: addExposedById just marks items as exposed; node indices are set later when defs are created
|
||||
if (self.env.module_kind == .type_module) {
|
||||
const module_name_text = self.env.module_name;
|
||||
for (self.parse_ir.store.statementSlice(file.statements)) |stmt_id| {
|
||||
|
|
@ -1254,8 +1240,8 @@ pub fn canonicalizeFile(
|
|||
}
|
||||
|
||||
// Phase 1.6: Now process all deferred type declaration associated blocks
|
||||
// All top-level placeholders are now in scope, and for type modules, all items are marked as exposed
|
||||
var assoc_blocks_found: usize = 0;
|
||||
// processAssociatedBlock creates placeholders for associated items via processAssociatedItemsFirstPass
|
||||
// This introduces nested types (like Foo.Bar) that other type declarations may reference
|
||||
for (self.parse_ir.store.statementSlice(file.statements)) |stmt_id| {
|
||||
const stmt = self.parse_ir.store.getStatement(stmt_id);
|
||||
if (stmt == .type_decl) {
|
||||
|
|
@ -1263,13 +1249,28 @@ pub fn canonicalizeFile(
|
|||
if (type_decl.associated) |assoc| {
|
||||
const type_header = self.parse_ir.store.getTypeHeader(type_decl.header) catch continue;
|
||||
const type_ident = self.parse_ir.tokens.resolveIdentifier(type_header.name) orelse continue;
|
||||
assoc_blocks_found += 1;
|
||||
|
||||
try self.processAssociatedBlock(type_ident, type_ident, assoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1.7: Process type declarations WITHOUT associated blocks
|
||||
// These can now reference nested types that were introduced in Phase 1.6
|
||||
for (self.parse_ir.store.statementSlice(file.statements)) |stmt_id| {
|
||||
const stmt = self.parse_ir.store.getStatement(stmt_id);
|
||||
switch (stmt) {
|
||||
.type_decl => |type_decl| {
|
||||
if (type_decl.associated == null) {
|
||||
try self.processTypeDeclFirstPass(type_decl, null, false); // no associated block to defer
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// Skip non-type-declaration statements
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: Process all other statements
|
||||
const ast_stmt_idxs = self.parse_ir.store.statementSlice(file.statements);
|
||||
var i: usize = 0;
|
||||
|
|
@ -4842,13 +4843,13 @@ fn canonicalizePattern(
|
|||
.ident = ident_idx,
|
||||
} }, region);
|
||||
|
||||
// Check if a placeholder exists for this identifier
|
||||
// Placeholders are tracked in placeholder_idents and exist only until replaced
|
||||
const placeholder_exists = self.placeholder_idents.contains(ident_idx);
|
||||
// Check if a placeholder exists for this identifier in the current scope
|
||||
// Placeholders are tracked in the placeholder_idents hash map
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
const placeholder_exists = self.isPlaceholder(ident_idx);
|
||||
|
||||
if (placeholder_exists) {
|
||||
// Replace the placeholder in the current scope
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
try self.updatePlaceholder(current_scope, ident_idx, pattern_idx);
|
||||
} else {
|
||||
// Introduce the identifier into scope mapping to this pattern node
|
||||
|
|
@ -8338,8 +8339,16 @@ fn introduceType(
|
|||
}
|
||||
}
|
||||
|
||||
/// Check if an identifier is a placeholder, with fast path for empty map (99% of files).
|
||||
/// Returns true if the identifier is tracked as a placeholder.
|
||||
fn isPlaceholder(self: *const Self, ident_idx: Ident.Idx) bool {
|
||||
// Fast path: if map is empty, no placeholders exist
|
||||
if (self.placeholder_idents.count() == 0) return false;
|
||||
return self.placeholder_idents.contains(ident_idx);
|
||||
}
|
||||
|
||||
/// Update a placeholder pattern in scope with the actual pattern.
|
||||
/// In debug builds, asserts that the old value was actually a placeholder (.assign pattern).
|
||||
/// In debug builds, asserts that the identifier was tracked as a placeholder.
|
||||
fn updatePlaceholder(
|
||||
self: *Self,
|
||||
scope: *Scope,
|
||||
|
|
@ -8347,14 +8356,13 @@ fn updatePlaceholder(
|
|||
pattern_idx: Pattern.Idx,
|
||||
) std.mem.Allocator.Error!void {
|
||||
if (builtin.mode == .Debug) {
|
||||
if (scope.idents.get(ident_idx)) |old_pattern_idx| {
|
||||
const old_pattern = self.env.store.getPattern(old_pattern_idx);
|
||||
std.debug.assert(old_pattern == .assign);
|
||||
}
|
||||
std.debug.assert(self.isPlaceholder(ident_idx));
|
||||
}
|
||||
// Remove from placeholder tracking since it's now a real definition
|
||||
if (self.placeholder_idents.count() > 0) {
|
||||
_ = self.placeholder_idents.remove(ident_idx);
|
||||
}
|
||||
try scope.idents.put(self.env.gpa, ident_idx, pattern_idx);
|
||||
// Remove from placeholder set since it's now a real definition
|
||||
_ = self.placeholder_idents.remove(ident_idx);
|
||||
}
|
||||
|
||||
fn scopeUpdateTypeDecl(
|
||||
|
|
|
|||
|
|
@ -64,9 +64,6 @@ Foo := [Whatever].{
|
|||
# CANONICALIZE
|
||||
~~~clojure
|
||||
(can-ir
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.baz"))
|
||||
(e-num (value "5")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.baz"))
|
||||
(e-num (value "5")))
|
||||
|
|
@ -86,7 +83,6 @@ Foo := [Whatever].{
|
|||
~~~clojure
|
||||
(inferred-types
|
||||
(defs
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]")))
|
||||
(type_decls
|
||||
|
|
@ -95,7 +91,6 @@ Foo := [Whatever].{
|
|||
(nominal (type "Foo.Bar")
|
||||
(ty-header (name "Foo.Bar"))))
|
||||
(expressions
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))))
|
||||
~~~
|
||||
|
|
|
|||
|
|
@ -110,15 +110,6 @@ deepType = C
|
|||
# CANONICALIZE
|
||||
~~~clojure
|
||||
(can-ir
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Level1.Level2.Level3.value"))
|
||||
(e-num (value "42")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Level1.Level2.Level3.value"))
|
||||
(e-num (value "42")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Level1.Level2.Level3.value"))
|
||||
(e-num (value "42")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Level1.Level2.Level3.value"))
|
||||
(e-num (value "42")))
|
||||
|
|
@ -154,9 +145,6 @@ deepType = C
|
|||
~~~clojure
|
||||
(inferred-types
|
||||
(defs
|
||||
(patt (type "Num(Int(Unsigned64))"))
|
||||
(patt (type "Num(Int(Unsigned64))"))
|
||||
(patt (type "Num(Int(Unsigned64))"))
|
||||
(patt (type "Num(Int(Unsigned64))"))
|
||||
(patt (type "Num(Int(Unsigned64))"))
|
||||
(patt (type "Foo.Level1.Level2.Level3")))
|
||||
|
|
@ -170,9 +158,6 @@ deepType = C
|
|||
(nominal (type "Foo.Level1.Level2.Level3")
|
||||
(ty-header (name "Foo.Level1.Level2.Level3"))))
|
||||
(expressions
|
||||
(expr (type "Num(Int(Unsigned64))"))
|
||||
(expr (type "Num(Int(Unsigned64))"))
|
||||
(expr (type "Num(Int(Unsigned64))"))
|
||||
(expr (type "Num(Int(Unsigned64))"))
|
||||
(expr (type "Num(Int(Unsigned64))"))
|
||||
(expr (type "Foo.Level1.Level2.Level3"))))
|
||||
|
|
|
|||
|
|
@ -84,9 +84,6 @@ myNum = Foo.Bar.baz
|
|||
# CANONICALIZE
|
||||
~~~clojure
|
||||
(can-ir
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.baz"))
|
||||
(e-num (value "5")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.baz"))
|
||||
(e-num (value "5")))
|
||||
|
|
@ -114,7 +111,6 @@ myNum = Foo.Bar.baz
|
|||
~~~clojure
|
||||
(inferred-types
|
||||
(defs
|
||||
(patt (type "Num(Int(Unsigned64))"))
|
||||
(patt (type "Num(Int(Unsigned64))"))
|
||||
(patt (type "Foo.Bar"))
|
||||
(patt (type "Num(Int(Unsigned64))")))
|
||||
|
|
@ -124,7 +120,6 @@ myNum = Foo.Bar.baz
|
|||
(nominal (type "Foo.Bar")
|
||||
(ty-header (name "Foo.Bar"))))
|
||||
(expressions
|
||||
(expr (type "Num(Int(Unsigned64))"))
|
||||
(expr (type "Num(Int(Unsigned64))"))
|
||||
(expr (type "Foo.Bar"))
|
||||
(expr (type "Num(Int(Unsigned64))"))))
|
||||
|
|
|
|||
|
|
@ -102,24 +102,6 @@ Foo := [Whatever].{
|
|||
# CANONICALIZE
|
||||
~~~clojure
|
||||
(can-ir
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.Baz.Qux.w"))
|
||||
(e-num (value "1")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.Baz.Qux.w"))
|
||||
(e-num (value "1")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.Baz.z"))
|
||||
(e-num (value "2")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.Baz.Qux.w"))
|
||||
(e-num (value "1")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.Baz.z"))
|
||||
(e-num (value "2")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.y"))
|
||||
(e-num (value "3")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.Baz.Qux.w"))
|
||||
(e-num (value "1")))
|
||||
|
|
@ -153,12 +135,6 @@ Foo := [Whatever].{
|
|||
~~~clojure
|
||||
(inferred-types
|
||||
(defs
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
|
|
@ -173,12 +149,6 @@ Foo := [Whatever].{
|
|||
(nominal (type "Foo.Bar.Baz.Qux")
|
||||
(ty-header (name "Foo.Bar.Baz.Qux"))))
|
||||
(expressions
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
|
|
|
|||
|
|
@ -64,9 +64,6 @@ Foo := [Whatever].{
|
|||
# CANONICALIZE
|
||||
~~~clojure
|
||||
(can-ir
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.y"))
|
||||
(e-num (value "6")))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.y"))
|
||||
(e-num (value "6")))
|
||||
|
|
@ -86,7 +83,6 @@ Foo := [Whatever].{
|
|||
~~~clojure
|
||||
(inferred-types
|
||||
(defs
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(patt (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]")))
|
||||
(type_decls
|
||||
|
|
@ -95,7 +91,6 @@ Foo := [Whatever].{
|
|||
(nominal (type "Foo.Bar")
|
||||
(ty-header (name "Foo.Bar"))))
|
||||
(expressions
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))
|
||||
(expr (type "num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]"))))
|
||||
~~~
|
||||
|
|
|
|||
|
|
@ -63,9 +63,6 @@ Foo := [Blah].{
|
|||
# CANONICALIZE
|
||||
~~~clojure
|
||||
(can-ir
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.baz"))
|
||||
(e-empty_record))
|
||||
(d-let
|
||||
(p-assign (ident "Foo.Bar.baz"))
|
||||
(e-empty_record))
|
||||
|
|
@ -84,7 +81,6 @@ Foo := [Blah].{
|
|||
~~~clojure
|
||||
(inferred-types
|
||||
(defs
|
||||
(patt (type "{}"))
|
||||
(patt (type "{}"))
|
||||
(patt (type "{}")))
|
||||
(type_decls
|
||||
|
|
@ -93,7 +89,6 @@ Foo := [Blah].{
|
|||
(nominal (type "Foo.Bar")
|
||||
(ty-header (name "Foo.Bar"))))
|
||||
(expressions
|
||||
(expr (type "{}"))
|
||||
(expr (type "{}"))
|
||||
(expr (type "{}"))))
|
||||
~~~
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue