Blog post: `reveal_type` in Crystal

Brian (@bcardiff) wrote a blog post about a handy way to reveal the type of an expression. You’ll learn not only a neat tool, but also a bit of macro magic :mage:

7 Likes

Following solution can be a supplement to above solution.

1. Add a BlackHole type for a universal type for any unknown type.

class BlackHole
  macro method_missing(call)
    raise "BUG: must implement {{call.name}}"
  end
end

2. for any places compiler complain “can not infer current type”, just use BlackHole as type.

3. compile again, compiler will tell you, “should be a String, not BlackHole”, then, you known, the correct type should be String.

I use this hack all the days when i porting Ruby gems into Crystal, this solution come from asterite by this reply

BTW, the code in blog conclusion not works on my laptop 1.7.2

# 1.cr

def reveal_type_helper(t : T, l) : T forall T
  {% puts "   : #{T}" %}
  t
end

macro reveal_type(t)
  {% loc = "#{t.filename.id}:#{t.line_number}:#{t.column_number}" %}
  {% puts "Revealed type #{loc.id}" %}
  {% puts "  #{t.id}" %}
  reveal_type_helper({{t}}, { {{loc.tr("/:.", "___").id}}: 1 })
end

x = "hello"

reveal_type x
Revealed type /home/zw963/1.cr:15:13
  x
There was a problem expanding macro 'reveal_type'

Code in src/1.cr:15:1

 15 | reveal_type x
      ^
Called macro defined in src/1.cr:6:1

 6 | macro reveal_type(t)

Which expanded to:

   2 |   
   3 |   
 > 4 |   reveal_type_helper(x, { _home_zw963_Crystal_crystal-china_ferrum_src_1_cr_15_13: 1 })
                                                                                        ^
Warning: space required before colon in type declaration (run `crystal tool format` to fix this)

A total of 1 warnings were found.
There was a problem expanding macro 'reveal_type'

Code in src/1.cr:15:1

 15 | reveal_type x
      ^
Called macro defined in src/1.cr:6:1

 6 | macro reveal_type(t)

Which expanded to:

 > 2 |   
 > 3 |   
 > 4 |   reveal_type_helper(x, { _home_zw963_Crystal_crystal-china_ferrum_src_1_cr_15_13: 1 })
                                                                                          ^
Error: unexpected token: "1"

The error happens because your path contains a dash (-) and the example code does not escape that.
It needs more complete sanitizing to produce only valid symbol names.
This particular issue can be fixed with using loc.tr("/:.-", "____"). But that’s just a minor improvement and it’s still susceptible to other problems.
Wrapping the name in quotes (and escaping ") would probably be a good idea.