crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.32k stars 1.61k forks source link

Bug: `Crystal::ClassDef#resolved_type cannot be nil (NilAssertionError)` #12052

Open LoadingBG opened 2 years ago

LoadingBG commented 2 years ago

I was making a simple Logo language DSL. Logo has the command TO which defines a new instruction with the following syntax:

I created the following macro to try to replicate the syntax as close as possible:

module Turtle
  class Turtle
    # other methods

    macro to(name)
      class Turtle
        def {{name.id}}
          {{with self yield}}
        end
      end
    end
  end
end

and used it like so:

to square do
  repeat 4 do
    forward 100
    right 90
  end
end

where repeat, forward and right are methods defined in the class.

The macro gives the following error:

Crystal::ClassDef#resolved_type cannot be nil (NilAssertionError)
  from /crystal/src/compiler/crystal/semantic/semantic_visitor.cr:268:16 in 'accept'
  from /crystal/src/compiler/crystal/semantic/semantic_visitor.cr:313:5 in 'expand_macro'
  from /crystal/src/compiler/crystal/semantic/main_visitor.cr:1272:10 in 'visit'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from /crystal/src/compiler/crystal/semantic/main_visitor.cr:3261:9 in 'visit'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from /crystal/src/compiler/crystal/semantic/main_visitor.cr:1090:7 in 'visit'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'visit'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from /crystal/src/compiler/crystal/semantic/main_visitor.cr:697:11 in 'visit'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from /crystal/src/compiler/crystal/semantic/call.cr:424:13 in 'lookup_matches_in_type'
  from /crystal/src/compiler/crystal/semantic/call.cr:20:5 in 'lookup_matches_without_splat'
  from /crystal/src/compiler/crystal/semantic/call.cr:130:17 in 'lookup_matches:with_autocast'
  from /crystal/src/compiler/crystal/semantic/call.cr:96:5 in 'recalculate'
  from /crystal/src/compiler/crystal/semantic/main_visitor.cr:1461:7 in 'visit'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from /crystal/src/compiler/crystal/semantic/main_visitor.cr:697:11 in 'visit'
  from /crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from /crystal/src/compiler/crystal/semantic/main_visitor.cr:6:7 in 'semantic:cleanup'
  from /crystal/src/compiler/crystal/compiler.cr:172:14 in 'compile'
  from /crystal/src/compiler/crystal/command.cr:215:5 in 'run_command'
  from /crystal/src/compiler/crystal/command.cr:122:7 in '__crystal_main'
  from /crystal/src/crystal/main.cr:115:5 in 'main'
  from src/env/__libc_start_main.c:94:2 in 'libc_start_main_stage2'
Error: you've found a bug in the Crystal compiler. Please open an issue, including source code that will allow us to reproduce the bug: https://github.com/crystal-lang/crystal/issues

Using {% debug %} at the end of the macro prints this:

class Turtle
  def square
    begin
      repeat(4) do
        forward(100)
        right(90)
      end
    end
  end
end

Versions: crystal -v:

Crystal 1.4.1 [b7377c041] (2022-04-22)

LLVM: 10.0.0
Default target: x86_64-unknown-linux-gnu

OS: Ubuntu 20.04 running in WSL

Full code if needed ```cr module Turtle class Turtle @x : Int32 @y : Int32 @initialX : Int32 @initialY : Int32 @angle : Int32 def initialize(@initialX : Int32, @initialY : Int32) : Nil @x = @initialX @y = @initialY @angle = 0 end private def to_radians(degrees : Int32) : Float64 degrees * Math.acos(-1) / 180 end def forward(steps : Int32) : Nil radians = to_radians(@angle) @x += (steps * Math.sin(radians)).to_i @y -= (steps * Math.cos(radians)).to_i end def backward(steps : Int32) : Nil forward(-steps) end def right(angle : Int32) : Nil @angle += angle end def left(angle : Int32) : Nil right(-angle) end def repeat(times : Int32, &instructions : Int32 ->) : Nil 1.upto(times) do |repcount| with self yield repcount end end macro to(name) class Turtle def {{name.id}} {{with self yield}} end end {% debug %} end def inspect(io : IO) : Nil io << "Turtle(" << @x << ", " << @y << ", " << @angle << ")" end def to_s : String builder = String::Builder.new inspect(builder) builder.to_s end end end def create_playground : Nil turtle = Turtle::Turtle.new(100, 100) with turtle yield end create_playground do to square do repeat 4 do forward 100 right 90 end end square puts to_s end ```
asterite commented 2 years ago

The problem is likely with self yield for which there's no defined implementation. That should probably produce a compiler error.

straight-shoota commented 2 years ago

To be clear: it's the macro expression with self yield.

You don't really need that, though. Just use {{ yield }}. Macro yield needs no with self because the block's code is statically embedded into the parent context and everything should work then as expected.

LoadingBG commented 2 years ago

Removing it gives the exact same error.

straight-shoota commented 2 years ago

Than it's something else apparently.

It probably create_playground which is a method and inside that method you re-open a class to define methods in that class. That doesn't work. Maybe the error message is weird because it happens from a macro expansion. Perhaps it's some other error in the macro evaluation.

asterite commented 2 years ago

Reduced:

struct Int32
  macro bar
    class Foo
    end
  end
end

def foo
  with 1 yield
end

foo do
  bar do
  end
end
asterite commented 2 years ago

It's trying to declare a class inside with ... yield. That should produce an error ("can't dynamically declare class") but that's not the case.