require "../../spec_helper" describe "Semantic: initialize" do it "types instance vars as nilable if doesn't invoke super in initialize" do assert_error <<-CRYSTAL, "this 'initialize' doesn't initialize instance variable '@baz' of Foo, with Bar < Foo, rendering it nilable" class Foo def initialize @baz = Baz.new @another = 1 end end class Bar < Foo def initialize @another = 2 end end class Baz end foo = Foo.new bar = Bar.new CRYSTAL end it "types instance vars as nilable if doesn't invoke super in initialize with deep subclass" do assert_error <<-CRYSTAL, "this 'initialize' doesn't initialize instance variable '@baz' of Foo, with BarBar < Foo, rendering it nilable" class Foo def initialize @baz = Baz.new @another = 1 end end class Bar < Foo def initialize super end end class BarBar < Bar def initialize @another = 2 end end class Baz end foo = Foo.new bar = Bar.new CRYSTAL end it "types instance vars as nilable if doesn't invoke super with default arguments" do node = parse(<<-CRYSTAL) class Foo def initialize @baz = Baz.new @another = 1 end end class Bar < Foo def initialize(x = 1) super() end end class Baz end foo = Foo.new bar = Bar.new(1) CRYSTAL result = semantic node mod = result.program foo = mod.types["Foo"].as(NonGenericClassType) foo.instance_vars["@baz"].type.should eq(mod.types["Baz"]) foo.instance_vars["@another"].type.should eq(mod.int32) end it "checks instance vars of included modules" do assert_error <<-CRYSTAL, "instance variable '@x' of Foo must be (Char | Nil), not Int32" module Lala def lala @x = 'a' end end class Foo include Lala end class Bar < Foo include Lala def initialize @x = 1 end end b = Bar.new f = Foo.new f.lala CRYSTAL end it "types instance var as nilable if not always assigned" do assert_error <<-CRYSTAL, "instance variable '@x' of Foo must be Int32, not Nil", inject_primitives: true class Foo def initialize if 1 == 2 @x = 1 end end def x @x end end foo = Foo.new foo.x CRYSTAL end it "types instance var as nilable if assigned in block" do assert_error <<-CRYSTAL, "Instance variable '@x' was used before it was initialized in one of the 'initialize' methods, rendering it nilable", inject_primitives: true def bar yield if 1 == 2 end class Foo def initialize bar do @x = 1 end end def x @x end end foo = Foo.new foo.x CRYSTAL end it "types instance var as not-nilable if assigned in block but previously assigned" do assert_type(<<-CRYSTAL, inject_primitives: true) { int32 } def bar yield if 1 == 2 end class Foo def initialize @x = 1 bar do @x = 2 end end def x @x end end foo = Foo.new foo.x CRYSTAL end it "types instance var as nilable if used before assignment" do assert_error <<-CRYSTAL, "Instance variable '@x' was used before it was initialized in one of the 'initialize' methods, rendering it nilable" class Foo def initialize x = @x @x = 1 end def x @x end end foo = Foo.new foo.x CRYSTAL end it "types instance var as non-nilable if calls super and super defines it" do assert_type(<<-CRYSTAL, inject_primitives: true) { int32 } class Parent def initialize @x = 1 end end class Foo < Parent def initialize super @x + 2 end def x @x end end foo = Foo.new foo.x CRYSTAL end it "types instance var as non-nilable if calls super and super defines it, with one level of indirection" do assert_type(<<-CRYSTAL, inject_primitives: true) { int32 } class Parent def initialize @x = 1 end end class SubParent < Parent end class Foo < SubParent def initialize super @x + 2 end def x @x end end foo = Foo.new foo.x CRYSTAL end it "doesn't type instance var as nilable if out" do assert_type(<<-CRYSTAL, inject_primitives: true) { int32 } lib LibC fun foo(x : Int32*) end class Foo def initialize LibC.foo(out @x) @x + 2 end def x @x end end foo = Foo.new foo.x CRYSTAL end it "types instance var as nilable if used after method call that reads var" do assert_error <<-CRYSTAL, "Instance variable '@x' was used before it was initialized in one of the 'initialize' methods, rendering it nilable" class Foo def initialize foo @x = 1 end def foo @x end def x @x end end foo = Foo.new foo.x CRYSTAL end it "types instance var as nilable if used after method call that reads var (2)" do assert_error <<-CRYSTAL, "Instance variable '@x' was used before it was initialized in one of the 'initialize' methods, rendering it nilable" class Bar def bar end end class Foo def initialize my_method @x = Bar.new end def my_method @x.bar end def x @x end end foo = Foo.new foo.x CRYSTAL end it "doesn't type instance var as nilable if used after global method call" do assert_type(<<-CRYSTAL) { int32 } def foo end class Foo def initialize foo @x = 1 end def x @x end end foo = Foo.new foo.x CRYSTAL end it "doesn't type instance var as nilable if used after method call inside typeof" do assert_type(<<-CRYSTAL) { int32 } class Foo def initialize typeof(foo) @x = 1 end def foo @x end def x @x end end foo = Foo.new foo.x CRYSTAL end it "doesn't type instance var as nilable if used after method call that doesn't read var" do assert_type(<<-CRYSTAL) { int32 } class Foo def initialize foo @x = 1 end def foo end def x @x end end foo = Foo.new foo.x CRYSTAL end it "types instance var as nilable if used after method call that reads var through other calls" do assert_error <<-CRYSTAL, "Instance variable '@x' was used before it was initialized in one of the 'initialize' methods, rendering it nilable" class Foo def initialize foo @x = 1 end def foo bar end def bar x = 1 || 1.5 baz(x) end def baz(x : Int32) @x end def baz(x : Float64) end def x @x end end foo = Foo.new foo.x CRYSTAL end it "doesn't type instance var as nilable if used after method call that assigns var" do assert_type(<<-CRYSTAL) { int32 } class Foo def initialize foo @x = 1 end def foo @x = 2 end def x @x end end foo = Foo.new foo.x CRYSTAL end it "finishes when analyzing recursive calls" do assert_type(<<-CRYSTAL) { int32 } class Foo def initialize foo @x = 1 end def foo bar end def bar foo end def x @x end end foo = Foo.new foo.x CRYSTAL end it "doesn't type instance var as nilable if not used in method call" do assert_type(<<-CRYSTAL) { int32 } class Foo def initialize @y = 2 foo @x = 1 end def foo @y end def x @x end end foo = Foo.new foo.x CRYSTAL end it "types instance var as nilable if used in first of two method calls" do assert_error <<-CRYSTAL, "Instance variable '@x' was used before it was initialized in one of the 'initialize' methods, rendering it nilable" class Foo def initialize foo @x = 1 end def foo @x end def x @x end end foo = Foo.new foo.x CRYSTAL end it "doesn't type instance var as nilable if assigned before method call" do assert_type(<<-CRYSTAL) { int32 } class Foo def initialize @x = 1 foo @x = 1 end def foo @x end def x @x end end foo = Foo.new foo.x CRYSTAL end it "marks instance variable as nilable in initialize if using self in method" do assert_error <<-CRYSTAL, "'self' was used before initializing instance variable '@foo', rendering it nilable" class Foo def initialize do_something @foo = 1 end def foo @foo end def do_something Other.new(self) end end class Other def initialize(foo) end end Foo.new.foo CRYSTAL end it "marks instance variable as nilable in initialize if using self" do assert_type(<<-CRYSTAL) { nilable int32 } class Foo def initialize Other.new(self) @foo = 1 end def foo @foo end end class Other def initialize(foo) end end Foo.new.foo CRYSTAL end it "marks instance variable as nilable in initialize if assigning self" do assert_type(<<-CRYSTAL) { nilable int32 } class Foo def initialize a = self @foo = 1 end def foo @foo end end class Other def initialize(foo) end end Foo.new.foo CRYSTAL end it "marks instance variable as nilable when using self in super" do assert_type(<<-CRYSTAL) { nilable int32 } class Parent def initialize(foo) end end class Foo < Parent def initialize super(self) @foo = 1 end def foo @foo end end Foo.new.foo CRYSTAL end it "errors if found matches for initialize but doesn't cover all (bug #204)" do assert_error <<-CRYSTAL, "expected argument #1 to 'Foo.new' to be Int32, not (Int32 | Nil)", inject_primitives: true class Foo def initialize(x : Int32) end end a = 1 > 0 ? nil : 1 Foo.new(a) CRYSTAL end it "doesn't mark instance variable as nilable when using self.class" do assert_type(<<-CRYSTAL, inject_primitives: true) { int32 } class Foo def initialize self.class.foo @foo = 1 end def foo @foo end def self.foo end end Foo.new.foo CRYSTAL end it "doesn't mark instance variable as nilable when using self.class in method" do assert_type(<<-CRYSTAL, inject_primitives: true) { int32 } class Foo def initialize bar @foo = 1 end def bar self.class.foo end def foo @foo end def self.foo end end Foo.new.foo CRYSTAL end it "types initializer of recursive generic type" do assert_type(<<-CRYSTAL, inject_primitives: true) { int32 } class Foo(T) @x = 1 def x @x end end alias Rec = Foo(Rec) Foo(Rec).new.x + 1 CRYSTAL end it "types initializer of generic type after instantiated" do assert_type(<<-CRYSTAL, inject_primitives: true) { int32 } class Foo(T) end alias Alias = Foo(Int32) class Foo(T) @x = 1 def x @x end end Foo(Int32).new.x + 1 CRYSTAL end it "errors on default new when using named arguments (#2245)" do assert_error <<-CRYSTAL, "no parameter named 'x'" class Foo end Foo.new(x: 1) CRYSTAL end it "doesn't type ivar as nilable if super call present and parent has already typed ivar (#4764)" do assert_type(<<-CRYSTAL) { types["Bar"] } class Foo def initialize(@a = 1) end end class Bar < Foo def initialize super end def initialize(@a) end end Bar.new CRYSTAL end it "doesn't type ivar having initializer as nilable even if it is used before assigned inside initialize (#5112)" do assert_type(<<-CRYSTAL) { int32 } class Foo @x = 42 def initialize @x = x end def x @x end end Foo.new.x CRYSTAL end end