diff --git a/src/canonicalize/Can.zig b/src/canonicalize/Can.zig index 4c32cdfffb..203708f9aa 100644 --- a/src/canonicalize/Can.zig +++ b/src/canonicalize/Can.zig @@ -139,6 +139,21 @@ const TypeVarProblem = struct { ast_anno: AST.TypeAnno.Idx, }; +const ModuleFoundStatus = enum { + module_was_found, + module_not_found, +}; + +const TypeBindingLocation = struct { + scope_index: usize, + binding: *Scope.TypeBinding, +}; + +const TypeBindingLocationConst = struct { + scope_index: usize, + binding: *const Scope.TypeBinding, +}; + /// Deinitialize canonicalizer resources pub fn deinit( self: *Self, @@ -237,17 +252,8 @@ pub fn init( // Use the same alias as the module name for auto-imports const alias = module_name_ident; - // Process the import using the shared helper function - // This will: - // - Get or create Import.Idx - // - Introduce module alias to scope - // - Process type imports - // - Introduce exposed items (including auto-expose for type modules) - // - Store import_indices mapping - // - Create CIR import statement - // - Add imported module to scope - // - Check module exists - _ = try result.processModuleImport(module_name_ident, alias, empty_exposed_span, auto_import_region); + // Process the import using the aliased import path (auto-imports always have an alias) + _ = try result.importWithAlias(module_name_ident, alias, empty_exposed_span, auto_import_region); } } } @@ -1389,7 +1395,7 @@ fn createExposedScope( // Use a dummy statement index - we just need to track that it's exposed const dummy_idx = @as(Statement.Idx, @enumFromInt(0)); - try self.exposed_scope.put(gpa, .type_decl, ident_idx, dummy_idx); + try self.exposed_scope.type_bindings.put(gpa, ident_idx, Scope.TypeBinding{ .local_nominal = dummy_idx }); } // Store by text in a temporary hash map, since indices may change @@ -1422,7 +1428,7 @@ fn createExposedScope( // Use a dummy statement index - we just need to track that it's exposed const dummy_idx = @as(Statement.Idx, @enumFromInt(0)); - try self.exposed_scope.put(gpa, .type_decl, ident_idx, dummy_idx); + try self.exposed_scope.type_bindings.put(gpa, ident_idx, Scope.TypeBinding{ .local_nominal = dummy_idx }); } // Store by text in a temporary hash map, since indices may change @@ -1597,7 +1603,75 @@ fn bringIngestedFileIntoScope( /// Process a module import with common logic shared by explicit imports and auto-imports. /// This handles everything after module name and alias resolution. -fn processModuleImport( +/// Process import with an alias (normal import like `import json.Json` or `import json.Json as J`) +fn importAliased( + self: *Self, + module_name: Ident.Idx, + alias_tok: ?Token.Idx, + exposed_items_span: CIR.ExposedItem.Span, + import_region: Region, +) std.mem.Allocator.Error!?Statement.Idx { + const module_name_text = self.env.getIdent(module_name); + + // 1. Get or create Import.Idx for this module + const module_import_idx = try self.env.imports.getOrPut( + self.env.gpa, + self.env.common.getStringStore(), + module_name_text, + ); + + // 2. Resolve the alias + const alias = try self.resolveModuleAlias(alias_tok, module_name) orelse return null; + + // 3. Add to scope: alias -> module_name mapping + try self.scopeIntroduceModuleAlias(alias, module_name); + + // 4. Process type imports from this module + try self.processTypeImports(module_name, alias); + + // 5. Introduce exposed items into scope (includes auto-expose for type modules) + try self.introduceItemsAliased(exposed_items_span, module_name, alias, import_region, module_import_idx); + + // 6. Store the mapping from module name to Import.Idx + try self.import_indices.put(self.env.gpa, module_name_text, module_import_idx); + + // 7. Create CIR import statement + const cir_import = Statement{ + .s_import = .{ + .module_name_tok = module_name, + .qualifier_tok = null, + .alias_tok = null, + .exposes = exposed_items_span, + }, + }; + + const import_idx = try self.env.addStatement(cir_import, import_region); + try self.env.store.addScratchStatement(import_idx); + + // 8. Add the module to the current scope so it can be used in qualified lookups + const current_scope = self.currentScope(); + _ = try current_scope.introduceImportedModule(self.env.gpa, module_name_text, module_import_idx); + + // 9. Check that this module actually exists, and if not report an error + if (self.module_envs) |envs_map| { + if (!envs_map.contains(module_name)) { + try self.env.pushDiagnostic(Diagnostic{ .module_not_found = .{ + .module_name = module_name, + .region = import_region, + } }); + } + } else { + try self.env.pushDiagnostic(Diagnostic{ .module_not_found = .{ + .module_name = module_name, + .region = import_region, + } }); + } + + return import_idx; +} + +/// Process import with an alias provided directly as an Ident.Idx (used for auto-imports) +fn importWithAlias( self: *Self, module_name: Ident.Idx, alias: Ident.Idx, @@ -1620,7 +1694,7 @@ fn processModuleImport( try self.processTypeImports(module_name, alias); // 4. Introduce exposed items into scope (includes auto-expose for type modules) - try self.introduceExposedItemsIntoScope(exposed_items_span, module_name, alias, import_region); + try self.introduceItemsAliased(exposed_items_span, module_name, alias, import_region, module_import_idx); // 5. Store the mapping from module name to Import.Idx try self.import_indices.put(self.env.gpa, module_name_text, module_import_idx); @@ -1644,9 +1718,64 @@ fn processModuleImport( // 8. Check that this module actually exists, and if not report an error if (self.module_envs) |envs_map| { - // Check if the module exists if (!envs_map.contains(module_name)) { - // Module not found - create diagnostic + try self.env.pushDiagnostic(Diagnostic{ .module_not_found = .{ + .module_name = module_name, + .region = import_region, + } }); + } + } else { + try self.env.pushDiagnostic(Diagnostic{ .module_not_found = .{ + .module_name = module_name, + .region = import_region, + } }); + } + + return import_idx; +} + +/// Process auto-expose import without alias (like `import json.Parser.Config`) +fn importUnaliased( + self: *Self, + module_name: Ident.Idx, + exposed_items_span: CIR.ExposedItem.Span, + import_region: Region, +) std.mem.Allocator.Error!Statement.Idx { + const module_name_text = self.env.getIdent(module_name); + + // 1. Get or create Import.Idx for this module + const module_import_idx = try self.env.imports.getOrPut( + self.env.gpa, + self.env.common.getStringStore(), + module_name_text, + ); + + // 2. Introduce exposed items into scope (no alias, no auto-expose of main type) + try self.introduceItemsUnaliased(exposed_items_span, module_name, import_region, module_import_idx); + + // 3. Store the mapping from module name to Import.Idx + try self.import_indices.put(self.env.gpa, module_name_text, module_import_idx); + + // 4. Create CIR import statement + const cir_import = Statement{ + .s_import = .{ + .module_name_tok = module_name, + .qualifier_tok = null, + .alias_tok = null, + .exposes = exposed_items_span, + }, + }; + + const import_idx = try self.env.addStatement(cir_import, import_region); + try self.env.store.addScratchStatement(import_idx); + + // 5. Add the module to the current scope so it can be used in qualified lookups + const current_scope = self.currentScope(); + _ = try current_scope.introduceImportedModule(self.env.gpa, module_name_text, module_import_idx); + + // 6. Check that this module actually exists, and if not report an error + if (self.module_envs) |envs_map| { + if (!envs_map.contains(module_name)) { try self.env.pushDiagnostic(Diagnostic{ .module_not_found = .{ .module_name = module_name, .region = import_region, @@ -1725,17 +1854,17 @@ fn canonicalizeImportStatement( } }; - // 2. Determine the alias (either explicit or default to last part) - const alias = try self.resolveModuleAlias(import_stmt.alias_tok, module_name) orelse return null; - - // 3. Convert exposed items to CIR + // 2. Convert exposed items to CIR const scratch_start = self.env.store.scratchExposedItemTop(); try self.convertASTExposesToCIR(import_stmt.exposes); const cir_exposes = try self.env.store.exposedItemSpanFrom(scratch_start); const import_region = self.parse_ir.tokenizedRegionToRegion(import_stmt.region); - // 4. Process the import using shared logic - return try self.processModuleImport(module_name, alias, cir_exposes, import_region); + // 3. Dispatch to the appropriate handler based on whether this is a nested import + return if (import_stmt.nested_import) + try self.importUnaliased(module_name, cir_exposes, import_region) + else + try self.importAliased(module_name, import_stmt.alias_tok, cir_exposes, import_region); } /// Resolve the module alias name from either explicit alias or module name @@ -1849,29 +1978,26 @@ fn convertASTExposesToCIR( } } -/// Introduce converted exposed items into scope for identifier resolution -fn introduceExposedItemsIntoScope( +/// Introduce converted exposed items into scope for aliased imports +/// For imports like `import json.Parser exposing [Config]`, this will: +/// 1. Auto-expose the module's main type if it's a type module +/// 2. Process explicitly exposed items +fn introduceItemsAliased( self: *Self, exposed_items_span: CIR.ExposedItem.Span, module_name: Ident.Idx, module_alias: Ident.Idx, import_region: Region, + module_import_idx: CIR.Import.Idx, ) std.mem.Allocator.Error!void { const exposed_items_slice = self.env.store.sliceExposedItems(exposed_items_span); + const current_scope = self.currentScope(); - // If we have module_envs, validate the imports if (self.module_envs) |envs_map| { - // Check if the module exists - if (!envs_map.contains(module_name)) { - // Module not found - Module existence check is already done in canonicalizeImportStatement, - // so there is no need to create another diagnostic here for module_not_found - return; - } + const module_entry = envs_map.get(module_name) orelse return; + const module_env = module_entry.env; - // Get the module's exposed_items - const module_env = envs_map.get(module_name).?.env; - - // For type modules, auto-introduce the main type with the alias name + // Auto-expose the module's main type for type modules switch (module_env.module_kind) { .type_module => |main_type_ident| { if (module_env.containsExposedById(main_type_ident)) { @@ -1880,6 +2006,18 @@ fn introduceExposedItemsIntoScope( .original_name = main_type_ident, }; try self.scopeIntroduceExposedItem(module_alias, item_info); + + const target_node_idx = module_env.getExposedNodeIndexById(main_type_ident); + try self.setExternalTypeBinding( + current_scope, + module_alias, + module_name, + main_type_ident, + target_node_idx, + module_import_idx, + import_region, + .module_was_found, + ); } }, else => {}, @@ -1942,6 +2080,129 @@ fn introduceExposedItemsIntoScope( } } +/// Introduce converted exposed items into scope for auto-expose imports +/// For imports like `import json.Parser.Config`, this will: +/// 1. Skip auto-exposing the module's main type (no alias exists) +/// 2. Process only explicitly exposed items +fn introduceItemsUnaliased( + self: *Self, + exposed_items_span: CIR.ExposedItem.Span, + module_name: Ident.Idx, + import_region: Region, + module_import_idx: CIR.Import.Idx, +) std.mem.Allocator.Error!void { + const exposed_items_slice = self.env.store.sliceExposedItems(exposed_items_span); + const current_scope = self.currentScope(); + + if (self.module_envs) |envs_map| { + const module_entry = envs_map.get(module_name) orelse return; + const module_env = module_entry.env; + + // No auto-expose of main type - only process explicitly exposed items + for (exposed_items_slice) |exposed_item_idx| { + const exposed_item = self.env.store.getExposedItem(exposed_item_idx); + const local_ident = exposed_item.alias orelse exposed_item.name; + const local_name_text = self.env.getIdent(local_ident); + + const target_ident = module_env.common.findIdent(self.env.getIdent(exposed_item.name)); + const is_type_name = local_name_text.len > 0 and local_name_text[0] >= 'A' and local_name_text[0] <= 'Z'; + + if (target_ident) |ident_in_module| { + if (!module_env.containsExposedById(ident_in_module)) { + if (is_type_name) { + try self.env.pushDiagnostic(Diagnostic{ .type_not_exposed = .{ + .module_name = module_name, + .type_name = exposed_item.name, + .region = import_region, + } }); + } else { + try self.env.pushDiagnostic(Diagnostic{ .value_not_exposed = .{ + .module_name = module_name, + .value_name = exposed_item.name, + .region = import_region, + } }); + } + continue; + } + + const target_node_idx = module_env.getExposedNodeIndexById(ident_in_module) orelse { + if (is_type_name) { + try self.env.pushDiagnostic(Diagnostic{ .type_not_exposed = .{ + .module_name = module_name, + .type_name = exposed_item.name, + .region = import_region, + } }); + } else { + try self.env.pushDiagnostic(Diagnostic{ .value_not_exposed = .{ + .module_name = module_name, + .value_name = exposed_item.name, + .region = import_region, + } }); + } + continue; + }; + + const item_info = Scope.ExposedItemInfo{ + .module_name = module_name, + .original_name = exposed_item.name, + }; + try self.scopeIntroduceExposedItem(local_ident, item_info); + + if (is_type_name) { + try self.setExternalTypeBinding( + current_scope, + local_ident, + module_name, + exposed_item.name, + target_node_idx, + module_import_idx, + import_region, + .module_was_found, + ); + } + } else { + if (local_name_text.len > 0 and local_name_text[0] >= 'A' and local_name_text[0] <= 'Z') { + try self.env.pushDiagnostic(Diagnostic{ .type_not_exposed = .{ + .module_name = module_name, + .type_name = exposed_item.name, + .region = import_region, + } }); + } else { + try self.env.pushDiagnostic(Diagnostic{ .value_not_exposed = .{ + .module_name = module_name, + .value_name = exposed_item.name, + .region = import_region, + } }); + } + } + } + } else { + for (exposed_items_slice) |exposed_item_idx| { + const exposed_item = self.env.store.getExposedItem(exposed_item_idx); + const local_ident = exposed_item.alias orelse exposed_item.name; + const local_name_text = self.env.getIdent(local_ident); + const item_info = Scope.ExposedItemInfo{ + .module_name = module_name, + .original_name = exposed_item.name, + }; + try self.scopeIntroduceExposedItem(local_ident, item_info); + + if (local_name_text.len > 0 and local_name_text[0] >= 'A' and local_name_text[0] <= 'Z') { + try self.setExternalTypeBinding( + current_scope, + local_ident, + module_name, + exposed_item.name, + null, + module_import_idx, + import_region, + .module_not_found, + ); + } + } + } +} + /// Canonicalize a decl with an annotation fn canonicalizeDeclWithAnnotation( self: *Self, @@ -5398,12 +5659,56 @@ fn canonicalizeTypeAnnoBasicType( .base = .{ .builtin = builtin_type }, } }, region); } else { - // If it's not a builtin, look up in scope - if (self.scopeLookupTypeDecl(type_name_ident)) |type_decl_idx| { - return try self.env.addTypeAnno(CIR.TypeAnno{ .lookup = .{ - .name = type_name_ident, - .base = .{ .local = .{ .decl_idx = type_decl_idx } }, - } }, region); + // If it's not a builtin, look up in scope using unified type bindings + if (self.scopeLookupTypeBinding(type_name_ident)) |binding_location| { + const binding = binding_location.binding.*; + return switch (binding) { + .local_nominal => |stmt| try self.env.addTypeAnno(CIR.TypeAnno{ .lookup = .{ + .name = type_name_ident, + .base = .{ .local = .{ .decl_idx = stmt } }, + } }, region), + .local_alias => |stmt| try self.env.addTypeAnno(CIR.TypeAnno{ .lookup = .{ + .name = type_name_ident, + .base = .{ .local = .{ .decl_idx = stmt } }, + } }, region), + .associated_nominal => |stmt| try self.env.addTypeAnno(CIR.TypeAnno{ .lookup = .{ + .name = type_name_ident, + .base = .{ .local = .{ .decl_idx = stmt } }, + } }, region), + .external_nominal => |external| blk: { + const import_idx = external.import_idx orelse { + break :blk try self.env.pushMalformed(TypeAnno.Idx, Diagnostic{ .module_not_imported = .{ + .module_name = external.module_ident, + .region = type_name_region, + } }); + }; + + const target_node_idx = external.target_node_idx orelse { + // Check if the module was not found + if (external.module_not_found) { + break :blk try self.env.pushMalformed(TypeAnno.Idx, Diagnostic{ .type_from_missing_module = .{ + .module_name = external.module_ident, + .type_name = type_name_ident, + .region = type_name_region, + } }); + } else { + break :blk try self.env.pushMalformed(TypeAnno.Idx, Diagnostic{ .type_not_exposed = .{ + .module_name = external.module_ident, + .type_name = type_name_ident, + .region = type_name_region, + } }); + } + }; + + break :blk try self.env.addTypeAnno(CIR.TypeAnno{ .lookup = .{ + .name = type_name_ident, + .base = .{ .external = .{ + .module_idx = import_idx, + .target_node_idx = target_node_idx, + } }, + } }, region); + }, + }; } // Check if this is an auto-imported type from module_envs @@ -7163,12 +7468,14 @@ fn scopeIntroduceTypeDecl( while (i > 0) { i -= 1; const scope = &self.scopes.items[i]; - switch (scope.lookupTypeDecl(name_ident)) { - .found => |type_decl_idx| { - shadowed_in_parent = type_decl_idx; - break; - }, - .not_found => continue, + if (scope.type_bindings.get(name_ident)) |binding| { + shadowed_in_parent = switch (binding) { + .local_nominal => |stmt| stmt, + .local_alias => |stmt| stmt, + .associated_nominal => |stmt| stmt, + .external_nominal => null, + }; + if (shadowed_in_parent) |_| break; } } } @@ -7274,15 +7581,40 @@ fn scopeLookupTypeDecl(self: *Self, ident_idx: Ident.Idx) ?Statement.Idx { i -= 1; const scope = &self.scopes.items[i]; - // Check for type aliases (unqualified names in associated blocks) - if (scope.lookupTypeAlias(ident_idx)) |aliased_decl| { - return aliased_decl; + // Check unified type bindings + if (scope.type_bindings.get(ident_idx)) |binding| { + return switch (binding) { + .local_nominal => |stmt| stmt, + .local_alias => |stmt| stmt, + .associated_nominal => |stmt| stmt, + .external_nominal => null, // External types don't have local Statement.Idx + }; } + } - // Check regular type declarations - switch (scope.lookupTypeDecl(ident_idx)) { - .found => |type_decl_idx| return type_decl_idx, - .not_found => continue, + return null; +} + +fn scopeLookupTypeBinding(self: *Self, ident_idx: Ident.Idx) ?TypeBindingLocation { + var i = self.scopes.items.len; + while (i > 0) { + i -= 1; + const scope = &self.scopes.items[i]; + if (scope.type_bindings.getPtr(ident_idx)) |binding_ptr| { + return TypeBindingLocation{ .scope_index = i, .binding = binding_ptr }; + } + } + + return null; +} + +fn scopeLookupTypeBindingConst(self: *const Self, ident_idx: Ident.Idx) ?TypeBindingLocationConst { + var i = self.scopes.items.len; + while (i > 0) { + i -= 1; + const scope = &self.scopes.items[i]; + if (scope.type_bindings.getPtr(ident_idx)) |binding_ptr| { + return TypeBindingLocationConst{ .scope_index = i, .binding = binding_ptr }; } } @@ -7428,6 +7760,30 @@ pub fn scopeIntroduceExposedItem(self: *Self, item_name: Ident.Idx, item_info: S } } +/// Set an external type binding for an imported nominal type +fn setExternalTypeBinding( + self: *Self, + scope: *Scope, + local_ident: Ident.Idx, + module_ident: Ident.Idx, + original_ident: Ident.Idx, + target_node_idx: ?u16, + module_import_idx: CIR.Import.Idx, + origin_region: Region, + module_found_status: ModuleFoundStatus, +) !void { + try scope.type_bindings.put(self.env.gpa, local_ident, Scope.TypeBinding{ + .external_nominal = .{ + .module_ident = module_ident, + .original_ident = original_ident, + .target_node_idx = target_node_idx, + .import_idx = module_import_idx, + .origin_region = origin_region, + .module_not_found = module_found_status == .module_not_found, + }, + }); +} + /// Look up an exposed item in parent scopes (for shadowing detection) fn scopeLookupExposedItemInParentScopes(self: *const Self, item_name: Ident.Idx) ?Scope.ExposedItemInfo { // Search from second-innermost to outermost scope (excluding current scope) diff --git a/src/canonicalize/Diagnostic.zig b/src/canonicalize/Diagnostic.zig index 232f258de9..dc425d0888 100644 --- a/src/canonicalize/Diagnostic.zig +++ b/src/canonicalize/Diagnostic.zig @@ -118,6 +118,11 @@ pub const Diagnostic = union(enum) { type_name: Ident.Idx, region: Region, }, + type_from_missing_module: struct { + module_name: Ident.Idx, + type_name: Ident.Idx, + region: Region, + }, module_not_imported: struct { module_name: Ident.Idx, region: Region, @@ -269,6 +274,7 @@ pub const Diagnostic = union(enum) { .module_not_found => |d| d.region, .value_not_exposed => |d| d.region, .type_not_exposed => |d| d.region, + .type_from_missing_module => |d| d.region, .module_not_imported => |d| d.region, .too_many_exports => |d| d.region, .undeclared_type => |d| d.region, @@ -889,9 +895,19 @@ pub const Diagnostic = union(enum) { ) !Report { var report = Report.init(allocator, "UNDECLARED TYPE", .runtime_error); const owned_type_name = try report.addOwnedString(type_name); - try report.document.addReflowingText("The type "); - try report.document.addType(owned_type_name); - try report.document.addReflowingText(" is not declared in this scope."); + + // Check if this looks like a qualified type (contains dots) + const has_dots = std.mem.indexOfScalar(u8, type_name, '.') != null; + + if (has_dots) { + try report.document.addReflowingText("Cannot resolve qualified type "); + try report.document.addType(owned_type_name); + try report.document.addReflowingText("."); + } else { + try report.document.addReflowingText("The type "); + try report.document.addType(owned_type_name); + try report.document.addReflowingText(" is not declared in this scope."); + } try report.document.addLineBreak(); try report.document.addLineBreak(); @@ -1411,13 +1427,32 @@ pub const Diagnostic = union(enum) { const owned_module = try report.addOwnedString(module_name); const owned_type = try report.addOwnedString(type_name); - try report.document.addReflowingText("The "); - try report.document.addModuleName(owned_module); - try report.document.addReflowingText(" module does not expose anything named "); - try report.document.addType(owned_type); - try report.document.addReflowingText("."); - try report.document.addLineBreak(); - try report.document.addReflowingText("Make sure the module exports this type, or use a type that is exposed."); + + // Check if trying to access a type with the same name as the module (e.g., Result.Result) + const is_same_name = std.mem.eql(u8, module_name, type_name); + + if (is_same_name) { + // Special message for Result.Result, Color.Color, etc. + const qualified_name = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ module_name, type_name }); + defer allocator.free(qualified_name); + const owned_qualified = try report.addOwnedString(qualified_name); + + try report.document.addReflowingText("There is no "); + try report.document.addType(owned_qualified); + try report.document.addReflowingText(" type."); + try report.document.addLineBreak(); + try report.document.addLineBreak(); + } else { + // Standard message for other cases (e.g., Color.RGB where Color is a nominal type) + const qualified_name = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ module_name, type_name }); + defer allocator.free(qualified_name); + const owned_qualified = try report.addOwnedString(qualified_name); + + try report.document.addType(owned_qualified); + try report.document.addReflowingText(" does not exist."); + try report.document.addLineBreak(); + try report.document.addLineBreak(); + } const owned_filename = try report.addOwnedString(filename); try report.document.addSourceRegion( @@ -1428,6 +1463,21 @@ pub const Diagnostic = union(enum) { line_starts, ); + // Add tip at the end + try report.document.addLineBreak(); + if (is_same_name) { + try report.document.addReflowingText("There is a "); + try report.document.addModuleName(owned_module); + try report.document.addReflowingText(" module, but it does not have a "); + try report.document.addType(owned_type); + try report.document.addReflowingText(" type nested inside it."); + } else { + try report.document.addType(owned_module); + try report.document.addReflowingText(" is a valid type, but it does not have an associated "); + try report.document.addType(owned_type); + try report.document.addReflowingText("."); + } + return report; } diff --git a/src/canonicalize/Node.zig b/src/canonicalize/Node.zig index 79481c7381..f29394c112 100644 --- a/src/canonicalize/Node.zig +++ b/src/canonicalize/Node.zig @@ -188,6 +188,7 @@ pub const Tag = enum { diag_module_not_found, diag_value_not_exposed, diag_type_not_exposed, + diag_type_from_missing_module, diag_module_not_imported, diag_too_many_exports, diag_nominal_type_redeclared, diff --git a/src/canonicalize/NodeStore.zig b/src/canonicalize/NodeStore.zig index 6c5268dd9c..41d5a26e42 100644 --- a/src/canonicalize/NodeStore.zig +++ b/src/canonicalize/NodeStore.zig @@ -128,7 +128,7 @@ pub fn deinit(store: *NodeStore) void { /// when adding/removing variants from ModuleEnv unions. Update these when modifying the unions. /// /// Count of the diagnostic nodes in the ModuleEnv -pub const MODULEENV_DIAGNOSTIC_NODE_COUNT = 56; +pub const MODULEENV_DIAGNOSTIC_NODE_COUNT = 57; /// Count of the expression nodes in the ModuleEnv pub const MODULEENV_EXPR_NODE_COUNT = 33; /// Count of the statement nodes in the ModuleEnv @@ -2831,6 +2831,12 @@ pub fn addDiagnostic(store: *NodeStore, reason: CIR.Diagnostic) Allocator.Error! node.data_1 = @as(u32, @bitCast(r.module_name)); node.data_2 = @as(u32, @bitCast(r.type_name)); }, + .type_from_missing_module => |r| { + node.tag = .diag_type_from_missing_module; + region = r.region; + node.data_1 = @as(u32, @bitCast(r.module_name)); + node.data_2 = @as(u32, @bitCast(r.type_name)); + }, .module_not_imported => |r| { node.tag = .diag_module_not_imported; region = r.region; @@ -3083,6 +3089,11 @@ pub fn getDiagnostic(store: *const NodeStore, diagnostic: CIR.Diagnostic.Idx) CI .type_name = @as(base.Ident.Idx, @bitCast(node.data_2)), .region = store.getRegionAt(node_idx), } }, + .diag_type_from_missing_module => return CIR.Diagnostic{ .type_from_missing_module = .{ + .module_name = @as(base.Ident.Idx, @bitCast(node.data_1)), + .type_name = @as(base.Ident.Idx, @bitCast(node.data_2)), + .region = store.getRegionAt(node_idx), + } }, .diag_module_not_imported => return CIR.Diagnostic{ .module_not_imported = .{ .module_name = @as(base.Ident.Idx, @bitCast(node.data_1)), .region = store.getRegionAt(node_idx), diff --git a/src/canonicalize/Scope.zig b/src/canonicalize/Scope.zig index 831513aa8b..1bc24bce11 100644 --- a/src/canonicalize/Scope.zig +++ b/src/canonicalize/Scope.zig @@ -7,17 +7,37 @@ const collections = @import("collections"); const CIR = @import("CIR.zig"); const Ident = base.Ident; +const Region = base.Region; const Scope = @This(); +/// Represents a type binding for a type imported from an external module. +/// Contains all necessary information to resolve the type from the imported module. +pub const ExternalTypeBinding = struct { + module_ident: Ident.Idx, + original_ident: Ident.Idx, + target_node_idx: ?u16, + import_idx: ?CIR.Import.Idx, + origin_region: Region, + /// True if the module was attempted to be imported but was not found. + /// This allows us to emit a more specific diagnostic when the type is used. + module_not_found: bool, +}; + +/// A unified type binding that can represent either a locally declared type or an externally imported type. +/// This is the single source of truth for all type resolution in a scope. +pub const TypeBinding = union(enum) { + local_nominal: CIR.Statement.Idx, + local_alias: CIR.Statement.Idx, + associated_nominal: CIR.Statement.Idx, + external_nominal: ExternalTypeBinding, +}; + /// Maps an Ident to a Pattern in the Can IR idents: std.AutoHashMapUnmanaged(Ident.Idx, CIR.Pattern.Idx), aliases: std.AutoHashMapUnmanaged(Ident.Idx, CIR.Pattern.Idx), -/// Maps type names to their type declaration statements -type_decls: std.AutoHashMapUnmanaged(Ident.Idx, CIR.Statement.Idx), -/// Maps unqualified type names to their fully qualified Statement.Idx (for associated types) -/// Example: within Foo's associated block, "Bar" -> statement for "Foo.Bar" -type_aliases: std.AutoHashMapUnmanaged(Ident.Idx, CIR.Statement.Idx), +/// Canonical bindings for type names (local, auto-imported, and imported types) +type_bindings: std.AutoHashMapUnmanaged(Ident.Idx, TypeBinding), /// Maps type variables to their type annotation indices type_vars: std.AutoHashMapUnmanaged(Ident.Idx, CIR.TypeAnno.Idx), /// Maps module alias names to their full module names @@ -33,8 +53,7 @@ pub fn init(is_function_boundary: bool) Scope { return Scope{ .idents = std.AutoHashMapUnmanaged(Ident.Idx, CIR.Pattern.Idx){}, .aliases = std.AutoHashMapUnmanaged(Ident.Idx, CIR.Pattern.Idx){}, - .type_decls = std.AutoHashMapUnmanaged(Ident.Idx, CIR.Statement.Idx){}, - .type_aliases = std.AutoHashMapUnmanaged(Ident.Idx, CIR.Statement.Idx){}, + .type_bindings = std.AutoHashMapUnmanaged(Ident.Idx, TypeBinding){}, .type_vars = std.AutoHashMapUnmanaged(Ident.Idx, CIR.TypeAnno.Idx){}, .module_aliases = std.AutoHashMapUnmanaged(Ident.Idx, Ident.Idx){}, .exposed_items = std.AutoHashMapUnmanaged(Ident.Idx, ExposedItemInfo){}, @@ -47,8 +66,7 @@ pub fn init(is_function_boundary: bool) Scope { pub fn deinit(self: *Scope, gpa: std.mem.Allocator) void { self.idents.deinit(gpa); self.aliases.deinit(gpa); - self.type_decls.deinit(gpa); - self.type_aliases.deinit(gpa); + self.type_bindings.deinit(gpa); self.type_vars.deinit(gpa); self.module_aliases.deinit(gpa); self.exposed_items.deinit(gpa); @@ -157,12 +175,11 @@ pub const ImportedModuleIntroduceResult = union(enum) { }; /// Item kinds in a scope -pub const ItemKind = enum { ident, alias, type_decl, type_var, module_alias, exposed_item }; +pub const ItemKind = enum { ident, alias, type_var, module_alias, exposed_item }; /// Get the appropriate map for the given item kind pub fn items(scope: *Scope, comptime item_kind: ItemKind) switch (item_kind) { .ident, .alias => *std.AutoHashMapUnmanaged(Ident.Idx, CIR.Pattern.Idx), - .type_decl => *std.AutoHashMapUnmanaged(Ident.Idx, CIR.Statement.Idx), .type_var => *std.AutoHashMapUnmanaged(Ident.Idx, CIR.TypeAnno.Idx), .module_alias => *std.AutoHashMapUnmanaged(Ident.Idx, Ident.Idx), .exposed_item => *std.AutoHashMapUnmanaged(Ident.Idx, ExposedItemInfo), @@ -170,7 +187,6 @@ pub fn items(scope: *Scope, comptime item_kind: ItemKind) switch (item_kind) { return switch (item_kind) { .ident => &scope.idents, .alias => &scope.aliases, - .type_decl => &scope.type_decls, .type_var => &scope.type_vars, .module_alias => &scope.module_aliases, .exposed_item => &scope.exposed_items, @@ -180,7 +196,6 @@ pub fn items(scope: *Scope, comptime item_kind: ItemKind) switch (item_kind) { /// Get the appropriate map for the given item kind (const version) pub fn itemsConst(scope: *const Scope, comptime item_kind: ItemKind) switch (item_kind) { .ident, .alias => *const std.AutoHashMapUnmanaged(Ident.Idx, CIR.Pattern.Idx), - .type_decl => *const std.AutoHashMapUnmanaged(Ident.Idx, CIR.Statement.Idx), .type_var => *const std.AutoHashMapUnmanaged(Ident.Idx, CIR.TypeAnno.Idx), .module_alias => *const std.AutoHashMapUnmanaged(Ident.Idx, Ident.Idx), .exposed_item => *const std.AutoHashMapUnmanaged(Ident.Idx, ExposedItemInfo), @@ -188,7 +203,6 @@ pub fn itemsConst(scope: *const Scope, comptime item_kind: ItemKind) switch (ite return switch (item_kind) { .ident => &scope.idents, .alias => &scope.aliases, - .type_decl => &scope.type_decls, .type_var => &scope.type_vars, .module_alias => &scope.module_aliases, .exposed_item => &scope.exposed_items, @@ -198,7 +212,6 @@ pub fn itemsConst(scope: *const Scope, comptime item_kind: ItemKind) switch (ite /// Put an item in the scope, panics on OOM pub fn put(scope: *Scope, gpa: std.mem.Allocator, comptime item_kind: ItemKind, name: Ident.Idx, value: switch (item_kind) { .ident, .alias => CIR.Pattern.Idx, - .type_decl => CIR.Statement.Idx, .type_var => CIR.TypeAnno.Idx, .module_alias => Ident.Idx, .exposed_item => ExposedItemInfo, @@ -214,13 +227,14 @@ pub fn introduceTypeDecl( type_decl: CIR.Statement.Idx, parent_lookup_fn: ?fn (Ident.Idx) ?CIR.Statement.Idx, ) std.mem.Allocator.Error!TypeIntroduceResult { - // Check if already exists in current scope by comparing text content - var iter = scope.type_decls.iterator(); - while (iter.next()) |entry| { - if (name.idx == entry.key_ptr.idx) { - // Type redeclaration is an error, not just a warning - return TypeIntroduceResult{ .redeclared_error = entry.value_ptr.* }; - } + // Check if type already exists in this scope + if (scope.type_bindings.getPtr(name)) |existing| { + return switch (existing.*) { + .local_nominal => |stmt| TypeIntroduceResult{ .redeclared_error = stmt }, + .local_alias => |stmt| TypeIntroduceResult{ .type_alias_redeclared = stmt }, + .associated_nominal => |stmt| TypeIntroduceResult{ .nominal_type_redeclared = stmt }, + .external_nominal => TypeIntroduceResult{ .nominal_type_redeclared = type_decl }, + }; } // Check for shadowing in parent scopes and issue warnings @@ -229,7 +243,8 @@ pub fn introduceTypeDecl( shadowed_stmt = lookup_fn(name); } - try scope.put(gpa, .type_decl, name, type_decl); + // Add type binding (single source of truth) + try scope.type_bindings.put(gpa, name, TypeBinding{ .local_nominal = type_decl }); if (shadowed_stmt) |stmt| { return TypeIntroduceResult{ .shadowing_warning = stmt }; @@ -238,26 +253,6 @@ pub fn introduceTypeDecl( return TypeIntroduceResult{ .success = {} }; } -/// Lookup a type declaration in the scope hierarchy -/// TODO: Optimize lookup performance - currently O(n) due to text comparison -/// TODO: Consider caching or using a more efficient data structure for type lookup -/// TODO: Support for nominal vs structural type distinction (future := operator) -pub fn lookupTypeDecl(scope: *const Scope, name: Ident.Idx) TypeLookupResult { - // Search by comparing text content, not identifier index - var iter = scope.type_decls.iterator(); - while (iter.next()) |entry| { - if (name.idx == entry.key_ptr.idx) { - return TypeLookupResult{ .found = entry.value_ptr.* }; - } - } - return TypeLookupResult{ .not_found = {} }; -} - -/// Look up an unqualified type alias (for associated types) -pub fn lookupTypeAlias(scope: *const Scope, name: Ident.Idx) ?CIR.Statement.Idx { - return scope.type_aliases.get(name); -} - /// Introduce an unqualified type alias (for associated types) /// Maps an unqualified name to a fully qualified type declaration pub fn introduceTypeAlias( @@ -266,7 +261,9 @@ pub fn introduceTypeAlias( unqualified_name: Ident.Idx, qualified_type_decl: CIR.Statement.Idx, ) !void { - try scope.type_aliases.put(gpa, unqualified_name, qualified_type_decl); + try scope.type_bindings.put(gpa, unqualified_name, TypeBinding{ + .associated_nominal = qualified_type_decl, + }); } /// Update an existing type declaration in the scope @@ -278,17 +275,17 @@ pub fn updateTypeDecl( name: Ident.Idx, new_type_decl: CIR.Statement.Idx, ) std.mem.Allocator.Error!void { - // Find the existing entry by comparing text content - var iter = scope.type_decls.iterator(); - while (iter.next()) |entry| { - if (name.idx == entry.key_ptr.idx) { - // Update the existing entry with the new statement index - entry.value_ptr.* = new_type_decl; - return; - } + if (scope.type_bindings.getPtr(name)) |binding_ptr| { + const current = binding_ptr.*; + binding_ptr.* = switch (current) { + .local_nominal => TypeBinding{ .local_nominal = new_type_decl }, + .local_alias => TypeBinding{ .local_alias = new_type_decl }, + .associated_nominal => TypeBinding{ .associated_nominal = new_type_decl }, + .external_nominal => current, + }; + } else { + try scope.type_bindings.put(gpa, name, TypeBinding{ .local_nominal = new_type_decl }); } - // If not found, add it as a new entry - try scope.put(gpa, .type_decl, name, new_type_decl); } /// Introduce a type variable into the scope diff --git a/src/canonicalize/test/node_store_test.zig b/src/canonicalize/test/node_store_test.zig index f75fb206f2..c4c1d687e3 100644 --- a/src/canonicalize/test/node_store_test.zig +++ b/src/canonicalize/test/node_store_test.zig @@ -790,6 +790,14 @@ test "NodeStore round trip - Diagnostics" { }, }); + try diagnostics.append(gpa, CIR.Diagnostic{ + .type_from_missing_module = .{ + .module_name = rand_ident_idx(), + .type_name = rand_ident_idx(), + .region = rand_region(), + }, + }); + // Test the round-trip for all diagnostics for (diagnostics.items) |diagnostic| { const idx = try store.addDiagnostic(diagnostic); diff --git a/src/parse/AST.zig b/src/parse/AST.zig index a316d7f92b..6e40ec6df5 100644 --- a/src/parse/AST.zig +++ b/src/parse/AST.zig @@ -822,6 +822,9 @@ pub const Statement = union(enum) { qualifier_tok: ?Token.Idx, alias_tok: ?Token.Idx, exposes: ExposedItem.Span, + /// True when importing like `import json.Parser.Config` where Config is auto-exposed + /// but Parser should not become an alias (unlike `import json.Parser exposing [Config]`) + nested_import: bool, region: TokenizedRegion, }, type_decl: struct { diff --git a/src/parse/NodeStore.zig b/src/parse/NodeStore.zig index 45542722a7..7fd691cc36 100644 --- a/src/parse/NodeStore.zig +++ b/src/parse/NodeStore.zig @@ -1172,6 +1172,7 @@ pub fn getStatement(store: *const NodeStore, statement_idx: AST.Statement.Idx) A .start = exposes_start, .len = exposes_len, } }, + .nested_import = false, .region = node.region, } }; }, diff --git a/src/parse/Parser.zig b/src/parse/Parser.zig index 38b83c685f..b78d4eece7 100644 --- a/src/parse/Parser.zig +++ b/src/parse/Parser.zig @@ -977,48 +977,83 @@ fn parseStmtByType(self: *Parser, statementType: StatementType) Error!AST.Statem } if ((qualifier == null and self.peek() == .UpperIdent) or (qualifier != null and (self.peek() == .NoSpaceDotUpperIdent or self.peek() == .DotUpperIdent))) { var exposes = AST.ExposedItem.Span{ .span = base.DataSpan.empty() }; - const module_name_tok = self.pos; - // Handle 'as' clause if present - if (self.peekNext() == .KwAs) { - self.advance(); // Advance past UpperIdent - self.advance(); // Advance past KwAs - alias_tok = self.pos; - self.expect(.UpperIdent) catch { - const malformed = try self.pushMalformed(AST.Statement.Idx, .expected_upper_name_after_import_as, start); - return malformed; - }; - } else { - self.advance(); // Advance past identifier + var nested_import = false; + + // Parse all uppercase segments: first.Second.Third... + var prev_upper_tok: ?TokenIdx = null; + var module_name_tok = self.pos; + self.advance(); // Advance past first UpperIdent + + // Keep consuming additional .UpperIdent segments + while (self.peek() == .NoSpaceDotUpperIdent or self.peek() == .DotUpperIdent) { + prev_upper_tok = module_name_tok; + module_name_tok = self.pos; + self.advance(); } - // Handle 'exposing' clause if present (can occur with or without 'as') - if (self.peek() == .KwExposing) { - self.advance(); // Advance past KwExposing - self.expect(.OpenSquare) catch { - return try self.pushMalformed(AST.Statement.Idx, .import_exposing_no_open, start); - }; + // If we have multiple uppercase segments and no explicit 'as' or 'exposing', + // auto-expose the final segment + const has_explicit_clause = self.peek() == .KwAs or self.peek() == .KwExposing; + if (prev_upper_tok != null and !has_explicit_clause) { + // Auto-expose pattern: import json.Parser.Config + // Module is everything before the last segment, last segment is auto-exposed + const final_segment_tok = module_name_tok; + module_name_tok = prev_upper_tok.?; + nested_import = true; + + // Create exposed item for the final segment const scratch_top = self.store.scratchExposedItemTop(); - self.parseCollectionSpan(AST.ExposedItem.Idx, .CloseSquare, NodeStore.addScratchExposedItem, Parser.parseExposedItem) catch |err| { - switch (err) { - error.ExpectedNotFound => { - while (self.peek() != .CloseSquare and self.peek() != .EndOfFile) { - self.advance(); - } - self.expect(.CloseSquare) catch {}; - self.store.clearScratchExposedItemsFrom(scratch_top); - return try self.pushMalformed(AST.Statement.Idx, .import_exposing_no_close, start); - }, - error.OutOfMemory => return error.OutOfMemory, - error.TooNested => return error.TooNested, - } - }; + const exposed_item = try self.store.addExposedItem(.{ .upper_ident = .{ + .region = .{ .start = final_segment_tok, .end = final_segment_tok }, + .ident = final_segment_tok, + .as = null, + } }); + try self.store.addScratchExposedItem(exposed_item); exposes = try self.store.exposedItemSpanFrom(scratch_top); + } else { + // Normal import: handle 'as' and 'exposing' clauses + + // Handle 'as' clause if present + if (self.peek() == .KwAs) { + self.advance(); // Advance past KwAs + alias_tok = self.pos; + self.expect(.UpperIdent) catch { + const malformed = try self.pushMalformed(AST.Statement.Idx, .expected_upper_name_after_import_as, start); + return malformed; + }; + } + + // Handle 'exposing' clause if present (can occur with or without 'as') + if (self.peek() == .KwExposing) { + self.advance(); // Advance past KwExposing + self.expect(.OpenSquare) catch { + return try self.pushMalformed(AST.Statement.Idx, .import_exposing_no_open, start); + }; + const scratch_top = self.store.scratchExposedItemTop(); + self.parseCollectionSpan(AST.ExposedItem.Idx, .CloseSquare, NodeStore.addScratchExposedItem, Parser.parseExposedItem) catch |err| { + switch (err) { + error.ExpectedNotFound => { + while (self.peek() != .CloseSquare and self.peek() != .EndOfFile) { + self.advance(); + } + self.expect(.CloseSquare) catch {}; + self.store.clearScratchExposedItemsFrom(scratch_top); + return try self.pushMalformed(AST.Statement.Idx, .import_exposing_no_close, start); + }, + error.OutOfMemory => return error.OutOfMemory, + error.TooNested => return error.TooNested, + } + }; + exposes = try self.store.exposedItemSpanFrom(scratch_top); + } } + const statement_idx = try self.store.addStatement(.{ .import = .{ .module_name_tok = module_name_tok, .qualifier_tok = qualifier, .alias_tok = alias_tok, .exposes = exposes, + .nested_import = nested_import, .region = .{ .start = start, .end = self.pos }, } }); return statement_idx; diff --git a/src/parse/test/ast_node_store_test.zig b/src/parse/test/ast_node_store_test.zig index 41d04bf349..ce0dd9f48d 100644 --- a/src/parse/test/ast_node_store_test.zig +++ b/src/parse/test/ast_node_store_test.zig @@ -189,6 +189,7 @@ test "NodeStore round trip - Statement" { .qualifier_tok = null, .region = rand_region(), .exposes = AST.ExposedItem.Span{ .span = rand_span() }, + .nested_import = false, }, }); // Import with alias @@ -199,6 +200,7 @@ test "NodeStore round trip - Statement" { .qualifier_tok = null, .region = rand_region(), .exposes = AST.ExposedItem.Span{ .span = rand_span() }, + .nested_import = false, }, }); // Import with qualifier but no alias @@ -209,6 +211,7 @@ test "NodeStore round trip - Statement" { .qualifier_tok = rand_token_idx(), .region = rand_region(), .exposes = AST.ExposedItem.Span{ .span = rand_span() }, + .nested_import = false, }, }); // Import with both qualifier and alias @@ -219,6 +222,7 @@ test "NodeStore round trip - Statement" { .qualifier_tok = rand_token_idx(), .region = rand_region(), .exposes = AST.ExposedItem.Span{ .span = rand_span() }, + .nested_import = false, }, }); try statements.append(gpa, AST.Statement{ diff --git a/src/snapshot_tool/main.zig b/src/snapshot_tool/main.zig index ce7ff7ea14..764e9fdd44 100644 --- a/src/snapshot_tool/main.zig +++ b/src/snapshot_tool/main.zig @@ -1925,7 +1925,7 @@ const Meta = struct { \\description=Hello world \\type=foobar ); - try std.testing.expectEqual(meta, Error.InvalidNodeType); + try std.testing.expectError(Error.InvalidNodeType, meta); } }; diff --git a/test/snapshots/can_import_nested_modules.md b/test/snapshots/can_import_nested_modules.md index 47621aef78..29c8eb25c5 100644 --- a/test/snapshots/can_import_nested_modules.md +++ b/test/snapshots/can_import_nested_modules.md @@ -31,186 +31,50 @@ validateAuth : HttpAuth.Credentials -> Result(HttpAuth.Token, HttpAuth.Error) validateAuth = |creds| HttpAuth.validate(creds) ~~~ # EXPECTED -PARSE ERROR - can_import_nested_modules.md:1:19:1:26 -PARSE ERROR - can_import_nested_modules.md:2:19:2:24 -PARSE ERROR - can_import_nested_modules.md:2:25:2:27 -PARSE ERROR - can_import_nested_modules.md:3:1:3:7 -PARSE ERROR - can_import_nested_modules.md:3:8:3:13 -PARSE ERROR - can_import_nested_modules.md:3:13:3:20 -PARSE ERROR - can_import_nested_modules.md:3:20:3:27 -PARSE ERROR - can_import_nested_modules.md:3:28:3:36 -PARSE ERROR - can_import_nested_modules.md:3:37:3:38 -PARSE ERROR - can_import_nested_modules.md:3:38:3:45 -PARSE ERROR - can_import_nested_modules.md:3:45:3:46 -MODULE NOT FOUND - can_import_nested_modules.md:1:1:1:19 -MODULE NOT FOUND - can_import_nested_modules.md:2:1:2:19 +MODULE NOT FOUND - can_import_nested_modules.md:1:1:1:26 +MODULE NOT FOUND - can_import_nested_modules.md:2:1:2:36 +MODULE NOT FOUND - can_import_nested_modules.md:3:1:3:46 MODULE NOT IMPORTED - can_import_nested_modules.md:6:15:6:30 UNDEFINED VARIABLE - can_import_nested_modules.md:7:26:7:41 -MODULE NOT IMPORTED - can_import_nested_modules.md:10:28:10:42 UNDEFINED VARIABLE - can_import_nested_modules.md:11:29:11:43 MODULE NOT IMPORTED - can_import_nested_modules.md:14:15:14:37 MODULE NOT IMPORTED - can_import_nested_modules.md:14:58:14:77 UNDEFINED VARIABLE - can_import_nested_modules.md:16:5:16:37 UNDEFINED VARIABLE - can_import_nested_modules.md:20:23:20:30 UNDEFINED VARIABLE - can_import_nested_modules.md:20:37:20:58 -MODULE NOT IMPORTED - can_import_nested_modules.md:23:16:23:36 -MODULE NOT IMPORTED - can_import_nested_modules.md:23:47:23:61 -MODULE NOT IMPORTED - can_import_nested_modules.md:23:63:23:77 UNDEFINED VARIABLE - can_import_nested_modules.md:24:24:24:41 # PROBLEMS -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**can_import_nested_modules.md:1:19:1:26:** -```roc -import json.Parser.Config -``` - ^^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**can_import_nested_modules.md:2:19:2:24:** -```roc -import http.Client.Auth as HttpAuth -``` - ^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**can_import_nested_modules.md:2:25:2:27:** -```roc -import http.Client.Auth as HttpAuth -``` - ^^ - - -**PARSE ERROR** -Type applications require parentheses around their type arguments. - -I found a type followed by what looks like a type argument, but they need to be connected with parentheses. - -Instead of: - **List U8** - -Use: - **List(U8)** - -Other valid examples: - `Dict(Str, Num)` - `Result(a, Str)` - `Maybe(List(U64))` - -**can_import_nested_modules.md:3:1:3:7:** -```roc -import utils.String.Format exposing [padLeft] -``` -^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**can_import_nested_modules.md:3:8:3:13:** -```roc -import utils.String.Format exposing [padLeft] -``` - ^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**can_import_nested_modules.md:3:13:3:20:** -```roc -import utils.String.Format exposing [padLeft] -``` - ^^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**can_import_nested_modules.md:3:20:3:27:** -```roc -import utils.String.Format exposing [padLeft] -``` - ^^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**can_import_nested_modules.md:3:28:3:36:** -```roc -import utils.String.Format exposing [padLeft] -``` - ^^^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**can_import_nested_modules.md:3:37:3:38:** -```roc -import utils.String.Format exposing [padLeft] -``` - ^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**can_import_nested_modules.md:3:38:3:45:** -```roc -import utils.String.Format exposing [padLeft] -``` - ^^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**can_import_nested_modules.md:3:45:3:46:** -```roc -import utils.String.Format exposing [padLeft] -``` - ^ - - **MODULE NOT FOUND** The module `json.Parser` was not found in this Roc project. You're attempting to use this module here: -**can_import_nested_modules.md:1:1:1:19:** +**can_import_nested_modules.md:1:1:1:26:** ```roc import json.Parser.Config ``` -^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^ **MODULE NOT FOUND** -The module `http.Client` was not found in this Roc project. +The module `http.Client.Auth` was not found in this Roc project. You're attempting to use this module here: -**can_import_nested_modules.md:2:1:2:19:** +**can_import_nested_modules.md:2:1:2:36:** ```roc import http.Client.Auth as HttpAuth ``` -^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +**MODULE NOT FOUND** +The module `utils.String.Format` was not found in this Roc project. + +You're attempting to use this module here: +**can_import_nested_modules.md:3:1:3:46:** +```roc +import utils.String.Format exposing [padLeft] +``` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **MODULE NOT IMPORTED** @@ -235,17 +99,6 @@ parseConfig = |settings| Config.toString(settings) ^^^^^^^^^^^^^^^ -**MODULE NOT IMPORTED** -There is no module with the name `HttpAuth` imported into this Roc file. - -You're attempting to use this module here: -**can_import_nested_modules.md:10:28:10:42:** -```roc -authenticate : Str, Str -> HttpAuth.Token -``` - ^^^^^^^^^^^^^^ - - **UNDEFINED VARIABLE** Nothing is named `login` in this scope. Is there an `import` or `exposing` missing up-top? @@ -312,39 +165,6 @@ formatOutput = |text| padLeft(text, Config.defaultPadding) ^^^^^^^^^^^^^^^^^^^^^ -**MODULE NOT IMPORTED** -There is no module with the name `HttpAuth` imported into this Roc file. - -You're attempting to use this module here: -**can_import_nested_modules.md:23:16:23:36:** -```roc -validateAuth : HttpAuth.Credentials -> Result(HttpAuth.Token, HttpAuth.Error) -``` - ^^^^^^^^^^^^^^^^^^^^ - - -**MODULE NOT IMPORTED** -There is no module with the name `HttpAuth` imported into this Roc file. - -You're attempting to use this module here: -**can_import_nested_modules.md:23:47:23:61:** -```roc -validateAuth : HttpAuth.Credentials -> Result(HttpAuth.Token, HttpAuth.Error) -``` - ^^^^^^^^^^^^^^ - - -**MODULE NOT IMPORTED** -There is no module with the name `HttpAuth` imported into this Roc file. - -You're attempting to use this module here: -**can_import_nested_modules.md:23:63:23:77:** -```roc -validateAuth : HttpAuth.Credentials -> Result(HttpAuth.Token, HttpAuth.Error) -``` - ^^^^^^^^^^^^^^ - - **UNDEFINED VARIABLE** Nothing is named `validate` in this scope. Is there an `import` or `exposing` missing up-top? @@ -379,19 +199,14 @@ EndOfFile, (file (type-module) (statements - (s-import (raw "json.Parser")) - (s-malformed (tag "statement_unexpected_token")) - (s-import (raw "http.Client")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "expected_colon_after_type_annotation")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) + (s-import (raw "json.Parser") + (exposing + (exposed-upper-ident (text "Config")))) + (s-import (raw "http.Auth") (alias "HttpAuth")) + (s-import (raw "utils.Format") + (exposing + (exposed-lower-ident + (text "padLeft")))) (s-type-anno (name "parseConfig") (ty-fn (ty (name "Config.Settings")) @@ -468,11 +283,9 @@ EndOfFile, ~~~ # FORMATTED ~~~roc -import json.Parser - -import http.Client - - +import json.Parser exposing [Config] +import http.Auth as HttpAuth +import utils.Format exposing [padLeft] # Test multi-level type qualification parseConfig : Config.Settings -> Str @@ -527,7 +340,7 @@ validateAuth = |creds| HttpAuth.validate(creds) (ty-fn (effectful false) (ty-lookup (name "Str") (external-module "Str")) (ty-lookup (name "Str") (external-module "Str")) - (ty-malformed)))) + (ty-lookup (name "Token") (external-module "http.Client.Auth"))))) (d-let (p-assign (ident "processData")) (e-lambda @@ -572,14 +385,18 @@ validateAuth = |creds| HttpAuth.validate(creds) (p-assign (ident "creds"))))) (annotation (ty-fn (effectful false) - (ty-malformed) + (ty-lookup (name "Credentials") (external-module "http.Client.Auth")) (ty-apply (name "Result") (external-module "Result") - (ty-malformed) - (ty-malformed))))) + (ty-lookup (name "Token") (external-module "http.Client.Auth")) + (ty-lookup (name "Error") (external-module "http.Client.Auth")))))) (s-import (module "json.Parser") + (exposes + (exposed (name "Config") (wildcard false)))) + (s-import (module "http.Client.Auth") (exposes)) - (s-import (module "http.Client") - (exposes))) + (s-import (module "utils.String.Format") + (exposes + (exposed (name "padLeft") (wildcard false))))) ~~~ # TYPES ~~~clojure diff --git a/test/snapshots/multi_qualified_import.md b/test/snapshots/multi_qualified_import.md index e7bcff8673..8445deb90a 100644 --- a/test/snapshots/multi_qualified_import.md +++ b/test/snapshots/multi_qualified_import.md @@ -19,10 +19,6 @@ data : json.Core.Utf8.EncodedData data = json.Core.Utf8.encode("hello") ~~~ # EXPECTED -PARSE ERROR - multi_qualified_import.md:1:17:1:22 -PARSE ERROR - multi_qualified_import.md:1:23:1:31 -PARSE ERROR - multi_qualified_import.md:1:32:1:33 -PARSE ERROR - multi_qualified_import.md:1:40:1:41 PARSE ERROR - multi_qualified_import.md:12:12:12:17 PARSE ERROR - multi_qualified_import.md:12:17:12:22 PARSE ERROR - multi_qualified_import.md:12:22:12:29 @@ -31,7 +27,7 @@ PARSE ERROR - multi_qualified_import.md:12:30:12:31 PARSE ERROR - multi_qualified_import.md:12:31:12:36 PARSE ERROR - multi_qualified_import.md:12:36:12:37 PARSE ERROR - multi_qualified_import.md:12:37:12:38 -MODULE NOT FOUND - multi_qualified_import.md:1:1:1:17 +MODULE NOT FOUND - multi_qualified_import.md:1:1:1:41 UNDECLARED TYPE - multi_qualified_import.md:3:16:3:23 UNDEFINED VARIABLE - multi_qualified_import.md:4:16:4:45 MODULE NOT IMPORTED - multi_qualified_import.md:7:11:7:33 @@ -43,62 +39,6 @@ UNDEFINED VARIABLE - multi_qualified_import.md:12:8:12:12 A parsing error occurred: `statement_unexpected_token` This is an unexpected parsing error. Please check your syntax. -**multi_qualified_import.md:1:17:1:22:** -```roc -import json.Core.Utf8 exposing [Encoder] -``` - ^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**multi_qualified_import.md:1:23:1:31:** -```roc -import json.Core.Utf8 exposing [Encoder] -``` - ^^^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**multi_qualified_import.md:1:32:1:33:** -```roc -import json.Core.Utf8 exposing [Encoder] -``` - ^ - - -**PARSE ERROR** -Type applications require parentheses around their type arguments. - -I found a type followed by what looks like a type argument, but they need to be connected with parentheses. - -Instead of: - **List U8** - -Use: - **List(U8)** - -Other valid examples: - `Dict(Str, Num)` - `Result(a, Str)` - `Maybe(List(U64))` - -**multi_qualified_import.md:1:40:1:41:** -```roc -import json.Core.Utf8 exposing [Encoder] -``` - ^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - **multi_qualified_import.md:12:12:12:17:** ```roc data = json.Core.Utf8.encode("hello") @@ -184,14 +124,14 @@ data = json.Core.Utf8.encode("hello") **MODULE NOT FOUND** -The module `json.Core` was not found in this Roc project. +The module `json.Core.Utf8` was not found in this Roc project. You're attempting to use this module here: -**multi_qualified_import.md:1:1:1:17:** +**multi_qualified_import.md:1:1:1:41:** ```roc import json.Core.Utf8 exposing [Encoder] ``` -^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **UNDECLARED TYPE** @@ -277,11 +217,9 @@ EndOfFile, (file (type-module) (statements - (s-import (raw "json.Core")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "expected_colon_after_type_annotation")) + (s-import (raw "json.Utf8") + (exposing + (exposed-upper-ident (text "Encoder")))) (s-type-anno (name "json_encoder") (ty (name "Encoder"))) (s-decl @@ -314,8 +252,7 @@ EndOfFile, ~~~ # FORMATTED ~~~roc -import json.Core - +import json.Utf8 exposing [Encoder] json_encoder : Encoder json_encoder = Json.Core.Utf8.defaultEncoder @@ -352,8 +289,9 @@ data = json (e-runtime-error (tag "ident_not_in_scope")) (annotation (ty-malformed))) - (s-import (module "json.Core") - (exposes))) + (s-import (module "json.Core.Utf8") + (exposes + (exposed (name "Encoder") (wildcard false))))) ~~~ # TYPES ~~~clojure diff --git a/test/snapshots/nominal/nominal_import_long_package.md b/test/snapshots/nominal/nominal_import_long_package.md index 2af5be1aaf..c57f848bc8 100644 --- a/test/snapshots/nominal/nominal_import_long_package.md +++ b/test/snapshots/nominal/nominal_import_long_package.md @@ -11,102 +11,18 @@ red : CE red = ... # not implemented ~~~ # EXPECTED -PARSE ERROR - nominal_import_long_package.md:1:21:1:27 -PARSE ERROR - nominal_import_long_package.md:1:28:1:36 -PARSE ERROR - nominal_import_long_package.md:1:37:1:38 -PARSE ERROR - nominal_import_long_package.md:1:46:1:48 -PARSE ERROR - nominal_import_long_package.md:1:51:1:52 -MODULE NOT FOUND - nominal_import_long_package.md:1:1:1:21 +MODULE NOT FOUND - nominal_import_long_package.md:1:1:1:52 UNDECLARED TYPE - nominal_import_long_package.md:3:7:3:9 # PROBLEMS -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**nominal_import_long_package.md:1:21:1:27:** -```roc -import design.Styles.Color exposing [Encoder as CE] -``` - ^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**nominal_import_long_package.md:1:28:1:36:** -```roc -import design.Styles.Color exposing [Encoder as CE] -``` - ^^^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**nominal_import_long_package.md:1:37:1:38:** -```roc -import design.Styles.Color exposing [Encoder as CE] -``` - ^ - - -**PARSE ERROR** -Type applications require parentheses around their type arguments. - -I found a type followed by what looks like a type argument, but they need to be connected with parentheses. - -Instead of: - **List U8** - -Use: - **List(U8)** - -Other valid examples: - `Dict(Str, Num)` - `Result(a, Str)` - `Maybe(List(U64))` - -**nominal_import_long_package.md:1:46:1:48:** -```roc -import design.Styles.Color exposing [Encoder as CE] -``` - ^^ - - -**PARSE ERROR** -Type applications require parentheses around their type arguments. - -I found a type followed by what looks like a type argument, but they need to be connected with parentheses. - -Instead of: - **List U8** - -Use: - **List(U8)** - -Other valid examples: - `Dict(Str, Num)` - `Result(a, Str)` - `Maybe(List(U64))` - -**nominal_import_long_package.md:1:51:1:52:** -```roc -import design.Styles.Color exposing [Encoder as CE] -``` - ^ - - **MODULE NOT FOUND** -The module `design.Styles` was not found in this Roc project. +The module `design.Styles.Color` was not found in this Roc project. You're attempting to use this module here: -**nominal_import_long_package.md:1:1:1:21:** +**nominal_import_long_package.md:1:1:1:52:** ```roc import design.Styles.Color exposing [Encoder as CE] ``` -^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **UNDECLARED TYPE** @@ -132,12 +48,9 @@ EndOfFile, (file (type-module) (statements - (s-import (raw "design.Styles")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "expected_colon_after_type_annotation")) - (s-malformed (tag "expected_colon_after_type_annotation")) + (s-import (raw "design.Color") + (exposing + (exposed-upper-ident (text "Encoder") (as "CE")))) (s-type-anno (name "red") (ty (name "CE"))) (s-decl @@ -146,8 +59,7 @@ EndOfFile, ~~~ # FORMATTED ~~~roc -import design.Styles - +import design.Color exposing [Encoder as CE] red : CE red = ... # not implemented @@ -160,8 +72,9 @@ red = ... # not implemented (e-not-implemented) (annotation (ty-malformed))) - (s-import (module "design.Styles") - (exposes))) + (s-import (module "design.Styles.Color") + (exposes + (exposed (name "Encoder") (alias "CE") (wildcard false))))) ~~~ # TYPES ~~~clojure diff --git a/test/snapshots/qualified_type_canonicalization.md b/test/snapshots/qualified_type_canonicalization.md index 11df4ffb88..44c354daab 100644 --- a/test/snapshots/qualified_type_canonicalization.md +++ b/test/snapshots/qualified_type_canonicalization.md @@ -53,12 +53,8 @@ transform = |result| # EXPECTED PARSE ERROR - qualified_type_canonicalization.md:8:1:8:7 PARSE ERROR - qualified_type_canonicalization.md:8:14:8:21 -PARSE ERROR - qualified_type_canonicalization.md:10:15:10:23 -PARSE ERROR - qualified_type_canonicalization.md:10:24:10:32 -PARSE ERROR - qualified_type_canonicalization.md:10:33:10:34 -PARSE ERROR - qualified_type_canonicalization.md:10:39:10:40 MODULE NOT FOUND - qualified_type_canonicalization.md:9:1:9:13 -MODULE NOT FOUND - qualified_type_canonicalization.md:10:1:10:15 +MODULE NOT FOUND - qualified_type_canonicalization.md:10:1:10:40 MODULE NOT FOUND - qualified_type_canonicalization.md:11:1:11:32 UNDECLARED TYPE - qualified_type_canonicalization.md:15:19:15:24 MODULE NOT IMPORTED - qualified_type_canonicalization.md:22:23:22:44 @@ -106,62 +102,6 @@ import Basics.Result ^^^^^^^ -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**qualified_type_canonicalization.md:10:15:10:23:** -```roc -import ModuleA.ModuleB exposing [TypeC] -``` - ^^^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**qualified_type_canonicalization.md:10:24:10:32:** -```roc -import ModuleA.ModuleB exposing [TypeC] -``` - ^^^^^^^^ - - -**PARSE ERROR** -A parsing error occurred: `statement_unexpected_token` -This is an unexpected parsing error. Please check your syntax. - -**qualified_type_canonicalization.md:10:33:10:34:** -```roc -import ModuleA.ModuleB exposing [TypeC] -``` - ^ - - -**PARSE ERROR** -Type applications require parentheses around their type arguments. - -I found a type followed by what looks like a type argument, but they need to be connected with parentheses. - -Instead of: - **List U8** - -Use: - **List(U8)** - -Other valid examples: - `Dict(Str, Num)` - `Result(a, Str)` - `Maybe(List(U64))` - -**qualified_type_canonicalization.md:10:39:10:40:** -```roc -import ModuleA.ModuleB exposing [TypeC] -``` - ^ - - **MODULE NOT FOUND** The module `Color` was not found in this Roc project. @@ -174,14 +114,14 @@ import Color **MODULE NOT FOUND** -The module `ModuleA` was not found in this Roc project. +The module `ModuleB` was not found in this Roc project. You're attempting to use this module here: -**qualified_type_canonicalization.md:10:1:10:15:** +**qualified_type_canonicalization.md:10:1:10:40:** ```roc import ModuleA.ModuleB exposing [TypeC] ``` -^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **MODULE NOT FOUND** @@ -358,11 +298,9 @@ EndOfFile, (statements (s-malformed (tag "expected_colon_after_type_annotation")) (s-import (raw "Color")) - (s-import (raw "ModuleA")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "statement_unexpected_token")) - (s-malformed (tag "expected_colon_after_type_annotation")) + (s-import (raw ".ModuleB") + (exposing + (exposed-upper-ident (text "TypeC")))) (s-import (raw "ExternalModule") (alias "ExtMod")) (s-type-anno (name "simpleQualified") (ty (name "Color.RGB"))) @@ -456,8 +394,7 @@ EndOfFile, ~~~roc import Color -import ModuleA - +import ModuleB exposing [TypeC] import ExternalModule as ExtMod # Simple qualified type @@ -582,8 +519,9 @@ transform = |result| (ty-malformed)))) (s-import (module "Color") (exposes)) - (s-import (module "ModuleA") - (exposes)) + (s-import (module "ModuleB") + (exposes + (exposed (name "TypeC") (wildcard false)))) (s-import (module "ExternalModule") (exposes))) ~~~ diff --git a/typos.toml b/typos.toml index e27c97fe9f..8d2982506a 100644 --- a/typos.toml +++ b/typos.toml @@ -1,5 +1,6 @@ [files] extend-exclude = [ + ".git/", "design/language/Abilities.md", "src/snapshots/fuzz_crash", "crates/vendor/",