Commit 515d6430c0

Andrew Kelley <andrew@ziglang.org>
2021-06-15 23:08:57
AstGen: support `@export` with field access
The Zig language specification will support identifiers and field access in order to refer to which declaration to export with `@export`. This commit implements the change in AstGen and updates the language reference.
1 parent 0f4173c
doc/langref.html.in
@@ -7525,13 +7525,22 @@ test "main" {
       {#header_close#}
 
       {#header_open|@export#}
-      <pre>{#syntax#}@export(identifier, comptime options: std.builtin.ExportOptions) void{#endsyntax#}</pre>
+      <pre>{#syntax#}@export(declaration, comptime options: std.builtin.ExportOptions) void{#endsyntax#}</pre>
       <p>
       Creates a symbol in the output object file.
       </p>
       <p>
-      This function can be called from a {#link|comptime#} block to conditionally export symbols.
-      When {#syntax#}identifier{#endsyntax#} is a function with the C calling convention and
+      <code>declaration</code> must be one of two things:
+      </p>
+      <ul>
+        <li>An identifier ({#syntax#}x{#endsyntax#}) identifying a {#link|function|Functions#} or
+          {#link|global variable|Global Variables#}.</li>
+        <li>Field access ({#syntax#}x.y{#endsyntax#}) looking up a {#link|function|Functions#} or
+          {#link|global variable|Global Variables#}.</li>
+      </ul>
+      <p>
+      This builtin can be called from a {#link|comptime#} block to conditionally export symbols.
+      When <code>declaration</code> is a function with the C calling convention and
       {#syntax#}options.linkage{#endsyntax#} is {#syntax#}Strong{#endsyntax#}, this is equivalent to
       the {#syntax#}export{#endsyntax#} keyword used on a function:
       </p>
src/AstGen.zig
@@ -6706,18 +6706,33 @@ fn builtinCall(
 
         .@"export" => {
             const node_tags = tree.nodes.items(.tag);
+            const node_datas = tree.nodes.items(.data);
             // This function causes a Decl to be exported. The first parameter is not an expression,
             // but an identifier of the Decl to be exported.
-            if (node_tags[params[0]] != .identifier) {
-                return astgen.failNode(params[0], "the first @export parameter must be an identifier", .{});
+            var namespace: Zir.Inst.Ref = .none;
+            var decl_name: u32 = 0;
+            switch (node_tags[params[0]]) {
+                .identifier => {
+                    const ident_token = main_tokens[params[0]];
+                    decl_name = try astgen.identAsString(ident_token);
+                    // TODO look for local variables in scope matching `decl_name` and emit a compile
+                    // error. Only top-level declarations can be exported. Until this is done, the
+                    // compile error will end up being "use of undeclared identifier" in Sema.
+                },
+                .field_access => {
+                    const namespace_node = node_datas[params[0]].lhs;
+                    namespace = try typeExpr(gz, scope, namespace_node);
+                    const dot_token = main_tokens[params[0]];
+                    const field_ident = dot_token + 1;
+                    decl_name = try astgen.identAsString(field_ident);
+                },
+                else => return astgen.failNode(
+                    params[0], "the first @export parameter must be an identifier", .{},
+                ),
             }
-            const ident_token = main_tokens[params[0]];
-            const decl_name = try astgen.identAsString(ident_token);
-            // TODO look for local variables in scope matching `decl_name` and emit a compile
-            // error. Only top-level declarations can be exported. Until this is done, the
-            // compile error will end up being "use of undeclared identifier" in Sema.
             const options = try comptimeExpr(gz, scope, .{ .ty = .export_options_type }, params[1]);
             _ = try gz.addPlNode(.@"export", node, Zir.Inst.Export{
+                .namespace = namespace,
                 .decl_name = decl_name,
                 .options = options,
             });
src/Sema.zig
@@ -1985,6 +1985,9 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!
     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 decl_name = sema.code.nullTerminatedString(extra.decl_name);
+    if (extra.namespace != .none) {
+        return sema.mod.fail(&block.base, src, "TODO: implement exporting with field access", .{});
+    }
     const decl = try sema.lookupIdentifier(block, lhs_src, decl_name);
     const options = try sema.resolveInstConst(block, rhs_src, extra.options);
     const struct_obj = options.ty.castTag(.@"struct").?.data;
src/Zir.zig
@@ -347,8 +347,9 @@ pub const Inst = struct {
         error_union_type,
         /// `error.Foo` syntax. Uses the `str_tok` field of the Data union.
         error_value,
-        /// Implements the `@export` builtin function.
-        /// Uses the `pl_node` union field. Payload is `Bin`.
+        /// Implements the `@export` builtin function, based on either an identifier to a Decl,
+        /// or field access of a Decl.
+        /// Uses the `pl_node` union field. Payload is `Export`.
         @"export",
         /// Given a pointer to a struct or object that contains virtual fields, returns a pointer
         /// to the named field. The field name is stored in string_bytes. Used by a.b syntax.
@@ -2738,6 +2739,9 @@ pub const Inst = struct {
     };
 
     pub const Export = struct {
+        /// If present, this is referring to a Decl via field access, e.g. `a.b`.
+        /// If omitted, this is referring to a Decl via identifier, e.g. `a`.
+        namespace: Ref,
         /// Null-terminated string index.
         decl_name: u32,
         options: Ref,
@@ -3284,7 +3288,8 @@ const Writer = struct {
         const extra = self.code.extraData(Inst.Export, inst_data.payload_index).data;
         const decl_name = self.code.nullTerminatedString(extra.decl_name);
 
-        try stream.print("{}, ", .{std.zig.fmtId(decl_name)});
+        try self.writeInstRef(stream, extra.namespace);
+        try stream.print(", {}, ", .{std.zig.fmtId(decl_name)});
         try self.writeInstRef(stream, extra.options);
         try stream.writeAll(") ");
         try self.writeSrc(stream, inst_data.src());