Skip to content

Commit

Permalink
Compiler: declare all instance vars initializers before typing them. F…
Browse files Browse the repository at this point in the history
…ixes #3988
  • Loading branch information
asterite committed Mar 4, 2017
1 parent f1f3892 commit a82ef1b
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 16 deletions.
45 changes: 45 additions & 0 deletions spec/compiler/semantic/instance_var_spec.cr
Expand Up @@ -4640,4 +4640,49 @@ describe "Semantic: instance var" do
),
"Can't infer the type of instance variable '@baz' of Foo"
end

it "instance variables initializers are used in class variables initialized objects (#3988)" do
assert_type(%(
class Foo
@@foo = Foo.new
@never_nil = 1
def initialize
if false
@never_nil = 2
end
end
end
Foo.new.@never_nil
)) { int32 }
end

it "allow usage of instance variable initializer from instance variable initializer" do
assert_type(%(
class Foo
@bar = Bar.new
@never_nil = 1
def initialize
if false
@never_nil = 2
end
end
end
class Bar
@never_nil = 1
def initialize
if false
@never_nil = 2
end
end
end
{Foo.new.@never_nil, Bar.new.@never_nil}
)) { tuple_of([int32, int32]) }
end
end
11 changes: 6 additions & 5 deletions src/compiler/crystal/semantic.cr
Expand Up @@ -21,6 +21,12 @@ class Crystal::Program
def semantic(node : ASTNode, cleanup = true) : ASTNode
node, processor = top_level_semantic(node)

Crystal.timing("Semantic (ivars initializers)", @wants_stats) do
visitor = InstanceVarsInitializerVisitor.new(self)
visit_with_finished_hooks(node, visitor)
visitor.finish
end

Crystal.timing("Semantic (cvars initializers)", @wants_stats) do
visit_class_vars_initializers(node)
end
Expand All @@ -29,11 +35,6 @@ class Crystal::Program
# give an error otherwise
processor.check_non_nilable_class_vars_without_initializers

Crystal.timing("Semantic (ivars initializers)", @wants_stats) do
visitor = InstanceVarsInitializerVisitor.new(self)
visit_with_finished_hooks(node, visitor)
end

result = Crystal.timing("Semantic (main)", @wants_stats) do
visit_main(node, process_finished_hooks: true, cleanup: cleanup)
end
Expand Down
37 changes: 26 additions & 11 deletions src/compiler/crystal/semantic/instance_vars_initializer_visitor.cr
Expand Up @@ -26,6 +26,9 @@ require "./semantic_visitor"
# end
# ```
class Crystal::InstanceVarsInitializerVisitor < Crystal::SemanticVisitor
record Initializer, scope : Type, target : InstanceVar, value : ASTNode, meta_vars : MetaVars
getter initializers = [] of Initializer

def visit_any(node)
case node
when Assign
Expand Down Expand Up @@ -60,21 +63,33 @@ class Crystal::InstanceVarsInitializerVisitor < Crystal::SemanticVisitor
when Program, FileModule
node.raise "can't use instance variables at the top level"
when ClassType, NonGenericModuleType, GenericModuleType
meta_vars = MetaVars.new
ivar_visitor = MainVisitor.new(program, meta_vars: meta_vars)
ivar_visitor.scope = current_type
initializers << Initializer.new(current_type, target, value, MetaVars.new)
node.type = @program.nil
return
end
end

unless current_type.is_a?(GenericType)
value.accept ivar_visitor
def finish
# First declare them, so when we type all of them we will have
# the info of which instance vars have initializers (so they are not nil)
initializers.each do |i|
scope = i.scope
unless scope.lookup_instance_var?(i.target.name)
program.undefined_instance_variable(i.target, scope, nil)
end

unless current_type.lookup_instance_var?(target.name)
ivar_visitor.undefined_instance_variable(current_type, target)
end
scope.add_instance_var_initializer(i.target.name, i.value, scope.is_a?(GenericType) ? nil : i.meta_vars)
end

current_type.add_instance_var_initializer(target.name, value, current_type.is_a?(GenericType) ? nil : meta_vars)
node.type = @program.nil
return
# Now type them
initializers.each do |i|
scope = i.scope

unless scope.is_a?(GenericType)
ivar_visitor = MainVisitor.new(program, meta_vars: i.meta_vars)
ivar_visitor.scope = scope
i.value.accept ivar_visitor
end
end
end
end

0 comments on commit a82ef1b

Please sign in to comment.