From b06c7b25241add5bd30de6a94f90ef1d25aa945d Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 15 Nov 2021 19:08:40 +0800 Subject: [PATCH] Add several missing `ASTNode` macro methods (#10811) Adds: * `Annotation#name` * #9326 converts the name to a `MacroId` on behalf of the user. This PR returns the `Path` directly (like `Generic`). * `{Case,When}#exhaustive?` * `Call#global?` * `Def#{abstract?,free_vars}` --- spec/compiler/macro/macro_methods_spec.cr | 37 +++++++++++++++++++++++ src/compiler/crystal/macros.cr | 27 ++++++++++++++++- src/compiler/crystal/macros/methods.cr | 18 +++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index f7d450ce26e7..95c24d56c07e 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2148,10 +2148,21 @@ module Crystal assert_macro %({{x.return_type}}), "", {x: Def.new("some_def")} end + it "executes free_vars" do + assert_macro %({{x.free_vars}}), "[] of ::NoReturn", {x: Def.new("some_def")} + assert_macro %({{x.free_vars}}), "[T]", {x: Def.new("some_def", free_vars: %w(T))} + assert_macro %({{x.free_vars}}), "[T, U, V]", {x: Def.new("some_def", free_vars: %w(T U V))} + end + it "executes receiver" do assert_macro %({{x.receiver}}), "self", {x: Def.new("some_def", receiver: Var.new("self"))} end + it "executes abstract?" do + assert_macro %({{x.abstract?}}), "false", {x: Def.new("some_def")} + assert_macro %({{x.abstract?}}), "true", {x: Def.new("some_def", abstract: true)} + end + it "executes visibility" do assert_macro %({{x.visibility}}), ":public", {x: Def.new("some_def")} assert_macro %({{x.visibility}}), ":private", {x: Def.new("some_def").tap { |d| d.visibility = Visibility::Private }} @@ -2294,6 +2305,11 @@ module Crystal it "executes named args value" do assert_macro %({{x.named_args[0].value}}), "1", {x: Call.new(1.int32, "some_call", named_args: [NamedArgument.new("a", 1.int32), NamedArgument.new("b", 2.int32)])} end + + it "executes global?" do + assert_macro %({{x.global?}}), "false", {x: Call.new(1.int32, "some_call")} + assert_macro %({{x.global?}}), "true", {x: Call.new(nil, "some_call", global: true)} + end end describe "arg methods" do @@ -2360,9 +2376,17 @@ module Crystal assert_macro %({{x.whens[0].body}}), "4", {x: case_node} end + it "executes when exhaustive?" do + assert_macro %({{x.whens[0].exhaustive?}}), "false", {x: case_node} + end + it "executes else" do assert_macro %({{x.else}}), "5", {x: case_node} end + + it "executes exhaustive?" do + assert_macro %({{x.exhaustive?}}), "false", {x: case_node} + end end describe "in" do @@ -2371,6 +2395,14 @@ module Crystal it "executes whens" do assert_macro %({{x.whens}}), "[in 2, 3\n 4\n]", {x: case_node} end + + it "executes when exhaustive?" do + assert_macro %({{x.whens[0].exhaustive?}}), "true", {x: case_node} + end + + it "executes exhaustive?" do + assert_macro %({{x.exhaustive?}}), "true", {x: case_node} + end end end @@ -2664,6 +2696,11 @@ module Crystal end describe "annotation methods" do + it "executes name" do + assert_macro %({{x.name}}), %(Foo), {x: Annotation.new(Path.new("Foo"))} + assert_macro %({{x.name}}), %(Foo::Bar), {x: Annotation.new(Path.new(["Foo", "Bar"]))} + end + it "executes [] with NumberLiteral" do assert_macro %({{x[y]}}), %(42), { x: Annotation.new(Path.new("Foo"), [42.int32] of ASTNode), diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 5f4ef695acea..99bed1e94a01 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -884,6 +884,10 @@ module Crystal::Macros # An annotation on top of a type or variable. class Annotation < ASTNode + # Returns the name of this annotation. + def name : Path + end + # Returns the value of a positional argument, # or NilLiteral if out of bounds. def [](index : NumberLiteral) : ASTNode @@ -947,6 +951,10 @@ module Crystal::Macros def receiver : ASTNode | Nop end + # Returns `true` if this call refers to a global method (starts with `::`). + def global? : BoolLiteral + end + # Returns this call's arguments. def args : ArrayLiteral end @@ -1140,6 +1148,11 @@ module Crystal::Macros def return_type : ASTNode | Nop end + # Returns the free variables of this method, or an empty `ArrayLiteral` if + # there are none. + def free_vars : ArrayLiteral(MacroId) + end + # Returns the body of this method. def body : ASTNode end @@ -1149,6 +1162,10 @@ module Crystal::Macros def receiver : ASTNode | Nop end + # Returns `true` is this method is declared as abstract, `false` otherwise. + def abstract? : BoolLiteral + end + # Returns the visibility of this def: `:public`, `:protected` or `:private`. def visibility : SymbolLiteral end @@ -1281,7 +1298,7 @@ module Crystal::Macros end end - # A `when` inside a `case`. + # A `when` or `in` inside a `case`. class When < ASTNode # Returns the conditions of this `when`. def conds : ArrayLiteral @@ -1290,6 +1307,10 @@ module Crystal::Macros # Returns the body of this `when`. def body : ASTNode end + + # Returns `true` if this is an `in`, or `false` if this is a `when`. + def exhaustive? : BoolLiteral + end end # A `case` expression. @@ -1305,6 +1326,10 @@ module Crystal::Macros # Returns the `else` of this `case`. def else : ArrayLiteral(When) end + + # Returns whether this `case` is exhaustive (`case ... in`). + def exhaustive? : BoolLiteral + end end # A `select` expression. diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 80535fdc5575..75535a0b8b1c 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1324,6 +1324,14 @@ module Crystal interpret_check_args { BoolLiteral.new(@yields != nil) } when "return_type" interpret_check_args { @return_type || Nop.new } + when "free_vars" + interpret_check_args do + if (free_vars = @free_vars) && !free_vars.empty? + ArrayLiteral.map(free_vars) { |free_var| MacroId.new(free_var) } + else + empty_no_return_array + end + end when "body" interpret_check_args { @body } when "receiver" @@ -1332,6 +1340,8 @@ module Crystal interpret_check_args do visibility_to_symbol(@visibility) end + when "abstract?" + interpret_check_args { BoolLiteral.new(@abstract) } when "annotation" fetch_annotation(self, method, args, named_args, block) do |type| self.annotation(type) @@ -1897,6 +1907,8 @@ module Crystal interpret_check_args { self.block || Nop.new } when "block_arg" interpret_check_args { self.block_arg || Nop.new } + when "global?" + interpret_check_args { BoolLiteral.new(@global) } else super end @@ -1948,6 +1960,8 @@ module Crystal interpret_check_args { ArrayLiteral.map whens, &.itself } when "else" interpret_check_args { self.else || Nop.new } + when "exhaustive?" + interpret_check_args { BoolLiteral.new(@exhaustive) } else super end @@ -1961,6 +1975,8 @@ module Crystal interpret_check_args { ArrayLiteral.new(conds) } when "body" interpret_check_args { body } + when "exhaustive?" + interpret_check_args { BoolLiteral.new(@exhaustive) } else super end @@ -2204,6 +2220,8 @@ module Crystal class Annotation 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 } when "[]" interpret_check_args do |arg| case arg