Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macro raise doesn't keep location #7147

Closed
bew opened this issue Dec 5, 2018 · 12 comments · Fixed by #13260
Closed

Macro raise doesn't keep location #7147

bew opened this issue Dec 5, 2018 · 12 comments · Fixed by #13260
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler

Comments

@bew
Copy link
Contributor

bew commented Dec 5, 2018

Extracted from #6125 (#6125 (comment))

macro raise_on(node)
  {% node.raise "node '#{node}' is here!" %}
end

macro will_raise_soon(node)
  # this is a helper macro
  raise_on {{node}}
end

macro will_raise(node)
  # this is a helper macro
  will_raise_soon {{node}}
end

will_raise :this

I think this should report the error node ':this' is here! at the last line (will_raise :this), without any trace (but allow to show it with --error-trace)

(might need a change in how macro's raise is reported)


thought on this by @makenowjust: #7008 (comment)

The reason what #6125 (comment) does not work is for re-raising an error with node calling macro location here:

  def eval_macro(node)
    yield
  rescue ex : MacroRaiseException
    node.raise ex.message, exception_type: MacroRaiseException
  rescue ex : Crystal::Exception
    node.raise "expanding macro", ex
  end
@RX14 RX14 added kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler labels Dec 6, 2018
@bew
Copy link
Contributor Author

bew commented Feb 8, 2019

Actually it doesn't even work correctly (IMO) currently with a single macro call:

For exemple, with this (normal macro raise):

macro macro_raise
  {% raise "Raised in here" %}
end

macro_raise

We get:

Error in macro_raise.cr:5: Raised in here

macro_raise
^~~~~~~~~~~

Which is nice.


Now if we raise on a specific node in a macro (doesn't work as I would expect):

macro macro_raise_on(arg)
  {% arg.raise "Raised here on #{arg}" %}
end

macro_raise_on :this

We get:

Error in macro_raise.cr:5: Raised here on :this

macro_raise_on :this
^~~~~~~~~~~~~~

But I would expect:

Error in macro_raise.cr:5: Raised here on :this

macro_raise_on :this
               ^~~~~

Fixing this might fix the original code (#7147 (comment)), but I'm not sure.

@asterite
Copy link
Member

Yes, this is strange. I think it used to work in the past.

@franciscoadasme
Copy link

franciscoadasme commented Mar 26, 2019

edit: I just realized that I meant to post this here instead of the other, related issue #7394. Original comment was deleted.

Another (more problematic) case is when requiring a file that calls a macro that raises, where the error message points to the require statement instead of the macro call location, e.g.,

# foo.cr
struct Foo
  macro foo
    {{ raise "error when calling foo" }}
  end
end

# bar.cr
require "./foo"

Foo.foo

# baz.cr
require "./bar.cr"

when running bar.cr, you get (expected behavior)

Error in bar.cr:3: error on foo

Foo.foo
    ^~~

but if you run baz.cr, you get

Error in baz.cr:1: error on foo

require "./bar"
^

If you have several nested files (or when bar.cr is very large), it'll be very difficult to find the aggravating line

@Blacksmoke16
Copy link
Member

Blacksmoke16 commented May 9, 2020

@bew, would you not expect the trace to show the line that was raised in macro land when using node.raise? I.e.

In test.cr:2:11

 2 | {% node.raise "node '#{node}' is here!" %}
             ^----
Error: node ':this' is here!

then using --error-trace would go up the tree:

ccrystal test.cr --error-trace
Using compiled compiler at .build/crystal
There was a problem expanding macro 'will_raise'

Called macro defined in test.cr:10:1

 10 | macro will_raise(node)

Which expanded to:

 > 1 | # this is a helper macro
 > 2 | will_raise_soon :this
Error: expanding macro


There was a problem expanding macro 'will_raise_soon'

Called macro defined in test.cr:5:1

 5 | macro will_raise_soon(node)

Which expanded to:

 > 1 | # this is a helper macro
 > 2 | raise_on :this
Error: expanding macro


In test.cr:2:11

 2 | {% node.raise "node '#{node}' is here!" %}
             ^----
Error: node ':this' is here!

@bew
Copy link
Contributor Author

bew commented May 9, 2020

No, that's the whole point of this issue: when I use node.raise, I expect to see the error pointing to the node in question, where it is actually written in the original code.

In your trace I find it hard to find the node you raised on for example.

Do you see what I mean?

@asterite
Copy link
Member

asterite commented May 9, 2020

I agree with @bew

@Blacksmoke16
Copy link
Member

Blacksmoke16 commented May 9, 2020

Ah, you mean like:

Error in macro_raise.cr:5: Raised here on :this

macro_raise_on :this
               ^~~~~

like you mentioned earlier.

Yea that makes sense.

Maybe this is a slightly different issue then. In Athena I provide a fair bit of compile time errors by raising on specific nodes (like a specific class/method). However currently this is what happens:

Showing last frame. Use --error-trace for full trace.

In test.cr:26:18

 26 | pp RouteResolver.new
                       ^--
Error: Routes can only be defined as instance methods.  Did you mean 'RoutingController#user'?

https://play.crystal-lang.org/#/r/91b6

Which isn't really helpful at all.

IMO ideally it would be like:

In test.cr:8:7

 8 | def self.user : Nil
         ^--------
Error: Routes can only be defined as instance methods.  Did you mean 'RoutingController#user'?

@asterite
Copy link
Member

asterite commented May 9, 2020

You both want the same thing. The problem is that raise on a node doesn't retain that location and it's swallowed by the call that invokes the macro. That's the bug. If we fix that, both cases will be fixed.

@Blacksmoke16
Copy link
Member

So I got annoyed enough to start looking into this. Basically figured out that we're rescuing and re-raising the exception like 3 times, each of which causes the location of the exception to change as previously mentioned.

My question now is there a way to create a new exception that retains the trace of the last one? Mainly given the trace is handled via some C lib and not really part of the exception API. My other question is do we really need to rescue and re-raise it? It seems like we're kinda doing this on purpose to mask the fact the macro was expanded?

For example, given the following code.

macro foo
  {{ raise "OH NO" }}
end

foo
The current full trace
$ crystal run --error-trace test.cr 
In test.cr:28:1

 28 | foo
      ^--
Error: OH NO
The trace with that code commented out
$ ccrystal run --error-trace test.cr 
Using compiled compiler at .build/crystal
In test.cr:28:1

 28 | foo
      ^--
Error: expanding macro


In test.cr:25:6

 25 | {{ raise "OH NO" }}
         ^----
Error: OH NO

So we basically just gain an extra frame showing the actual location of the raise versus hiding the fact that the error happens somewhere in foo.

In the case of raising on a specific node:

macro foo(x)
  {{ x.raise "OH\nNO" }}
end

foo(1)
The current full trace
$ crystal run --error-trace test.cr 
In test.cr:28:1

 28 | foo(1)
      ^--
Error: OH

NO
The trace with that code commented out
$ ccrystal run --error-trace test.cr 
Using compiled compiler at .build/crystal
In test.cr:28:1

 28 | foo(1)
      ^--
Error: expanding macro


In test.cr:25:8

 25 | {{ x.raise "OH\nNO" }}
           ^----
Error: OH

NO


In test.cr:28:5

 28 | foo(1)
          ^
Error: OH

NO

A similar case where we get some more frames, but the last one is essentially more accurate than before.

In a more complex example based on one of my previous comments:

class RoutingController < ATH::Controller
  @[ARTA::Get]
  def self.user : Nil
  end
end
The current last few frames
$ crystal run --error-trace test.cr 
There was a problem expanding macro 'getter'

Called macro defined in macro 'macro_140663609395280'

 117 | macro getter(*names, &block)

Which expanded to:

 >  1 |       
 >  2 |         
 >  3 | 
 >  4 |         
 >  5 | 
 >  6 |         
 >  7 |           @router : Athena::Framework::Routing::Router?
 >  8 | 
 >  9 |           def router : Athena::Framework::Routing::Router
 > 10 |             if (value = @router).nil?
 > 11 |               @router = begin Athena::Framework::Routing::Router.new(nil, true, ACF.parameters.routing.base_uri) end
 > 12 |             else
 > 13 |               value
 > 14 |             end
 > 15 |           end
 > 16 |         
 > 17 |       
 > 18 |     
Error: instantiating 'Athena::Framework::Routing::Router.class#new(Nil, Bool, (URI | Nil))'


In lib/athena/src/ext/routing/router.cr:9:47

 9 | super(ATH::Routing::AnnotationRouteLoader.route_collection,
                                               ^---------------
Error: Routes can only be defined as instance methods. Did you mean 'RoutingController#user'?
The trace with that code commented out
$ ccrystal run --error-trace test.cr 
There was a problem expanding macro 'getter'

Called macro defined in macro 'macro_140578656645456'

 117 | macro getter(*names, &block)

Which expanded to:

 >  1 |       
 >  2 |         
 >  3 | 
 >  4 |         
 >  5 | 
 >  6 |         
 >  7 |           @router : Athena::Framework::Routing::Router?
 >  8 | 
 >  9 |           def router : Athena::Framework::Routing::Router
 > 10 |             if (value = @router).nil?
 > 11 |               @router = begin Athena::Framework::Routing::Router.new(nil, true, ACF.parameters.routing.base_uri) end
 > 12 |             else
 > 13 |               value
 > 14 |             end
 > 15 |           end
 > 16 |         
 > 17 |       
 > 18 |     
Error: instantiating 'Athena::Framework::Routing::Router.class#new(Nil, Bool, (URI | Nil))'


In lib/athena/src/ext/routing/router.cr:9:47

 9 | super(ATH::Routing::AnnotationRouteLoader.route_collection,
                                               ^---------------
Error: instantiating 'Athena::Framework::Routing::AnnotationRouteLoader:Module#route_collection()'


In lib/athena/src/ext/routing/annotation_route_loader.cr:8:5

 8 | populate_collection
     ^------------------
Error: expanding macro


In lib/athena/src/ext/routing/annotation_route_loader.cr:26:33

 26 | class_actions.first.raise "Routes can only be defined as instance methods. Did you mean '#{klass.name}##{class_actions.first.name}'?"
                          ^----
Error: Routes can only be defined as instance methods. Did you mean 'RoutingController#user'?


In src/controllers/example_controller.cr:15:12

 15 | def self.user : Nil
               ^---
Error: Routes can only be defined as instance methods. Did you mean 'RoutingController#user'?

@straight-shoota
Copy link
Member

straight-shoota commented Mar 30, 2023

My question now is there a way to create a new exception that retains the trace of the last one? Mainly given the trace is handled via some C lib and not really part of the exception API.

The source code traces are actually unrelated to libunwind. The frames are represented as nested instances of CodeError, each with an associated source location.

OT: If it was an actual runtime stacktrace, the Exception#callstack property can be freely assigned and copied (example: https://carc.in/#/r/es60). You could technically even craft a Exception::CallStack instance manually.

@Blacksmoke16
Copy link
Member

Blacksmoke16 commented Mar 31, 2023

Okay, so I was playing around with this more today. I was able to have it generate full traces like the following by changing

node.raise ex.message, exception_type: MacroRaiseException
to simply be raise ex so it retains the location of the previous node, not the this new one.

In test.cr:28:5

 28 | foo(1)
          ^
Error: OH

NO

and

In test.cr:31:6

 31 | {{ raise "OH NO" }}
         ^----
Error: OH NO

with correct locations, but without the extra expanding macro and such. However the more complex Athena based example isn't working exactly like I'd expect and is acting like it is in master.

I did some debugging by updating

::raise exception_type.for_node(self, message, inner)
to be pp!(exception_type.for_node(self, message, inner)) and the previous examples produce a single line like exception_type.for_node(self, message, inner) # => #<Crystal::MacroRaiseException:OH NO>. However the more complex example is like:

exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:undefined constant U>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:undefined constant U>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:undefined constant T>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:undefined constant T>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:undefined constant T>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:undefined constant T>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:undefined constant T>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Number in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use Hash(K, V) in unions yet, use a more specific type>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:can't use underscore as generic type argument>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:undefined constant ADVD::Valdatable
Did you mean 'AVD'?>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:undefined constant ADVD::Valdatable
Did you mean 'AVD'?>
exception_type.for_node(self, message, inner) # => #<Crystal::MacroRaiseException:Routes can only be defined as instance methods. Did you mean 'RoutingController#user'?>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:Routes can only be defined as instance methods. Did you mean 'RoutingController#user'?>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:expanding macro>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:expanding macro>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:expanding macro>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:expanding macro>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:expanding macro>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:instantiating 'Athena::DependencyInjection::ServiceContainer#athena_route_handler()'>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:instantiating 'Athena::Framework::Server.class#new(Int32, String, Bool, Nil, Array(HTTP::Handler))'>
exception_type.for_node(self, message, inner) # => #<Crystal::TypeException:instantiating 'Athena::Framework:Module#run()'>

So to me it seems there's a bunch of other random errors that muddy the trace/location of the error such that it uses the location of my actual Routes can only be defined as instance methods. Did you mean 'RoutingController#user' error on the Def node.

Imma try to reduce this behavior to something easier to debug. Could just be a different bug 🤷.

@Blacksmoke16
Copy link
Member

Blacksmoke16 commented Mar 31, 2023

Reduced:

annotation Get; end

abstract class Controller; end

class RoutingController < Controller
  @[Get]
  def self.user : Nil
  end
end

module AnnotationRouteLoader
  class_getter route_collection : Nil do
    populate_collection
  end

  macro populate_collection(base = nil)
    {% begin %}
      {% for klass, c_idx in (base ? [base.resolve] : Controller.all_subclasses.reject &.abstract?) %}
        {%
          class_actions = klass.class.methods.select { |m| m.annotation(Get) }

          unless class_actions.empty?
            class_actions.first.raise "Routes can only be defined as instance methods. Did you mean '#{klass.name}##{class_actions.first.name}'?"
          end
        %}
      {% end %}
    {% end %}
  end
end

AnnotationRouteLoader.route_collection

Produces:

$ ccrystal test.cr
Using compiled compiler at .build/crystal
#<Crystal::TypeException:undefined constant U>
#<Crystal::TypeException:undefined constant U>
#<Crystal::MacroRaiseException:Routes can only be defined as instance methods. Did you mean 'RoutingController#user'?>
#<Crystal::TypeException:Routes can only be defined as instance methods. Did you mean 'RoutingController#user'?>
Showing last frame. Use --error-trace for full trace.

In test.cr:34:23

 34 | AnnotationRouteLoader.route_collection
                            ^---------------
Error: Routes can only be defined as instance methods. Did you mean 'RoutingController#user'?

Will start looking into it more.

EDIT: It seems to also reproduce (with diff errors) with any arbitrary code. E.g.

2 + 2

yields (also printing backtraces):

$ ccrystal run --error-trace test.cr
Using compiled compiler at .build/crystal
#<Crystal::TypeException:undefined constant U>
In src/slice.cr:813:25

 813 | def <=>(other : Slice(U)) forall U
                             ^
Error: undefined constant U
["src/compiler/crystal/semantic/ast.cr:16:10 in 'raise'",
 "src/compiler/crystal/semantic/ast.cr:11:5 in 'raise'",
 "src/compiler/crystal/semantic/call_error.cr:34:7 in 'raise_undefined_constant'",
 "src/compiler/crystal/semantic/type_lookup.cr:413:7 in 'raise_undefined_constant'",
 "src/compiler/crystal/semantic/type_lookup.cr:103:11 in 'lookup'",
 "src/compiler/crystal/semantic/type_lookup.cr:276:34 in 'lookup'",
 "src/compiler/crystal/semantic/type_lookup.cr:45:5 in 'lookup_type:free_vars'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:272:15 in 'check_arg'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:194:29 in 'implements?'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:121:12 in 'implements?'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:96:20 in 'implements_with_ancestors?'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:77:15 in 'check_implemented_in_subtypes'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:63:5 in 'check_implemented_in_subtypes'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:53:13 in 'check_single'",
 "src/int.cr:560:7 in 'check_types'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:31:5 in 'run'",
 "src/compiler/crystal/progress_tracker.cr:23:20 in 'top_level_semantic'",
 "src/compiler/crystal/semantic.cr:22:23 in 'semantic:cleanup'",
 "src/compiler/crystal/compiler.cr:161:14 in 'compile'",
 "src/compiler/crystal/command.cr:338:3 in 'compile'",
 "src/compiler/crystal/command.cr:223:5 in 'run_command'",
 "src/compiler/crystal/command.cr:108:7 in 'run'",
 "src/compiler/crystal/command.cr:51:5 in 'run'",
 "src/compiler/crystal/command.cr:50:3 in 'run'",
 "src/compiler/crystal.cr:11:1 in '__crystal_main'",
 "src/crystal/main.cr:115:5 in 'main_user_code'",
 "src/crystal/main.cr:101:7 in 'main'",
 "src/crystal/main.cr:127:3 in 'main'",
 "/usr/lib/dyld in 'start'"]


#<Crystal::TypeException:undefined constant U>
In src/slice.cr:813:25

 813 | def <=>(other : Slice(U)) forall U
                             ^
Error: undefined constant U
["src/compiler/crystal/semantic/ast.cr:16:10 in 'raise'",
 "src/compiler/crystal/semantic/ast.cr:11:5 in 'raise'",
 "src/compiler/crystal/semantic/call_error.cr:34:7 in 'raise_undefined_constant'",
 "src/compiler/crystal/semantic/type_lookup.cr:413:7 in 'raise_undefined_constant'",
 "src/compiler/crystal/semantic/type_lookup.cr:103:11 in 'lookup'",
 "src/compiler/crystal/semantic/type_lookup.cr:276:34 in 'lookup'",
 "src/compiler/crystal/semantic/type_lookup.cr:45:5 in 'lookup_type:free_vars'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:272:15 in 'check_arg'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:194:29 in 'implements?'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:121:12 in 'implements?'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:96:20 in 'implements_with_ancestors?'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:77:15 in 'check_implemented_in_subtypes'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:63:5 in 'check_implemented_in_subtypes'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:53:13 in 'check_single'",
 "src/int.cr:560:7 in 'check_types'",
 "src/compiler/crystal/semantic/abstract_def_checker.cr:31:5 in 'run'",
 "src/compiler/crystal/progress_tracker.cr:23:20 in 'top_level_semantic'",
 "src/compiler/crystal/semantic.cr:22:23 in 'semantic:cleanup'",
 "src/compiler/crystal/compiler.cr:161:14 in 'compile'",
 "src/compiler/crystal/command.cr:338:3 in 'compile'",
 "src/compiler/crystal/command.cr:223:5 in 'run_command'",
 "src/compiler/crystal/command.cr:108:7 in 'run'",
 "src/compiler/crystal/command.cr:51:5 in 'run'",
 "src/compiler/crystal/command.cr:50:3 in 'run'",
 "src/compiler/crystal.cr:11:1 in '__crystal_main'",
 "src/crystal/main.cr:115:5 in 'main_user_code'",
 "src/crystal/main.cr:101:7 in 'main'",
 "src/crystal/main.cr:127:3 in 'main'",
 "/usr/lib/dyld in 'start'"]


#<Crystal::TypeException:invalid constant value>
Error: invalid constant value
["src/compiler/crystal/semantic/ast.cr:16:10 in 'raise'",
 "src/compiler/crystal/semantic/ast.cr:11:5 in 'raise'",
 "src/compiler/crystal/semantic/math_interpreter.cr:131:5 in 'interpret'",
 "src/compiler/crystal/codegen/types.cr:223:35 in 'compile_time_value'",
 "src/compiler/crystal/codegen/codegen.cr:282:17 in 'initialize_simple_constants'",
 "src/compiler/crystal/codegen/codegen.cr:264:7 in 'initialize'",
 "src/compiler/crystal/codegen/codegen.cr:179:5 in 'new:single_module:debug'",
 "src/compiler/crystal/codegen/codegen.cr:73:17 in 'codegen'",
 "src/compiler/crystal/codegen/codegen.cr:72:5 in 'codegen:debug:single_module'",
 "src/compiler/crystal/progress_tracker.cr:22:7 in 'codegen'",
 "src/compiler/crystal/compiler.cr:162:16 in 'compile'",
 "src/compiler/crystal/command.cr:338:3 in 'compile'",
 "src/compiler/crystal/command.cr:223:5 in 'run_command'",
 "src/compiler/crystal/command.cr:108:7 in 'run'",
 "src/compiler/crystal/command.cr:51:5 in 'run'",
 "src/compiler/crystal/command.cr:50:3 in 'run'",
 "src/compiler/crystal.cr:11:1 in '__crystal_main'",
 "src/crystal/main.cr:115:5 in 'main_user_code'",
 "src/crystal/main.cr:101:7 in 'main'",
 "src/crystal/main.cr:127:3 in 'main'",
 "/usr/lib/dyld in 'start'"]


#<Crystal::TypeException:invalid constant value>
In src/string.cr:139:13

 139 | TYPE_ID = "".crystal_type_id
                 ^
Error: invalid constant value
["src/compiler/crystal/semantic/ast.cr:16:10 in 'raise'",
 "src/compiler/crystal/semantic/ast.cr:11:5 in 'raise'",
 "src/compiler/crystal/semantic/math_interpreter.cr:131:5 in 'interpret'",
 "src/compiler/crystal/semantic/math_interpreter.cr:42:16 in 'interpret'",
 "src/compiler/crystal/codegen/types.cr:223:35 in 'compile_time_value'",
 "src/compiler/crystal/codegen/codegen.cr:282:17 in 'initialize_simple_constants'",
 "src/compiler/crystal/codegen/codegen.cr:264:7 in 'initialize'",
 "src/compiler/crystal/codegen/codegen.cr:179:5 in 'new:single_module:debug'",
 "src/compiler/crystal/codegen/codegen.cr:73:17 in 'codegen'",
 "src/compiler/crystal/codegen/codegen.cr:72:5 in 'codegen:debug:single_module'",
 "src/compiler/crystal/progress_tracker.cr:22:7 in 'codegen'",
 "src/compiler/crystal/compiler.cr:162:16 in 'compile'",
 "src/compiler/crystal/command.cr:338:3 in 'compile'",
 "src/compiler/crystal/command.cr:223:5 in 'run_command'",
 "src/compiler/crystal/command.cr:108:7 in 'run'",
 "src/compiler/crystal/command.cr:51:5 in 'run'",
 "src/compiler/crystal/command.cr:50:3 in 'run'",
 "src/compiler/crystal.cr:11:1 in '__crystal_main'",
 "src/crystal/main.cr:115:5 in 'main_user_code'",
 "src/crystal/main.cr:101:7 in 'main'",
 "src/crystal/main.cr:127:3 in 'main'",
 "/usr/lib/dyld in 'start'"]


#<Crystal::TypeException:invalid constant value>
In src/file.cr:69:32

 69 | DEFAULT_CREATE_PERMISSIONS = File::Permissions.new(0o644)
                                   ^----------------
Error: invalid constant value
["src/compiler/crystal/semantic/ast.cr:16:10 in 'raise'",
 "src/compiler/crystal/semantic/ast.cr:11:5 in 'raise'",
 "src/compiler/crystal/semantic/math_interpreter.cr:118:7 in 'interpret'",
 "src/compiler/crystal/semantic/math_interpreter.cr:61:16 in 'interpret'",
 "src/compiler/crystal/codegen/types.cr:223:35 in 'compile_time_value'",
 "src/compiler/crystal/codegen/codegen.cr:282:17 in 'initialize_simple_constants'",
 "src/compiler/crystal/codegen/codegen.cr:264:7 in 'initialize'",
 "src/compiler/crystal/codegen/codegen.cr:179:5 in 'new:single_module:debug'",
 "src/compiler/crystal/codegen/codegen.cr:73:17 in 'codegen'",
 "src/compiler/crystal/codegen/codegen.cr:72:5 in 'codegen:debug:single_module'",
 "src/compiler/crystal/progress_tracker.cr:22:7 in 'codegen'",
 "src/compiler/crystal/compiler.cr:162:16 in 'compile'",
 "src/compiler/crystal/command.cr:338:3 in 'compile'",
 "src/compiler/crystal/command.cr:223:5 in 'run_command'",
 "src/compiler/crystal/command.cr:108:7 in 'run'",
 "src/compiler/crystal/command.cr:51:5 in 'run'",
 "src/compiler/crystal/command.cr:50:3 in 'run'",
 "src/compiler/crystal.cr:11:1 in '__crystal_main'",
 "src/crystal/main.cr:115:5 in 'main_user_code'",
 "src/crystal/main.cr:101:7 in 'main'",
 "src/crystal/main.cr:127:3 in 'main'",
 "/usr/lib/dyld in 'start'"]


#<Crystal::TypeException:invalid constant value>
In src/int.cr:1263:9

 1263 | MIN = new(1) << 127
              ^--
Error: invalid constant value
["src/compiler/crystal/semantic/ast.cr:16:10 in 'raise'",
 "src/compiler/crystal/semantic/ast.cr:11:5 in 'raise'",
 "src/compiler/crystal/semantic/math_interpreter.cr:92:7 in 'interpret_call_macro'",
 "src/compiler/crystal/semantic/math_interpreter.cr:86:7 in 'interpret'",
 "src/compiler/crystal/semantic/math_interpreter.cr:61:16 in 'interpret'",
 "src/compiler/crystal/codegen/types.cr:223:35 in 'compile_time_value'",
 "src/compiler/crystal/codegen/codegen.cr:282:17 in 'initialize_simple_constants'",
 "src/compiler/crystal/codegen/codegen.cr:264:7 in 'initialize'",
 "src/compiler/crystal/codegen/codegen.cr:179:5 in 'new:single_module:debug'",
 "src/compiler/crystal/codegen/codegen.cr:73:17 in 'codegen'",
 "src/compiler/crystal/codegen/codegen.cr:72:5 in 'codegen:debug:single_module'",
 "src/compiler/crystal/progress_tracker.cr:22:7 in 'codegen'",
 "src/compiler/crystal/compiler.cr:162:16 in 'compile'",
 "src/compiler/crystal/command.cr:338:3 in 'compile'",
 "src/compiler/crystal/command.cr:223:5 in 'run_command'",
 "src/compiler/crystal/command.cr:108:7 in 'run'",
 "src/compiler/crystal/command.cr:51:5 in 'run'",
 "src/compiler/crystal/command.cr:50:3 in 'run'",
 "src/compiler/crystal.cr:11:1 in '__crystal_main'",
 "src/crystal/main.cr:115:5 in 'main_user_code'",
 "src/crystal/main.cr:101:7 in 'main'",
 "src/crystal/main.cr:127:3 in 'main'",
 "/usr/lib/dyld in 'start'"]


#<Crystal::TypeException:invalid constant value>
In src/int.cr:1263:9

 1263 | MIN = new(1) << 127
              ^--
Error: invalid constant value
["src/compiler/crystal/semantic/ast.cr:16:10 in 'raise'",
 "src/compiler/crystal/semantic/ast.cr:11:5 in 'raise'",
 "src/compiler/crystal/semantic/math_interpreter.cr:92:7 in 'interpret_call_macro'",
 "src/compiler/crystal/semantic/math_interpreter.cr:86:7 in 'interpret'",
 "src/compiler/crystal/semantic/math_interpreter.cr:61:16 in 'interpret'",
 "src/compiler/crystal/semantic/math_interpreter.cr:116:7 in 'interpret'",
 "src/compiler/crystal/semantic/math_interpreter.cr:42:16 in 'interpret'",
 "src/compiler/crystal/codegen/types.cr:223:35 in 'compile_time_value'",
 "src/compiler/crystal/codegen/codegen.cr:282:17 in 'initialize_simple_constants'",
 "src/compiler/crystal/codegen/codegen.cr:264:7 in 'initialize'",
 "src/compiler/crystal/codegen/codegen.cr:179:5 in 'new:single_module:debug'",
 "src/compiler/crystal/codegen/codegen.cr:73:17 in 'codegen'",
 "src/compiler/crystal/codegen/codegen.cr:72:5 in 'codegen:debug:single_module'",
 "src/compiler/crystal/progress_tracker.cr:22:7 in 'codegen'",
 "src/compiler/crystal/compiler.cr:162:16 in 'compile'",
 "src/compiler/crystal/command.cr:338:3 in 'compile'",
 "src/compiler/crystal/command.cr:223:5 in 'run_command'",
 "src/compiler/crystal/command.cr:108:7 in 'run'",
 "src/compiler/crystal/command.cr:51:5 in 'run'",
 "src/compiler/crystal/command.cr:50:3 in 'run'",
 "src/compiler/crystal.cr:11:1 in '__crystal_main'",
 "src/crystal/main.cr:115:5 in 'main_user_code'",
 "src/crystal/main.cr:101:7 in 'main'",
 "src/crystal/main.cr:127:3 in 'main'",
 "/usr/lib/dyld in 'start'"]


#<Crystal::TypeException:invalid constant value>
In src/int.cr:1781:9

 1781 | MIN = new 0
              ^--
Error: invalid constant value
["src/compiler/crystal/semantic/ast.cr:16:10 in 'raise'",
 "src/compiler/crystal/semantic/ast.cr:11:5 in 'raise'",
 "src/compiler/crystal/semantic/math_interpreter.cr:92:7 in 'interpret_call_macro'",
 "src/compiler/crystal/semantic/math_interpreter.cr:86:7 in 'interpret'",
 "src/compiler/crystal/codegen/types.cr:223:35 in 'compile_time_value'",
 "src/compiler/crystal/codegen/codegen.cr:282:17 in 'initialize_simple_constants'",
 "src/compiler/crystal/codegen/codegen.cr:264:7 in 'initialize'",
 "src/compiler/crystal/codegen/codegen.cr:179:5 in 'new:single_module:debug'",
 "src/compiler/crystal/codegen/codegen.cr:73:17 in 'codegen'",
 "src/compiler/crystal/codegen/codegen.cr:72:5 in 'codegen:debug:single_module'",
 "src/compiler/crystal/progress_tracker.cr:22:7 in 'codegen'",
 "src/compiler/crystal/compiler.cr:162:16 in 'compile'",
 "src/compiler/crystal/command.cr:338:3 in 'compile'",
 "src/compiler/crystal/command.cr:223:5 in 'run_command'",
 "src/compiler/crystal/command.cr:108:7 in 'run'",
 "src/compiler/crystal/command.cr:51:5 in 'run'",
 "src/compiler/crystal/command.cr:50:3 in 'run'",
 "src/compiler/crystal.cr:11:1 in '__crystal_main'",
 "src/crystal/main.cr:115:5 in 'main_user_code'",
 "src/crystal/main.cr:101:7 in 'main'",
 "src/crystal/main.cr:127:3 in 'main'",
 "/usr/lib/dyld in 'start'"]


#<Crystal::TypeException:invalid constant value>
In src/int.cr:1781:9

 1781 | MIN = new 0
              ^--
Error: invalid constant value
["src/compiler/crystal/semantic/ast.cr:16:10 in 'raise'",
 "src/compiler/crystal/semantic/ast.cr:11:5 in 'raise'",
 "src/compiler/crystal/semantic/math_interpreter.cr:92:7 in 'interpret_call_macro'",
 "src/compiler/crystal/semantic/math_interpreter.cr:86:7 in 'interpret'",
 "src/compiler/crystal/semantic/math_interpreter.cr:116:7 in 'interpret'",
 "src/compiler/crystal/semantic/math_interpreter.cr:42:16 in 'interpret'",
 "src/compiler/crystal/codegen/types.cr:223:35 in 'compile_time_value'",
 "src/compiler/crystal/codegen/codegen.cr:282:17 in 'initialize_simple_constants'",
 "src/compiler/crystal/codegen/codegen.cr:264:7 in 'initialize'",
 "src/compiler/crystal/codegen/codegen.cr:179:5 in 'new:single_module:debug'",
 "src/compiler/crystal/codegen/codegen.cr:73:17 in 'codegen'",
 "src/compiler/crystal/codegen/codegen.cr:72:5 in 'codegen:debug:single_module'",
 "src/compiler/crystal/progress_tracker.cr:22:7 in 'codegen'",
 "src/compiler/crystal/compiler.cr:162:16 in 'compile'",
 "src/compiler/crystal/command.cr:338:3 in 'compile'",
 "src/compiler/crystal/command.cr:223:5 in 'run_command'",
 "src/compiler/crystal/command.cr:108:7 in 'run'",
 "src/compiler/crystal/command.cr:51:5 in 'run'",
 "src/compiler/crystal/command.cr:50:3 in 'run'",
 "src/compiler/crystal.cr:11:1 in '__crystal_main'",
 "src/crystal/main.cr:115:5 in 'main_user_code'",
 "src/crystal/main.cr:101:7 in 'main'",
 "src/crystal/main.cr:127:3 in 'main'",
 "/usr/lib/dyld in 'start'"]

Not sure if these are expected or not given there ultimately isn't actually an error raised, so they must be getting handled somewhere. But there's no backtrace on them so might be a bit annoying to figure out where they're coming from.

EDIT2: Remembered the caller method. Updated example with traces.

EDIT3:

@compile_time_value = interpreter.interpret(value) rescue nil

Probably not an issue itself, but will need to figure out where the location info is going wrong in this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants