mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Reduce canonicalization passes
This commit is contained in:
parent
99b2032059
commit
b217547e67
1 changed files with 137 additions and 145 deletions
|
|
@ -543,11 +543,145 @@ fn processTypeDeclFirstPass(
|
|||
const type_text = self.env.getIdent(type_header.name);
|
||||
_ = self.exposed_type_texts.remove(type_text);
|
||||
|
||||
// Process associated items recursively in the first pass to introduce names
|
||||
// Aliases are introduced in the current scope (not a nested scope) during first pass
|
||||
// They will be available when we process the associated block in the second pass
|
||||
// Process associated items completely (both symbol introduction and canonicalization)
|
||||
// This eliminates the need for a separate third pass
|
||||
if (type_decl.associated) |assoc| {
|
||||
// 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;
|
||||
|
||||
// First, 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_header.name, parent_type_decl_idx);
|
||||
}
|
||||
|
||||
// Re-introduce the aliases from first pass into this scope
|
||||
// (We need to rebuild them since we're in a new scope)
|
||||
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 qualified name
|
||||
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);
|
||||
|
||||
// Look up and alias
|
||||
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);
|
||||
}
|
||||
|
||||
// Also re-introduce associated items of this nested type
|
||||
// so that both `my_not` and `MyBool.my_not` work within the associated block
|
||||
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
|
||||
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);
|
||||
// Also add name qualified with nested type (e.g., "MyBool.my_not")
|
||||
const nested_decl_text2 = self.env.getIdent(nested_decl_ident);
|
||||
const nested_qualified_ident_idx = try self.env.insertQualifiedIdent(nested_type_text, nested_decl_text2);
|
||||
try scope.idents.put(self.env.gpa, nested_qualified_ident_idx, pattern_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 qualified name
|
||||
const parent_text = self.env.getIdent(qualified_name_idx);
|
||||
const decl_text = self.env.getIdent(decl_ident);
|
||||
const qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, decl_text);
|
||||
|
||||
// Look up the qualified pattern
|
||||
switch (self.scopeLookup(.ident, qualified_ident_idx)) {
|
||||
.found => |pattern_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
// Add both unqualified and qualified names to the current scope
|
||||
// This allows both `my_not` and `MyBool.my_not` to work inside the associated block
|
||||
try current_scope.idents.put(self.env.gpa, decl_ident, pattern_idx);
|
||||
try current_scope.idents.put(self.env.gpa, 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, assoc.statements);
|
||||
|
||||
// After processing, re-introduce anno-only defs into the associated block scope
|
||||
// (They were just created by processAssociatedItemsSecondPass and need to be available
|
||||
// for use within the associated block)
|
||||
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 qualified name
|
||||
const parent_text = self.env.getIdent(qualified_name_idx);
|
||||
const anno_text = self.env.getIdent(anno_ident);
|
||||
const qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, anno_text);
|
||||
|
||||
// Look up the qualified pattern that was just created
|
||||
switch (self.scopeLookup(.ident, qualified_ident_idx)) {
|
||||
.found => |pattern_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
// Add both unqualified and qualified names to the current scope
|
||||
// This allows both `len` and `List.len` to work inside the associated block
|
||||
try current_scope.idents.put(self.env.gpa, anno_ident, pattern_idx);
|
||||
try current_scope.idents.put(self.env.gpa, 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 => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1223,148 +1357,6 @@ pub fn canonicalizeFile(
|
|||
}
|
||||
}
|
||||
|
||||
// Third pass: Process associated items in type declarations
|
||||
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) |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;
|
||||
|
||||
// Enter a new scope for the associated block
|
||||
try self.scopeEnter(self.env.gpa, false); // false = not a function boundary
|
||||
defer self.scopeExit(self.env.gpa) catch unreachable;
|
||||
|
||||
// Re-introduce the aliases from first pass
|
||||
// (We need to rebuild them since we're in a new scope)
|
||||
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 qualified name
|
||||
const parent_text = self.env.getIdent(type_ident);
|
||||
const type_text = self.env.getIdent(unqualified_ident);
|
||||
const qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, type_text);
|
||||
|
||||
// Look up and alias
|
||||
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);
|
||||
}
|
||||
|
||||
// Also re-introduce associated items of this nested type
|
||||
// so that both `my_not` and `MyBool.my_not` work within the associated block
|
||||
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
|
||||
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);
|
||||
// Also add name qualified with nested type (e.g., "MyBool.my_not")
|
||||
const nested_decl_text2 = self.env.getIdent(nested_decl_ident);
|
||||
const nested_qualified_ident_idx = try self.env.insertQualifiedIdent(type_text, nested_decl_text2);
|
||||
try scope.idents.put(self.env.gpa, nested_qualified_ident_idx, pattern_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 qualified name
|
||||
const parent_text = self.env.getIdent(type_ident);
|
||||
const decl_text = self.env.getIdent(decl_ident);
|
||||
const qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, decl_text);
|
||||
|
||||
// Look up the qualified pattern
|
||||
switch (self.scopeLookup(.ident, qualified_ident_idx)) {
|
||||
.found => |pattern_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
// Add both unqualified and qualified names to the current scope
|
||||
// This allows both `my_not` and `MyBool.my_not` to work inside the associated block
|
||||
try current_scope.idents.put(self.env.gpa, decl_ident, pattern_idx);
|
||||
try current_scope.idents.put(self.env.gpa, 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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
try self.processAssociatedItemsSecondPass(type_ident, assoc.statements);
|
||||
|
||||
// After processing, re-introduce anno-only defs into the associated block scope
|
||||
// (They were just created by processAssociatedItemsSecondPass and need to be available
|
||||
// for use within the associated block)
|
||||
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 qualified name
|
||||
const parent_text = self.env.getIdent(type_ident);
|
||||
const anno_text = self.env.getIdent(anno_ident);
|
||||
const qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, anno_text);
|
||||
|
||||
// Look up the qualified pattern that was just created
|
||||
switch (self.scopeLookup(.ident, qualified_ident_idx)) {
|
||||
.found => |pattern_idx| {
|
||||
const current_scope = &self.scopes.items[self.scopes.items.len - 1];
|
||||
// Add both unqualified and qualified names to the current scope
|
||||
// This allows both `len` and `List.len` to work inside the associated block
|
||||
try current_scope.idents.put(self.env.gpa, anno_ident, pattern_idx);
|
||||
try current_scope.idents.put(self.env.gpa, 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 => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// Skip non-type-declaration statements in third pass
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Check for exposed but not implemented items
|
||||
try self.checkExposedButNotImplemented();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue