Storing type declaration produces a bug

Hey everyone,

I’m trying to emulate the trick used in Lucky for defining required arguments on views with the needs macro, which then auto-generates an initializer.

class Users::IndexPage < MainLayout
  needs page : Int32 = 1
  needs status : String?
end

Users::IndexPage.new(page: 5)
Users::IndexPage.new(page: 5, status: "ok")
Users::IndexPage.new                        # error: page is missing
Users::IndexPage.new(page: "10")            # error: incorrect type

I compiled a minimal implementation from the Lucky repo but it produces a compiler error while it clearly works in Lucky. Is it indeed a bug or am I missing something?

class Foo
  macro needs(declaration)
    {% ASSIGNS << declaration %}
  end

  macro inherited
    ASSIGNS = [] of Nil

    macro finished
      def assigns
        ASSIGNS
      end
    end
  end
end

class Bar < Foo
  needs x : Float64
end

Bar.assigns

It produces:

Crystal::ASTNode#dependencies cannot be nil (NilAssertionError)
  from ../../../../../../crystal/src/hash.cr:1030:49 in 'reset_block_vars'
  from ../../../../../../crystal/src/compiler/crystal/codegen/codegen.cr:1506:11 in 'accept'
  from ../../../../../../crystal/src/compiler/crystal/codegen/codegen.cr:2193:7 in 'prepare_call_args_non_external'
  from ../../../../../../crystal/src/compiler/crystal/codegen/call.cr:57:7 in 'visit'
  from ../../../../../../crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from ../../../../../../crystal/src/compiler/crystal/semantic/bindings.cr:17:7 in 'prepare_call_args_non_external'
  from ../../../../../../crystal/src/compiler/crystal/codegen/call.cr:57:7 in 'visit'
  from ../../../../../../crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from ../../../../../../crystal/src/compiler/crystal/codegen/codegen.cr:628:9 in 'accept'
  from ../../../../../../crystal/src/compiler/crystal/codegen/codegen.cr:2193:7 in 'visit'
  from ../../../../../../crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from ../../../../../../crystal/src/compiler/crystal/codegen/const.cr:2188:9 in 'initialize_const'
  from ../../../../../../crystal/src/compiler/crystal/codegen/codegen.cr:939:9 in 'visit'
  from ../../../../../../crystal/src/compiler/crystal/syntax/visitor.cr:27:12 in 'accept'
  from ../../../../../../crystal/src/compiler/crystal/codegen/codegen.cr:628:9 in 'accept'
  from ../../../../../../crystal/src/compiler/crystal/codegen/codegen.cr:679:9 in 'accept'
  from ../../../../../../crystal/src/compiler/crystal/codegen/codegen.cr:628:9 in 'accept'
  from ../../../../../../crystal/src/compiler/crystal/codegen/codegen.cr:2158:7 in 'codegen'
  from ../../../../../../crystal/src/compiler/crystal/compiler.cr:172:16 in 'compile'
  from ../../../../../../crystal/src/compiler/crystal/command.cr:209:5 in 'run_command'
  from ../../../../../../crystal/src/compiler/crystal/command.cr:116:7 in 'run'
  from ../../../../../../crystal/src/compiler/crystal.cr:11:1 in '__crystal_main'
  from ../../../../../../crystal/src/crystal/main.cr:105: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

The ASSIGNS = [] of Nil declaration seems to be the culprit; if I changed it to an array of String and then ASSIGNS << declaration.stringify, it works. Is there another possibility to save the TypeDeclaration since it is not a valid type?

Thanks.

I’m pretty sure Lucky only uses that ASSIGNS constant at compile-time, in other macros. If you try to use it at runtime you hit a bug because it will generate something like:

[x : Float64] of Nil

and I think x : Float64 doesn’t get a type in the compiler (in fact, if you try compiling the above snippet you get the same bug). It’s still a bug, but when we fix it it won’t help you solve your problem.

How are you planning to use the ASSIGNS constant?

For instance, this works fine:

class Foo
  macro needs(declaration)
    {% ASSIGNS << declaration %}
  end

  macro inherited
    ASSIGNS = [] of Nil

    macro finished
      def initialize(
        \{% for assign in ASSIGNS %}
          @\{{assign}},
        \{% end %}
        )
      end
    end
  end
end

class Bar < Foo
  needs x : Float64
end

Bar.new(x: 1.2)
2 Likes

Thanks for the reply. Well, I intended to use it like that but I focused on the error when returning ASSIGNS even though it was just temporary code to print the declarations :upside_down_face:

What you try to do may also be doable with macros. User-defined macros were probably not yet available when Lucky’s API was designed. But this works now:

annotation Needs
end

class Foo
  macro inherited
    macro finished
      def initialize(
        \{% for need in @type.annotations(Needs) %}
          @\{{ need[0].id }},
        \{% end %}
        )
      end
    end
  end
end

@[Needs(x : Float64)]
class Bar < Foo
end

Bar.new(x: 1.2)
1 Like

You can also use puts inside macros to debug those things :slight_smile:

1 Like