Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add macro methods for lib-related nodes #14218

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions spec/compiler/macro/macro_methods_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
200 changes: 179 additions & 21 deletions src/compiler/crystal/macros.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down
Loading