diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-08-28 15:35:59 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-09-01 17:54:06 -0700 |
| commit | 332eafeb7f3d866556ab767b960a04661bc43bc7 (patch) | |
| tree | d299cbc233ca756ca42885e66f54e2b08f2a3d9a /src/Sema.zig | |
| parent | c05a20fc8c36742dab8792d15e79716da1a55759 (diff) | |
| download | zig-332eafeb7f3d866556ab767b960a04661bc43bc7.tar.gz zig-332eafeb7f3d866556ab767b960a04661bc43bc7.zip | |
stage2: first pass at implementing usingnamespace
Ran into a design flaw here which will need to get solved by having
AstGen annotate ZIR with which instructions are closed over.
Diffstat (limited to 'src/Sema.zig')
| -rw-r--r-- | src/Sema.zig | 115 |
1 files changed, 99 insertions, 16 deletions
diff --git a/src/Sema.zig b/src/Sema.zig index 108470ec79..b3eb3b1b85 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -58,6 +58,9 @@ comptime_args_fn_inst: Zir.Inst.Index = 0, /// extra hash table lookup in the `monomorphed_funcs` set. /// Sema will set this to null when it takes ownership. preallocated_new_func: ?*Module.Fn = null, +/// Collects struct, union, enum, and opaque decls which need to have their +/// fields resolved before this Sema is deinitialized. +types_pending_resolution: std.ArrayListUnmanaged(Type) = .{}, const std = @import("std"); const mem = std.mem; @@ -90,6 +93,7 @@ pub fn deinit(sema: *Sema) void { sema.air_values.deinit(gpa); sema.inst_map.deinit(gpa); sema.decl_val_table.deinit(gpa); + sema.types_pending_resolution.deinit(gpa); sema.* = undefined; } @@ -908,7 +912,9 @@ fn zirStructDecl( &struct_obj.namespace, new_decl, new_decl.name, }); try sema.analyzeStructDecl(new_decl, inst, struct_obj); + try sema.types_pending_resolution.ensureUnusedCapacity(sema.gpa, 1); try new_decl.finalizeNewArena(&new_decl_arena); + sema.types_pending_resolution.appendAssumeCapacity(struct_ty); return sema.analyzeDeclVal(block, src, new_decl); } @@ -1198,7 +1204,9 @@ fn zirUnionDecl( _ = try sema.mod.scanNamespace(&union_obj.namespace, extra_index, decls_len, new_decl); + try sema.types_pending_resolution.ensureUnusedCapacity(sema.gpa, 1); try new_decl.finalizeNewArena(&new_decl_arena); + sema.types_pending_resolution.appendAssumeCapacity(union_ty); return sema.analyzeDeclVal(block, src, new_decl); } @@ -2324,42 +2332,105 @@ fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr } fn lookupIdentifier(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, name: []const u8) !*Decl { - // TODO emit a compile error if more than one decl would be matched. var namespace = sema.namespace; while (true) { - if (try sema.lookupInNamespace(namespace, name)) |decl| { + if (try sema.lookupInNamespace(block, src, namespace, name, false)) |decl| { return decl; } namespace = namespace.parent orelse break; } - return sema.mod.fail(&block.base, src, "use of undeclared identifier '{s}'", .{name}); + unreachable; // AstGen detects use of undeclared identifier errors. } /// This looks up a member of a specific namespace. It is affected by `usingnamespace` but /// only for ones in the specified namespace. fn lookupInNamespace( sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, namespace: *Scope.Namespace, ident_name: []const u8, + observe_usingnamespace: bool, ) CompileError!?*Decl { + const mod = sema.mod; + const namespace_decl = namespace.getDecl(); if (namespace_decl.analysis == .file_failure) { - try sema.mod.declareDeclDependency(sema.owner_decl, namespace_decl); + try mod.declareDeclDependency(sema.owner_decl, namespace_decl); return error.AnalysisFail; } - // TODO implement usingnamespace - if (namespace.decls.get(ident_name)) |decl| { - try sema.mod.declareDeclDependency(sema.owner_decl, decl); + if (observe_usingnamespace and namespace.usingnamespace_set.count() != 0) { + const src_file = block.src_decl.namespace.file_scope; + + const gpa = sema.gpa; + var checked_namespaces: std.AutoArrayHashMapUnmanaged(*Scope.Namespace, void) = .{}; + defer checked_namespaces.deinit(gpa); + + // Keep track of name conflicts for error notes. + var candidates: std.ArrayListUnmanaged(*Decl) = .{}; + defer candidates.deinit(gpa); + + try checked_namespaces.put(gpa, namespace, {}); + var check_i: usize = 0; + + while (check_i < checked_namespaces.count()) : (check_i += 1) { + const check_ns = checked_namespaces.keys()[check_i]; + if (check_ns.decls.get(ident_name)) |decl| { + // Skip decls which are not marked pub, which are in a different + // file than the `a.b`/`@hasDecl` syntax. + if (decl.is_pub or src_file == decl.namespace.file_scope) { + try candidates.append(gpa, decl); + } + } + var it = check_ns.usingnamespace_set.iterator(); + while (it.next()) |entry| { + const sub_usingnamespace_decl = entry.key_ptr.*; + const sub_is_pub = entry.value_ptr.*; + if (!sub_is_pub and src_file != sub_usingnamespace_decl.namespace.file_scope) { + // Skip usingnamespace decls which are not marked pub, which are in + // a different file than the `a.b`/`@hasDecl` syntax. + continue; + } + try sema.ensureDeclAnalyzed(sub_usingnamespace_decl); + const ns_ty = sub_usingnamespace_decl.val.castTag(.ty).?.data; + const sub_ns = ns_ty.getNamespace().?; + try checked_namespaces.put(gpa, sub_ns, {}); + } + } + + switch (candidates.items.len) { + 0 => {}, + 1 => { + const decl = candidates.items[0]; + try mod.declareDeclDependency(sema.owner_decl, decl); + return decl; + }, + else => { + const msg = msg: { + const msg = try mod.errMsg(&block.base, src, "ambiguous reference", .{}); + errdefer msg.destroy(gpa); + for (candidates.items) |candidate| { + const src_loc = candidate.srcLoc(); + try mod.errNoteNonLazy(src_loc, msg, "declared here", .{}); + } + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + }, + } + } else if (namespace.decls.get(ident_name)) |decl| { + try mod.declareDeclDependency(sema.owner_decl, decl); return decl; } + log.debug("{*} ({s}) depends on non-existence of '{s}' in {*} ({s})", .{ sema.owner_decl, sema.owner_decl.name, ident_name, namespace_decl, namespace_decl.name, }); // TODO This dependency is too strong. Really, it should only be a dependency // on the non-existence of `ident_name` in the namespace. We can lessen the number of // outdated declarations by making this dependency more sophisticated. - try sema.mod.declareDeclDependency(sema.owner_decl, namespace_decl); + try mod.declareDeclDependency(sema.owner_decl, namespace_decl); return null; } @@ -2727,10 +2798,7 @@ fn analyzeCall( // we need to resolve the field type expressions right here, right now, while // the child `Sema` is still available, with the AIR instruction map intact, // because the field type expressions may reference into it. - if (sema.typeOf(result).zigTypeTag() == .Type) { - const ty = try sema.analyzeAsType(&child_block, call_src, result); - try sema.resolveDeclFields(&child_block, call_src, ty); - } + try sema.resolvePendingTypes(&child_block); } break :res2 result; @@ -5332,6 +5400,7 @@ fn zirHasField(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr fn zirHasDecl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const src = inst_data.src(); const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const container_type = try sema.resolveType(block, lhs_src, extra.lhs); @@ -5344,7 +5413,7 @@ fn zirHasDecl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr "expected struct, enum, union, or opaque, found '{}'", .{container_type}, ); - if (try sema.lookupInNamespace(namespace, decl_name)) |decl| { + if (try sema.lookupInNamespace(block, src, namespace, decl_name, true)) |decl| { if (decl.is_pub or decl.namespace.file_scope == block.base.namespace().file_scope) { return Air.Inst.Ref.bool_true; } @@ -8203,7 +8272,7 @@ fn namespaceLookup( ) CompileError!?*Decl { const mod = sema.mod; const gpa = sema.gpa; - if (try sema.lookupInNamespace(namespace, decl_name)) |decl| { + if (try sema.lookupInNamespace(block, src, namespace, decl_name, true)) |decl| { if (!decl.is_pub and decl.namespace.file_scope != block.getFileScope()) { const msg = msg: { const msg = try mod.errMsg(&block.base, src, "'{s}' is not marked 'pub'", .{ @@ -8919,8 +8988,7 @@ fn analyzeDeclVal( return result; } -fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { - try sema.mod.declareDeclDependency(sema.owner_decl, decl); +fn ensureDeclAnalyzed(sema: *Sema, decl: *Decl) CompileError!void { sema.mod.ensureDeclAnalyzed(decl) catch |err| { if (sema.owner_func) |owner_func| { owner_func.state = .dependency_failure; @@ -8929,6 +8997,11 @@ fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { } return err; }; +} + +fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { + try sema.mod.declareDeclDependency(sema.owner_decl, decl); + try sema.ensureDeclAnalyzed(decl); const decl_tv = try decl.typedValue(); if (decl_tv.val.castTag(.variable)) |payload| { @@ -9560,6 +9633,16 @@ pub fn resolveTypeLayout( } } +pub fn resolvePendingTypes(sema: *Sema, block: *Scope.Block) !void { + for (sema.types_pending_resolution.items) |ty| { + // If an error happens resolving the fields of a struct, it will be marked + // invalid and a proper compile error set up. But we should still look at the + // other types pending resolution. + const src: LazySrcLoc = .{ .node_offset = 0 }; + sema.resolveDeclFields(block, src, ty) catch continue; + } +} + /// `sema` and `block` are expected to be the same ones used for the `Decl`. pub fn resolveDeclFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void { switch (ty.tag()) { |
