diff --git a/src/build/builtin_compiler/main.zig b/src/build/builtin_compiler/main.zig index 03bb18df6b..87b64783fe 100644 --- a/src/build/builtin_compiler/main.zig +++ b/src/build/builtin_compiler/main.zig @@ -149,11 +149,18 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) { if (env.common.findIdent("Builtin.Str.is_empty")) |str_is_empty_ident| { try low_level_map.put(str_is_empty_ident, .str_is_empty); } + if (env.common.findIdent("Builtin.List.len")) |list_len_ident| { + try low_level_map.put(list_len_ident, .list_len); + } + if (env.common.findIdent("Builtin.List.is_empty")) |list_is_empty_ident| { + try low_level_map.put(list_is_empty_ident, .list_is_empty); + } + if (env.common.findIdent("list_get_unsafe")) |list_get_unsafe_ident| { + try low_level_map.put(list_get_unsafe_ident, .list_get_unsafe); + } if (env.common.findIdent("Builtin.Set.is_empty")) |set_is_empty_ident| { try low_level_map.put(set_is_empty_ident, .set_is_empty); } - - // Bool operations if (env.common.findIdent("Builtin.Bool.is_eq")) |bool_is_eq_ident| { try low_level_map.put(bool_is_eq_ident, .bool_is_eq); } @@ -637,7 +644,8 @@ fn compileModule( const box_ident = try module_env.insertIdent(base.Ident.for_text("Box")); // Use provided bool_stmt and try_stmt if available, otherwise use undefined - const common_idents: Check.CommonIdents = .{ + // For Builtin module, these will be found after canonicalization and updated before type checking + var common_idents: Check.CommonIdents = .{ .module_name = module_ident, .list = list_ident, .box = box_ident, @@ -668,19 +676,7 @@ fn compileModule( return error.ParseError; } - // 4. Create module imports map (for cross-module references) - var module_envs = std.AutoHashMap(base.Ident.Idx, Can.AutoImportedType).init(gpa); - defer module_envs.deinit(); - - // Add dependencies (e.g., Dict for Set, Bool for Str) - // IMPORTANT: Use the module's own ident store, not a temporary one, - // because auto-import lookups will use the module's ident store - for (deps) |dep| { - const dep_ident = try module_env.insertIdent(base.Ident.for_text(dep.name)); - try module_envs.put(dep_ident, .{ .env = dep.env }); - } - - // 5. Canonicalize + // 4. Canonicalize try module_env.initCIRFields(gpa, module_name); var can_result = try gpa.create(Can); @@ -689,7 +685,8 @@ fn compileModule( gpa.destroy(can_result); } - can_result.* = try Can.init(module_env, parse_ast, &module_envs); + // When compiling Builtin itself, pass null for module_envs so setupAutoImportedBuiltinTypes doesn't run + can_result.* = try Can.init(module_env, parse_ast, null); try can_result.canonicalizeFile(); try can_result.validateForChecking(); @@ -705,6 +702,10 @@ fn compileModule( const type_name = module_env.getIdentText(d.name); std.debug.print(" - Undeclared type: {s}\n", .{type_name}); }, + .ident_not_in_scope => |d| { + const ident_name = module_env.getIdentText(d.ident); + std.debug.print(" - Ident not in scope: {s}\n", .{ident_name}); + }, .nested_value_not_found => |d| { const parent = module_env.getIdentText(d.parent_name); const nested = module_env.getIdentText(d.nested_name); @@ -770,6 +771,22 @@ fn compileModule( eval_order_ptr.* = eval_order; module_env.evaluation_order = eval_order_ptr; } + + // Find Bool and Try statements before type checking + // When compiling Builtin, bool_stmt and try_stmt are initially undefined, + // but they must be set before type checking begins + const found_bool_stmt = findTypeDeclaration(module_env, "Bool") catch { + std.debug.print("Error: Could not find Bool type in Builtin module\n", .{}); + return error.TypeDeclarationNotFound; + }; + const found_try_stmt = findTypeDeclaration(module_env, "Try") catch { + std.debug.print("Error: Could not find Try type in Builtin module\n", .{}); + return error.TypeDeclarationNotFound; + }; + + // Update common_idents with the found statement indices + common_idents.bool_stmt = found_bool_stmt; + common_idents.try_stmt = found_try_stmt; } // 6. Type check @@ -782,6 +799,9 @@ fn compileModule( try imported_envs.append(gpa, dep.env); } + var module_envs = std.AutoHashMap(base.Ident.Idx, Can.AutoImportedType).init(gpa); + defer module_envs.deinit(); + var checker = try Check.init( gpa, &module_env.types, diff --git a/src/build/roc/Builtin.roc b/src/build/roc/Builtin.roc index feb433ccf7..0ec1278c6d 100644 --- a/src/build/roc/Builtin.roc +++ b/src/build/roc/Builtin.roc @@ -7,14 +7,18 @@ Builtin := [].{ } List := [ProvidedByCompiler].{ - len : List(a) -> U64 - len = |_| 0 + len : List(_elem) -> U64 + is_empty : List(_elem) -> Bool - is_empty : List(a) -> Bool - is_empty = |_| True + first : List(elem) -> Try(elem, [ListWasEmpty]) + first = |list| List.get(list, 0) - first : List(a) -> Try(a, [ListWasEmpty]) - first = |_| Err(ListWasEmpty) + get : List(elem), U64 -> Try(elem, [ListWasEmpty]) + get = |list, index| if index < List.len(list) { + Try.Ok(list_get_unsafe(list, index)) + } else { + Try.Err(ListWasEmpty) + } map : List(a), (a -> b) -> List(b) map = |_, _| [] @@ -335,3 +339,7 @@ Builtin := [].{ } } } + +# Private top-level function for unsafe list access +# This is a low-level operation that gets replaced by the compiler +list_get_unsafe : List(elem), U64 -> elem diff --git a/src/builtins/list.zig b/src/builtins/list.zig index ce32f60886..8c312c9b61 100644 --- a/src/builtins/list.zig +++ b/src/builtins/list.zig @@ -375,6 +375,28 @@ pub fn listIncref(list: RocList, amount: isize, elements_refcounted: bool) callc list.incref(amount, elements_refcounted); } +/// Get the number of elements in the list. +pub fn listLen(list: RocList) callconv(.c) usize { + return list.len(); +} + +/// Check if the list is empty. +pub fn listIsEmpty(list: RocList) callconv(.c) bool { + return list.isEmpty(); +} + +/// Get a pointer to an element at the given index without bounds checking. +/// UNSAFE: No bounds checking is performed. Index must be < list.len(). +/// This is intended for internal use by low-level operations only. +/// Returns a pointer to the element at the given index. +pub fn listGetUnsafe(list: RocList, index: u64, element_width: usize) callconv(.c) ?[*]u8 { + if (list.bytes) |bytes| { + const byte_offset = @as(usize, @intCast(index)) * element_width; + return bytes + byte_offset; + } + return null; +} + /// Decrement reference count and deallocate when no longer shared. pub fn listDecref( list: RocList, diff --git a/src/canonicalize/Can.zig b/src/canonicalize/Can.zig index 92cc1c7d63..6fc2c02515 100644 --- a/src/canonicalize/Can.zig +++ b/src/canonicalize/Can.zig @@ -4,6 +4,7 @@ //! constructs into a simplified, normalized form suitable for type inference. const std = @import("std"); +const builtin = @import("builtin"); const testing = std.testing; const base = @import("base"); const parse = @import("parse"); @@ -43,6 +44,9 @@ exposed_scope: Scope = undefined, 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), /// Maps var patterns to the function region they were declared in @@ -165,6 +169,7 @@ pub fn deinit( self.exposed_scope.deinit(gpa); self.exposed_ident_texts.deinit(gpa); self.exposed_type_texts.deinit(gpa); + self.placeholder_idents.deinit(gpa); for (0..self.scopes.items.len) |i| { var scope = &self.scopes.items[i]; @@ -282,62 +287,39 @@ pub fn populateModuleEnvs( } /// Set up auto-imported builtin types (Bool, Result, Dict, Set, Str, and numeric types) from the Builtin module. -/// This function is shared between production and test environments to ensure consistency. -/// -/// These nested types in Builtin.roc need special handling: -/// 1. Add them to scope's type_bindings so type annotations work +/// Used for all modules EXCEPT Builtin itself. pub fn setupAutoImportedBuiltinTypes( self: *Self, env: *ModuleEnv, gpa: std.mem.Allocator, module_envs: ?*const std.AutoHashMap(Ident.Idx, AutoImportedType), ) std.mem.Allocator.Error!void { - // Auto-import builtin types (Bool, Result, Dict, Set, Str, and numeric types) - // These are nested types in Builtin module but need to be auto-imported like standalone modules if (module_envs) |envs_map| { const zero_region = Region{ .start = Region.Position.zero(), .end = Region.Position.zero() }; - const current_scope = &self.scopes.items[0]; // Top-level scope + const current_scope = &self.scopes.items[0]; + + const builtin_import_idx = try self.env.imports.getOrPut( + gpa, + self.env.common.getStringStore(), + "Builtin", + ); + try self.import_indices.put(gpa, "Builtin", builtin_import_idx); const builtin_types = [_][]const u8{ "Bool", "Result", "Dict", "Set", "Str", "U8", "I8", "U16", "I16", "U32", "I32", "U64", "I64", "U128", "I128", "Dec", "F32", "F64" }; for (builtin_types) |type_name_text| { const type_ident = try env.insertIdent(base.Ident.for_text(type_name_text)); if (envs_map.get(type_ident)) |type_entry| { - const module_env = type_entry.env; - - // Create an import for the parent Builtin module (only once, shared across all types) - const builtin_module_name = module_env.module_name; - - // Check if we already have this import in our indices - const is_new_import = !self.import_indices.contains(builtin_module_name); - - const module_import_idx = try self.env.imports.getOrPut( - gpa, - self.env.common.getStringStore(), - builtin_module_name, - ); - - if (is_new_import) { - // Add to import_indices so getOrCreateAutoImport can find it - try self.import_indices.put(gpa, builtin_module_name, module_import_idx); - - // Also add to current scope so scopeLookupImportedModule can find it - // This ensures consistency with getOrCreateAutoImport - _ = try current_scope.introduceImportedModule(gpa, builtin_module_name, module_import_idx); - } - - // Get target_node_idx from statement_idx const target_node_idx = if (type_entry.statement_idx) |stmt_idx| - module_env.getExposedNodeIndexByStatementIdx(stmt_idx) + type_entry.env.getExposedNodeIndexByStatementIdx(stmt_idx) else null; - // Add type binding to scope try current_scope.type_bindings.put(gpa, type_ident, Scope.TypeBinding{ .external_nominal = .{ - .module_ident = type_ident, // Use type name as module ident for module_envs lookup + .module_ident = type_ident, .original_ident = type_ident, .target_node_idx = target_node_idx, - .import_idx = module_import_idx, + .import_idx = builtin_import_idx, .origin_region = zero_region, .module_not_found = false, }, @@ -345,22 +327,16 @@ pub fn setupAutoImportedBuiltinTypes( } } - // Also add primitive builtin types (List, Box) to type_bindings - // so we can detect conflicts with O(1) HashMap lookup instead of string scanning - // Note: Str is NOT primitive anymore - it's auto-imported like Bool - const primitive_builtins = [_][]const u8{ "List", "Box" }; for (primitive_builtins) |type_name_text| { const type_ident = try env.insertIdent(base.Ident.for_text(type_name_text)); - // Add a minimal type binding to detect conflicts - // These primitives don't have module entries, so we use a marker binding try current_scope.type_bindings.put(gpa, type_ident, Scope.TypeBinding{ .external_nominal = .{ .module_ident = type_ident, .original_ident = type_ident, .target_node_idx = null, - .import_idx = @enumFromInt(0), // Dummy import index for primitives + .import_idx = @enumFromInt(0), .origin_region = zero_region, .module_not_found = false, }, @@ -395,6 +371,7 @@ fn processTypeDeclFirstPass( self: *Self, type_decl: anytype, parent_name: ?Ident.Idx, + defer_associated_blocks: bool, ) std.mem.Allocator.Error!void { // Canonicalize the type declaration header first const header_idx = try self.canonicalizeTypeHeader(type_decl.header); @@ -447,7 +424,15 @@ fn processTypeDeclFirstPass( const type_decl_stmt_idx = try self.env.addStatement(placeholder_cir_type_decl, region); // Introduce the type name into scope early to support recursive references - try self.scopeIntroduceTypeDecl(qualified_name_idx, type_decl_stmt_idx, region); + try self.introduceType(qualified_name_idx, type_decl_stmt_idx, region); + + // For nested types, also add an unqualified alias so child scopes can find it + // E.g., when introducing "Builtin.Bool", also add "Bool" -> "Builtin.Bool" + // This allows nested scopes (like Str's or Num.U8's associated blocks) to find Bool via scope lookup + if (parent_name != null) { + const current_scope = &self.scopes.items[self.scopes.items.len - 1]; + try current_scope.introduceTypeAlias(self.env.gpa, type_header.name, type_decl_stmt_idx); + } // Process type parameters and annotation in a separate scope const anno_idx = blk: { @@ -508,11 +493,199 @@ 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 - if (type_decl.associated) |assoc| { - try self.processAssociatedItemsFirstPass(qualified_name_idx, assoc.statements); + // Process associated items completely (both symbol introduction and canonicalization) + // This eliminates the need for a separate third pass + // Unless defer_associated_blocks is true (when called from processAssociatedItemsFirstPass + // to handle sibling type forward references) + if (!defer_associated_blocks) { + if (type_decl.associated) |assoc| { + try self.processAssociatedBlock(qualified_name_idx, type_header.name, assoc); + } + } +} + +/// Process an associated block: introduce all items, set up scope with aliases, and canonicalize +fn processAssociatedBlock( + self: *Self, + qualified_name_idx: Ident.Idx, + 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, type_name, 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; + + // 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. + + // 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); + + // Introduce type aliases (fully qualified is already in parent scope from processTypeDeclFirstPass) + if (self.scopeLookupTypeDecl(qualified_ident_idx)) |qualified_type_decl_idx| { + const current_scope = &self.scopes.items[self.scopes.items.len - 1]; + + // Add unqualified alias (e.g., "Bar" -> the fully qualified type) + try current_scope.introduceTypeAlias(self.env.gpa, unqualified_ident, qualified_type_decl_idx); + + // Add user-facing qualified alias (e.g., "Foo.Bar" -> the fully qualified type) + // This allows users to write "Foo.Bar" in type annotations + // Re-fetch nested_type_text since insertQualifiedIdent may have reallocated + const type_name_text_str = self.env.getIdent(type_name); + const nested_type_text_str = self.env.getIdent(unqualified_ident); + const user_qualified_ident_idx = try self.env.insertQualifiedIdent(type_name_text_str, nested_type_text_str); + try current_scope.introduceTypeAlias(self.env.gpa, user_qualified_ident_idx, 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); + + // 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]; + + // Check if this is a placeholder + const is_placeholder = self.isPlaceholder(full_qualified_ident_idx); + + // 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]; + + // Check if this is a placeholder by checking if the identifier is tracked + const is_placeholder = self.isPlaceholder(fully_qualified_ident_idx); + + // 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 => {}, + } + } + } + }, + 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 => {}, + } } } @@ -599,6 +772,7 @@ fn canonicalizeAssociatedDeclWithAnno( fn processAssociatedItemsSecondPass( self: *Self, parent_name: Ident.Idx, + parent_type_name: Ident.Idx, statements: AST.Statement.Span, ) std.mem.Allocator.Error!void { const stmt_idxs = self.parse_ir.store.statementSlice(statements); @@ -607,19 +781,9 @@ fn processAssociatedItemsSecondPass( const stmt_idx = stmt_idxs[i]; const stmt = self.parse_ir.store.getStatement(stmt_idx); switch (stmt) { - .type_decl => |type_decl| { - // Recursively process nested type declarations - 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; - - // Build qualified name for nested type - const parent_text = self.env.getIdent(parent_name); - const type_text = self.env.getIdent(type_ident); - const qualified_idx = try self.env.insertQualifiedIdent(parent_text, type_text); - - try self.processAssociatedItemsSecondPass(qualified_idx, assoc.statements); - } + .type_decl => { + // Skip nested type declarations - they're already processed by processAssociatedItemsFirstPass Phase 2 + // which calls processAssociatedBlock for each nested type }, .type_anno => |ta| { const name_ident = self.parse_ir.tokens.resolveIdentifier(ta.name) orelse { @@ -656,64 +820,105 @@ fn processAssociatedItemsSecondPass( // Now, check the next stmt to see if it matches this anno const next_i = i + 1; - if (next_i < stmt_idxs.len) { + const has_matching_decl = if (next_i < stmt_idxs.len) blk: { const next_stmt_id = stmt_idxs[next_i]; const next_stmt = self.parse_ir.store.getStatement(next_stmt_id); - switch (next_stmt) { - .decl => |decl| { - // Check if the declaration pattern matches the annotation name - 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| { - // Check if names match - if (name_ident.idx == decl_ident.idx) { - // Skip the next statement since we're processing it now - i = next_i; + if (next_stmt == .decl) { + const decl = next_stmt.decl; + // Check if the declaration pattern matches the annotation name + 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| { + // Check if names match + if (name_ident.idx == decl_ident.idx) { + // Skip the next statement since we're processing it now + i = next_i; - // Build qualified name (e.g., "Foo.bar") - const parent_text = self.env.getIdent(parent_name); - const decl_text = self.env.getIdent(decl_ident); - const qualified_idx = try self.env.insertQualifiedIdent(parent_text, decl_text); + // Build qualified name (e.g., "Foo.bar") + const parent_text = self.env.getIdent(parent_name); + const decl_text = self.env.getIdent(decl_ident); + const qualified_idx = try self.env.insertQualifiedIdent(parent_text, decl_text); - // Canonicalize with the qualified name and type annotation - const def_idx = try self.canonicalizeAssociatedDeclWithAnno( - decl, - qualified_idx, - type_anno_idx, - where_clauses, - ); - try self.env.store.addScratchDef(def_idx); + // Canonicalize with the qualified name and type annotation + const def_idx = try self.canonicalizeAssociatedDeclWithAnno( + decl, + qualified_idx, + type_anno_idx, + where_clauses, + ); + try self.env.store.addScratchDef(def_idx); - // Register this associated item by its qualified name - const def_idx_u16: u16 = @intCast(@intFromEnum(def_idx)); - try self.env.setExposedNodeIndexById(qualified_idx, def_idx_u16); - } else {} + // Register this associated item by its qualified name + const def_idx_u16: u16 = @intCast(@intFromEnum(def_idx)); + try self.env.setExposedNodeIndexById(qualified_idx, def_idx_u16); + + // Make the real pattern available in current scope (replaces placeholder) + // We already added unqualified and type-qualified names earlier, + // but need to update them to point to the real pattern instead of placeholder. + const def_cir = self.env.store.getDef(def_idx); + const pattern_idx = def_cir.pattern; + const current_scope = &self.scopes.items[self.scopes.items.len - 1]; + + // Update unqualified name (e.g., "my_not") + try self.updatePlaceholder(current_scope, decl_ident, pattern_idx); + + // 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); + 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") + 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 } } - }, - else => { - // If the next stmt does not match this annotation, - // create an anno-only def for the associated item - - const region = self.parse_ir.tokenizedRegionToRegion(ta.region); - - // Build qualified name for the annotation (e.g., "Str.isEmpty") - const parent_text = self.env.getIdent(parent_name); - const name_text = self.env.getIdent(name_ident); - const qualified_idx = try self.env.insertQualifiedIdent(parent_text, name_text); - - // Create anno-only def with the qualified name - const def_idx = try self.createAnnoOnlyDef(qualified_idx, type_anno_idx, where_clauses, region); - - // Register this associated item by its qualified name - const def_idx_u16: u16 = @intCast(@intFromEnum(def_idx)); - try self.env.setExposedNodeIndexById(qualified_idx, def_idx_u16); - - try self.env.store.addScratchDef(def_idx); - }, + } } + break :blk false; // No matching decl found + } else false; // No next statement + + // If there's no matching decl, create an anno-only def + if (!has_matching_decl) { + const region = self.parse_ir.tokenizedRegionToRegion(ta.region); + + // Build qualified name for the annotation (e.g., "Str.isEmpty") + const parent_text = self.env.getIdent(parent_name); + const name_text = self.env.getIdent(name_ident); + const qualified_idx = try self.env.insertQualifiedIdent(parent_text, name_text); + + // Create anno-only def with the qualified name + const def_idx = try self.createAnnoOnlyDef(qualified_idx, type_anno_idx, where_clauses, region); + + // Register this associated item by its qualified name + const def_idx_u16: u16 = @intCast(@intFromEnum(def_idx)); + try self.env.setExposedNodeIndexById(qualified_idx, def_idx_u16); + + // Make the real pattern available in current scope (replaces placeholder) + const def_cir = self.env.store.getDef(def_idx); + const pattern_idx = def_cir.pattern; + const current_scope = &self.scopes.items[self.scopes.items.len - 1]; + + // Update unqualified name (e.g., "is_empty") + try self.updatePlaceholder(current_scope, name_ident, pattern_idx); + + // 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); + 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") + 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); } }, .decl => |decl| { @@ -771,25 +976,134 @@ fn processAssociatedItemsSecondPass( fn processAssociatedItemsFirstPass( self: *Self, parent_name: Ident.Idx, + parent_type_name: Ident.Idx, statements: AST.Statement.Span, ) std.mem.Allocator.Error!void { + // Multi-phase approach for sibling types: + // Phase 1a: Introduce nominal type declarations (defer annotations and associated blocks) + // Phase 1b: Add user-facing aliases for the nominal types + // Phase 1c: Process type aliases (which can now reference the nominal types) + // Phase 2: Process deferred associated blocks + + // Phase 1a: Introduce nominal type declarations WITHOUT processing annotations/associated blocks + // This creates placeholder types that can be referenced + for (self.parse_ir.store.statementSlice(statements)) |stmt_idx| { + const stmt = self.parse_ir.store.getStatement(stmt_idx); + if (stmt == .type_decl) { + const type_decl = stmt.type_decl; + // Only process nominal types in this phase; aliases will be processed later + if (type_decl.kind == .nominal) { + try self.processTypeDeclFirstPass(type_decl, parent_name, true); // defer associated blocks + } + } + } + + // Phase 1b: Add user-facing qualified aliases for nominal types + // This must happen before Phase 1c so that type aliases can reference these types + for (self.parse_ir.store.statementSlice(statements)) |stmt_idx| { + const stmt = self.parse_ir.store.getStatement(stmt_idx); + if (stmt == .type_decl) { + const type_decl = stmt.type_decl; + if (type_decl.kind == .nominal) { + const type_header = self.parse_ir.store.getTypeHeader(type_decl.header) catch continue; + const nested_type_ident = self.parse_ir.tokens.resolveIdentifier(type_header.name) orelse continue; + + // Build fully qualified name (e.g., "module.Foo.Bar") + const parent_text = self.env.getIdent(parent_name); + const nested_type_text = self.env.getIdent(nested_type_ident); + const fully_qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, nested_type_text); + + // Look up the fully qualified type that was just registered + if (self.scopeLookupTypeDecl(fully_qualified_ident_idx)) |type_decl_idx| { + const current_scope = &self.scopes.items[self.scopes.items.len - 1]; + + // Build user-facing qualified name by stripping module prefix + // Re-fetch strings since insertQualifiedIdent may have reallocated + const fully_qualified_text = self.env.getIdent(fully_qualified_ident_idx); + const module_prefix = self.env.module_name; + + // Check if the fully qualified name starts with the module name + const user_facing_text = if (std.mem.startsWith(u8, fully_qualified_text, module_prefix) and + fully_qualified_text.len > module_prefix.len and + fully_qualified_text[module_prefix.len] == '.') + fully_qualified_text[module_prefix.len + 1 ..] // Skip "module." + else + fully_qualified_text; // No module prefix, use as-is + + // Only add alias if it's different from the fully qualified name + if (!std.mem.eql(u8, user_facing_text, fully_qualified_text)) { + const user_qualified_ident_idx = try self.env.insertIdent(base.Ident.for_text(user_facing_text)); + try current_scope.introduceTypeAlias(self.env.gpa, user_qualified_ident_idx, type_decl_idx); + } + } + } + } + } + + // Phase 1c: Now process type aliases (which can reference the nominal types registered above) + for (self.parse_ir.store.statementSlice(statements)) |stmt_idx| { + const stmt = self.parse_ir.store.getStatement(stmt_idx); + if (stmt == .type_decl) { + const type_decl = stmt.type_decl; + if (type_decl.kind == .alias) { + try self.processTypeDeclFirstPass(type_decl, parent_name, true); // defer associated blocks + } + } + } + + // Phase 1d: Add user-facing aliases for type aliases too + for (self.parse_ir.store.statementSlice(statements)) |stmt_idx| { + const stmt = self.parse_ir.store.getStatement(stmt_idx); + if (stmt == .type_decl) { + const type_decl = stmt.type_decl; + if (type_decl.kind == .alias) { + const type_header = self.parse_ir.store.getTypeHeader(type_decl.header) catch continue; + const nested_type_ident = self.parse_ir.tokens.resolveIdentifier(type_header.name) orelse continue; + + // Build fully qualified name + const parent_text = self.env.getIdent(parent_name); + const nested_type_text = self.env.getIdent(nested_type_ident); + const fully_qualified_ident_idx = try self.env.insertQualifiedIdent(parent_text, nested_type_text); + + // Look up the type alias + if (self.scopeLookupTypeDecl(fully_qualified_ident_idx)) |type_decl_idx| { + const current_scope = &self.scopes.items[self.scopes.items.len - 1]; + + // Build user-facing qualified name by stripping module prefix + const fully_qualified_text = self.env.getIdent(fully_qualified_ident_idx); + const module_prefix = self.env.module_name; + + const user_facing_text = if (std.mem.startsWith(u8, fully_qualified_text, module_prefix) and + fully_qualified_text.len > module_prefix.len and + fully_qualified_text[module_prefix.len] == '.') + fully_qualified_text[module_prefix.len + 1 ..] + else + fully_qualified_text; + + if (!std.mem.eql(u8, user_facing_text, fully_qualified_text)) { + const user_qualified_ident_idx = try self.env.insertIdent(base.Ident.for_text(user_facing_text)); + try current_scope.introduceTypeAlias(self.env.gpa, user_qualified_ident_idx, type_decl_idx); + } + } + } + } + } + + // Phase 2a: Introduce all value declarations and type annotations first (before processing associated blocks) + // This ensures that sibling items (like list_get_unsafe) are available + // when processing associated blocks (like List's) for (self.parse_ir.store.statementSlice(statements)) |stmt_idx| { const stmt = self.parse_ir.store.getStatement(stmt_idx); switch (stmt) { - .type_decl => |type_decl| { - // Recursively process nested type declarations (this introduces the qualified name) - try self.processTypeDeclFirstPass(type_decl, parent_name); - }, .decl => |decl| { - // Introduce declarations with qualified names for recursive references + // Create placeholder for declarations so they can be referenced by sibling types + // processAssociatedItemsSecondPass will later use updatePlaceholder to replace these 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 (e.g., "Foo.Bar.baz") - const parent_text = self.env.getIdent(parent_name); - const decl_text = self.env.getIdent(decl_ident); - const qualified_idx = try self.env.insertQualifiedIdent(parent_text, decl_text); + const qualified_idx = try self.env.insertQualifiedIdent(self.env.getIdent(parent_name), self.env.getIdent(decl_ident)); // Create placeholder pattern with qualified name const region = self.parse_ir.tokenizedRegionToRegion(decl.region); @@ -800,30 +1114,76 @@ fn processAssociatedItemsFirstPass( }; const placeholder_pattern_idx = try self.env.addPattern(placeholder_pattern, region); - // Introduce the qualified name to scope - switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, qualified_idx, placeholder_pattern_idx, false, true)) { - .success => {}, - .shadowing_warning => |shadowed_pattern_idx| { - const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); - try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ - .ident = qualified_idx, - .region = region, - .original_region = original_region, - } }); - }, - .top_level_var_error => { - // This shouldn't happen for declarations in associated blocks - }, - .var_across_function_boundary => { - // This shouldn't happen for declarations in associated blocks - }, - } + // Also compute type-qualified name (e.g., "List.map") + // Re-fetch identifiers since insertQualifiedIdent may have reallocated the identifier table + const type_qualified_idx = try self.env.insertQualifiedIdent(self.env.getIdent(parent_type_name), self.env.getIdent(decl_ident)); + + // Track all three identifiers as placeholders + try self.placeholder_idents.put(self.env.gpa, qualified_idx, {}); + try self.placeholder_idents.put(self.env.gpa, decl_ident, {}); + try self.placeholder_idents.put(self.env.gpa, type_qualified_idx, {}); + + // 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]; + try current_scope.idents.put(self.env.gpa, qualified_idx, placeholder_pattern_idx); + try current_scope.idents.put(self.env.gpa, decl_ident, placeholder_pattern_idx); + try current_scope.idents.put(self.env.gpa, type_qualified_idx, placeholder_pattern_idx); } } }, - else => { - // Skip other statement types in first pass + .type_anno => |type_anno| { + // Create placeholder for anno-only defs so they can be referenced by sibling types + // processAssociatedItemsSecondPass will later use updatePlaceholder to replace these + if (self.parse_ir.tokens.resolveIdentifier(type_anno.name)) |anno_ident| { + const qualified_idx = try self.env.insertQualifiedIdent(self.env.getIdent(parent_name), self.env.getIdent(anno_ident)); + + const region = self.parse_ir.tokenizedRegionToRegion(type_anno.region); + const placeholder_pattern = Pattern{ + .assign = .{ + .ident = qualified_idx, + }, + }; + const placeholder_pattern_idx = try self.env.addPattern(placeholder_pattern, region); + + // Also compute type-qualified name (e.g., "List.is_empty") + // Re-fetch anno_text since insertQualifiedIdent may have reallocated the identifier table + const type_qualified_idx = try self.env.insertQualifiedIdent(self.env.getIdent(parent_type_name), self.env.getIdent(anno_ident)); + + // Track all three identifiers as placeholders + try self.placeholder_idents.put(self.env.gpa, qualified_idx, {}); + try self.placeholder_idents.put(self.env.gpa, anno_ident, {}); + try self.placeholder_idents.put(self.env.gpa, type_qualified_idx, {}); + + // 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]; + try current_scope.idents.put(self.env.gpa, qualified_idx, placeholder_pattern_idx); + try current_scope.idents.put(self.env.gpa, anno_ident, placeholder_pattern_idx); + try current_scope.idents.put(self.env.gpa, type_qualified_idx, placeholder_pattern_idx); + } }, + else => { + // Skip other statement types + }, + } + } + + // Phase 2b: Now process all deferred associated blocks + // All sibling types and declarations are now in scope + for (self.parse_ir.store.statementSlice(statements)) |stmt_idx| { + const stmt = self.parse_ir.store.getStatement(stmt_idx); + if (stmt == .type_decl) { + const type_decl = stmt.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; + const parent_text = self.env.getIdent(parent_name); + const type_text = self.env.getIdent(type_ident); + const qualified_idx = try self.env.insertQualifiedIdent(parent_text, type_text); + + try self.processAssociatedBlock(qualified_idx, type_ident, assoc); + } } } } @@ -904,12 +1264,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 + // 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); + if (type_decl.associated) |_| { + try self.processTypeDeclFirstPass(type_decl, null, true); // defer associated blocks + } }, else => { // Skip non-type-declaration statements in first pass @@ -917,26 +1280,122 @@ pub fn canonicalizeFile( } } - // For type modules, expose the main type and all associated items before the second pass - // This ensures unused variable checking in the third pass doesn't flag exposed items - 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| { - const stmt = self.parse_ir.store.getStatement(stmt_id); - if (stmt == .type_decl) { - const type_decl = stmt.type_decl; - const type_header = self.parse_ir.store.getTypeHeader(type_decl.header) catch continue; - const type_name_ident = self.parse_ir.tokens.resolveIdentifier(type_header.name) orelse continue; - const type_name_text = self.env.getIdent(type_name_ident); + // Phase 1.5.5: Process anno-only top-level type annotations EARLY + // For type-modules, anno-only top-level type annotations (like list_get_unsafe) need to be + // processed before associated blocks so they can be referenced inside those blocks + // IMPORTANT: Only process anno-only (no matching decl), and only for type-modules + switch (self.env.module_kind) { + .type_module => { + const top_level_stmts = self.parse_ir.store.statementSlice(file.statements); + var i: usize = 0; + while (i < top_level_stmts.len) : (i += 1) { + const stmt_id = top_level_stmts[i]; + const stmt = self.parse_ir.store.getStatement(stmt_id); + if (stmt == .type_anno) { + const ta = stmt.type_anno; + const name_ident = self.parse_ir.tokens.resolveIdentifier(ta.name) orelse continue; - if (std.mem.eql(u8, type_name_text, module_name_text)) { - // Expose the main type - try self.env.addExposedById(type_name_ident); - // Expose all associated items recursively - try self.exposeAssociatedItems(type_name_ident, type_decl); - break; + // Check if there's a matching decl (skipping malformed statements) + const has_matching_decl = blk: { + var next_i = i + 1; + while (next_i < top_level_stmts.len) : (next_i += 1) { + const next_stmt = self.parse_ir.store.getStatement(top_level_stmts[next_i]); + // Skip malformed statements + if (next_stmt == .malformed) continue; + // Check if this is a matching decl + if (next_stmt == .decl) { + const next_pattern = self.parse_ir.store.getPattern(next_stmt.decl.pattern); + if (next_pattern == .ident) { + if (self.parse_ir.tokens.resolveIdentifier(next_pattern.ident.ident_tok)) |decl_ident| { + break :blk name_ident.idx == decl_ident.idx; + } + } + } + // Found a non-malformed, non-matching statement + break :blk false; + } + // Reached end of statements + break :blk false; + }; + + // Skip if there's a matching decl - it will be processed normally + if (has_matching_decl) continue; + + const region = self.parse_ir.tokenizedRegionToRegion(ta.region); + + // Extract type variables and canonicalize the annotation + const type_vars_top: u32 = @intCast(self.scratch_idents.top()); + try self.extractTypeVarIdentsFromASTAnno(ta.anno, type_vars_top); + const type_var_scope = self.scopeEnterTypeVar(); + defer self.scopeExitTypeVar(type_var_scope); + const type_anno_idx = try self.canonicalizeTypeAnno(ta.anno, .inline_anno); + + // Canonicalize where clauses if present + const where_clauses = if (ta.where) |where_coll| blk: { + const where_slice = self.parse_ir.store.whereClauseSlice(.{ .span = self.parse_ir.store.getCollection(where_coll).span }); + const where_start = self.env.store.scratchWhereClauseTop(); + for (where_slice) |where_idx| { + const canonicalized_where = try self.canonicalizeWhereClause(where_idx, .inline_anno); + try self.env.store.addScratchWhereClause(canonicalized_where); + } + break :blk try self.env.store.whereClauseSpanFrom(where_start); + } else null; + + // Create the anno-only def immediately + const def_idx = try self.createAnnoOnlyDef(name_ident, type_anno_idx, where_clauses, region); + try self.env.store.addScratchDef(def_idx); + + // If exposed, register it + const ident_text = self.env.getIdent(name_ident); + if (self.exposed_ident_texts.contains(ident_text)) { + const def_idx_u16: u16 = @intCast(@intFromEnum(def_idx)); + try self.env.setExposedNodeIndexById(name_ident, def_idx_u16); + } } } + }, + else => {}, + } + + // Phase 1.6: Now process all deferred type declaration associated blocks + // 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) { + const type_decl = stmt.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; + + // Build fully qualified name (e.g., "Builtin.Str") + // For type-modules where the main type name equals the module name, + // use just the module name to avoid "Builtin.Builtin" + const module_name_text = self.env.module_name; + const type_name_text = self.env.getIdent(type_ident); + const qualified_type_ident = if (std.mem.eql(u8, module_name_text, type_name_text)) + type_ident // Type-module: use unqualified name + else + try self.env.insertQualifiedIdent(module_name_text, type_name_text); + + try self.processAssociatedBlock(qualified_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 + }, } } @@ -1052,6 +1511,41 @@ pub fn canonicalizeFile( continue; }; + // For type-modules, check if this is an anno-only annotation that was already processed in Phase 1.5.5 + // We need to check if there's a matching decl - if there isn't, this was processed early + switch (self.env.module_kind) { + .type_module => { + // Check if there's a matching decl (skipping malformed statements) + const has_matching_decl = blk: { + var check_i = i + 1; + while (check_i < ast_stmt_idxs.len) : (check_i += 1) { + const check_stmt = self.parse_ir.store.getStatement(ast_stmt_idxs[check_i]); + // Skip malformed statements + if (check_stmt == .malformed) continue; + // Check if this is a matching decl + if (check_stmt == .decl) { + const check_pattern = self.parse_ir.store.getPattern(check_stmt.decl.pattern); + if (check_pattern == .ident) { + if (self.parse_ir.tokens.resolveIdentifier(check_pattern.ident.ident_tok)) |decl_ident| { + break :blk name_ident.idx == decl_ident.idx; + } + } + } + // Found a non-malformed, non-matching statement + break :blk false; + } + // Reached end of statements + break :blk false; + }; + + // Skip if this is anno-only (no matching decl) - it was processed in Phase 1.5.5 + if (!has_matching_decl) { + continue; + } + }, + else => {}, + } + // First, make the top of our scratch list const type_vars_top: u32 = @intCast(self.scratch_idents.top()); @@ -1148,6 +1642,20 @@ pub fn canonicalizeFile( } break; } + + // If we didn't find any next statement, create an anno-only def + // (This handles the case where the type annotation is the last statement in the file) + if (next_i >= ast_stmt_idxs.len) { + const def_idx = try self.createAnnoOnlyDef(name_ident, type_anno_idx, where_clauses, region); + try self.env.store.addScratchDef(def_idx); + + // If this identifier should be exposed, register it + const ident_text = self.env.getIdent(name_ident); + if (self.exposed_ident_texts.contains(ident_text)) { + const def_idx_u16: u16 = @intCast(@intFromEnum(def_idx)); + try self.env.setExposedNodeIndexById(name_ident, def_idx_u16); + } + } }, .malformed => |malformed| { // We won't touch this since it's already a parse error. @@ -1156,111 +1664,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 => {}, - } - } - - try self.processAssociatedItemsSecondPass(type_ident, assoc.statements); - } - }, - else => { - // Skip non-type-declaration statements in third pass - }, - } - } - // Check for exposed but not implemented items try self.checkExposedButNotImplemented(); @@ -1345,27 +1748,54 @@ fn createAnnoOnlyDef( where_clauses: ?WhereClause.Span, region: Region, ) std.mem.Allocator.Error!CIR.Def.Idx { - // Create the pattern for this def - const pattern = Pattern{ - .assign = .{ - .ident = ident, - }, - }; - const pattern_idx = try self.env.addPattern(pattern, region); - - // Introduce the name to scope - switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, ident, pattern_idx, false, true)) { - .success => {}, - .shadowing_warning => |shadowed_pattern_idx| { - const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); - try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ + // Check if a placeholder exists for this identifier (from multi-phase canonicalization) + const pattern_idx = if (self.isPlaceholder(ident)) placeholder_check: { + // Use scopeLookup to search up the scope chain for the placeholder + switch (self.scopeLookup(.ident, ident)) { + .found => |existing_pattern| { + // Note: We don't remove from placeholder_idents here. The calling code + // (processAssociatedItemsSecondPass) will call updatePlaceholder to do that. + break :placeholder_check existing_pattern; + }, + .not_found => { + // Placeholder is tracked but not found in any scope - this shouldn't happen + // Create a new pattern as fallback + const pattern = Pattern{ + .assign = .{ + .ident = ident, + }, + }; + break :placeholder_check try self.env.addPattern(pattern, region); + }, + } + } else create_new: { + // No placeholder - create new pattern and introduce to scope + const pattern = Pattern{ + .assign = .{ .ident = ident, - .region = region, - .original_region = original_region, - } }); - }, - else => {}, - } + }, + }; + const new_pattern_idx = try self.env.addPattern(pattern, region); + + // Introduce the identifier to scope so it can be referenced + switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, ident, new_pattern_idx, false, true)) { + .success => {}, + .shadowing_warning => |shadowed_pattern_idx| { + const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); + try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ + .ident = ident, + .region = region, + .original_region = original_region, + } }); + }, + else => {}, + } + break :create_new new_pattern_idx; + }; + + // Note: We don't update placeholders here. For associated items, the calling code + // (processAssociatedItemsSecondPass) will update all three identifiers (qualified, + // type-qualified, unqualified). For top-level items, there are no placeholders to update. // Create the e_anno_only expression const anno_only_expr = try self.env.addExpr(Expr{ .e_anno_only = .{} }, region); @@ -1564,8 +1994,8 @@ fn createExposedScope( // Get the interned identifier if (self.parse_ir.tokens.resolveIdentifier(type_name.ident)) |ident_idx| { - // Add to exposed_items for permanent storage (unconditionally) - try self.env.addExposedById(ident_idx); + // Don't add types to exposed_items - types are not values + // Only add to type_bindings for type resolution // Use a dummy statement index - we just need to track that it's exposed const dummy_idx = @as(Statement.Idx, @enumFromInt(0)); @@ -1597,8 +2027,8 @@ fn createExposedScope( // Get the interned identifier if (self.parse_ir.tokens.resolveIdentifier(type_with_constructors.ident)) |ident_idx| { - // Add to exposed_items for permanent storage (unconditionally) - try self.env.addExposedById(ident_idx); + // Don't add types to exposed_items - types are not values + // Only add to type_bindings for type resolution // Use a dummy statement index - we just need to track that it's exposed const dummy_idx = @as(Statement.Idx, @enumFromInt(0)); @@ -2755,21 +3185,49 @@ pub fn canonicalizeExpr( break :blk null; } orelse { // Not a module alias and not an auto-imported module - // This is a qualified identifier with an invalid qualifier + // Check if the qualifier is a type - if so, try to lookup associated items + if (self.scopeLookupTypeBinding(module_alias)) |_| { + // This is a type with a potential associated item + // Build the fully qualified name and try to look it up + const type_text = self.env.getIdent(module_alias); + const field_text = self.env.getIdent(ident); + const type_qualified_idx = try self.env.insertQualifiedIdent(type_text, field_text); - // Check if the qualifier is in scope as a type/value - // If so, provide a more helpful error message - const diagnostic = if (self.scopeLookupTypeBinding(module_alias) != null) - Diagnostic{ .nested_value_not_found = .{ - .parent_name = module_alias, - .nested_name = ident, - .region = region, - } } - else - Diagnostic{ .qualified_ident_does_not_exist = .{ - .ident = qualified_ident, - .region = region, - } }; + // Try to look up the associated item in the current scope + switch (self.scopeLookup(.ident, type_qualified_idx)) { + .found => |found_pattern_idx| { + // Found the associated item! Mark it as used. + try self.used_patterns.put(self.env.gpa, found_pattern_idx, {}); + + // Return a local lookup expression + const expr_idx = try self.env.addExpr(CIR.Expr{ .e_lookup_local = .{ + .pattern_idx = found_pattern_idx, + } }, region); + + const free_vars_start = self.scratch_free_vars.top(); + try self.scratch_free_vars.append(found_pattern_idx); + return CanonicalizedExpr{ .idx = expr_idx, .free_vars = DataSpan.init(free_vars_start, 1) }; + }, + .not_found => { + // Associated item not found - generate error + const diagnostic = Diagnostic{ .nested_value_not_found = .{ + .parent_name = module_alias, + .nested_name = ident, + .region = region, + } }; + return CanonicalizedExpr{ + .idx = try self.env.pushMalformed(Expr.Idx, diagnostic), + .free_vars = null, + }; + }, + } + } + + // Not a type either - generate appropriate error + const diagnostic = Diagnostic{ .qualified_ident_does_not_exist = .{ + .ident = qualified_ident, + .region = region, + } }; return CanonicalizedExpr{ .idx = try self.env.pushMalformed(Expr.Idx, diagnostic), @@ -4417,31 +4875,41 @@ fn canonicalizePattern( .ident = ident_idx, } }, region); - // Introduce the identifier into scope mapping to this pattern node - switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, ident_idx, pattern_idx, false, true)) { - .success => {}, - .shadowing_warning => |shadowed_pattern_idx| { - const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); - try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ - .ident = ident_idx, - .region = region, - .original_region = original_region, - } }); - }, - .top_level_var_error => { - return try self.env.pushMalformed(Pattern.Idx, Diagnostic{ - .invalid_top_level_statement = .{ - .stmt = try self.env.insertString("var"), + // 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 + try self.updatePlaceholder(current_scope, ident_idx, pattern_idx); + } else { + // Introduce the identifier into scope mapping to this pattern node + switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, ident_idx, pattern_idx, false, true)) { + .success => {}, + .shadowing_warning => |shadowed_pattern_idx| { + const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); + try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ + .ident = ident_idx, .region = region, - }, - }); - }, - .var_across_function_boundary => { - return try self.env.pushMalformed(Pattern.Idx, Diagnostic{ .ident_already_in_scope = .{ - .ident = ident_idx, - .region = region, - } }); - }, + .original_region = original_region, + } }); + }, + .top_level_var_error => { + return try self.env.pushMalformed(Pattern.Idx, Diagnostic{ + .invalid_top_level_statement = .{ + .stmt = try self.env.insertString("var"), + .region = region, + }, + }); + }, + .var_across_function_boundary => { + return try self.env.pushMalformed(Pattern.Idx, Diagnostic{ .ident_already_in_scope = .{ + .ident = ident_idx, + .region = region, + } }); + }, + } } return pattern_idx; @@ -6950,39 +7418,139 @@ pub fn canonicalizeBlockStatement(self: *Self, ast_stmt: AST.Statement, ast_stmt switch (next_stmt) { .decl => |decl| { - // Immediately process the next decl, with the annotation - mb_canonicailzed_stmt = try self.canonicalizeBlockDecl(decl, TypeAnnoIdent{ - .name = name_ident, - .anno_idx = type_anno_idx, - .where = where_clauses, - }); - stmts_processed = .two; + // Check if the decl name matches the anno name + const decl_pattern = self.parse_ir.store.getPattern(decl.pattern); + const names_match = name_check: { + if (decl_pattern == .ident) { + if (self.parse_ir.tokens.resolveIdentifier(decl_pattern.ident.ident_tok)) |decl_ident| { + break :name_check name_ident.idx == decl_ident.idx; + } + } + break :name_check false; + }; + + if (names_match) { + // Names match - immediately process the next decl with the annotation + mb_canonicailzed_stmt = try self.canonicalizeBlockDecl(decl, TypeAnnoIdent{ + .name = name_ident, + .anno_idx = type_anno_idx, + .where = where_clauses, + }); + stmts_processed = .two; + } else { + // Names don't match - create anno-only def for this anno + // and let the decl be processed separately in the next iteration + + // Check if a placeholder already exists (from Phase 1.5.5) + const pattern_idx = if (self.isPlaceholder(name_ident)) placeholder_check: { + // Reuse the existing placeholder pattern + const current_scope = &self.scopes.items[self.scopes.items.len - 1]; + const existing_pattern = current_scope.idents.get(name_ident) orelse { + // This shouldn't happen, but handle it gracefully + const pattern = Pattern{ + .assign = .{ + .ident = name_ident, + }, + }; + break :placeholder_check try self.env.addPattern(pattern, region); + }; + // Remove from placeholder tracking since we're making it real + _ = self.placeholder_idents.remove(name_ident); + break :placeholder_check existing_pattern; + } else create_new: { + // No placeholder - create new pattern and introduce to scope + const pattern = Pattern{ + .assign = .{ + .ident = name_ident, + }, + }; + const new_pattern_idx = try self.env.addPattern(pattern, region); + + // Introduce the name to scope + switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, name_ident, new_pattern_idx, false, true)) { + .success => {}, + .shadowing_warning => |shadowed_pattern_idx| { + const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); + try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ + .ident = name_ident, + .region = region, + .original_region = original_region, + } }); + }, + else => {}, + } + break :create_new new_pattern_idx; + }; + + // Create the e_anno_only expression + const anno_only_expr = try self.env.addExpr(Expr{ .e_anno_only = .{} }, region); + + // Create the annotation structure + const annotation = CIR.Annotation{ + .anno = type_anno_idx, + .where = where_clauses, + }; + const annotation_idx = try self.env.addAnnotation(annotation, region); + + // Add the decl as a def so it gets included in all_defs + const def_idx = try self.env.addDef(.{ + .pattern = pattern_idx, + .expr = anno_only_expr, + .annotation = annotation_idx, + .kind = .let, + }, region); + try self.env.store.addScratchDef(def_idx); + + // Create the statement + const stmt_idx = try self.env.addStatement(Statement{ .s_decl = .{ + .pattern = pattern_idx, + .expr = anno_only_expr, + .anno = annotation_idx, + } }, region); + mb_canonicailzed_stmt = CanonicalizedStatement{ .idx = stmt_idx, .free_vars = null }; + stmts_processed = .one; + } }, else => { // If the next stmt does not match this annotation, // create a Def with an e_anno_only body - // Create the pattern for this def - const pattern = Pattern{ - .assign = .{ - .ident = name_ident, - }, - }; - const pattern_idx = try self.env.addPattern(pattern, region); - - // Introduce the name to scope - switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, name_ident, pattern_idx, false, true)) { - .success => {}, - .shadowing_warning => |shadowed_pattern_idx| { - const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); - try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ + // Check if a placeholder already exists (from Phase 1.5.5) + const pattern_idx = if (self.isPlaceholder(name_ident)) placeholder_check2: { + // Reuse the existing placeholder pattern + const current_scope = &self.scopes.items[self.scopes.items.len - 1]; + const existing_pattern = current_scope.idents.get(name_ident) orelse { + const pattern = Pattern{ + .assign = .{ + .ident = name_ident, + }, + }; + break :placeholder_check2 try self.env.addPattern(pattern, region); + }; + _ = self.placeholder_idents.remove(name_ident); + break :placeholder_check2 existing_pattern; + } else create_new2: { + const pattern = Pattern{ + .assign = .{ .ident = name_ident, - .region = region, - .original_region = original_region, - } }); - }, - else => {}, - } + }, + }; + const new_pattern_idx = try self.env.addPattern(pattern, region); + + switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, name_ident, new_pattern_idx, false, true)) { + .success => {}, + .shadowing_warning => |shadowed_pattern_idx| { + const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); + try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ + .ident = name_ident, + .region = region, + .original_region = original_region, + } }); + }, + else => {}, + } + break :create_new2 new_pattern_idx; + }; // Create the e_anno_only expression const anno_only_expr = try self.env.addExpr(Expr{ .e_anno_only = .{} }, region); @@ -7017,27 +7585,42 @@ pub fn canonicalizeBlockStatement(self: *Self, ast_stmt: AST.Statement, ast_stmt // If the next stmt does not match this annotation, // create a Def with an e_anno_only body - // Create the pattern for this def - const pattern = Pattern{ - .assign = .{ - .ident = name_ident, - }, - }; - const pattern_idx = try self.env.addPattern(pattern, region); - - // Introduce the name to scope - switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, name_ident, pattern_idx, false, true)) { - .success => {}, - .shadowing_warning => |shadowed_pattern_idx| { - const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); - try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ + // Check if a placeholder already exists (from Phase 1.5.5) + const pattern_idx = if (self.isPlaceholder(name_ident)) placeholder_check3: { + // Reuse the existing placeholder pattern + const current_scope = &self.scopes.items[self.scopes.items.len - 1]; + const existing_pattern = current_scope.idents.get(name_ident) orelse { + const pattern = Pattern{ + .assign = .{ + .ident = name_ident, + }, + }; + break :placeholder_check3 try self.env.addPattern(pattern, region); + }; + _ = self.placeholder_idents.remove(name_ident); + break :placeholder_check3 existing_pattern; + } else create_new3: { + const pattern = Pattern{ + .assign = .{ .ident = name_ident, - .region = region, - .original_region = original_region, - } }); - }, - else => {}, - } + }, + }; + const new_pattern_idx = try self.env.addPattern(pattern, region); + + switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, name_ident, new_pattern_idx, false, true)) { + .success => {}, + .shadowing_warning => |shadowed_pattern_idx| { + const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); + try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ + .ident = name_ident, + .region = region, + .original_region = original_region, + } }); + }, + else => {}, + } + break :create_new3 new_pattern_idx; + }; // Create the e_anno_only expression const anno_only_expr = try self.env.addExpr(Expr{ .e_anno_only = .{} }, region); @@ -7653,6 +8236,27 @@ pub fn scopeIntroduceInternal( return Scope.IntroduceResult{ .success = {} }; } +/// Introduce a value identifier to scope and report shadowing diagnostics if needed +fn introduceValue( + self: *Self, + ident_idx: base.Ident.Idx, + pattern_idx: Pattern.Idx, + region: Region, +) std.mem.Allocator.Error!void { + switch (try self.scopeIntroduceInternal(self.env.gpa, .ident, ident_idx, pattern_idx, false, true)) { + .success => {}, + .shadowing_warning => |shadowed_pattern_idx| { + const original_region = self.env.store.getPatternRegion(shadowed_pattern_idx); + try self.env.pushDiagnostic(Diagnostic{ .shadowing_warning = .{ + .ident = ident_idx, + .region = region, + .original_region = original_region, + } }); + }, + .top_level_var_error, .var_across_function_boundary => {}, + } +} + /// Check if an identifier is marked as ignored (underscore prefix) fn identIsIgnored(ident_idx: base.Ident.Idx) bool { return ident_idx.attributes.ignored; @@ -7750,7 +8354,7 @@ fn checkScopeForUnusedVariables(self: *Self, scope: *const Scope) std.mem.Alloca } /// Introduce a type declaration into the current scope -fn scopeIntroduceTypeDecl( +fn introduceType( self: *Self, name_ident: Ident.Idx, type_decl_stmt: Statement.Idx, @@ -7882,6 +8486,32 @@ fn scopeIntroduceTypeDecl( } } +/// 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 identifier was tracked as a placeholder. +fn updatePlaceholder( + self: *Self, + scope: *Scope, + ident_idx: Ident.Idx, + pattern_idx: Pattern.Idx, +) std.mem.Allocator.Error!void { + if (builtin.mode == .Debug) { + 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); +} + fn scopeUpdateTypeDecl( self: *Self, name_ident: Ident.Idx, @@ -8757,10 +9387,8 @@ fn exposeAssociatedItems(self: *Self, parent_name: Ident.Idx, type_decl: anytype const nested_text = self.env.getIdent(nested_ident); const qualified_idx = try self.env.insertQualifiedIdent(parent_text, nested_text); - // Expose the nested type - try self.env.addExposedById(qualified_idx); - - // Recursively expose its associated items + // Don't expose the nested type itself - types are not values + // Only recursively expose its associated items (defs, not types) try self.exposeAssociatedItems(qualified_idx, nested_type_decl); }, .decl => |decl| { @@ -8779,6 +9407,20 @@ fn exposeAssociatedItems(self: *Self, parent_name: Ident.Idx, type_decl: anytype } } }, + .type_anno => |type_anno| { + // Get the annotation name (for annotation-only definitions like compiler intrinsics) + if (self.parse_ir.tokens.resolveIdentifier(type_anno.name)) |anno_ident| { + // Build qualified name (e.g., "Bool.is_ne") + const parent_text = self.env.getIdent(parent_name); + const anno_text = self.env.getIdent(anno_ident); + const qualified_idx = try self.env.insertQualifiedIdent(parent_text, anno_text); + + // Expose the qualified name + // The unqualified name is added to scope but doesn't need to be in exposed_items + // because lookups use the qualified name + try self.env.addExposedById(qualified_idx); + } + }, else => {}, } } diff --git a/src/canonicalize/Expression.zig b/src/canonicalize/Expression.zig index 3229d1ad7f..36d15ef5aa 100644 --- a/src/canonicalize/Expression.zig +++ b/src/canonicalize/Expression.zig @@ -387,6 +387,11 @@ pub const Expr = union(enum) { // String operations str_is_empty, + // List operations + list_len, + list_is_empty, + list_get_unsafe, + // Set operations set_is_empty, diff --git a/src/canonicalize/ModuleEnv.zig b/src/canonicalize/ModuleEnv.zig index 067aea17d7..56c3de04cd 100644 --- a/src/canonicalize/ModuleEnv.zig +++ b/src/canonicalize/ModuleEnv.zig @@ -1587,9 +1587,12 @@ pub const Serialized = struct { // Overwrite ourself with the deserialized version, and return our pointer after casting it to Self. const env = @as(*Self, @ptrFromInt(@intFromPtr(self))); + // Deserialize common env first so we can look up identifiers + const common = self.common.deserialize(offset, source).*; + env.* = Self{ .gpa = gpa, - .common = self.common.deserialize(offset, source).*, + .common = common, .types = self.types.deserialize(offset, gpa).*, .module_kind = self.module_kind, .all_defs = self.all_defs, @@ -1604,11 +1607,11 @@ pub const Serialized = struct { .store = self.store.deserialize(offset, gpa).*, .evaluation_order = null, // Not serialized, will be recomputed if needed // Well-known identifiers for type checking - look them up in the deserialized common env - .from_int_digits_ident = env.common.findIdent(Ident.FROM_INT_DIGITS_METHOD_NAME) orelse unreachable, - .from_dec_digits_ident = env.common.findIdent(Ident.FROM_DEC_DIGITS_METHOD_NAME) orelse unreachable, - .try_ident = env.common.findIdent("Try") orelse unreachable, - .out_of_range_ident = env.common.findIdent("OutOfRange") orelse unreachable, - .builtin_module_ident = env.common.findIdent("Builtin") orelse unreachable, + .from_int_digits_ident = common.findIdent(Ident.FROM_INT_DIGITS_METHOD_NAME) orelse unreachable, + .from_dec_digits_ident = common.findIdent(Ident.FROM_DEC_DIGITS_METHOD_NAME) orelse unreachable, + .try_ident = common.findIdent("Try") orelse unreachable, + .out_of_range_ident = common.findIdent("OutOfRange") orelse unreachable, + .builtin_module_ident = common.findIdent("Builtin") orelse unreachable, }; return env; diff --git a/src/canonicalize/test/exposed_shadowing_test.zig b/src/canonicalize/test/exposed_shadowing_test.zig index 30fa77c0f3..b9dabcffc2 100644 --- a/src/canonicalize/test/exposed_shadowing_test.zig +++ b/src/canonicalize/test/exposed_shadowing_test.zig @@ -339,15 +339,15 @@ test "exposed_items is populated correctly" { .canonicalizeFile(); // Check that exposed_items contains the correct number of items // The exposed items were added during canonicalization - // Should have exactly 3 entries (duplicates not stored) - try testing.expectEqual(@as(usize, 3), env.common.exposed_items.count()); - // Check that exposed_items contains all exposed items + // Should have exactly 2 value entries (duplicates not stored, types not included) + // Types are not stored in exposed_items - they are handled by the type system + try testing.expectEqual(@as(usize, 2), env.common.exposed_items.count()); + // Check that exposed_items contains all exposed values (not types) const foo_idx = env.common.idents.findByString("foo").?; const bar_idx = env.common.idents.findByString("bar").?; - const mytype_idx = env.common.idents.findByString("MyType").?; try testing.expect(env.common.exposed_items.containsById(env.gpa, @bitCast(foo_idx))); try testing.expect(env.common.exposed_items.containsById(env.gpa, @bitCast(bar_idx))); - try testing.expect(env.common.exposed_items.containsById(env.gpa, @bitCast(mytype_idx))); + // MyType is not in exposed_items because it's a type, not a value } test "exposed_items persists after canonicalization" { diff --git a/src/check/Check.zig b/src/check/Check.zig index 2740baf35e..2f6824ab9c 100644 --- a/src/check/Check.zig +++ b/src/check/Check.zig @@ -591,7 +591,7 @@ fn updateVar(self: *Self, target_var: Var, content: types_mod.Content, rank: typ // file // /// Check the types for all defs -/// Copy builtin types (Bool, Result) from their modules into the current module's type store +/// Copy builtin types from their modules into the current module's type store /// This is necessary because type variables are module-specific - we can't use Vars from /// other modules directly. The Bool and Result types are used in language constructs like /// `if` conditions and need to be available in every module's type store. @@ -618,9 +618,6 @@ pub fn checkFile(self: *Self) std.mem.Allocator.Error!void { try ensureTypeStoreIsFilled(self); - // Copy builtin types (Bool, Result) into this module's type store - try self.copyBuiltinTypes(); - // First, iterate over the builtin statements, generating types for each type declaration const builtin_stmts_slice = self.cir.store.sliceStatements(self.cir.builtin_statements); for (builtin_stmts_slice) |builtin_stmt_idx| { @@ -638,6 +635,11 @@ pub fn checkFile(self: *Self) std.mem.Allocator.Error!void { try self.generateStmtTypeDeclType(stmt_idx); } + // Copy builtin types into this module's type store + // This must happen AFTER type declarations are generated so that when compiling + // Builtin itself, the Bool and Try types have already been created + try self.copyBuiltinTypes(); + // First pass: assign placeholder type vars const defs_slice = self.cir.store.sliceDefs(self.cir.all_defs); for (defs_slice) |def_idx| { @@ -689,7 +691,7 @@ pub fn checkFile(self: *Self) std.mem.Allocator.Error!void { pub fn checkExprRepl(self: *Self, expr_idx: CIR.Expr.Idx) std.mem.Allocator.Error!void { try ensureTypeStoreIsFilled(self); - // Copy builtin types (Bool, Result) into this module's type store + // Copy builtin types into this module's type store try self.copyBuiltinTypes(); // First, iterate over the statements, generating types for each type declaration @@ -948,7 +950,7 @@ fn generateAnnotationType(self: *Self, annotation_idx: CIR.Annotation.Idx) std.m try self.generateAnnoTypeInPlace(annotation.anno, .annotation); // Redirect the root annotation to inner annotation - _ = try self.types.setVarRedirect(ModuleEnv.varFrom(annotation_idx), ModuleEnv.varFrom(annotation.anno)); + try self.types.setVarRedirect(ModuleEnv.varFrom(annotation_idx), ModuleEnv.varFrom(annotation.anno)); } /// Given a where clause, generate static dispatch constraints and add to scratch_static_dispatch_constraints @@ -2453,9 +2455,9 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, rank: types_mod.Rank, expected // We never instantiate rigid variables if (resolved_pat.rank == Rank.generalized and resolved_pat.content != .rigid) { const instantiated = try self.instantiateVar(pat_var, rank, .use_last_var); - _ = try self.types.setVarRedirect(expr_var, instantiated); + try self.types.setVarRedirect(expr_var, instantiated); } else { - _ = try self.types.setVarRedirect(expr_var, pat_var); + try self.types.setVarRedirect(expr_var, pat_var); } // Unify this expression with the referenced pattern @@ -2647,7 +2649,7 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, rank: types_mod.Rank, expected }, .e_closure => |closure| { does_fx = try self.checkExpr(closure.lambda_idx, rank, expected) or does_fx; - _ = try self.types.setVarRedirect(expr_var, ModuleEnv.varFrom(closure.lambda_idx)); + try self.types.setVarRedirect(expr_var, ModuleEnv.varFrom(closure.lambda_idx)); }, // function calling // .e_call => |call| { @@ -2796,7 +2798,7 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, rank: types_mod.Rank, expected } // Redirect the expr to the function's return type - _ = try self.types.setVarRedirect(expr_var, func.ret); + try self.types.setVarRedirect(expr_var, func.ret); } else { // TODO(jared): Better arity difference error message @@ -2813,7 +2815,7 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, rank: types_mod.Rank, expected try self.var_pool.addVarToRank(call_func_var, rank); _ = try self.unify(func_var, call_func_var, rank); - _ = try self.types.setVarRedirect(expr_var, call_func_ret); + try self.types.setVarRedirect(expr_var, call_func_ret); } } else { // We get here if the type of expr being called @@ -2841,7 +2843,7 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, rank: types_mod.Rank, expected // Then, we set the root expr to redirect to the return // type of that function, since a call expr ultimate // resolve to the returned type - _ = try self.types.setVarRedirect(expr_var, call_func_ret); + try self.types.setVarRedirect(expr_var, call_func_ret); } } }, @@ -2966,7 +2968,7 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, rank: types_mod.Rank, expected }, .e_dbg => |dbg| { does_fx = try self.checkExpr(dbg.expr, rank, expected) or does_fx; - _ = try self.types.setVarRedirect(expr_var, ModuleEnv.varFrom(dbg.expr)); + try self.types.setVarRedirect(expr_var, ModuleEnv.varFrom(dbg.expr)); }, .e_expect => |expect| { does_fx = try self.checkExpr(expect.body, rank, expected) or does_fx; @@ -2979,14 +2981,14 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, rank: types_mod.Rank, expected // For annotation-only expressions, the type comes from the annotation. // This case should only occur when the expression has an annotation (which is // enforced during canonicalization), so the expected type should be set. - // The type will be unified with the expected type in the code below. switch (expected) { .no_expectation => { // This shouldn't happen since we always create e_anno_only with an annotation try self.updateVar(expr_var, .err, rank); }, - .expected => { - // The expr_var will be unified with the annotation var below + .expected => |expected_type| { + // Redirect expr_var to the annotation var so that lookups get the correct type + try self.types.setVarRedirect(expr_var, expected_type.var_); }, } }, diff --git a/src/check/mod.zig b/src/check/mod.zig index b31fd3f9b9..6f62c93860 100644 --- a/src/check/mod.zig +++ b/src/check/mod.zig @@ -40,4 +40,5 @@ test "check tests" { std.testing.refAllDecls(@import("test/let_polymorphism_integration_test.zig")); std.testing.refAllDecls(@import("test/num_type_inference_test.zig")); std.testing.refAllDecls(@import("test/num_type_requirements_test.zig")); + std.testing.refAllDecls(@import("test/builtin_scope_test.zig")); } diff --git a/src/check/test/TestEnv.zig b/src/check/test/TestEnv.zig index ab3f271d1e..318e4b1006 100644 --- a/src/check/test/TestEnv.zig +++ b/src/check/test/TestEnv.zig @@ -215,21 +215,21 @@ pub fn initWithImport(module_name: []const u8, source: []const u8, other_module_ .builtin_module = other_test_env.builtin_module.env, }; - // Build imported_envs array dynamically based on module_env.imports order - // This matches the production approach in compile_package.zig - const import_count = module_env.imports.imports.items.items.len; - var imported_envs = try std.ArrayList(*const ModuleEnv).initCapacity(gpa, import_count); + // Build imported_envs array + // Always include the builtin module for auto-imported types (Bool, Str, etc.) + var imported_envs = try std.ArrayList(*const ModuleEnv).initCapacity(gpa, 2); defer imported_envs.deinit(gpa); + // Add builtin module unconditionally (needed for auto-imported types) + try imported_envs.append(gpa, other_test_env.builtin_module.env); + + // Process explicit imports + const import_count = module_env.imports.imports.items.items.len; for (module_env.imports.imports.items.items[0..import_count]) |str_idx| { const import_name = module_env.getString(str_idx); - if (std.mem.eql(u8, import_name, "Builtin")) { - try imported_envs.append(gpa, other_test_env.builtin_module.env); - } else if (std.mem.eql(u8, import_name, other_module_name)) { + if (std.mem.eql(u8, import_name, other_module_name)) { // Cross-module import - append the other test module's env try imported_envs.append(gpa, other_test_env.module_env); - } else { - std.debug.print("WARNING: Unknown import in test: {s}\n", .{import_name}); } } @@ -331,22 +331,13 @@ pub fn init(module_name: []const u8, source: []const u8) !TestEnv { .builtin_module = builtin_module.env, }; - // Build imported_envs array dynamically based on module_env.imports order - // This matches the production approach in compile_package.zig - const import_count = module_env.imports.imports.items.items.len; - var imported_envs = try std.ArrayList(*const ModuleEnv).initCapacity(gpa, import_count); + // Build imported_envs array + // Always include the builtin module for auto-imported types (Bool, Str, etc.) + var imported_envs = try std.ArrayList(*const ModuleEnv).initCapacity(gpa, 2); defer imported_envs.deinit(gpa); - for (module_env.imports.imports.items.items[0..import_count]) |str_idx| { - const import_name = module_env.getString(str_idx); - // For tests, all imports are to the Builtin module - if (std.mem.eql(u8, import_name, "Builtin")) { - try imported_envs.append(gpa, builtin_module.env); - } else { - // If there are other imports in the future, handle them here - std.debug.print("WARNING: Unknown import in test: {s}\n", .{import_name}); - } - } + // Add builtin module unconditionally (needed for auto-imported types) + try imported_envs.append(gpa, builtin_module.env); // Type Check - Pass the imported modules in other_modules parameter var checker = try Check.init( diff --git a/src/check/test/builtin_scope_test.zig b/src/check/test/builtin_scope_test.zig new file mode 100644 index 0000000000..5935ae587b --- /dev/null +++ b/src/check/test/builtin_scope_test.zig @@ -0,0 +1,98 @@ +//! Tests verifying that "Builtin" is not in scope and cannot be imported, +//! but that nested types like Str, List, etc. are available. + +const TestEnv = @import("./TestEnv.zig"); +const testing = @import("std").testing; +const std = @import("std"); + +test "cannot import Builtin module" { + const src = + \\import Builtin + \\ + \\x = 5 + ; + + var test_env = try TestEnv.init("Test", src); + defer test_env.deinit(); + + // Should have a canonicalization problem because Builtin is not a module that can be imported + const diagnostics = try test_env.module_env.getDiagnostics(); + defer test_env.module_env.gpa.free(diagnostics); + + // Expect at least one diagnostic (module not found error) + try testing.expect(diagnostics.len > 0); +} + +test "can define userspace type named Builtin" { + const src = + \\Test := [A, B, C] + \\ + \\Builtin := [D, E, F] + \\ + \\x : Builtin + \\x = D + ; + + var test_env = try TestEnv.init("Test", src); + defer test_env.deinit(); + + // Should have no problems - Builtin is a valid userspace name + try test_env.assertDefType("x", "Builtin"); +} + +test "builtin types are still available without import" { + const src = + \\Test := [Whatever] + \\ + \\x : Str + \\x = "hello" + \\ + \\y : List(U64) + \\y = [1, 2, 3] + ; + + var test_env = try TestEnv.init("Test", src); + defer test_env.deinit(); + + // Builtin types like Str and List should work without importing Builtin + try test_env.assertDefType("x", "Str"); + try test_env.assertDefType("y", "List(Num(Int(Unsigned64)))"); +} + +test "can import userspace Builtin module" { + const builtin_module_src = + \\Builtin := [D, E, F] + \\ + \\value : Builtin + \\value = D + ; + + var builtin_module = try TestEnv.init("Builtin", builtin_module_src); + defer builtin_module.deinit(); + + const main_src = + \\Main := [Whatever] + \\ + \\import Builtin + \\ + \\x : Builtin + \\x = Builtin.value + ; + + var main_module = try TestEnv.initWithImport("Main", main_src, "Builtin", &builtin_module); + defer main_module.deinit(); + + // Should successfully import the userspace Builtin module without "module not found" error + const diagnostics = try main_module.module_env.getDiagnostics(); + defer main_module.module_env.gpa.free(diagnostics); + + // Check that there's no "module not found" error for "Builtin" + for (diagnostics) |diag| { + if (diag == .module_not_found) { + const module_name = main_module.module_env.getIdent(diag.module_not_found.module_name); + if (std.mem.eql(u8, module_name, "Builtin")) { + try testing.expect(false); // Should not have module_not_found for Builtin + } + } + } +} diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index 56fa5a9d9b..834613333d 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -2109,16 +2109,97 @@ pub const Interpreter = struct { switch (op) { .str_is_empty => { // Str.is_empty : Str -> Bool - if (args.len != 1) return error.TypeMismatch; + std.debug.assert(args.len == 1); // low-level .str_is_empty expects 1 argument const str_arg = args[0]; - if (str_arg.ptr == null) return error.TypeMismatch; + std.debug.assert(str_arg.ptr != null); // low-level .str_is_empty expects non-null string pointer const roc_str: *const RocStr = @ptrCast(@alignCast(str_arg.ptr.?)); const result = builtins.str.isEmpty(roc_str.*); return try self.makeSimpleBoolValue(result); }, + .list_len => { + // List.len : List(a) -> U64 + // Note: listLen returns usize, but List.len always returns U64. + // We need to cast usize -> u64 for 32-bit targets (e.g. wasm32). + std.debug.assert(args.len == 1); // low-level .list_len expects 1 argument + + const list_arg = args[0]; + std.debug.assert(list_arg.ptr != null); // low-level .list_len expects non-null list pointer + + const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(list_arg.ptr.?)); + const len_usize = builtins.list.listLen(roc_list.*); + const len_u64: u64 = @intCast(len_usize); + + const result_layout = layout.Layout.int(.u64); + var out = try self.pushRaw(result_layout, 0); + out.is_initialized = false; + out.setInt(@intCast(len_u64)); + out.is_initialized = true; + return out; + }, + .list_is_empty => { + // List.is_empty : List(a) -> Bool + std.debug.assert(args.len == 1); // low-level .list_is_empty expects 1 argument + + const list_arg = args[0]; + std.debug.assert(list_arg.ptr != null); // low-level .list_is_empty expects non-null list pointer + + const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(list_arg.ptr.?)); + const result = builtins.list.listIsEmpty(roc_list.*); + + return try self.makeSimpleBoolValue(result); + }, + .list_get_unsafe => { + // Internal operation: Get element at index without bounds checking + // Args: List(a), U64 (index) + // Returns: a (the element) + std.debug.assert(args.len == 2); // low-level .list_get_unsafe expects 2 arguments + + const list_arg = args[0]; + const index_arg = args[1]; + + std.debug.assert(list_arg.ptr != null); // low-level .list_get_unsafe expects non-null list pointer + + // Extract element layout from List(a) + std.debug.assert(list_arg.layout.tag == .list or list_arg.layout.tag == .list_of_zst); // low-level .list_get_unsafe expects list layout + + const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(list_arg.ptr.?)); + const index = index_arg.asI128(); // U64 stored as i128 + + // Get element layout + const elem_layout_idx = list_arg.layout.data.list; + const elem_layout = self.runtime_layout_store.getLayout(elem_layout_idx); + const elem_size = self.runtime_layout_store.layoutSize(elem_layout); + + if (elem_size == 0) { + // ZST element - return zero-sized value + return StackValue{ + .layout = elem_layout, + .ptr = null, + .is_initialized = true, + }; + } + + // Get pointer to element (no bounds checking!) + const elem_ptr = builtins.list.listGetUnsafe(roc_list.*, @intCast(index), elem_size); + + if (elem_ptr == null) { + self.triggerCrash("list_get_unsafe: null pointer returned", false, roc_ops); + return error.Crash; + } + + // Create StackValue pointing to the element + const elem_value = StackValue{ + .layout = elem_layout, + .ptr = @ptrCast(elem_ptr.?), + .is_initialized = true, + }; + + // Copy to new location and increment refcount + return try self.pushCopy(elem_value, roc_ops); + }, .set_is_empty => { // TODO: implement Set.is_empty self.triggerCrash("Set.is_empty not yet implemented", false, roc_ops); @@ -2128,7 +2209,7 @@ pub const Interpreter = struct { // Bool operations .bool_is_eq => { // Bool.is_eq : Bool, Bool -> Bool - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .bool_is_eq expects 2 arguments const lhs = args[0].asBool(); const rhs = args[1].asBool(); const result = lhs == rhs; @@ -2136,7 +2217,7 @@ pub const Interpreter = struct { }, .bool_is_ne => { // Bool.is_ne : Bool, Bool -> Bool - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .bool_is_ne expects 2 arguments const lhs = args[0].asBool(); const rhs = args[1].asBool(); const result = lhs != rhs; @@ -2146,7 +2227,7 @@ pub const Interpreter = struct { // Numeric type checking operations .num_is_zero => { // num.is_zero : num -> Bool - if (args.len != 1) return error.TypeMismatch; + std.debug.assert(args.len == 1); // low-level .num_is_zero expects 1 argument const num_val = try self.extractNumericValue(args[0]); const result = switch (num_val) { .int => |i| i == 0, @@ -2158,7 +2239,7 @@ pub const Interpreter = struct { }, .num_is_negative => { // num.is_negative : num -> Bool (signed types only) - if (args.len != 1) return error.TypeMismatch; + std.debug.assert(args.len == 1); // low-level .num_is_negative expects 1 argument const num_val = try self.extractNumericValue(args[0]); const result = switch (num_val) { .int => |i| i < 0, @@ -2170,7 +2251,7 @@ pub const Interpreter = struct { }, .num_is_positive => { // num.is_positive : num -> Bool (signed types only) - if (args.len != 1) return error.TypeMismatch; + std.debug.assert(args.len == 1); // low-level .num_is_positive expects 1 argument const num_val = try self.extractNumericValue(args[0]); const result = switch (num_val) { .int => |i| i > 0, @@ -2184,7 +2265,7 @@ pub const Interpreter = struct { // Numeric comparison operations .num_is_eq => { // num.is_eq : num, num -> Bool (all integer types + Dec, NOT F32/F64) - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_is_eq expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result = switch (lhs) { @@ -2199,7 +2280,7 @@ pub const Interpreter = struct { }, .num_is_ne => { // num.is_ne : num, num -> Bool (Dec only) - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_is_ne expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result = switch (lhs) { @@ -2213,7 +2294,7 @@ pub const Interpreter = struct { }, .num_is_gt => { // num.is_gt : num, num -> Bool - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_is_gt expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result = switch (lhs) { @@ -2226,7 +2307,7 @@ pub const Interpreter = struct { }, .num_is_gte => { // num.is_gte : num, num -> Bool - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_is_gte expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result = switch (lhs) { @@ -2239,7 +2320,7 @@ pub const Interpreter = struct { }, .num_is_lt => { // num.is_lt : num, num -> Bool - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_is_lt expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result = switch (lhs) { @@ -2252,7 +2333,7 @@ pub const Interpreter = struct { }, .num_is_lte => { // num.is_lte : num, num -> Bool - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_is_lte expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result = switch (lhs) { @@ -2267,7 +2348,7 @@ pub const Interpreter = struct { // Numeric arithmetic operations .num_negate => { // num.negate : num -> num (signed types only) - if (args.len != 1) return error.TypeMismatch; + std.debug.assert(args.len == 1); // low-level .num_negate expects 1 argument const num_val = try self.extractNumericValue(args[0]); const result_layout = args[0].layout; @@ -2284,7 +2365,7 @@ pub const Interpreter = struct { return out; }, .num_plus => { - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_plus expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result_layout = args[0].layout; @@ -2302,7 +2383,7 @@ pub const Interpreter = struct { return out; }, .num_minus => { - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_minus expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result_layout = args[0].layout; @@ -2320,7 +2401,7 @@ pub const Interpreter = struct { return out; }, .num_times => { - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_times expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result_layout = args[0].layout; @@ -2338,7 +2419,7 @@ pub const Interpreter = struct { return out; }, .num_div_by => { - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_div_by expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result_layout = args[0].layout; @@ -2369,7 +2450,7 @@ pub const Interpreter = struct { return out; }, .num_rem_by => { - if (args.len != 2) return error.TypeMismatch; + std.debug.assert(args.len == 2); // low-level .num_rem_by expects 2 arguments const lhs = try self.extractNumericValue(args[0]); const rhs = try self.extractNumericValue(args[1]); const result_layout = args[0].layout; diff --git a/src/eval/test/anno_only_interp_test.zig b/src/eval/test/anno_only_interp_test.zig index c7936312ed..167901aba0 100644 --- a/src/eval/test/anno_only_interp_test.zig +++ b/src/eval/test/anno_only_interp_test.zig @@ -210,3 +210,110 @@ test "e_anno_only - value only crashes when accessed (False branch)" { try testing.expectEqual(@as(u32, 2), summary.evaluated); try testing.expectEqual(@as(u32, 0), summary.crashed); } + +test "List.first on nonempty list" { + const src = + \\result = List.first([1, 2, 3]) + ; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + // Should evaluate 1 declaration with 0 crashes (List.first should succeed) + try testing.expectEqual(@as(u32, 1), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); +} + +test "List.get with valid index returns Ok" { + const src = + \\result = List.get([1, 2, 3], 1) + ; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + // Should evaluate 1 declaration with 0 crashes (List.get should succeed) + try testing.expectEqual(@as(u32, 1), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); +} + +test "List.get with invalid index returns Err" { + const src = + \\result = List.get([1, 2, 3], 10) + ; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + // Should evaluate 1 declaration with 0 crashes (List.get should return Err but not crash) + try testing.expectEqual(@as(u32, 1), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); +} + +test "List.get on empty list returns Err" { + const src = + \\empty : List(U64) + \\empty = [] + \\result = List.get(empty, 0) + ; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + // Should evaluate 2 declarations with 0 crashes (List.get should return Err but not crash) + try testing.expectEqual(@as(u32, 2), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); +} + +test "List.get with different element types - Str" { + const src = + \\result = List.get(["foo", "bar", "baz"], 1) + ; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + // Should evaluate 1 declaration with 0 crashes + try testing.expectEqual(@as(u32, 1), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); +} + +test "List.get with different element types - Bool" { + const src = + \\result = List.get([True, False, True], 2) + ; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + // Should evaluate 1 declaration with 0 crashes + try testing.expectEqual(@as(u32, 1), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); +} + +test "List.get with nested lists" { + const src = + \\result = List.get([[1, 2], [3, 4], [5, 6]], 1) + ; + + var result = try parseCheckAndEvalModule(src); + defer cleanupEvalModule(&result); + + const summary = try result.evaluator.evalAll(); + + // Should evaluate 1 declaration with 0 crashes + try testing.expectEqual(@as(u32, 1), summary.evaluated); + try testing.expectEqual(@as(u32, 0), summary.crashed); +} diff --git a/src/eval/test/comptime_eval_test.zig b/src/eval/test/comptime_eval_test.zig index 63d9cf2e80..7d40526d39 100644 --- a/src/eval/test/comptime_eval_test.zig +++ b/src/eval/test/comptime_eval_test.zig @@ -19,13 +19,19 @@ const ModuleEnv = can.ModuleEnv; const testing = std.testing; const test_allocator = testing.allocator; -/// Helper to parse, canonicalize, type-check, and run comptime evaluation on a full module -fn parseCheckAndEvalModule(src: []const u8) !struct { +const EvalModuleResult = struct { module_env: *ModuleEnv, evaluator: ComptimeEvaluator, problems: *check.problem.Store, builtin_module: builtin_loading.LoadedModule, -} { +}; + +/// Helper to parse, canonicalize, type-check, and run comptime evaluation on a full module +fn parseCheckAndEvalModule(src: []const u8) !EvalModuleResult { + return parseCheckAndEvalModuleWithName(src, "TestModule"); +} + +fn parseCheckAndEvalModuleWithName(src: []const u8, module_name: []const u8) !EvalModuleResult { const gpa = test_allocator; const module_env = try gpa.create(ModuleEnv); @@ -34,7 +40,7 @@ fn parseCheckAndEvalModule(src: []const u8) !struct { errdefer module_env.deinit(); module_env.common.source = src; - module_env.module_name = "TestModule"; + module_env.module_name = module_name; try module_env.common.calcLineStarts(module_env.gpa); // Parse the source code @@ -51,9 +57,9 @@ fn parseCheckAndEvalModule(src: []const u8) !struct { errdefer builtin_module.deinit(); // Initialize CIR fields in ModuleEnv - try module_env.initCIRFields(gpa, "test"); + try module_env.initCIRFields(gpa, module_name); const common_idents: Check.CommonIdents = .{ - .module_name = try module_env.insertIdent(base.Ident.for_text("test")), + .module_name = try module_env.insertIdent(base.Ident.for_text(module_name)), .list = try module_env.insertIdent(base.Ident.for_text("List")), .box = try module_env.insertIdent(base.Ident.for_text("Box")), .bool_stmt = builtin_indices.bool_type, @@ -91,14 +97,16 @@ fn parseCheckAndEvalModule(src: []const u8) !struct { }; } -/// Helper to parse, canonicalize, type-check, and run comptime evaluation with imported modules -fn parseCheckAndEvalModuleWithImport(src: []const u8, import_name: []const u8, imported_module: *const ModuleEnv) !struct { +const EvalModuleWithImportResult = struct { module_env: *ModuleEnv, evaluator: ComptimeEvaluator, problems: *check.problem.Store, other_envs: []const *const ModuleEnv, builtin_module: builtin_loading.LoadedModule, -} { +}; + +/// Helper to parse, canonicalize, type-check, and run comptime evaluation with imported modules +fn parseCheckAndEvalModuleWithImport(src: []const u8, import_name: []const u8, imported_module: *const ModuleEnv) !EvalModuleWithImportResult { const gpa = test_allocator; const module_env = try gpa.create(ModuleEnv); @@ -138,6 +146,9 @@ fn parseCheckAndEvalModuleWithImport(src: []const u8, import_name: []const u8, i var module_envs = std.AutoHashMap(base.Ident.Idx, Can.AutoImportedType).init(gpa); defer module_envs.deinit(); + // Populate module_envs with builtin types (like production does) + try Can.populateModuleEnvs(&module_envs, module_env, builtin_module.env, builtin_indices); + // Convert import name to Ident.Idx using the MODULE's ident store (not a temporary one!) // This is important because the canonicalizer will look up identifiers in this same store const import_ident = try module_env.insertIdent(base.Ident.for_text(import_name)); @@ -382,7 +393,7 @@ test "comptime eval - cross-module crash is detected" { \\} ; - var result_a = try parseCheckAndEvalModule(src_a); + var result_a = try parseCheckAndEvalModuleWithName(src_a, "A"); defer cleanupEvalModule(&result_a); const summary_a = try result_a.evaluator.evalAll(); diff --git a/test/snapshots/file/inline_ingested_file.md b/test/snapshots/file/inline_ingested_file.md index af56a94354..8d01f05bc9 100644 --- a/test/snapshots/file/inline_ingested_file.md +++ b/test/snapshots/file/inline_ingested_file.md @@ -137,9 +137,9 @@ foo = Json.parse(data) ~~~clojure (inferred-types (defs - (patt (type "Error")) + (patt (type "Str")) (patt (type "Error"))) (expressions - (expr (type "Error")) + (expr (type "Str")) (expr (type "Error")))) ~~~ diff --git a/test/snapshots/formatting/multiline/everything.md b/test/snapshots/formatting/multiline/everything.md index 735fe5f096..767eea4f43 100644 --- a/test/snapshots/formatting/multiline/everything.md +++ b/test/snapshots/formatting/multiline/everything.md @@ -840,7 +840,7 @@ h = |x, y| { ~~~clojure (inferred-types (defs - (patt (type "Error")) + (patt (type "e -> e")) (patt (type "[Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j, [Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j -> c"))) (type_decls (alias (type "A(a)") @@ -866,6 +866,6 @@ h = |x, y| { (alias (type "F") (ty-header (name "F")))) (expressions - (expr (type "Error")) + (expr (type "e -> e")) (expr (type "[Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j, [Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j -> c")))) ~~~ diff --git a/test/snapshots/formatting/multiline/hosted.md b/test/snapshots/formatting/multiline/hosted.md index cd8adcf901..2064cfe200 100644 --- a/test/snapshots/formatting/multiline/hosted.md +++ b/test/snapshots/formatting/multiline/hosted.md @@ -76,6 +76,13 @@ NO CHANGE (d-let (p-assign (ident "a!")) (e-anno-only) + (annotation + (ty-fn (effectful true) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "b!")) + (e-anno-only) (annotation (ty-fn (effectful true) (ty-lookup (name "Str") (builtin)) @@ -85,7 +92,9 @@ NO CHANGE ~~~clojure (inferred-types (defs - (patt (type "Error"))) + (patt (type "Str => Str")) + (patt (type "Str => Str"))) (expressions - (expr (type "Error")))) + (expr (type "Str => Str")) + (expr (type "Str => Str")))) ~~~ diff --git a/test/snapshots/formatting/multiline/package.md b/test/snapshots/formatting/multiline/package.md index 0f0fcb805b..529e861dc9 100644 --- a/test/snapshots/formatting/multiline/package.md +++ b/test/snapshots/formatting/multiline/package.md @@ -93,6 +93,13 @@ NO CHANGE (d-let (p-assign (ident "a!")) (e-anno-only) + (annotation + (ty-fn (effectful true) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "b!")) + (e-anno-only) (annotation (ty-fn (effectful true) (ty-lookup (name "Str") (builtin)) @@ -102,7 +109,9 @@ NO CHANGE ~~~clojure (inferred-types (defs - (patt (type "Error"))) + (patt (type "Str => Str")) + (patt (type "Str => Str"))) (expressions - (expr (type "Error")))) + (expr (type "Str => Str")) + (expr (type "Str => Str")))) ~~~ diff --git a/test/snapshots/formatting/multiline_without_comma/everything.md b/test/snapshots/formatting/multiline_without_comma/everything.md index 2ca4829e6e..abd7875e90 100644 --- a/test/snapshots/formatting/multiline_without_comma/everything.md +++ b/test/snapshots/formatting/multiline_without_comma/everything.md @@ -197,11 +197,11 @@ PARSE ERROR - everything.md:56:37:56:38 PARSE ERROR - everything.md:56:38:56:39 PARSE ERROR - everything.md:56:39:56:40 PARSE ERROR - everything.md:56:40:56:42 +MALFORMED WHERE CLAUSE - everything.md:56:12:56:17 WHERE CLAUSE NOT ALLOWED IN TYPE DECLARATION - everything.md:12:1:13:7 UNDECLARED TYPE - everything.md:43:5:43:6 MODULE NOT FOUND - everything.md:2:1:5:2 MODULE NOT FOUND - everything.md:6:1:9:2 -MALFORMED WHERE CLAUSE - everything.md:56:12:56:17 UNUSED VARIABLE - everything.md:88:5:88:6 UNUSED VARIABLE - everything.md:93:4:93:5 UNUSED VARIABLE - everything.md:98:5:98:6 @@ -1174,6 +1174,17 @@ g : e -> e where module(e).A, module(e).B ^^ +**MALFORMED WHERE CLAUSE** +This where clause could not be parsed correctly. + +**everything.md:56:12:56:17:** +```roc +g : e -> e where module(e).A, module(e).B +``` + ^^^^^ + +Check the syntax of your where clause. + **WHERE CLAUSE NOT ALLOWED IN TYPE DECLARATION** You cannot define a `where` clause inside a type declaration. @@ -1222,17 +1233,6 @@ import I2 exposing [ ``` -**MALFORMED WHERE CLAUSE** -This where clause could not be parsed correctly. - -**everything.md:56:12:56:17:** -```roc -g : e -> e where module(e).A, module(e).B -``` - ^^^^^ - -Check the syntax of your where clause. - **UNUSED VARIABLE** Variable `b` is not used anywhere in your code. @@ -1904,7 +1904,7 @@ h = |x, y| { ~~~clojure (inferred-types (defs - (patt (type "Error")) + (patt (type "e -> e")) (patt (type "[Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j, [Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j -> c"))) (type_decls (alias (type "A(a)") @@ -1921,6 +1921,6 @@ h = |x, y| { (alias (type "F") (ty-header (name "F")))) (expressions - (expr (type "Error")) + (expr (type "e -> e")) (expr (type "[Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j, [Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j -> c")))) ~~~ diff --git a/test/snapshots/formatting/multiline_without_comma/hosted.md b/test/snapshots/formatting/multiline_without_comma/hosted.md index d7c78a79e8..b819f8277a 100644 --- a/test/snapshots/formatting/multiline_without_comma/hosted.md +++ b/test/snapshots/formatting/multiline_without_comma/hosted.md @@ -82,6 +82,13 @@ b! : Str => Str (d-let (p-assign (ident "a!")) (e-anno-only) + (annotation + (ty-fn (effectful true) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "b!")) + (e-anno-only) (annotation (ty-fn (effectful true) (ty-lookup (name "Str") (builtin)) @@ -91,7 +98,9 @@ b! : Str => Str ~~~clojure (inferred-types (defs - (patt (type "Error"))) + (patt (type "Str => Str")) + (patt (type "Str => Str"))) (expressions - (expr (type "Error")))) + (expr (type "Str => Str")) + (expr (type "Str => Str")))) ~~~ diff --git a/test/snapshots/formatting/multiline_without_comma/package.md b/test/snapshots/formatting/multiline_without_comma/package.md index f700d33e00..ec42d1f8a7 100644 --- a/test/snapshots/formatting/multiline_without_comma/package.md +++ b/test/snapshots/formatting/multiline_without_comma/package.md @@ -104,6 +104,13 @@ b! : Str => Str (d-let (p-assign (ident "a!")) (e-anno-only) + (annotation + (ty-fn (effectful true) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "b!")) + (e-anno-only) (annotation (ty-fn (effectful true) (ty-lookup (name "Str") (builtin)) @@ -113,7 +120,9 @@ b! : Str => Str ~~~clojure (inferred-types (defs - (patt (type "Error"))) + (patt (type "Str => Str")) + (patt (type "Str => Str"))) (expressions - (expr (type "Error")))) + (expr (type "Str => Str")) + (expr (type "Str => Str")))) ~~~ diff --git a/test/snapshots/formatting/singleline/everything.md b/test/snapshots/formatting/singleline/everything.md index 81f191a4aa..969ec0aa0c 100644 --- a/test/snapshots/formatting/singleline/everything.md +++ b/test/snapshots/formatting/singleline/everything.md @@ -528,7 +528,7 @@ NO CHANGE ~~~clojure (inferred-types (defs - (patt (type "Error")) + (patt (type "e -> e")) (patt (type "[Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j, [Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j -> c"))) (type_decls (alias (type "A(a)") @@ -554,6 +554,6 @@ NO CHANGE (alias (type "F") (ty-header (name "F")))) (expressions - (expr (type "Error")) + (expr (type "e -> e")) (expr (type "[Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j, [Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j -> c")))) ~~~ diff --git a/test/snapshots/formatting/singleline/hosted.md b/test/snapshots/formatting/singleline/hosted.md index d463426603..6905a493e8 100644 --- a/test/snapshots/formatting/singleline/hosted.md +++ b/test/snapshots/formatting/singleline/hosted.md @@ -70,6 +70,13 @@ NO CHANGE (d-let (p-assign (ident "a!")) (e-anno-only) + (annotation + (ty-fn (effectful true) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "b!")) + (e-anno-only) (annotation (ty-fn (effectful true) (ty-lookup (name "Str") (builtin)) @@ -79,7 +86,9 @@ NO CHANGE ~~~clojure (inferred-types (defs - (patt (type "Error"))) + (patt (type "Str => Str")) + (patt (type "Str => Str"))) (expressions - (expr (type "Error")))) + (expr (type "Str => Str")) + (expr (type "Str => Str")))) ~~~ diff --git a/test/snapshots/formatting/singleline/package.md b/test/snapshots/formatting/singleline/package.md index 1715b77289..26d8ab4681 100644 --- a/test/snapshots/formatting/singleline/package.md +++ b/test/snapshots/formatting/singleline/package.md @@ -77,6 +77,13 @@ NO CHANGE (d-let (p-assign (ident "a!")) (e-anno-only) + (annotation + (ty-fn (effectful true) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "b!")) + (e-anno-only) (annotation (ty-fn (effectful true) (ty-lookup (name "Str") (builtin)) @@ -86,7 +93,9 @@ NO CHANGE ~~~clojure (inferred-types (defs - (patt (type "Error"))) + (patt (type "Str => Str")) + (patt (type "Str => Str"))) (expressions - (expr (type "Error")))) + (expr (type "Str => Str")) + (expr (type "Str => Str")))) ~~~ diff --git a/test/snapshots/formatting/singleline_with_comma/everything.md b/test/snapshots/formatting/singleline_with_comma/everything.md index de29e14916..7a6ec9de8f 100644 --- a/test/snapshots/formatting/singleline_with_comma/everything.md +++ b/test/snapshots/formatting/singleline_with_comma/everything.md @@ -641,7 +641,7 @@ h = | ~~~clojure (inferred-types (defs - (patt (type "Error")) + (patt (type "e -> e")) (patt (type "[Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j, [Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j -> c"))) (type_decls (alias (type "A(a)") @@ -667,6 +667,6 @@ h = | (alias (type "F") (ty-header (name "F")))) (expressions - (expr (type "Error")) + (expr (type "e -> e")) (expr (type "[Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j, [Z1((c, d)), Z2(c, f), Z3({ a: c, b: i }), Z4(List(c))]j -> c")))) ~~~ diff --git a/test/snapshots/formatting/singleline_with_comma/hosted.md b/test/snapshots/formatting/singleline_with_comma/hosted.md index b83c6554c6..39b24517ab 100644 --- a/test/snapshots/formatting/singleline_with_comma/hosted.md +++ b/test/snapshots/formatting/singleline_with_comma/hosted.md @@ -76,6 +76,13 @@ b! : Str => Str (d-let (p-assign (ident "a!")) (e-anno-only) + (annotation + (ty-fn (effectful true) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "b!")) + (e-anno-only) (annotation (ty-fn (effectful true) (ty-lookup (name "Str") (builtin)) @@ -85,7 +92,9 @@ b! : Str => Str ~~~clojure (inferred-types (defs - (patt (type "Error"))) + (patt (type "Str => Str")) + (patt (type "Str => Str"))) (expressions - (expr (type "Error")))) + (expr (type "Str => Str")) + (expr (type "Str => Str")))) ~~~ diff --git a/test/snapshots/formatting/singleline_with_comma/package.md b/test/snapshots/formatting/singleline_with_comma/package.md index 3b1d217d1b..45eac5f650 100644 --- a/test/snapshots/formatting/singleline_with_comma/package.md +++ b/test/snapshots/formatting/singleline_with_comma/package.md @@ -88,6 +88,13 @@ b! : Str => Str (d-let (p-assign (ident "a!")) (e-anno-only) + (annotation + (ty-fn (effectful true) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "b!")) + (e-anno-only) (annotation (ty-fn (effectful true) (ty-lookup (name "Str") (builtin)) @@ -97,7 +104,9 @@ b! : Str => Str ~~~clojure (inferred-types (defs - (patt (type "Error"))) + (patt (type "Str => Str")) + (patt (type "Str => Str"))) (expressions - (expr (type "Error")))) + (expr (type "Str => Str")) + (expr (type "Str => Str")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_002.md b/test/snapshots/fuzz_crash/fuzz_crash_002.md index d92caaeb98..0125186fcb 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_002.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_002.md @@ -287,11 +287,18 @@ modu : ~~~ # CANONICALIZE ~~~clojure -(can-ir (empty true)) +(can-ir + (d-let + (p-assign (ident "modu")) + (e-anno-only) + (annotation + (ty-malformed)))) ~~~ # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "Error"))) + (expressions + (expr (type "Error")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_018.md b/test/snapshots/fuzz_crash/fuzz_crash_018.md index bd1c7b9016..b457375506 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_018.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_018.md @@ -83,11 +83,18 @@ b : S ~~~ # CANONICALIZE ~~~clojure -(can-ir (empty true)) +(can-ir + (d-let + (p-assign (ident "b")) + (e-anno-only) + (annotation + (ty-malformed)))) ~~~ # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "Error"))) + (expressions + (expr (type "Error")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_019.md b/test/snapshots/fuzz_crash/fuzz_crash_019.md index a77b3cd864..187aa279f0 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_019.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_019.md @@ -2007,14 +2007,14 @@ expect { ~~~clojure (inferred-types (defs - (patt (type "Error")) + (patt (type "()")) (patt (type "Bool -> num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]")) (patt (type "Error")) (patt (type "Bool -> Error")) (patt (type "[Blue]_others, [Tb]_others2 -> Error")) (patt (type "Error")) (patt (type "_arg -> [Stdo!(Error)]_others")) - (patt (type "Error")) + (patt (type "{ }")) (patt (type "{}")) (patt (type "Error"))) (type_decls @@ -2044,14 +2044,14 @@ expect { (ty-args (ty-rigid-var (name "a")))))) (expressions - (expr (type "Error")) + (expr (type "()")) (expr (type "Bool -> num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]")) (expr (type "Error")) (expr (type "Bool -> Error")) (expr (type "[Blue]_others, [Tb]_others2 -> Error")) (expr (type "Error")) (expr (type "_arg -> [Stdo!(Error)]_others")) - (expr (type "Error")) + (expr (type "{ }")) (expr (type "{}")) (expr (type "Error")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_020.md b/test/snapshots/fuzz_crash/fuzz_crash_020.md index 0922812a64..2840a7649f 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_020.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_020.md @@ -1986,14 +1986,14 @@ expect { ~~~clojure (inferred-types (defs - (patt (type "Error")) + (patt (type "()")) (patt (type "Bool -> num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]")) (patt (type "Error")) (patt (type "[Rum]_others -> Error")) (patt (type "[Blue]_others -> Error")) (patt (type "Error")) (patt (type "_arg -> [Stdo!(Error)]_others")) - (patt (type "Error")) + (patt (type "{ }")) (patt (type "{}")) (patt (type "Error"))) (type_decls @@ -2023,14 +2023,14 @@ expect { (ty-args (ty-rigid-var (name "a")))))) (expressions - (expr (type "Error")) + (expr (type "()")) (expr (type "Bool -> num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]")) (expr (type "Error")) (expr (type "[Rum]_others -> Error")) (expr (type "[Blue]_others -> Error")) (expr (type "Error")) (expr (type "_arg -> [Stdo!(Error)]_others")) - (expr (type "Error")) + (expr (type "{ }")) (expr (type "{}")) (expr (type "Error")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_022.md b/test/snapshots/fuzz_crash/fuzz_crash_022.md index 701d19372d..fb5d884dc6 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_022.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_022.md @@ -245,7 +245,7 @@ ain! = |_| getUser(900) (inferred-types (defs (patt (type "Error")) - (patt (type "Error")) + (patt (type "UserId -> Str")) (patt (type "_arg -> Error")) (patt (type "_arg -> Error"))) (type_decls @@ -253,7 +253,7 @@ ain! = |_| getUser(900) (ty-header (name "UserId")))) (expressions (expr (type "Error")) - (expr (type "Error")) + (expr (type "UserId -> Str")) (expr (type "_arg -> Error")) (expr (type "_arg -> Error")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_023.md b/test/snapshots/fuzz_crash/fuzz_crash_023.md index e546ad9ab1..08e407831e 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_023.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_023.md @@ -269,6 +269,7 @@ DOES NOT EXIST - fuzz_crash_023.md:193:4:193:13 UNUSED VARIABLE - fuzz_crash_023.md:164:2:164:18 UNUSED VARIABLE - fuzz_crash_023.md:165:2:165:14 UNUSED VARIABLE - fuzz_crash_023.md:178:2:178:8 +UNUSED VARIABLE - fuzz_crash_023.md:178:47:178:71 UNUSED VARIABLE - fuzz_crash_023.md:180:2:180:17 UNUSED VARIABLE - fuzz_crash_023.md:188:2:188:15 UNUSED VARIABLE - fuzz_crash_023.md:189:2:189:23 @@ -846,6 +847,18 @@ The unused variable is declared here: ^^^^^^ +**UNUSED VARIABLE** +Variable `qux` is not used anywhere in your code. + +If you don't need this variable, prefix it with an underscore like `_qux` to suppress this warning. +The unused variable is declared here: +**fuzz_crash_023.md:178:47:178:71:** +```roc + record = { foo: 123, bar: "Hello", ;az: tag, qux: Ok(world), punned } +``` + ^^^^^^^^^^^^^^^^^^^^^^^^ + + **UNUSED VARIABLE** Variable `multiline_tuple` is not used anywhere in your code. @@ -2211,6 +2224,11 @@ expect { (p-applied-tag))) (value (e-num (value "1000")))))))))) + (d-let + (p-assign (ident "qux")) + (e-anno-only) + (annotation + (ty-malformed))) (d-let (p-assign (ident "main!")) (e-closure @@ -2310,6 +2328,9 @@ expect { (p-assign (ident "tag")))) (s-expr (e-runtime-error (tag "expr_not_canonicalized"))) + (s-let + (p-assign (ident "qux")) + (e-anno-only)) (s-let (p-assign (ident "tuple")) (e-tuple @@ -2554,6 +2575,7 @@ expect { (patt (type "Bool -> num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]")) (patt (type "Num(Int(Unsigned64)) -> Num(Int(Unsigned64))")) (patt (type "[Red][Blue, Green]_others, _arg -> Error")) + (patt (type "Error")) (patt (type "List(Error) -> Error")) (patt (type "{}")) (patt (type "Error"))) @@ -2600,6 +2622,7 @@ expect { (expr (type "Bool -> num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]")) (expr (type "Num(Int(Unsigned64)) -> Num(Int(Unsigned64))")) (expr (type "[Red][Blue, Green]_others, _arg -> Error")) + (expr (type "Error")) (expr (type "List(Error) -> Error")) (expr (type "{}")) (expr (type "Error")))) diff --git a/test/snapshots/fuzz_crash/fuzz_crash_026.md b/test/snapshots/fuzz_crash/fuzz_crash_026.md index 31ff2b2970..6ae9223b5d 100644 Binary files a/test/snapshots/fuzz_crash/fuzz_crash_026.md and b/test/snapshots/fuzz_crash/fuzz_crash_026.md differ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_027.md b/test/snapshots/fuzz_crash/fuzz_crash_027.md index 9766c6ec03..e2d36ee04e 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_027.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_027.md @@ -2249,7 +2249,7 @@ expect { ~~~clojure (inferred-types (defs - (patt (type "Error")) + (patt (type "(Error, Error)")) (patt (type "Bool -> num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]")) (patt (type "Num(Int(Unsigned64)) -> Num(Int(Unsigned64))")) (patt (type "[Red, Blue]_others, _arg -> Error")) @@ -2286,7 +2286,7 @@ expect { (ty-args (ty-rigid-var (name "a")))))) (expressions - (expr (type "Error")) + (expr (type "(Error, Error)")) (expr (type "Bool -> num where [num.from_int_digits : List(U8) -> Try(num, [OutOfRange])]")) (expr (type "Num(Int(Unsigned64)) -> Num(Int(Unsigned64))")) (expr (type "[Red, Blue]_others, _arg -> Error")) diff --git a/test/snapshots/fuzz_crash/fuzz_crash_028.md b/test/snapshots/fuzz_crash/fuzz_crash_028.md index 2611ecf986..564b8d8700 100644 Binary files a/test/snapshots/fuzz_crash/fuzz_crash_028.md and b/test/snapshots/fuzz_crash/fuzz_crash_028.md differ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_029.md b/test/snapshots/fuzz_crash/fuzz_crash_029.md index 4c24265f7f..b72dd803e5 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_029.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_029.md @@ -455,11 +455,18 @@ pkg : ~~~ # CANONICALIZE ~~~clojure -(can-ir (empty true)) +(can-ir + (d-let + (p-assign (ident "pkg")) + (e-anno-only) + (annotation + (ty-malformed)))) ~~~ # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "Error"))) + (expressions + (expr (type "Error")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_040.md b/test/snapshots/fuzz_crash/fuzz_crash_040.md index 49eb589644..19b2b78516 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_040.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_040.md @@ -89,11 +89,18 @@ o : ~~~ # CANONICALIZE ~~~clojure -(can-ir (empty true)) +(can-ir + (d-let + (p-assign (ident "o")) + (e-anno-only) + (annotation + (ty-malformed)))) ~~~ # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "Error"))) + (expressions + (expr (type "Error")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_042.md b/test/snapshots/fuzz_crash/fuzz_crash_042.md index e74ff88e96..537b108063 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_042.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_042.md @@ -9,8 +9,8 @@ import u.R}g:r->R.a.E ~~~ # EXPECTED PARSE ERROR - fuzz_crash_042.md:1:11:1:12 -MODULE NOT FOUND - fuzz_crash_042.md:1:1:1:11 MODULE NOT IMPORTED - fuzz_crash_042.md:1:17:1:22 +MODULE NOT FOUND - fuzz_crash_042.md:1:1:1:11 # PROBLEMS **PARSE ERROR** A parsing error occurred: `statement_unexpected_token` @@ -23,17 +23,6 @@ import u.R}g:r->R.a.E ^ -**MODULE NOT FOUND** -The module `u.R` was not found in this Roc project. - -You're attempting to use this module here: -**fuzz_crash_042.md:1:1:1:11:** -```roc -import u.R}g:r->R.a.E -``` -^^^^^^^^^^ - - **MODULE NOT IMPORTED** There is no module with the name `R.a` imported into this Roc file. @@ -45,6 +34,17 @@ import u.R}g:r->R.a.E ^^^^^ +**MODULE NOT FOUND** +The module `u.R` was not found in this Roc project. + +You're attempting to use this module here: +**fuzz_crash_042.md:1:1:1:11:** +```roc +import u.R}g:r->R.a.E +``` +^^^^^^^^^^ + + # TOKENS ~~~zig KwImport,LowerIdent,NoSpaceDotUpperIdent,CloseCurly,LowerIdent,OpColon,LowerIdent,OpArrow,UpperIdent,NoSpaceDotLowerIdent,NoSpaceDotUpperIdent, @@ -70,12 +70,21 @@ g : r -> R.a.E # CANONICALIZE ~~~clojure (can-ir + (d-let + (p-assign (ident "g")) + (e-anno-only) + (annotation + (ty-fn (effectful false) + (ty-rigid-var (name "r")) + (ty-malformed)))) (s-import (module "u.R") (exposes))) ~~~ # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "r -> Error"))) + (expressions + (expr (type "r -> Error")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_043.md b/test/snapshots/fuzz_crash/fuzz_crash_043.md index 0be33a2e45..0643763136 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_043.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_043.md @@ -102,11 +102,18 @@ o : ~~~ # CANONICALIZE ~~~clojure -(can-ir (empty true)) +(can-ir + (d-let + (p-assign (ident "o")) + (e-anno-only) + (annotation + (ty-malformed)))) ~~~ # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "Error"))) + (expressions + (expr (type "Error")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_048.md b/test/snapshots/fuzz_crash/fuzz_crash_048.md index 7539b8ba9c..5ac1049a26 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_048.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_048.md @@ -157,21 +157,28 @@ tag_tuple : Value((a, b, c)) (ty-malformed)) (ty-apply (name "Result") (builtin) (ty-record) - (ty-underscore)))))) + (ty-underscore))))) + (d-let + (p-assign (ident "tag_tuple")) + (e-anno-only) + (annotation + (ty-malformed)))) ~~~ # TYPES ~~~clojure (inferred-types (defs + (patt (type "Num(Int(Unsigned64))")) (patt (type "Error")) - (patt (type "Error")) - (patt (type "Error")) - (patt (type "Error")) + (patt (type "(a, b, c)")) + (patt (type "Num(Int(Unsigned8)), Num(Int(Unsigned16)) -> Num(Int(Unsigned32))")) + (patt (type "List(Error) -> Try({ }, _d)")) (patt (type "Error"))) (expressions + (expr (type "Num(Int(Unsigned64))")) (expr (type "Error")) - (expr (type "Error")) - (expr (type "Error")) - (expr (type "Error")) + (expr (type "(a, b, c)")) + (expr (type "Num(Int(Unsigned8)), Num(Int(Unsigned16)) -> Num(Int(Unsigned32))")) + (expr (type "List(Error) -> Try({ }, _d)")) (expr (type "Error")))) ~~~ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_049.md b/test/snapshots/fuzz_crash/fuzz_crash_049.md index 7c318353aa..e6029d0eea 100644 Binary files a/test/snapshots/fuzz_crash/fuzz_crash_049.md and b/test/snapshots/fuzz_crash/fuzz_crash_049.md differ diff --git a/test/snapshots/fuzz_crash/fuzz_crash_079.md b/test/snapshots/fuzz_crash/fuzz_crash_079.md index 763bd83f8a..5d1c7229bb 100644 --- a/test/snapshots/fuzz_crash/fuzz_crash_079.md +++ b/test/snapshots/fuzz_crash/fuzz_crash_079.md @@ -45,11 +45,18 @@ b : r ~~~ # CANONICALIZE ~~~clojure -(can-ir (empty true)) +(can-ir + (d-let + (p-assign (ident "b")) + (e-anno-only) + (annotation + (ty-rigid-var (name "r"))))) ~~~ # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "r"))) + (expressions + (expr (type "r")))) ~~~ diff --git a/test/snapshots/nominal/nominal_associated_alias_within_block.md b/test/snapshots/nominal/nominal_associated_alias_within_block.md index 5fa33c4dec..60da9d276d 100644 --- a/test/snapshots/nominal/nominal_associated_alias_within_block.md +++ b/test/snapshots/nominal/nominal_associated_alias_within_block.md @@ -85,15 +85,15 @@ external = Foo.defaultBaz ~~~clojure (can-ir (d-let - (p-assign (ident "external")) - (e-lookup-local - (p-assign (ident "Foo.defaultBaz"))) + (p-assign (ident "nominal_associated_alias_within_block.Foo.defaultBaz")) + (e-nominal (nominal "nominal_associated_alias_within_block.Foo.Bar") + (e-tag (name "X"))) (annotation (ty-lookup (name "Foo.Baz") (local)))) (d-let - (p-assign (ident "Foo.defaultBaz")) - (e-nominal (nominal "Foo.Bar") - (e-tag (name "X"))) + (p-assign (ident "external")) + (e-lookup-local + (p-assign (ident "nominal_associated_alias_within_block.Foo.defaultBaz"))) (annotation (ty-lookup (name "Foo.Baz") (local)))) (s-nominal-decl @@ -101,29 +101,29 @@ external = Foo.defaultBaz (ty-tag-union (ty-tag-name (name "Whatever")))) (s-nominal-decl - (ty-header (name "Foo.Bar")) + (ty-header (name "nominal_associated_alias_within_block.Foo.Bar")) (ty-tag-union (ty-tag-name (name "X")) (ty-tag-name (name "Y")) (ty-tag-name (name "Z")))) (s-alias-decl - (ty-header (name "Foo.Baz")) + (ty-header (name "nominal_associated_alias_within_block.Foo.Baz")) (ty-lookup (name "Foo.Bar") (local)))) ~~~ # TYPES ~~~clojure (inferred-types (defs - (patt (type "Foo.Baz")) - (patt (type "Foo.Baz"))) + (patt (type "nominal_associated_alias_within_block.Foo.Baz")) + (patt (type "nominal_associated_alias_within_block.Foo.Baz"))) (type_decls (nominal (type "Foo") (ty-header (name "Foo"))) - (nominal (type "Foo.Bar") - (ty-header (name "Foo.Bar"))) - (alias (type "Foo.Baz") - (ty-header (name "Foo.Baz")))) + (nominal (type "nominal_associated_alias_within_block.Foo.Bar") + (ty-header (name "nominal_associated_alias_within_block.Foo.Bar"))) + (alias (type "nominal_associated_alias_within_block.Foo.Baz") + (ty-header (name "nominal_associated_alias_within_block.Foo.Baz")))) (expressions - (expr (type "Foo.Baz")) - (expr (type "Foo.Baz")))) + (expr (type "nominal_associated_alias_within_block.Foo.Baz")) + (expr (type "nominal_associated_alias_within_block.Foo.Baz")))) ~~~ diff --git a/test/snapshots/nominal/nominal_associated_deep_nesting.md b/test/snapshots/nominal/nominal_associated_deep_nesting.md index 80a32c5034..f86ca29bde 100644 --- a/test/snapshots/nominal/nominal_associated_deep_nesting.md +++ b/test/snapshots/nominal/nominal_associated_deep_nesting.md @@ -110,6 +110,9 @@ 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 "deepValue")) (e-lookup-local @@ -121,9 +124,6 @@ deepType = C (e-tag (name "C")) (annotation (ty-lookup (name "Foo.Level1.Level2.Level3") (local)))) - (d-let - (p-assign (ident "Foo.Level1.Level2.Level3.value")) - (e-num (value "42"))) (s-nominal-decl (ty-header (name "Foo")) (ty-tag-union @@ -146,8 +146,8 @@ deepType = C (inferred-types (defs (patt (type "Num(Int(Unsigned64))")) - (patt (type "Foo.Level1.Level2.Level3")) - (patt (type "Num(Int(Unsigned64))"))) + (patt (type "Num(Int(Unsigned64))")) + (patt (type "Foo.Level1.Level2.Level3"))) (type_decls (nominal (type "Foo") (ty-header (name "Foo"))) @@ -159,6 +159,6 @@ deepType = C (ty-header (name "Foo.Level1.Level2.Level3")))) (expressions (expr (type "Num(Int(Unsigned64))")) - (expr (type "Foo.Level1.Level2.Level3")) - (expr (type "Num(Int(Unsigned64))")))) + (expr (type "Num(Int(Unsigned64))")) + (expr (type "Foo.Level1.Level2.Level3")))) ~~~ diff --git a/test/snapshots/nominal/nominal_associated_lookup_decl.md b/test/snapshots/nominal/nominal_associated_lookup_decl.md index a771002e86..44d860fd95 100644 --- a/test/snapshots/nominal/nominal_associated_lookup_decl.md +++ b/test/snapshots/nominal/nominal_associated_lookup_decl.md @@ -58,15 +58,15 @@ useBar = Foo.bar # CANONICALIZE ~~~clojure (can-ir + (d-let + (p-assign (ident "Foo.bar")) + (e-num (value "42"))) (d-let (p-assign (ident "useBar")) (e-lookup-local (p-assign (ident "Foo.bar"))) (annotation (ty-lookup (name "U64") (builtin)))) - (d-let - (p-assign (ident "Foo.bar")) - (e-num (value "42"))) (s-nominal-decl (ty-header (name "Foo")) (ty-tag-union diff --git a/test/snapshots/nominal/nominal_associated_lookup_mixed.md b/test/snapshots/nominal/nominal_associated_lookup_mixed.md index 98c6aac71d..091fe735ce 100644 --- a/test/snapshots/nominal/nominal_associated_lookup_mixed.md +++ b/test/snapshots/nominal/nominal_associated_lookup_mixed.md @@ -89,15 +89,6 @@ result = Foo.transform(Foo.defaultBar) # CANONICALIZE ~~~clojure (can-ir - (d-let - (p-assign (ident "result")) - (e-call - (e-lookup-local - (p-assign (ident "Foo.transform"))) - (e-lookup-local - (p-assign (ident "Foo.defaultBar")))) - (annotation - (ty-lookup (name "Foo.Bar") (local)))) (d-let (p-assign (ident "Foo.defaultBar")) (e-nominal (nominal "Foo.Bar") @@ -113,6 +104,15 @@ result = Foo.transform(Foo.defaultBar) (ty-fn (effectful false) (ty-lookup (name "Foo.Bar") (local)) (ty-lookup (name "Foo.Bar") (local))))) + (d-let + (p-assign (ident "result")) + (e-call + (e-lookup-local + (p-assign (ident "Foo.transform"))) + (e-lookup-local + (p-assign (ident "Foo.defaultBar")))) + (annotation + (ty-lookup (name "Foo.Bar") (local)))) (s-nominal-decl (ty-header (name "Foo")) (ty-tag-union @@ -129,8 +129,8 @@ result = Foo.transform(Foo.defaultBar) (inferred-types (defs (patt (type "Foo.Bar")) - (patt (type "Foo.Bar")) - (patt (type "Foo.Bar -> Foo.Bar"))) + (patt (type "Foo.Bar -> Foo.Bar")) + (patt (type "Foo.Bar"))) (type_decls (nominal (type "Foo") (ty-header (name "Foo"))) @@ -138,6 +138,6 @@ result = Foo.transform(Foo.defaultBar) (ty-header (name "Foo.Bar")))) (expressions (expr (type "Foo.Bar")) - (expr (type "Foo.Bar")) - (expr (type "Foo.Bar -> Foo.Bar")))) + (expr (type "Foo.Bar -> Foo.Bar")) + (expr (type "Foo.Bar")))) ~~~ diff --git a/test/snapshots/nominal/nominal_associated_lookup_nested.md b/test/snapshots/nominal/nominal_associated_lookup_nested.md index b23ccce688..2a579f7a22 100644 --- a/test/snapshots/nominal/nominal_associated_lookup_nested.md +++ b/test/snapshots/nominal/nominal_associated_lookup_nested.md @@ -84,6 +84,9 @@ 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 "myType")) (e-tag (name "Something")) @@ -95,9 +98,6 @@ myNum = Foo.Bar.baz (p-assign (ident "Foo.Bar.baz"))) (annotation (ty-lookup (name "U64") (builtin)))) - (d-let - (p-assign (ident "Foo.Bar.baz")) - (e-num (value "5"))) (s-nominal-decl (ty-header (name "Foo")) (ty-tag-union @@ -111,8 +111,8 @@ myNum = Foo.Bar.baz ~~~clojure (inferred-types (defs - (patt (type "Foo.Bar")) (patt (type "Num(Int(Unsigned64))")) + (patt (type "Foo.Bar")) (patt (type "Num(Int(Unsigned64))"))) (type_decls (nominal (type "Foo") @@ -120,7 +120,7 @@ myNum = Foo.Bar.baz (nominal (type "Foo.Bar") (ty-header (name "Foo.Bar")))) (expressions - (expr (type "Foo.Bar")) (expr (type "Num(Int(Unsigned64))")) + (expr (type "Foo.Bar")) (expr (type "Num(Int(Unsigned64))")))) ~~~ diff --git a/test/snapshots/nominal/nominal_associated_self_reference.md b/test/snapshots/nominal/nominal_associated_self_reference.md index f19e2d2344..e09e5a299b 100644 --- a/test/snapshots/nominal/nominal_associated_self_reference.md +++ b/test/snapshots/nominal/nominal_associated_self_reference.md @@ -101,12 +101,6 @@ external = Foo.defaultBar # CANONICALIZE ~~~clojure (can-ir - (d-let - (p-assign (ident "external")) - (e-lookup-local - (p-assign (ident "Foo.defaultBar"))) - (annotation - (ty-lookup (name "Foo.Bar") (local)))) (d-let (p-assign (ident "Foo.defaultBar")) (e-tag (name "X")) @@ -130,6 +124,12 @@ external = Foo.defaultBar (p-assign (ident "Foo.transform"))) (e-lookup-local (p-assign (ident "Foo.defaultBar"))))) + (d-let + (p-assign (ident "external")) + (e-lookup-local + (p-assign (ident "Foo.defaultBar"))) + (annotation + (ty-lookup (name "Foo.Bar") (local)))) (s-nominal-decl (ty-header (name "Foo")) (ty-tag-union @@ -145,9 +145,9 @@ external = Foo.defaultBar ~~~clojure (inferred-types (defs - (patt (type "Foo.Bar")) (patt (type "Foo.Bar")) (patt (type "Foo.Bar -> Foo.Bar")) + (patt (type "Foo.Bar")) (patt (type "Foo.Bar"))) (type_decls (nominal (type "Foo") @@ -155,8 +155,8 @@ external = Foo.defaultBar (nominal (type "Foo.Bar") (ty-header (name "Foo.Bar")))) (expressions - (expr (type "Foo.Bar")) (expr (type "Foo.Bar")) (expr (type "Foo.Bar -> Foo.Bar")) + (expr (type "Foo.Bar")) (expr (type "Foo.Bar")))) ~~~ diff --git a/test/snapshots/nominal/nominal_associated_type_alias.md b/test/snapshots/nominal/nominal_associated_type_alias.md index bc17622bf2..7e61cf3bde 100644 --- a/test/snapshots/nominal/nominal_associated_type_alias.md +++ b/test/snapshots/nominal/nominal_associated_type_alias.md @@ -76,7 +76,7 @@ useMyBar = Foo.Bar.X (can-ir (d-let (p-assign (ident "useMyBar")) - (e-nominal (nominal "Foo.Bar") + (e-nominal (nominal "nominal_associated_type_alias.Foo.Bar") (e-tag (name "X"))) (annotation (ty-lookup (name "MyBar") (local)))) @@ -85,7 +85,7 @@ useMyBar = Foo.Bar.X (ty-tag-union (ty-tag-name (name "Whatever")))) (s-nominal-decl - (ty-header (name "Foo.Bar")) + (ty-header (name "nominal_associated_type_alias.Foo.Bar")) (ty-tag-union (ty-tag-name (name "X")) (ty-tag-name (name "Y")) @@ -102,8 +102,8 @@ useMyBar = Foo.Bar.X (type_decls (nominal (type "Foo") (ty-header (name "Foo"))) - (nominal (type "Foo.Bar") - (ty-header (name "Foo.Bar"))) + (nominal (type "nominal_associated_type_alias.Foo.Bar") + (ty-header (name "nominal_associated_type_alias.Foo.Bar"))) (alias (type "MyBar") (ty-header (name "MyBar")))) (expressions diff --git a/test/snapshots/nominal/nominal_associated_value_alias.md b/test/snapshots/nominal/nominal_associated_value_alias.md index 3fdcc0c4a3..a915622393 100644 --- a/test/snapshots/nominal/nominal_associated_value_alias.md +++ b/test/snapshots/nominal/nominal_associated_value_alias.md @@ -73,10 +73,13 @@ result = myBar # CANONICALIZE ~~~clojure (can-ir + (d-let + (p-assign (ident "nominal_associated_value_alias.Foo.bar")) + (e-num (value "42"))) (d-let (p-assign (ident "myBar")) (e-lookup-local - (p-assign (ident "Foo.bar"))) + (p-assign (ident "nominal_associated_value_alias.Foo.bar"))) (annotation (ty-lookup (name "U64") (builtin)))) (d-let @@ -85,9 +88,6 @@ result = myBar (p-assign (ident "myBar"))) (annotation (ty-lookup (name "U64") (builtin)))) - (d-let - (p-assign (ident "Foo.bar")) - (e-num (value "42"))) (s-nominal-decl (ty-header (name "Foo")) (ty-tag-union diff --git a/test/snapshots/nominal/nominal_associated_vs_module.md b/test/snapshots/nominal/nominal_associated_vs_module.md index 6f2966099f..86b223880c 100644 --- a/test/snapshots/nominal/nominal_associated_vs_module.md +++ b/test/snapshots/nominal/nominal_associated_vs_module.md @@ -93,7 +93,7 @@ useBar = Something (ty-tag-union (ty-tag-name (name "Whatever")))) (s-nominal-decl - (ty-header (name "Foo.Bar")) + (ty-header (name "nominal_associated_vs_module.Foo.Bar")) (ty-tag-union (ty-tag-name (name "Something"))))) ~~~ @@ -101,12 +101,12 @@ useBar = Something ~~~clojure (inferred-types (defs - (patt (type "Foo.Bar"))) + (patt (type "nominal_associated_vs_module.Foo.Bar"))) (type_decls (nominal (type "Foo") (ty-header (name "Foo"))) - (nominal (type "Foo.Bar") - (ty-header (name "Foo.Bar")))) + (nominal (type "nominal_associated_vs_module.Foo.Bar") + (ty-header (name "nominal_associated_vs_module.Foo.Bar")))) (expressions - (expr (type "Foo.Bar")))) + (expr (type "nominal_associated_vs_module.Foo.Bar")))) ~~~ diff --git a/test/snapshots/nominal/nominal_associated_with_final_expression.md b/test/snapshots/nominal/nominal_associated_with_final_expression.md index 1027264d5e..ada1b3f2de 100644 --- a/test/snapshots/nominal/nominal_associated_with_final_expression.md +++ b/test/snapshots/nominal/nominal_associated_with_final_expression.md @@ -59,7 +59,7 @@ Foo := [A, B, C].{ ~~~clojure (can-ir (d-let - (p-assign (ident "Foo.x")) + (p-assign (ident "nominal_associated_with_final_expression.Foo.x")) (e-num (value "5"))) (s-nominal-decl (ty-header (name "Foo")) diff --git a/test/snapshots/nominal/nominal_deeply_nested_tag.md b/test/snapshots/nominal/nominal_deeply_nested_tag.md index 2798850cf8..ddca910133 100644 --- a/test/snapshots/nominal/nominal_deeply_nested_tag.md +++ b/test/snapshots/nominal/nominal_deeply_nested_tag.md @@ -78,7 +78,7 @@ x = Foo.Bar.Baz.X (can-ir (d-let (p-assign (ident "x")) - (e-nominal (nominal "Foo.Bar.Baz") + (e-nominal (nominal "nominal_deeply_nested_tag.Foo.Bar.Baz") (e-tag (name "X"))) (annotation (ty-lookup (name "Foo.Bar.Baz") (local)))) @@ -87,11 +87,11 @@ x = Foo.Bar.Baz.X (ty-tag-union (ty-tag-name (name "Whatever")))) (s-nominal-decl - (ty-header (name "Foo.Bar")) + (ty-header (name "nominal_deeply_nested_tag.Foo.Bar")) (ty-tag-union (ty-tag-name (name "Something")))) (s-nominal-decl - (ty-header (name "Foo.Bar.Baz")) + (ty-header (name "nominal_deeply_nested_tag.Foo.Bar.Baz")) (ty-tag-union (ty-tag-name (name "X")) (ty-tag-name (name "Y")) @@ -101,14 +101,14 @@ x = Foo.Bar.Baz.X ~~~clojure (inferred-types (defs - (patt (type "Foo.Bar.Baz"))) + (patt (type "nominal_deeply_nested_tag.Foo.Bar.Baz"))) (type_decls (nominal (type "Foo") (ty-header (name "Foo"))) - (nominal (type "Foo.Bar") - (ty-header (name "Foo.Bar"))) - (nominal (type "Foo.Bar.Baz") - (ty-header (name "Foo.Bar.Baz")))) + (nominal (type "nominal_deeply_nested_tag.Foo.Bar") + (ty-header (name "nominal_deeply_nested_tag.Foo.Bar"))) + (nominal (type "nominal_deeply_nested_tag.Foo.Bar.Baz") + (ty-header (name "nominal_deeply_nested_tag.Foo.Bar.Baz")))) (expressions - (expr (type "Foo.Bar.Baz")))) + (expr (type "nominal_deeply_nested_tag.Foo.Bar.Baz")))) ~~~ diff --git a/test/snapshots/nominal/nominal_four_level_nested_tag.md b/test/snapshots/nominal/nominal_four_level_nested_tag.md index daca2687a2..861124ef5a 100644 --- a/test/snapshots/nominal/nominal_four_level_nested_tag.md +++ b/test/snapshots/nominal/nominal_four_level_nested_tag.md @@ -91,7 +91,7 @@ value = Foo.Bar.Baz.Qux.Y (can-ir (d-let (p-assign (ident "value")) - (e-nominal (nominal "Foo.Bar.Baz.Qux") + (e-nominal (nominal "nominal_four_level_nested_tag.Foo.Bar.Baz.Qux") (e-tag (name "Y"))) (annotation (ty-lookup (name "Foo.Bar.Baz.Qux") (local)))) @@ -100,15 +100,15 @@ value = Foo.Bar.Baz.Qux.Y (ty-tag-union (ty-tag-name (name "A")))) (s-nominal-decl - (ty-header (name "Foo.Bar")) + (ty-header (name "nominal_four_level_nested_tag.Foo.Bar")) (ty-tag-union (ty-tag-name (name "B")))) (s-nominal-decl - (ty-header (name "Foo.Bar.Baz")) + (ty-header (name "nominal_four_level_nested_tag.Foo.Bar.Baz")) (ty-tag-union (ty-tag-name (name "C")))) (s-nominal-decl - (ty-header (name "Foo.Bar.Baz.Qux")) + (ty-header (name "nominal_four_level_nested_tag.Foo.Bar.Baz.Qux")) (ty-tag-union (ty-tag-name (name "X")) (ty-tag-name (name "Y")) @@ -118,16 +118,16 @@ value = Foo.Bar.Baz.Qux.Y ~~~clojure (inferred-types (defs - (patt (type "Foo.Bar.Baz.Qux"))) + (patt (type "nominal_four_level_nested_tag.Foo.Bar.Baz.Qux"))) (type_decls (nominal (type "Foo") (ty-header (name "Foo"))) - (nominal (type "Foo.Bar") - (ty-header (name "Foo.Bar"))) - (nominal (type "Foo.Bar.Baz") - (ty-header (name "Foo.Bar.Baz"))) - (nominal (type "Foo.Bar.Baz.Qux") - (ty-header (name "Foo.Bar.Baz.Qux")))) + (nominal (type "nominal_four_level_nested_tag.Foo.Bar") + (ty-header (name "nominal_four_level_nested_tag.Foo.Bar"))) + (nominal (type "nominal_four_level_nested_tag.Foo.Bar.Baz") + (ty-header (name "nominal_four_level_nested_tag.Foo.Bar.Baz"))) + (nominal (type "nominal_four_level_nested_tag.Foo.Bar.Baz.Qux") + (ty-header (name "nominal_four_level_nested_tag.Foo.Bar.Baz.Qux")))) (expressions - (expr (type "Foo.Bar.Baz.Qux")))) + (expr (type "nominal_four_level_nested_tag.Foo.Bar.Baz.Qux")))) ~~~ diff --git a/test/snapshots/nominal/nominal_nested_type_ref.md b/test/snapshots/nominal/nominal_nested_type_ref.md index 1ec77095fe..9a3adc519f 100644 --- a/test/snapshots/nominal/nominal_nested_type_ref.md +++ b/test/snapshots/nominal/nominal_nested_type_ref.md @@ -65,7 +65,7 @@ x = Foo.Bar.X (can-ir (d-let (p-assign (ident "x")) - (e-nominal (nominal "Foo.Bar") + (e-nominal (nominal "nominal_nested_type_ref.Foo.Bar") (e-tag (name "X"))) (annotation (ty-lookup (name "Foo.Bar") (local)))) @@ -74,7 +74,7 @@ x = Foo.Bar.X (ty-tag-union (ty-tag-name (name "Whatever")))) (s-nominal-decl - (ty-header (name "Foo.Bar")) + (ty-header (name "nominal_nested_type_ref.Foo.Bar")) (ty-tag-union (ty-tag-name (name "X")) (ty-tag-name (name "Y")) @@ -84,12 +84,12 @@ x = Foo.Bar.X ~~~clojure (inferred-types (defs - (patt (type "Foo.Bar"))) + (patt (type "nominal_nested_type_ref.Foo.Bar"))) (type_decls (nominal (type "Foo") (ty-header (name "Foo"))) - (nominal (type "Foo.Bar") - (ty-header (name "Foo.Bar")))) + (nominal (type "nominal_nested_type_ref.Foo.Bar") + (ty-header (name "nominal_nested_type_ref.Foo.Bar")))) (expressions - (expr (type "Foo.Bar")))) + (expr (type "nominal_nested_type_ref.Foo.Bar")))) ~~~ diff --git a/test/snapshots/nominal/nominal_simple_nested_tag.md b/test/snapshots/nominal/nominal_simple_nested_tag.md index 130da115ab..9878867430 100644 --- a/test/snapshots/nominal/nominal_simple_nested_tag.md +++ b/test/snapshots/nominal/nominal_simple_nested_tag.md @@ -60,14 +60,14 @@ x = Foo.Bar.X (can-ir (d-let (p-assign (ident "x")) - (e-nominal (nominal "Foo.Bar") + (e-nominal (nominal "nominal_simple_nested_tag.Foo.Bar") (e-tag (name "X")))) (s-nominal-decl (ty-header (name "Foo")) (ty-tag-union (ty-tag-name (name "Whatever")))) (s-nominal-decl - (ty-header (name "Foo.Bar")) + (ty-header (name "nominal_simple_nested_tag.Foo.Bar")) (ty-tag-union (ty-tag-name (name "X")) (ty-tag-name (name "Y")) @@ -77,12 +77,12 @@ x = Foo.Bar.X ~~~clojure (inferred-types (defs - (patt (type "Foo.Bar"))) + (patt (type "nominal_simple_nested_tag.Foo.Bar"))) (type_decls (nominal (type "Foo") (ty-header (name "Foo"))) - (nominal (type "Foo.Bar") - (ty-header (name "Foo.Bar")))) + (nominal (type "nominal_simple_nested_tag.Foo.Bar") + (ty-header (name "nominal_simple_nested_tag.Foo.Bar")))) (expressions - (expr (type "Foo.Bar")))) + (expr (type "nominal_simple_nested_tag.Foo.Bar")))) ~~~ diff --git a/test/snapshots/platform/platform_int.md b/test/snapshots/platform/platform_int.md index 10b4934bed..ab5254cd77 100644 --- a/test/snapshots/platform/platform_int.md +++ b/test/snapshots/platform/platform_int.md @@ -63,11 +63,21 @@ multiplyInts : I64, I64 -> I64 ~~~ # CANONICALIZE ~~~clojure -(can-ir (empty true)) +(can-ir + (d-let + (p-assign (ident "multiplyInts")) + (e-anno-only) + (annotation + (ty-fn (effectful false) + (ty-lookup (name "I64") (builtin)) + (ty-lookup (name "I64") (builtin)) + (ty-lookup (name "I64") (builtin)))))) ~~~ # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "Num(Int(Signed64)), Num(Int(Signed64)) -> Num(Int(Signed64))"))) + (expressions + (expr (type "Num(Int(Signed64)), Num(Int(Signed64)) -> Num(Int(Signed64))")))) ~~~ diff --git a/test/snapshots/platform/platform_str.md b/test/snapshots/platform/platform_str.md index b95e1d501f..94120c53ca 100644 --- a/test/snapshots/platform/platform_str.md +++ b/test/snapshots/platform/platform_str.md @@ -61,11 +61,20 @@ processString : Str -> Str ~~~ # CANONICALIZE ~~~clojure -(can-ir (empty true)) +(can-ir + (d-let + (p-assign (ident "processString")) + (e-anno-only) + (annotation + (ty-fn (effectful false) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Str") (builtin)))))) ~~~ # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "Str -> Str"))) + (expressions + (expr (type "Str -> Str")))) ~~~ diff --git a/test/snapshots/static_dispatch/Adv.md b/test/snapshots/static_dispatch/Adv.md index 04fe8feaad..857f3ea72f 100644 --- a/test/snapshots/static_dispatch/Adv.md +++ b/test/snapshots/static_dispatch/Adv.md @@ -317,6 +317,80 @@ main = { # CANONICALIZE ~~~clojure (can-ir + (d-let + (p-assign (ident "Adv.to_str")) + (e-closure + (captures + (capture (ident "s"))) + (e-lambda + (args + (p-nominal + (p-applied-tag))) + (e-lookup-local + (p-assign (ident "s"))))) + (annotation + (ty-fn (effectful false) + (ty-lookup (name "Adv") (local)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "Adv.to_u64")) + (e-closure + (captures + (capture (ident "u"))) + (e-lambda + (args + (p-nominal + (p-applied-tag))) + (e-lookup-local + (p-assign (ident "u"))))) + (annotation + (ty-fn (effectful false) + (ty-lookup (name "Adv") (local)) + (ty-lookup (name "U64") (builtin))))) + (d-let + (p-assign (ident "Adv.update_str")) + (e-closure + (captures + (capture (ident "u64"))) + (e-lambda + (args + (p-nominal + (p-applied-tag)) + (p-assign (ident "next_str"))) + (e-nominal (nominal "Adv") + (e-tag (name "Val") + (args + (e-lookup-local + (p-assign (ident "u64"))) + (e-lookup-local + (p-assign (ident "next_str")))))))) + (annotation + (ty-fn (effectful false) + (ty-lookup (name "Adv") (local)) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Adv") (local))))) + (d-let + (p-assign (ident "Adv.update_u64")) + (e-closure + (captures + (capture (ident "str"))) + (e-lambda + (args + (p-nominal + (p-applied-tag)) + (p-assign (ident "next_u64"))) + (e-nominal (nominal "Adv") + (e-tag (name "Val") + (args + (e-lookup-local + (p-assign (ident "next_u64"))) + (e-lookup-local + (p-assign (ident "str")))))))) + (annotation + (ty-fn (effectful false) + (ty-lookup (name "Adv") (local)) + (ty-lookup (name "U64") (builtin)) + (ty-lookup (name "Adv") (local))))) (d-let (p-assign (ident "mismatch")) (e-block @@ -411,80 +485,6 @@ main = { (ty-tuple (ty-lookup (name "Str") (builtin)) (ty-lookup (name "U64") (builtin))))) - (d-let - (p-assign (ident "Adv.to_str")) - (e-closure - (captures - (capture (ident "s"))) - (e-lambda - (args - (p-nominal - (p-applied-tag))) - (e-lookup-local - (p-assign (ident "s"))))) - (annotation - (ty-fn (effectful false) - (ty-lookup (name "Adv") (local)) - (ty-lookup (name "Str") (builtin))))) - (d-let - (p-assign (ident "Adv.to_u64")) - (e-closure - (captures - (capture (ident "u"))) - (e-lambda - (args - (p-nominal - (p-applied-tag))) - (e-lookup-local - (p-assign (ident "u"))))) - (annotation - (ty-fn (effectful false) - (ty-lookup (name "Adv") (local)) - (ty-lookup (name "U64") (builtin))))) - (d-let - (p-assign (ident "Adv.update_str")) - (e-closure - (captures - (capture (ident "u64"))) - (e-lambda - (args - (p-nominal - (p-applied-tag)) - (p-assign (ident "next_str"))) - (e-nominal (nominal "Adv") - (e-tag (name "Val") - (args - (e-lookup-local - (p-assign (ident "u64"))) - (e-lookup-local - (p-assign (ident "next_str")))))))) - (annotation - (ty-fn (effectful false) - (ty-lookup (name "Adv") (local)) - (ty-lookup (name "Str") (builtin)) - (ty-lookup (name "Adv") (local))))) - (d-let - (p-assign (ident "Adv.update_u64")) - (e-closure - (captures - (capture (ident "str"))) - (e-lambda - (args - (p-nominal - (p-applied-tag)) - (p-assign (ident "next_u64"))) - (e-nominal (nominal "Adv") - (e-tag (name "Val") - (args - (e-lookup-local - (p-assign (ident "next_u64"))) - (e-lookup-local - (p-assign (ident "str")))))))) - (annotation - (ty-fn (effectful false) - (ty-lookup (name "Adv") (local)) - (ty-lookup (name "U64") (builtin)) - (ty-lookup (name "Adv") (local))))) (s-nominal-decl (ty-header (name "Adv")) (ty-tag-union @@ -496,24 +496,24 @@ main = { ~~~clojure (inferred-types (defs - (patt (type "_a")) - (patt (type "_a")) - (patt (type "_a")) - (patt (type "(Str, Num(Int(Unsigned64)))")) (patt (type "Adv -> Str")) (patt (type "Adv -> Num(Int(Unsigned64))")) (patt (type "Adv, Str -> Adv")) - (patt (type "Adv, Num(Int(Unsigned64)) -> Adv"))) + (patt (type "Adv, Num(Int(Unsigned64)) -> Adv")) + (patt (type "_a")) + (patt (type "_a")) + (patt (type "_a")) + (patt (type "(Str, Num(Int(Unsigned64)))"))) (type_decls (nominal (type "Adv") (ty-header (name "Adv")))) (expressions - (expr (type "_a")) - (expr (type "_a")) - (expr (type "_a")) - (expr (type "(Str, Num(Int(Unsigned64)))")) (expr (type "Adv -> Str")) (expr (type "Adv -> Num(Int(Unsigned64))")) (expr (type "Adv, Str -> Adv")) - (expr (type "Adv, Num(Int(Unsigned64)) -> Adv")))) + (expr (type "Adv, Num(Int(Unsigned64)) -> Adv")) + (expr (type "_a")) + (expr (type "_a")) + (expr (type "_a")) + (expr (type "(Str, Num(Int(Unsigned64)))")))) ~~~ diff --git a/test/snapshots/static_dispatch/Basic.md b/test/snapshots/static_dispatch/Basic.md index bfe89124b1..43c80003c7 100644 --- a/test/snapshots/static_dispatch/Basic.md +++ b/test/snapshots/static_dispatch/Basic.md @@ -167,6 +167,35 @@ main = (helper1(val), helper2(val)) # CANONICALIZE ~~~clojure (can-ir + (d-let + (p-assign (ident "Basic.to_str")) + (e-closure + (captures + (capture (ident "s"))) + (e-lambda + (args + (p-nominal + (p-applied-tag))) + (e-lookup-local + (p-assign (ident "s"))))) + (annotation + (ty-fn (effectful false) + (ty-lookup (name "Basic") (local)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "Basic.to_str2")) + (e-lambda + (args + (p-assign (ident "test"))) + (e-dot-access (field "to_str") + (receiver + (e-lookup-local + (p-assign (ident "test")))) + (args))) + (annotation + (ty-fn (effectful false) + (ty-lookup (name "Basic") (local)) + (ty-lookup (name "Str") (builtin))))) (d-let (p-assign (ident "helper1")) (e-lambda @@ -232,35 +261,6 @@ main = (helper1(val), helper2(val)) (ty-tuple (ty-lookup (name "Str") (builtin)) (ty-lookup (name "Str") (builtin))))) - (d-let - (p-assign (ident "Basic.to_str")) - (e-closure - (captures - (capture (ident "s"))) - (e-lambda - (args - (p-nominal - (p-applied-tag))) - (e-lookup-local - (p-assign (ident "s"))))) - (annotation - (ty-fn (effectful false) - (ty-lookup (name "Basic") (local)) - (ty-lookup (name "Str") (builtin))))) - (d-let - (p-assign (ident "Basic.to_str2")) - (e-lambda - (args - (p-assign (ident "test"))) - (e-dot-access (field "to_str") - (receiver - (e-lookup-local - (p-assign (ident "test")))) - (args))) - (annotation - (ty-fn (effectful false) - (ty-lookup (name "Basic") (local)) - (ty-lookup (name "Str") (builtin))))) (s-nominal-decl (ty-header (name "Basic")) (ty-tag-union @@ -271,20 +271,20 @@ main = (helper1(val), helper2(val)) ~~~clojure (inferred-types (defs + (patt (type "Basic -> Str")) + (patt (type "Basic -> Str")) (patt (type "a -> b where [a.to_str : a -> b]")) (patt (type "a -> b where [a.to_str2 : a -> b]")) (patt (type "Basic")) - (patt (type "(Str, Str)")) - (patt (type "Basic -> Str")) - (patt (type "Basic -> Str"))) + (patt (type "(Str, Str)"))) (type_decls (nominal (type "Basic") (ty-header (name "Basic")))) (expressions + (expr (type "Basic -> Str")) + (expr (type "Basic -> Str")) (expr (type "a -> b where [a.to_str : a -> b]")) (expr (type "a -> b where [a.to_str2 : a -> b]")) (expr (type "Basic")) - (expr (type "(Str, Str)")) - (expr (type "Basic -> Str")) - (expr (type "Basic -> Str")))) + (expr (type "(Str, Str)")))) ~~~ diff --git a/test/snapshots/static_dispatch/BasicNoAnno.md b/test/snapshots/static_dispatch/BasicNoAnno.md index 141d45e519..e409204e09 100644 --- a/test/snapshots/static_dispatch/BasicNoAnno.md +++ b/test/snapshots/static_dispatch/BasicNoAnno.md @@ -124,6 +124,27 @@ main = (helper1(val), helper2(val)) # CANONICALIZE ~~~clojure (can-ir + (d-let + (p-assign (ident "BasicNoAnno.to_str")) + (e-closure + (captures + (capture (ident "s"))) + (e-lambda + (args + (p-nominal + (p-applied-tag))) + (e-lookup-local + (p-assign (ident "s")))))) + (d-let + (p-assign (ident "BasicNoAnno.to_str2")) + (e-lambda + (args + (p-assign (ident "test"))) + (e-dot-access (field "to_str") + (receiver + (e-lookup-local + (p-assign (ident "test")))) + (args)))) (d-let (p-assign (ident "helper1")) (e-lambda @@ -169,27 +190,6 @@ main = (helper1(val), helper2(val)) (ty-tuple (ty-lookup (name "Str") (builtin)) (ty-lookup (name "Str") (builtin))))) - (d-let - (p-assign (ident "BasicNoAnno.to_str")) - (e-closure - (captures - (capture (ident "s"))) - (e-lambda - (args - (p-nominal - (p-applied-tag))) - (e-lookup-local - (p-assign (ident "s")))))) - (d-let - (p-assign (ident "BasicNoAnno.to_str2")) - (e-lambda - (args - (p-assign (ident "test"))) - (e-dot-access (field "to_str") - (receiver - (e-lookup-local - (p-assign (ident "test")))) - (args)))) (s-nominal-decl (ty-header (name "BasicNoAnno")) (ty-tag-union @@ -200,20 +200,20 @@ main = (helper1(val), helper2(val)) ~~~clojure (inferred-types (defs + (patt (type "BasicNoAnno -> Str")) + (patt (type "a -> b where [a.to_str : a -> b]")) (patt (type "a -> b where [a.to_str : a -> b]")) (patt (type "a -> b where [a.to_str2 : a -> b]")) (patt (type "BasicNoAnno")) - (patt (type "(Str, Str)")) - (patt (type "BasicNoAnno -> Str")) - (patt (type "a -> b where [a.to_str : a -> b]"))) + (patt (type "(Str, Str)"))) (type_decls (nominal (type "BasicNoAnno") (ty-header (name "BasicNoAnno")))) (expressions + (expr (type "BasicNoAnno -> Str")) + (expr (type "a -> b where [a.to_str : a -> b]")) (expr (type "a -> b where [a.to_str : a -> b]")) (expr (type "a -> b where [a.to_str2 : a -> b]")) (expr (type "BasicNoAnno")) - (expr (type "(Str, Str)")) - (expr (type "BasicNoAnno -> Str")) - (expr (type "a -> b where [a.to_str : a -> b]")))) + (expr (type "(Str, Str)")))) ~~~ diff --git a/test/snapshots/static_dispatch/MethodDispatch.md b/test/snapshots/static_dispatch/MethodDispatch.md index 6a93251051..9e0068399d 100644 --- a/test/snapshots/static_dispatch/MethodDispatch.md +++ b/test/snapshots/static_dispatch/MethodDispatch.md @@ -231,6 +231,47 @@ NO CHANGE # CANONICALIZE ~~~clojure (can-ir + (d-let + (p-assign (ident "Container.get_value")) + (e-closure + (captures + (capture (ident "s"))) + (e-lambda + (args + (p-nominal + (p-applied-tag))) + (e-lookup-local + (p-assign (ident "s"))))) + (annotation + (ty-fn (effectful false) + (ty-lookup (name "Container") (local)) + (ty-lookup (name "Str") (builtin))))) + (d-let + (p-assign (ident "Container.transform")) + (e-closure + (captures + (capture (ident "s"))) + (e-lambda + (args + (p-nominal + (p-applied-tag)) + (p-assign (ident "fn"))) + (e-nominal (nominal "Container") + (e-tag (name "Box") + (args + (e-call + (e-lookup-local + (p-assign (ident "fn"))) + (e-lookup-local + (p-assign (ident "s"))))))))) + (annotation + (ty-fn (effectful false) + (ty-lookup (name "Container") (local)) + (ty-parens + (ty-fn (effectful false) + (ty-lookup (name "Str") (builtin)) + (ty-lookup (name "Str") (builtin)))) + (ty-lookup (name "Container") (local))))) (d-let (p-assign (ident "extract")) (e-lambda @@ -351,47 +392,6 @@ NO CHANGE (ty-lookup (name "Str") (builtin)) (ty-lookup (name "Str") (builtin)) (ty-lookup (name "Str") (builtin))))) - (d-let - (p-assign (ident "Container.get_value")) - (e-closure - (captures - (capture (ident "s"))) - (e-lambda - (args - (p-nominal - (p-applied-tag))) - (e-lookup-local - (p-assign (ident "s"))))) - (annotation - (ty-fn (effectful false) - (ty-lookup (name "Container") (local)) - (ty-lookup (name "Str") (builtin))))) - (d-let - (p-assign (ident "Container.transform")) - (e-closure - (captures - (capture (ident "s"))) - (e-lambda - (args - (p-nominal - (p-applied-tag)) - (p-assign (ident "fn"))) - (e-nominal (nominal "Container") - (e-tag (name "Box") - (args - (e-call - (e-lookup-local - (p-assign (ident "fn"))) - (e-lookup-local - (p-assign (ident "s"))))))))) - (annotation - (ty-fn (effectful false) - (ty-lookup (name "Container") (local)) - (ty-parens - (ty-fn (effectful false) - (ty-lookup (name "Str") (builtin)) - (ty-lookup (name "Str") (builtin)))) - (ty-lookup (name "Container") (local))))) (s-nominal-decl (ty-header (name "Container")) (ty-tag-union @@ -402,6 +402,8 @@ NO CHANGE ~~~clojure (inferred-types (defs + (patt (type "Container -> Str")) + (patt (type "Container, Str -> Str -> Container")) (patt (type "a -> Str where [a.get_value : a -> Str]")) (patt (type "a, Str -> Str -> a where [a.transform : a, Str -> Str -> aa.transform : a, Str -> Str -> a]")) (patt (type "Container")) @@ -409,13 +411,13 @@ NO CHANGE (patt (type "Str")) (patt (type "Str")) (patt (type "Container")) - (patt (type "(Str, Str, Str)")) - (patt (type "Container -> Str")) - (patt (type "Container, Str -> Str -> Container"))) + (patt (type "(Str, Str, Str)"))) (type_decls (nominal (type "Container") (ty-header (name "Container")))) (expressions + (expr (type "Container -> Str")) + (expr (type "Container, Str -> Str -> Container")) (expr (type "a -> Str where [a.get_value : a -> Str]")) (expr (type "a, Str -> Str -> a where [a.transform : a, Str -> Str -> aa.transform : a, Str -> Str -> a]")) (expr (type "Container")) @@ -423,7 +425,5 @@ NO CHANGE (expr (type "Str")) (expr (type "Str")) (expr (type "Container")) - (expr (type "(Str, Str, Str)")) - (expr (type "Container -> Str")) - (expr (type "Container, Str -> Str -> Container")))) + (expr (type "(Str, Str, Str)")))) ~~~ diff --git a/test/snapshots/type_annotation_missing_parens.md b/test/snapshots/type_annotation_missing_parens.md index 6156718772..7b3824382a 100644 --- a/test/snapshots/type_annotation_missing_parens.md +++ b/test/snapshots/type_annotation_missing_parens.md @@ -9,6 +9,7 @@ nums : List U8 ~~~ # EXPECTED PARSE ERROR - type_annotation_missing_parens.md:2:1:2:1 +TOO FEW ARGS - type_annotation_missing_parens.md:1:8:1:12 # PROBLEMS **PARSE ERROR** Type applications require parentheses around their type arguments. @@ -33,6 +34,16 @@ Other valid examples: ^ +**TOO FEW ARGS** +The type _List_ expects argument, but got instead. +**type_annotation_missing_parens.md:1:8:1:12:** +```roc +nums : List U8 +``` + ^^^^ + + + # TOKENS ~~~zig LowerIdent,OpColon,UpperIdent,UpperIdent, @@ -53,11 +64,18 @@ nums : List ~~~ # CANONICALIZE ~~~clojure -(can-ir (empty true)) +(can-ir + (d-let + (p-assign (ident "nums")) + (e-anno-only) + (annotation + (ty-lookup (name "List") (builtin))))) ~~~ # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "Error"))) + (expressions + (expr (type "Error")))) ~~~ diff --git a/test/snapshots/type_annotations.md b/test/snapshots/type_annotations.md index c690c20373..5084ca529a 100644 --- a/test/snapshots/type_annotations.md +++ b/test/snapshots/type_annotations.md @@ -143,21 +143,28 @@ NO CHANGE (ty-malformed)) (ty-apply (name "Result") (builtin) (ty-record) - (ty-underscore)))))) + (ty-underscore))))) + (d-let + (p-assign (ident "tag_tuple")) + (e-anno-only) + (annotation + (ty-malformed)))) ~~~ # TYPES ~~~clojure (inferred-types (defs + (patt (type "Num(Int(Unsigned64))")) (patt (type "Error")) - (patt (type "Error")) - (patt (type "Error")) - (patt (type "Error")) + (patt (type "(_a, _b, _c)")) + (patt (type "Num(Int(Unsigned8)), Num(Int(Unsigned16)) -> Num(Int(Unsigned32))")) + (patt (type "List(Error) -> Try({ }, _a)")) (patt (type "Error"))) (expressions + (expr (type "Num(Int(Unsigned64))")) (expr (type "Error")) - (expr (type "Error")) - (expr (type "Error")) - (expr (type "Error")) + (expr (type "(_a, _b, _c)")) + (expr (type "Num(Int(Unsigned8)), Num(Int(Unsigned16)) -> Num(Int(Unsigned32))")) + (expr (type "List(Error) -> Try({ }, _a)")) (expr (type "Error")))) ~~~ diff --git a/test/snapshots/where_clause/where_clauses_10.md b/test/snapshots/where_clause/where_clauses_10.md index ccafb9e033..82afe21de1 100644 --- a/test/snapshots/where_clause/where_clauses_10.md +++ b/test/snapshots/where_clause/where_clauses_10.md @@ -71,6 +71,18 @@ decode_things # After member name # CANONICALIZE ~~~clojure (can-ir + (d-let + (p-assign (ident "decode_things")) + (e-anno-only) + (annotation + (ty-fn (effectful false) + (ty-apply (name "List") (builtin) + (ty-apply (name "List") (builtin) + (ty-lookup (name "U8") (builtin)))) + (ty-apply (name "List") (builtin) + (ty-rigid-var (name "a")))) + (where + (alias (ty-rigid-var-lookup (ty-rigid-var (name "a"))) (name "Decode"))))) (s-import (module "Decode") (exposes (exposed (name "Decode") (wildcard false))))) @@ -78,6 +90,8 @@ decode_things # After member name # TYPES ~~~clojure (inferred-types - (defs) - (expressions)) + (defs + (patt (type "List(List(Num(Int(Unsigned8)))) -> List(a)"))) + (expressions + (expr (type "List(List(Num(Int(Unsigned8)))) -> List(a)")))) ~~~ diff --git a/test/snapshots/where_clause/where_clauses_error_cases.md b/test/snapshots/where_clause/where_clauses_error_cases.md index d4490b8753..3ef4e7ec2f 100644 --- a/test/snapshots/where_clause/where_clauses_error_cases.md +++ b/test/snapshots/where_clause/where_clauses_error_cases.md @@ -194,15 +194,29 @@ broken_fn3 : a -> b (ty-rigid-var (name "a")) (ty-rigid-var (name "b"))) (where - (malformed))))) + (malformed)))) + (d-let + (p-assign (ident "broken_fn3")) + (e-anno-only) + (annotation + (ty-fn (effectful false) + (ty-rigid-var (name "a")) + (ty-rigid-var (name "b"))) + (where + (method (ty-rigid-var (name "c")) (name "method") + (args + (ty-rigid-var-lookup (ty-rigid-var (name "c")))) + (ty-rigid-var (name "d"))))))) ~~~ # TYPES ~~~clojure (inferred-types (defs - (patt (type "Error")) - (patt (type "Error"))) + (patt (type "a -> b")) + (patt (type "a -> b")) + (patt (type "a -> b"))) (expressions - (expr (type "Error")) - (expr (type "Error")))) + (expr (type "a -> b")) + (expr (type "a -> b")) + (expr (type "a -> b")))) ~~~