Code setup
macro emphasize_type(type)
{% if type.resolve == Int32 %}
"***Int32***"
{% else %}
"***No type***"
{% end %}
end
class Foo
macro emphasize_type(type)
{% if type.resolve == Int32 %}
"***Int32***"
{% else %}
"***No type***"
{% end %}
end
end
class Bar(T)
def func1
puts emphasize_type(T)
end
def func2
puts Foo.emphasize_type(T)
end
end
This works
Bar(Int32).new.func1
[gshah@workstation Desktop]$ crystal delete.cr --error-trace
***Int32***
This does not
Bar(Int32).new.func2
[gshah@workstation Desktop]$ crystal delete.cr --error-trace
Showing last frame. Use --error-trace for full trace.
In delete.cr:25:26
25 | puts Foo.emphasize_type(T)
^
Error: undefined constant T
Why should defining a macro inside a class throw an error?
I think this is a catch of macros in that T
is being passed as a Path
literal and not the TypeNode
that path points to. When you then call .resolve
it’s trying to resolve it in the context of Foo
so it loses the reference to the actual T
. This can be seen more clearly if you run with --error-trace
that its failing on the .resolve
method:
Error: instantiating 'Bar(Int32)#func2()'
In test.cr:25:14
25 | puts Foo.emphasize_type(T)
^-------------
Error: expanding macro
In test.cr:11:16
11 | {% if type.resolve == Int32 %}
^------
Error: undefined constant T
In test.cr:25:29
25 | puts Foo.emphasize_type(T)
^
Error: undefined constant T
In order to pass the type it represents try Foo.emphasize_type({{T}})
which then works.
1 Like
Thanks @Blacksmoke16.
That solves it for simple types like Int32, but not for instances of generic classes like Array(String).
For example, with this code set up
class Foo
macro emphasize_type(type)
{% if type.resolve == Int32 %}
"***Int32***"
{% else %}
"***No type***"
{% end %}
end
end
class Bar(T)
def func2
puts Foo.emphasize_type({{T}})
end
end
as @Blacksmoke16 stated, this works now.
Bar(Int32).new.func2
[gshah@workstation ~]$ crystal Desktop/delete.cr --error-trace
***Int32***
But this still does not work
Bar(Array(String)).new.func2
[gshah@workstation ~]$ crystal Desktop/delete.cr --error-trace
Showing last frame. Use --error-trace for full trace.
There was a problem expanding macro ‘macro_140386515094528’
Code in Desktop/delete.cr:25:28
25 | puts Foo.emphasize_type({{T}})
^
Called macro defined in Desktop/delete.cr:25:28
25 | puts Foo.emphasize_type({{T}})
Which expanded to:
1 | Array(String)
^
Error: Array(T).class is not a generic type, it’s a metaclass
Can it be made to work with Array(String) ?
I think this is essentially the same problem as before, but one that isn’t solved by {{T}}
. Basically Array(Int32)
is being passed where Int32
actually is just the name of the generic var, essentially the same as Array(T)
. Somewhat of a hack but you could use parse_type
to parse it into a TypeNode
which you could then use. Then check to see if its an Array
type, and if so check its type vars to see if any of them are Int32
. Something like:
macro emphasize_type(type)
{% type = parse_type(type.stringify) %}
{% if type.resolve <= Array && type.type_vars.any? &.resolve.==(Int32) %}
"***Int32***"
{% else %}
"***No type***"
{% end %}
end
But what exactly are you wanting to do that you need this for? There might be a better less brittle way to go about it that doesn’t involve you needing to handle all these diff cases.
2 Likes
Is that code a simplified version of what you want/need, or exactly what you need? Because if so, you don’t need macros at all:
def emphasize_type(type : Int32.class)
"***Int32***"
end
def emphasize_type(type : Class)
"***No type***"
end
class Foo
def self.emphasize_type(type : Int32.class)
"***Int32***"
end
def self.emphasize_type(type : Class)
"***No type***"
end
end
class Bar(T)
def func1
puts emphasize_type(T)
end
def func2
puts Foo.emphasize_type(T)
end
end
puts Bar(Int32).new.func1
puts Bar(Int32).new.func2
1 Like
The code I shared is highly simplified version of what I am trying to do but Blacksmoke’s solution using parse_type
works for me.
Thanks everyone for quick response and help with this.