Merge remote-tracking branch 'origin/main' into builtin-str2

This commit is contained in:
Richard Feldman 2025-10-24 12:23:45 -04:00
commit 3fa79da27c
No known key found for this signature in database
16 changed files with 693 additions and 620 deletions

View file

@ -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)

View file

@ -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;
}

View file

@ -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,

View file

@ -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),

View file

@ -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

View file

@ -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);

View file

@ -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 {

View file

@ -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,
} };
},

View file

@ -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;

View file

@ -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{

View file

@ -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);
}
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)))
~~~

View file

@ -1,5 +1,6 @@
[files]
extend-exclude = [
".git/",
"design/language/Abilities.md",
"src/snapshots/fuzz_crash",
"crates/vendor/",