diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 9d6304e81f01..4580222eeab3 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -3279,6 +3279,127 @@ module Crystal end end + describe LibDef do + lib_def = LibDef.new(Path.new("Foo", "Bar", global: true), FunDef.new("foo")) + + it "executes kind" do + assert_macro %({{x.kind}}), %(lib), {x: lib_def} + end + + it "executes name" do + assert_macro %({{x.name}}), %(::Foo::Bar), {x: lib_def} + assert_macro %({{x.name(generic_args: true)}}), %(::Foo::Bar), {x: lib_def} + assert_macro %({{x.name(generic_args: false)}}), %(::Foo::Bar), {x: lib_def} + assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to LibDef#name must be a BoolLiteral, not NumberLiteral", {x: lib_def} + end + + it "executes body" do + assert_macro %({{x.body}}), %(fun foo), {x: lib_def} + end + end + + describe CStructOrUnionDef do + c_struct_def = CStructOrUnionDef.new("Foo", TypeDeclaration.new("x".var, "Int".path)) + c_union_def = CStructOrUnionDef.new("Bar", Include.new("Foo".path), union: true) + + it "executes kind" do + assert_macro %({{x.kind}}), %(struct), {x: c_struct_def} + assert_macro %({{x.kind}}), %(union), {x: c_union_def} + end + + it "executes name" do + assert_macro %({{x.name}}), %(Foo), {x: c_struct_def} + assert_macro %({{x.name(generic_args: true)}}), %(Foo), {x: c_struct_def} + assert_macro %({{x.name(generic_args: false)}}), %(Foo), {x: c_struct_def} + assert_macro_error %({{x.name(generic_args: 99)}}), "named argument 'generic_args' to CStructOrUnionDef#name must be a BoolLiteral, not NumberLiteral", {x: c_struct_def} + end + + it "executes body" do + assert_macro %({{x.body}}), %(x : Int), {x: c_struct_def} + assert_macro %({{x.body}}), %(include Foo), {x: c_union_def} + end + + it "executes union?" do + assert_macro %({{x.union?}}), %(false), {x: c_struct_def} + assert_macro %({{x.union?}}), %(true), {x: c_union_def} + end + end + + describe FunDef do + lib_fun = FunDef.new("foo") + top_level_fun = FunDef.new("bar", [Arg.new("x", restriction: "Int32".path), Arg.new("", restriction: "Char".path)], "Void".path, true, 1.int32, "y.z") + top_level_fun2 = FunDef.new("baz", body: Nop.new) + + it "executes name" do + assert_macro %({{x.name}}), %(foo), {x: lib_fun} + assert_macro %({{x.name}}), %(bar), {x: top_level_fun} + end + + it "executes real_name" do + assert_macro %({{x.real_name}}), %(), {x: lib_fun} + assert_macro %({{x.real_name}}), %("y.z"), {x: top_level_fun} + end + + it "executes args" do + assert_macro %({{x.args}}), %([]), {x: lib_fun} + assert_macro %({{x.args}}), %([x : Int32, : Char]), {x: top_level_fun} + end + + it "executes variadic?" do + assert_macro %({{x.variadic?}}), %(false), {x: lib_fun} + assert_macro %({{x.variadic?}}), %(true), {x: top_level_fun} + end + + it "executes return_type" do + assert_macro %({{x.return_type}}), %(), {x: lib_fun} + assert_macro %({{x.return_type}}), %(Void), {x: top_level_fun} + end + + it "executes body" do + assert_macro %({{x.body}}), %(), {x: lib_fun} + assert_macro %({{x.body}}), %(1), {x: top_level_fun} + assert_macro %({{x.body}}), %(), {x: top_level_fun2} + end + + it "executes has_body?" do + assert_macro %({{x.has_body?}}), %(false), {x: lib_fun} + assert_macro %({{x.has_body?}}), %(true), {x: top_level_fun} + assert_macro %({{x.has_body?}}), %(true), {x: top_level_fun2} + end + end + + describe TypeDef do + type_def = TypeDef.new("Foo", Path.new("Bar", "Baz", global: true)) + + it "executes name" do + assert_macro %({{x.name}}), %(Foo), {x: type_def} + end + + it "executes type" do + assert_macro %({{x.type}}), %(::Bar::Baz), {x: type_def} + end + end + + describe ExternalVar do + external_var1 = ExternalVar.new("foo", Path.new("Bar", "Baz")) + external_var2 = ExternalVar.new("X", Generic.new(Path.global("Pointer"), ["Char".path] of ASTNode), real_name: "y.z") + + it "executes name" do + assert_macro %({{x.name}}), %(foo), {x: external_var1} + assert_macro %({{x.name}}), %(X), {x: external_var2} + end + + it "executes real_name" do + assert_macro %({{x.real_name}}), %(), {x: external_var1} + assert_macro %({{x.real_name}}), %("y.z"), {x: external_var2} + end + + it "executes type" do + assert_macro %({{x.type}}), %(Bar::Baz), {x: external_var1} + assert_macro %({{x.type}}), %(::Pointer(Char)), {x: external_var2} + end + end + describe "env" do it "has key" do ENV["FOO"] = "foo" diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 90c802bad25e..c18782dac954 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -1806,6 +1806,185 @@ module Crystal::Macros end end + # A lib definition. + # + # Every lib definition `node` is equivalent to: + # + # ``` + # {% begin %} + # {{ node.kind }} {{ node.name }} + # {{ node.body }} + # end + # {% end %} + # ``` + class LibDef < ASTNode + # Returns the keyword used to define this type. + # + # For `LibDef` this is always `lib`. + def kind : MacroId + end + + # Returns the name of this type definition. + # + # *generic_args* has no effect. It exists solely to match the interface of + # other related AST nodes. + def name(*, generic_args : BoolLiteral = true) : Path + end + + # Returns the body of this type definition. + def body : ASTNode + end + end + + # A struct or union definition inside a lib. + # + # Every type definition `node` is equivalent to: + # + # ``` + # {% begin %} + # {{ node.kind }} {{ node.name }} + # {{ node.body }} + # end + # {% end %} + # ``` + class CStructOrUnionDef < ASTNode + # Returns whether this node defines a C union. + def union? : BoolLiteral + end + + # Returns the keyword used to define this type. + # + # For `CStructOrUnionDef` this is either `struct` or `union`. + def kind : MacroId + end + + # Returns the name of this type definition. + # + # *generic_args* has no effect. It exists solely to match the interface of + # other related AST nodes. + def name(*, generic_args : BoolLiteral = true) : Path + end + + # Returns the body of this type definition. + def body : ASTNode + end + end + + # A function declaration inside a lib, or a top-level C function definition. + # + # Every function `node` is equivalent to: + # + # ``` + # fun {{ node.name }} {% if real_name = node.real_name %}= {{ real_name }}{% end %}( + # {% for arg in node.args %} {{ arg }}, {% end %} + # {% if node.variadic? %} ... {% end %} + # ) {% if return_type = node.return_type %}: {{ return_type }}{% end %} + # {% if node.has_body? %} + # {{ body }} + # end + # {% end %} + # ``` + class FunDef < ASTNode + # Returns the name of the function in Crystal. + def name : MacroId + end + + # Returns the real C name of the function, if any. + def real_name : StringLiteral | Nop + end + + # Returns the parameters of the function. + # + # This does not include the variadic parameter. + def args : ArrayLiteral(Arg) + end + + # Returns whether the function is variadic. + def variadic? : BoolLiteral + end + + # Returns the return type of the function, if specified. + def return_type : ASTNode | Nop + end + + # Returns the body of the function, if any. + # + # Both top-level funs and lib funs may return a `Nop`. Instead, `#has_body?` + # can be used to distinguish between the two. + # + # ``` + # macro body_class(x) + # {{ (x.is_a?(LibDef) ? x.body : x).body.class_name }} + # end + # + # body_class(lib MyLib + # fun foo + # end) # => "Nop" + # + # body_class(fun foo + # end) # => "Nop" + # ``` + def body : ASTNode | Nop + end + + # Returns whether this function has a body. + # + # Top-level funs have a body, whereas lib funs do not. + # + # ``` + # macro has_body(x) + # {{ (x.is_a?(LibDef) ? x.body : x).has_body? }} + # end + # + # has_body(lib MyLib + # fun foo + # end) # => false + # + # has_body(fun foo + # end) # => true + # ``` + def has_body? : BoolLiteral + end + end + + # A typedef inside a lib. + # + # Every typedef `node` is equivalent to: + # + # ``` + # type {{ node.name }} = {{ node.type }} + # ``` + class TypeDef < ASTNode + # Returns the name of the typedef. + def name : Path + end + + # Returns the name of the type this typedef is equivalent to. + def type : ASTNode + end + end + + # An external variable declaration inside a lib. + # + # Every variable `node` is equivalent to: + # + # ``` + # ${{ node.name }} {% if real_name = node.real_name %}= {{ real_name }}{% end %} : {{ node.type }} + # ``` + class ExternalVar < ASTNode + # Returns the name of the variable in Crystal, without the preceding `$`. + def name : MacroId + end + + # Returns the real C name of the variable, if any. + def real_name : StringLiteral | Nop + end + + # Returns the name of the variable's type. + def type : ASTNode + end + end + # A `while` expression class While < ASTNode # Returns this while's condition. @@ -2034,27 +2213,6 @@ module Crystal::Macros end end - # class LibDef < ASTNode - # end - - # class FunDef < ASTNode - # end - - # class TypeDef < ASTNode - # end - - # abstract class CStructOrUnionDef < ASTNode - # end - - # class StructDef < CStructOrUnionDef - # end - - # class UnionDef < CStructOrUnionDef - # end - - # class ExternalVar < ASTNode - # end - # class Alias < ASTNode # end diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 24d3d8bbd14d..3f16d1e16eb5 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2522,6 +2522,97 @@ module Crystal end end end + + class LibDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "kind" + interpret_check_args { MacroId.new("lib") } + when "name" + interpret_check_args(named_params: ["generic_args"]) do + # parse the argument, but ignore it otherwise + parse_generic_args_argument(self, method, named_args, default: true) + @name + end + when "body" + interpret_check_args { @body } + else + super + end + end + end + + class CStructOrUnionDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "kind" + interpret_check_args { MacroId.new(@union ? "union" : "struct") } + when "name" + interpret_check_args(named_params: ["generic_args"]) do + # parse the argument, but ignore it otherwise + parse_generic_args_argument(self, method, named_args, default: true) + Path.new(@name) + end + when "body" + interpret_check_args { @body } + when "union?" + interpret_check_args { BoolLiteral.new(@union) } + else + super + end + end + end + + class FunDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { MacroId.new(@name) } + when "real_name" + interpret_check_args { @real_name != @name ? StringLiteral.new(@real_name) : Nop.new } + when "args" + interpret_check_args { ArrayLiteral.map(@args, &.itself) } + when "variadic?" + interpret_check_args { BoolLiteral.new(@varargs) } + when "return_type" + interpret_check_args { @return_type || Nop.new } + when "body" + interpret_check_args { @body || Nop.new } + when "has_body?" + interpret_check_args { BoolLiteral.new(!@body.nil?) } + else + super + end + end + end + + class TypeDef + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { Path.new(@name).at(@name_location) } + when "type" + interpret_check_args { @type_spec } + else + super + end + end + end + + class ExternalVar + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "name" + interpret_check_args { MacroId.new(@name) } + when "real_name" + interpret_check_args { (real_name = @real_name) ? StringLiteral.new(real_name) : Nop.new } + when "type" + interpret_check_args { @type_spec } + else + super + end + end + end end private def get_named_annotation_args(object)