Commit 269a53b6af

Andrew Kelley <andrew@ziglang.org>
2019-05-26 22:21:03
introduce @hasDecl builtin function
closes #1439
1 parent 21ed939
doc/langref.html.in
@@ -6734,6 +6734,38 @@ export fn @"A function name that is a complete sentence."() void {}
       </p>
       {#header_close#}
 
+      {#header_open|@hasDecl#}
+      <pre>{#syntax#}@hasDecl(comptime container: type, comptime name: []const u8) bool{#endsyntax#}</pre>
+      <p>
+      Returns whether or not a {#link|struct#}, {#link|enum#}, or {#link|union#} has a declaration
+      matching {#syntax#}name{#endsyntax#}.
+      </p>
+      {#code_begin|test#}
+const std = @import("std");
+const assert = std.debug.assert;
+
+const Foo = struct {
+    nope: i32,
+
+    pub var blah = "xxx";
+    const hi = 1;
+};
+
+test "@hasDecl" {
+    assert(@hasDecl(Foo, "blah"));
+
+    // Even though `hi` is private, @hasDecl returns true because this test is
+    // in the same file scope as Foo. It would return false if Foo was declared
+    // in a different file.
+    assert(@hasDecl(Foo, "hi"));
+
+    // @hasDecl is for declarations; not fields.
+    assert(!@hasDecl(Foo, "nope"));
+    assert(!@hasDecl(Foo, "nope1234"));
+}
+      {#code_end#}
+      {#header_close#}
+
       {#header_open|@import#}
       <pre>{#syntax#}@import(comptime path: []u8) type{#endsyntax#}</pre>
       <p>
src/all_types.hpp
@@ -1471,6 +1471,7 @@ enum BuiltinFnId {
     BuiltinFnIdErrorReturnTrace,
     BuiltinFnIdAtomicRmw,
     BuiltinFnIdAtomicLoad,
+    BuiltinFnIdHasDecl,
 };
 
 struct BuiltinFnEntry {
@@ -2297,6 +2298,7 @@ enum IrInstructionId {
     IrInstructionIdArrayToVector,
     IrInstructionIdAssertZero,
     IrInstructionIdAssertNonNull,
+    IrInstructionIdHasDecl,
 };
 
 struct IrInstruction {
@@ -3503,6 +3505,13 @@ struct IrInstructionAssertNonNull {
     IrInstruction *target;
 };
 
+struct IrInstructionHasDecl {
+    IrInstruction base;
+
+    IrInstruction *container;
+    IrInstruction *name;
+};
+
 static const size_t slice_ptr_index = 0;
 static const size_t slice_len_index = 1;
 
src/codegen.cpp
@@ -5616,6 +5616,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
         case IrInstructionIdLoadPtr:
         case IrInstructionIdBitCast:
         case IrInstructionIdGlobalAsm:
+        case IrInstructionIdHasDecl:
             zig_unreachable();
 
         case IrInstructionIdDeclVarGen:
@@ -7409,6 +7410,7 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdToBytes, "sliceToBytes", 1);
     create_builtin_fn(g, BuiltinFnIdFromBytes, "bytesToSlice", 2);
     create_builtin_fn(g, BuiltinFnIdThis, "This", 0);
+    create_builtin_fn(g, BuiltinFnIdHasDecl, "hasDecl", 2);
 }
 
 static const char *bool_to_str(bool b) {
src/ir.cpp
@@ -1011,6 +1011,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionAssertNonNull *)
     return IrInstructionIdAssertNonNull;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionHasDecl *) {
+    return IrInstructionIdHasDecl;
+}
+
 template<typename T>
 static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) {
     T *special_instruction = allocate<T>(1);
@@ -3014,6 +3018,19 @@ static IrInstruction *ir_build_sqrt(IrBuilder *irb, Scope *scope, AstNode *sourc
     return &instruction->base;
 }
 
+static IrInstruction *ir_build_has_decl(IrBuilder *irb, Scope *scope, AstNode *source_node,
+        IrInstruction *container, IrInstruction *name)
+{
+    IrInstructionHasDecl *instruction = ir_build_instruction<IrInstructionHasDecl>(irb, scope, source_node);
+    instruction->container = container;
+    instruction->name = name;
+
+    ir_ref_instruction(container, irb->current_basic_block);
+    ir_ref_instruction(name, irb->current_basic_block);
+
+    return &instruction->base;
+}
+
 static IrInstruction *ir_build_check_runtime_scope(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *scope_is_comptime, IrInstruction *is_comptime) {
     IrInstructionCheckRuntimeScope *instruction = ir_build_instruction<IrInstructionCheckRuntimeScope>(irb, scope, source_node);
     instruction->scope_is_comptime = scope_is_comptime;
@@ -5098,6 +5115,21 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
                 }
                 return ir_lval_wrap(irb, scope, result, lval);
             }
+        case BuiltinFnIdHasDecl:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
+                if (arg0_value == irb->codegen->invalid_instruction)
+                    return arg0_value;
+
+                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
+                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
+                if (arg1_value == irb->codegen->invalid_instruction)
+                    return arg1_value;
+
+                IrInstruction *has_decl = ir_build_has_decl(irb, scope, node, arg0_value, arg1_value);
+                return ir_lval_wrap(irb, scope, has_decl, lval);
+            }
     }
     zig_unreachable();
 }
@@ -23173,6 +23205,33 @@ static IrInstruction *ir_analyze_instruction_check_runtime_scope(IrAnalyze *ira,
     return ir_const_void(ira, &instruction->base);
 }
 
+static IrInstruction *ir_analyze_instruction_has_decl(IrAnalyze *ira, IrInstructionHasDecl *instruction) {
+    ZigType *container_type = ir_resolve_type(ira, instruction->container->child);
+    if (type_is_invalid(container_type))
+        return ira->codegen->invalid_instruction;
+
+    Buf *name = ir_resolve_str(ira, instruction->name->child);
+    if (name == nullptr)
+        return ira->codegen->invalid_instruction;
+
+    if (!is_container(container_type)) {
+        ir_add_error(ira, instruction->container,
+            buf_sprintf("expected struct, enum, or union; found '%s'", buf_ptr(&container_type->name)));
+        return ira->codegen->invalid_instruction;
+    }
+
+    ScopeDecls *container_scope = get_container_scope(container_type);
+    Tld *tld = find_container_decl(ira->codegen, container_scope, name);
+    if (tld == nullptr)
+        return ir_const_bool(ira, &instruction->base, false);
+
+    if (tld->visib_mod == VisibModPrivate && tld->import != get_scope_import(instruction->base.scope)) {
+        return ir_const_bool(ira, &instruction->base, false);
+    }
+
+    return ir_const_bool(ira, &instruction->base, true);
+}
+
 static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
@@ -23467,6 +23526,8 @@ static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructio
             return ir_analyze_instruction_enum_to_int(ira, (IrInstructionEnumToInt *)instruction);
         case IrInstructionIdCheckRuntimeScope:
             return ir_analyze_instruction_check_runtime_scope(ira, (IrInstructionCheckRuntimeScope *)instruction);
+        case IrInstructionIdHasDecl:
+            return ir_analyze_instruction_has_decl(ira, (IrInstructionHasDecl *)instruction);
     }
     zig_unreachable();
 }
@@ -23703,6 +23764,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdEnumToInt:
         case IrInstructionIdVectorToArray:
         case IrInstructionIdArrayToVector:
+        case IrInstructionIdHasDecl:
             return false;
 
         case IrInstructionIdAsm:
src/ir_print.cpp
@@ -1453,6 +1453,14 @@ static void ir_print_decl_var_gen(IrPrint *irp, IrInstructionDeclVarGen *decl_va
     }
 }
 
+static void ir_print_has_decl(IrPrint *irp, IrInstructionHasDecl *instruction) {
+    fprintf(irp->f, "@hasDecl(");
+    ir_print_other_instruction(irp, instruction->container);
+    fprintf(irp->f, ",");
+    ir_print_other_instruction(irp, instruction->name);
+    fprintf(irp->f, ")");
+}
+
 static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
     ir_print_prefix(irp, instruction);
     switch (instruction->id) {
@@ -1920,6 +1928,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdResizeSlice:
             ir_print_resize_slice(irp, (IrInstructionResizeSlice *)instruction);
             break;
+        case IrInstructionIdHasDecl:
+            ir_print_has_decl(irp, (IrInstructionHasDecl *)instruction);
+            break;
     }
     fprintf(irp->f, "\n");
 }
test/stage1/behavior/hasdecl/foo.zig
@@ -0,0 +1,2 @@
+pub const public_thing = 42;
+const private_thing = 666;
test/stage1/behavior/hasdecl.zig
@@ -0,0 +1,21 @@
+const std = @import("std");
+const expect = std.testing.expect;
+
+const Foo = @import("hasdecl/foo.zig");
+
+const Bar = struct {
+    nope: i32,
+
+    const hi = 1;
+    pub var blah = "xxx";
+};
+
+test "@hasDecl" {
+    expect(@hasDecl(Foo, "public_thing"));
+    expect(!@hasDecl(Foo, "private_thing"));
+    expect(!@hasDecl(Foo, "no_thing"));
+
+    expect(@hasDecl(Bar, "hi"));
+    expect(@hasDecl(Bar, "blah"));
+    expect(!@hasDecl(Bar, "nope"));
+}
test/stage1/behavior.zig
@@ -53,6 +53,7 @@ comptime {
     _ = @import("behavior/fn_in_struct_in_comptime.zig");
     _ = @import("behavior/for.zig");
     _ = @import("behavior/generics.zig");
+    _ = @import("behavior/hasdecl.zig");
     _ = @import("behavior/if.zig");
     _ = @import("behavior/import.zig");
     _ = @import("behavior/incomplete_struct_param_tld.zig");
test/compile_errors.zig
@@ -2,6 +2,15 @@ const tests = @import("tests.zig");
 const builtin = @import("builtin");
 
 pub fn addCases(cases: *tests.CompileErrorContext) void {
+    cases.add(
+        "@hasDecl with non-container",
+        \\export fn entry() void {
+        \\    _ = @hasDecl(i32, "hi");
+        \\}
+    ,
+        "tmp.zig:2:18: error: expected struct, enum, or union; found 'i32'",
+    );
+
     cases.add(
         "field access of slices",
         \\export fn entry() void {