Search code examples
abstract-syntax-treecrystal-lang

Crystal parser creates an ASTNode that should be Crystal::Expressions at runtime but is somehow a Crystal::Nop


require "compiler/crystal/syntax"

s = "a = 5; puts a + 3"

nodes = Crystal::Parser.parse(s)
puts nodes.class                      # => Crystal::Expressions
puts nodes.is_a? Crystal::Expressions # => true
puts nodes.is_a? Crystal::Nop         # => false

puts nodes.expressions

So, I would assume the last expression to give an Array (or an ArrayLiteral node). However, I get

undefined method 'expressions' for Crystal::Nop (compile-time type is Crystal::ASTNode+)

Which makes no sense. .class and .is_a? are runtime checks, so the nodes , which has a compile-time type of ASTNode should be Crystal::Expressions, not Crystal::Nop.

The behaviour is the same on crystal versions 0.25.0, 0.25.1, 0.26.0, 0.26.1 and the version currently on the master branch of the git repo.


Solution

  • The error is raised at compile time because not all subclasses of Crystal::ASTNode have #expressions method. Crystal::Nop just happens to be the first such subclass the compiler checks and subsequently decides to throw an error. The way to fix the code is:

    require "compiler/crystal/syntax"
    
    s = "a = 5; puts a + 3"
    
    nodes = Crystal::Parser.parse(s)
    case nodes
    when Crystal::Expressions
        puts nodes.expressions
    when Crystal::Nop
        puts "It's a nop"
    else
        puts "Unhandles case for #{nodes.class}"
    end
    

    Alternatively, you can force the compiler to assume it's Crystal::Expressions by using .as:

    nodes.as(Crystal::Expressions).expressions
    

    Credit for this answer goes to straight-shoota on this Github issue