Weird crystal message error output

Hi guys, I just wanted to report to you a very not meaningful message that generate crystal in some circumstances.

You can reproduce easily that.

When you assign a wrong type to a var and then use it badly, the message is just not helpful at all.

Look at that code:

size = "wrong type"

(0..size).each do |number|
    puts "hello"

That is the error I have got:

zohran@alienware-m17-r3:~/Downloads$ crystal 
Showing last frame. Use --error-trace for full trace.

There was a problem expanding macro 'macro_140325733465824'

Code in /usr/share/crystal/src/

 120 | {% if E == Nil %}
Called macro defined in /usr/share/crystal/src/

 120 | {% if E == Nil %}

Which expanded to:

 > 1 | 
 > 2 |       end_value = @end
 > 3 |       while end_value.nil? || current < end_value
Error: expected argument #1 to 'Int32#<' to be Float32, Float64, Int128, Int16, Int32, Int64, Int8, Number, UInt128, UInt16, UInt32, UInt64 or UInt8, not String

Overloads are:
 - Int32#<(other : Int8)
 - Int32#<(other : Int16)
 - Int32#<(other : Int32)
 - Int32#<(other : Int64)
 - Int32#<(other : Int128)
 - Int32#<(other : UInt8)
 - Int32#<(other : UInt16)
 - Int32#<(other : UInt32)
 - Int32#<(other : UInt64)
 - Int32#<(other : UInt128)
 - Int32#<(other : Float32)
 - Int32#<(other : Float64)
 - Comparable(T)#<(other : T)

That was fine because I know perfectly my project, but good luck to track that in a big project :face_with_peeking_eye:

I think will be helpful if we can just get the line when the problem occured

An other example to show sometime crystal will compile a program even obviously a variable is not declared at all in a function:

def returnBug
    text = "hello"
    return "#{fakevar}"

That code is totally compilable

I wanted to report that before but I didn’t have the time, sorry for that.

I just try to show some weakness of the compiler output. Because if a beginner want to use crystal, it can be a big wall I think

If you run with --error-trace, you’ll be able to see the source of the error:

 3 | (0..size).each do |number|
Error: instantiating 'Range(Int32, String)#each()'

What’s going on here is here is there is no enforcement that both sides of a Range are the same type. Would be curious to see if anyone has any use cases for this, as I’d imagine most have both sides being the same. Otherwise may be worth exploring if there’s something that could be done to prevent these kinds of issues at compile time. Like how we have the strict_multi_assign flag.

What is likely happening is if you never call the method, the compiler excludes it from type checking since it’s never used. But it most certainly results in a error if you were to try and use it. E.g.

def returnBug
  text = "hello"
  return "#{fakevar}"

pp returnBug

Results in:

 3 | return "#{fakevar}"
Error: undefined local variable or method 'fakevar' for top-level

So in that case, I think that output for the log should be not an option but returned by default like that. But okay I didn’t know we can do like that :slightly_smiling_face:

It’s just I think undeclared variable should be detected before the compilation, without extra code

It’s called out in the first line of the default trace ;)

zohran@alienware-m17-r3:~/Downloads$ crystal 
Showing last frame. Use --error-trace for full trace.

Related: Improve compile time error with --error-trace disabled · Issue #8410 · crystal-lang/crystal · GitHub

Compilation has to happen for there to be a compiler error. This specific case just doesn’t get caught due to the “feature” of the compiler I mentioned earlier. This is why it’s good to have test coverage on your methods/types. Because it’s better to have the tests detect typos or something that would cause compile time errors than the end user when they go to use something for the first time.