Skip to content

Commit

Permalink
Add macro method ASTNode#nil? (#10850)
Browse files Browse the repository at this point in the history
#10616 removed a bug that the compiler accepted calls to nil? in macro expressions where that method does not exist. These calls were internally transformed to is_a?(Nil). In the macro interpreter, no value can be of type Nil because that's a runtime type, so any nil? call would always return false.
Removing that transformation from the parser made nil? calls in macro expressions errors because that method is not defined.

But users would expect nil.nil? to return true. So this patch implements ASTNode#nil? in the macro interpreter. It returns true if the value is a NilLiteral or Nop.

This is technically a feature addition, but we should still merge it for 1.1 despite the feature freeze. It actually avoids a breaking change that would occur with #10616 alone. Code that worked in 1.0 would no longer compile (even though it was already broken before and just worked by chance). With this PR, it continues to compile and fulfills the intended purpose.
  • Loading branch information
straight-shoota authored Jun 25, 2021
1 parent 71d6e9b commit 278c15b
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 0 deletions.
14 changes: 14 additions & 0 deletions spec/compiler/macro/macro_methods_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ module Crystal
assert_macro "x", "{{x.class_name}}", [ArrayLiteral.new([Path.new("Foo"), Path.new("Bar")] of ASTNode)] of ASTNode, "\"ArrayLiteral\""
end
end

describe "#nil?" do
it "NumberLiteral" do
assert_macro "", "{{ 1.nil? }}", [] of ASTNode, "false"
end

it "NilLiteral" do
assert_macro "", "{{ nil.nil? }}", [] of ASTNode, "true"
end

it "Nop" do
assert_macro "x", "{{ x.nil? }}", [Nop.new] of ASTNode, "true"
end
end
end

describe "number methods" do
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/crystal/macros.cr
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ module Crystal::Macros
# highlight this node in the error message.
def raise(message) : NoReturn
end

# Returns `true` if this node is a `NilLiteral` or `Nop`.
def __crystal_pseudo_nil? : BoolLiteral
end
end

# The empty node. Similar to a `NilLiteral` but its textual representation
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/crystal/macros/methods.cr
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,10 @@ module Crystal
end
when "!"
BoolLiteral.new(!truthy?)
when "nil?"
interpret_argless_method("nil?", args) do
BoolLiteral.new(is_a?(NilLiteral) || is_a?(Nop))
end
else
raise "undefined macro method '#{class_desc}##{method}'", exception_type: Crystal::UndefinedMacroMethodError
end
Expand Down

0 comments on commit 278c15b

Please sign in to comment.