Skip to content

Commit

Permalink
Compiler: always resolve T in main code as non-virtual. Fixes #3989
Browse files Browse the repository at this point in the history
  • Loading branch information
asterite committed Feb 9, 2017
1 parent b0b4497 commit ecda856
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 3 deletions.
48 changes: 48 additions & 0 deletions spec/compiler/semantic/generic_class_spec.cr
Expand Up @@ -994,4 +994,52 @@ describe "Semantic: generic class" do
),
"private method 'new' called"
end

it "never types Path as virtual outside generic type parameter (#3989)" do
assert_type(%(
class Base
end
class Derived < Base
def initialize(x : Int32)
end
end
class Generic(T)
def initialize
T.new
end
def t
T
end
end
Generic(Base).new.t
)) { types["Base"].metaclass }
end

it "never types Generic as virtual outside generic type parameter (#3989)" do
assert_type(%(
class Base(T)
end
class Derived(T) < Base(T)
def initialize(x : Int32)
end
end
class Generic(T)
def initialize
T.new
end
def t
T
end
end
Generic(Base(Int32)).new.t
)) { generic_class("Base", int32).metaclass }
end
end
12 changes: 11 additions & 1 deletion src/compiler/crystal/semantic/main_visitor.cr
Expand Up @@ -166,7 +166,17 @@ module Crystal
node.target_const = type
node.bind_to type.value
when Type
node.type = check_type_in_type_args(type.remove_alias_if_simple)
# We devirtualize the type because in an expression like
#
# T.new
#
# even if T is a virtual type that resulted from a generic
# type argument, creating an instance or invoking methods
# on the type itself don't need to resolve virtually.
#
# It's different if from a virtual type we do `v.class.new`
# because the class could be any in the hierarchy.
node.type = check_type_in_type_args(type.remove_alias_if_simple).devirtualize
when ASTNode
type.accept self unless type.type?
node.syntax_replacement = type
Expand Down
17 changes: 15 additions & 2 deletions src/compiler/crystal/types.cr
Expand Up @@ -226,8 +226,17 @@ module Crystal
false
end

# Returns the non-virtual type of a given type
# (returns self if self is already non-virtual)
def devirtualize
self.is_a?(VirtualTypeLookup) ? self.base_type : self
case self
when VirtualType
self.base_type
when VirtualMetaclassType
self.base_type.metaclass
else
self
end
end

def implements?(other_type : Type)
Expand Down Expand Up @@ -2796,6 +2805,7 @@ module Crystal
include InstanceVarContainer
include ClassVarContainer

# Given `Foo+`, this returns `Foo`.
getter base_type : Type

def initialize(program, @base_type)
Expand Down Expand Up @@ -2898,7 +2908,10 @@ module Crystal
instance_type.leaf?
end

delegate base_type, lookup_first_def, to: instance_type
# Given `Foo+:Class` returns `Foo` (not `Foo:Class`)
delegate base_type, to: instance_type

delegate lookup_first_def, to: instance_type.metaclass

def each_concrete_type
instance_type.subtypes.each do |type|
Expand Down

0 comments on commit ecda856

Please sign in to comment.