Skip to content

Commit

Permalink
Make #is_a? in macros respect the AST node hierarchy (#11062)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil committed Aug 12, 2021
1 parent 57afad5 commit 5410318
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 4 deletions.
12 changes: 12 additions & 0 deletions spec/compiler/macro/macro_methods_spec.cr
Expand Up @@ -771,6 +771,7 @@ module Crystal

it "executes is_a?" do
assert_macro "", %({{[1, 2, 3].is_a?(ArrayLiteral)}}), [] of ASTNode, "true"
assert_macro "", %({{[1, 2, 3].is_a?(ASTNode)}}), [] of ASTNode, "true"
assert_macro "", %({{[1, 2, 3].is_a?(NumberLiteral)}}), [] of ASTNode, "false"
end

Expand Down Expand Up @@ -883,6 +884,7 @@ module Crystal

it "executes is_a?" do
assert_macro "", %({{{:a => 1}.is_a?(HashLiteral)}}), [] of ASTNode, "true"
assert_macro "", %({{{:a => 1}.is_a?(ASTNode)}}), [] of ASTNode, "true"
assert_macro "", %({{{:a => 1}.is_a?(RangeLiteral)}}), [] of ASTNode, "false"
end

Expand Down Expand Up @@ -1018,6 +1020,7 @@ module Crystal

it "executes is_a?" do
assert_macro "", %({{{a: 1}.is_a?(NamedTupleLiteral)}}), [] of ASTNode, "true"
assert_macro "", %({{{a: 1}.is_a?(ASTNode)}}), [] of ASTNode, "true"
assert_macro "", %({{{a: 1}.is_a?(RangeLiteral)}}), [] of ASTNode, "false"
end

Expand Down Expand Up @@ -1264,6 +1267,7 @@ module Crystal

it "executes is_a?" do
assert_macro "", %({{ {1, 2, 3}.is_a?(TupleLiteral) }}), [] of ASTNode, "true"
assert_macro "", %({{ {1, 2, 3}.is_a?(ASTNode) }}), [] of ASTNode, "true"
assert_macro "", %({{ {1, 2, 3}.is_a?(ArrayLiteral) }}), [] of ASTNode, "false"
end

Expand Down Expand Up @@ -2189,6 +2193,14 @@ module Crystal
it "executes exp" do
assert_macro "x", %({{x.exp}}), [Not.new("some_call".call)] of ASTNode, "some_call"
end

it "executes is_a?" do
assert_macro "x", %({{ x.is_a?(Not) }}), [Not.new("some_call".call)] of ASTNode, "true"
assert_macro "x", %({{ x.is_a?(Splat) }}), [Not.new("some_call".call)] of ASTNode, "false"
assert_macro "x", %({{ x.is_a?(UnaryExpression) }}), [Not.new("some_call".call)] of ASTNode, "true"
assert_macro "x", %({{ x.is_a?(ASTNode) }}), [Not.new("some_call".call)] of ASTNode, "true"
assert_macro "x", %({{ x.is_a?(TypeNode) }}), [Not.new("some_call".call)] of ASTNode, "false"
end
end

describe "offsetof methods" do
Expand Down
14 changes: 14 additions & 0 deletions src/compiler/crystal/macros.cr
Expand Up @@ -273,6 +273,20 @@ module Crystal::Macros
def raise(message) : NoReturn
end

# Returns `true` if this node's type is the given *type* or any of its
# subclasses.
#
# *type* always refers to an AST node type, never a type in the program.
#
# ```
# {{ 1.is_a?(NumberLiteral) }} # => true
# {{ 1.is_a?(BoolLiteral) }} # => false
# {{ 1.is_a?(ASTNode) }} # => true
# {{ 1.is_a?(Int32) }} # => false
# ```
def __crystal_pseudo_is_a?(type : TypeNode) : BoolLiteral
end

# Returns `true` if this node is a `NilLiteral` or `Nop`.
def __crystal_pseudo_nil? : BoolLiteral
end
Expand Down
3 changes: 1 addition & 2 deletions src/compiler/crystal/macros/interpreter.cr
Expand Up @@ -536,8 +536,7 @@ module Crystal
def visit(node : IsA)
node.obj.accept self
const_name = node.const.to_s
obj_class_desc = @last.class_desc
@last = BoolLiteral.new(@last.class_desc == const_name)
@last = BoolLiteral.new(@last.class_desc_is_a?(const_name))
false
end

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/semantic/ast.cr
Expand Up @@ -791,7 +791,7 @@ module Crystal
def initialize(@name, @type)
end

def class_desc
def self.class_desc
"MetaVar"
end

Expand Down
27 changes: 26 additions & 1 deletion src/compiler/crystal/syntax/ast.cr
Expand Up @@ -82,10 +82,35 @@ module Crystal
self.is_a?(BoolLiteral) && !self.value
end

def class_desc : String
def self.class_desc : String
{{@type.name.split("::").last.id.stringify}}
end

def class_desc
self.class.class_desc
end

def class_desc_is_a?(name)
# e.g. for `Splat < UnaryExpression < ASTNode` this produces:
#
# ```
# name.in?({class_desc, UnaryExpression.class_desc, ASTNode.class_desc})
# ```
#
# this assumes the macro AST node hierarchy matches exactly that of the
# compiler internal node types
{% begin %}
name.in?({
class_desc,
{% for t in @type.ancestors %}
{% if t <= Crystal::ASTNode %}
{{ t }}.class_desc,
{% end %}
{% end %}
})
{% end %}
end

def pretty_print(pp)
pp.text to_s
end
Expand Down

0 comments on commit 5410318

Please sign in to comment.