Skip to content

Commit

Permalink
Allow assignment to class var when fit this class var types of ancest…
Browse files Browse the repository at this point in the history
…ors (#4871)

Fix #4869
  • Loading branch information
makenowjust authored and RX14 committed Sep 1, 2017
1 parent 4d9aed0 commit 842548e
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 7 deletions.
18 changes: 18 additions & 0 deletions spec/compiler/semantic/class_var_spec.cr
Expand Up @@ -497,4 +497,22 @@ describe "Semantic: class var" do
),
"class variable '@@foo' of Foo is not nilable (it's Int32) so it must have an initializer"
end

it "can assign to class variable if this type can be up-casted to ancestors class variable type (#4869)" do
assert_type(%(
class Foo
@@x : Int32?
def self.x
@@x
end
end
class Bar < Foo
@@x = 42
end
Bar.x
)) { nilable(int32) }
end
end
21 changes: 14 additions & 7 deletions src/compiler/crystal/semantic/type_declaration_processor.cr
Expand Up @@ -135,7 +135,8 @@ struct Crystal::TypeDeclarationProcessor
# Process class variables
type_guess_visitor.class_vars.each do |owner, vars|
vars.each do |name, info|
declare_meta_type_var(owner.class_vars, owner, name, info)
# No need to freeze its type because it is frozen by check_class_var_errors
declare_meta_type_var(owner.class_vars, owner, name, info, freeze_type: false)
end
end

Expand All @@ -155,7 +156,7 @@ struct Crystal::TypeDeclarationProcessor
{node, self}
end

private def declare_meta_type_var(vars, owner, name, type : Type, location : Location? = nil, instance_var = false)
private def declare_meta_type_var(vars, owner, name, type : Type, location : Location? = nil, instance_var = false, freeze_type = true)
if instance_var && location && !owner.allows_instance_vars?
raise_cant_declare_instance_var(owner, location)
end
Expand All @@ -174,24 +175,24 @@ struct Crystal::TypeDeclarationProcessor
var.owner = owner
var.type = type
var.bind_to(var)
var.freeze_type = type
var.freeze_type = type if freeze_type
var.location = location
vars[name] = var
var
end

private def declare_meta_type_var(vars, owner, name, info : TypeGuessVisitor::TypeInfo)
private def declare_meta_type_var(vars, owner, name, info : TypeGuessVisitor::TypeInfo, freeze_type = true)
type = info.type
type = Type.merge!(type, @program.nil) unless info.outside_def
declare_meta_type_var(vars, owner, name, type)
declare_meta_type_var(vars, owner, name, type, freeze_type: freeze_type)
end

private def declare_meta_type_var(vars, owner, name, info : TypeDeclarationWithLocation, instance_var = false, check_nilable = true)
private def declare_meta_type_var(vars, owner, name, info : TypeDeclarationWithLocation, instance_var = false, check_nilable = true, freeze_type = true)
if instance_var && !owner.allows_instance_vars?
raise_cant_declare_instance_var(owner, info.location)
end

var = declare_meta_type_var(vars, owner, name, info.type.as(Type), info.location)
var = declare_meta_type_var(vars, owner, name, info.type.as(Type), info.location, freeze_type: freeze_type)
var.location = info.location

# Check if var is uninitialized
Expand Down Expand Up @@ -646,10 +647,16 @@ struct Crystal::TypeDeclarationProcessor
ancestor_class_var = ancestor.lookup_class_var?(name)
next unless ancestor_class_var

if owner_class_var.type.implements?(ancestor_class_var.type)
owner_class_var.type = ancestor_class_var.type
end

if owner_class_var.type != ancestor_class_var.type
raise TypeException.new("class variable '#{name}' of #{owner} is already defined as #{ancestor_class_var.type} in #{ancestor}", info.location)
end
end

owner_class_var.freeze_type = owner_class_var.type
end
end
end
Expand Down

0 comments on commit 842548e

Please sign in to comment.