-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Extracted from #14967
class Foo
end
class Bar < Foo
end
foo = Bar.new.as(Foo)
puts typeof(foo) == Foo # interpreted: false, compiled: trueOK, It seems like AI found the root cause and fix for the
typeof(foo) == Foo # falsediscrepancy in interpreter.AI Summary:
Root Cause
The issue stemmed from how the Crystal interpreter handled the typeof() operator. When typeof() was used on a variable that was upcasted (e.g., Bar.new.as(Foo)), the compiler's semantic analysis assigned a virtual metaclass (e.g., Foo+.class) as the type of the TypeOf AST node. This virtual type represents not just the class itself, but the class and all of its descendants.
The interpreter was using this virtual type directly. When typeof(foo) == Foo was evaluated, it was comparing the virtual metaclass Foo+.class with the concrete metaclass Foo.class. These types are not equivalent, so the comparison correctly returned false, leading to the bug. The compiled version of the code does not have this issue because the typeof operator is correctly resolved to the static, concrete type at compile time.
The Fix
The fix was to ensure the interpreter uses a concrete type for typeof expressions, mirroring the behavior of compiled code. This was achieved by modifying the visit(node : TypeOf) method in /home/chao/git/personal/crystal/src/compiler/crystal/interpreter/compiler.cr.
By calling node.type.devirtualize, the virtual metaclass provided by the TypeOf node is converted into its concrete counterpart. This ensures that typeof(foo) resolves to Foo.class, allowing the comparison typeof(foo) == Foo to correctly evaluate to true.
The git patch
--- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -1421,7 +1421,10 @@ class Crystal::Repl::Compiler < Crystal::Visitor def visit(node : TypeOf) return false unless @wants_value - put_type node.type, node: node + # The type of a typeof node can be a virtual metaclass, but typeof + # should return a concrete type, so we devirtualize it. + type = node.type.devirtualize + put_type type, node: node false endThe compiler also devirtualizes the raw type when visiting the TypeOf node:
crystal/src/compiler/crystal/codegen/codegen.cr
Lines 951 to 957 in adff98f
def visit(node : TypeOf) # convert virtual metaclasses to non-virtual ones, because only the # non-virtual type IDs are needed set_current_debug_location(node) if @debug.line_numbers? @last = type_id(node.type.devirtualize) false end