The Crystal Programming Language Forum

Can't infer the type of instance variable in class

hello, everyone! My code is like this:

class Hello
  def initialize()
  end
  def go(name : Int32|String = 32)
	name = "" if name.is_a?(Int32)
	@name = name
  end
end

p = Hello.new
p.go()

then build

$ crystal build tmp6.cr 

got this weird error:

Showing last frame. Use --error-trace for full trace.

In tmp6.cr:7:2

 7 | @name = name
     ^----
Error: can't infer the type of instance variable '@name' of Hello

The type of a instance variable, if not declared explicitly with
`@name : Type`, is inferred from assignments to it across
the whole program.
he assignments must look like this:

  1. `@name = 1` (or other literals), inferred to the literal's type
  2. `@name = Type.new`, type is inferred to be Type
  3. `@name = Type.method`, where `method` has a return type
     annotation, type is inferred from it
  4. `@name = arg`, with 'arg' being a method argument with a
     type restriction 'Type', type is inferred to be Type
  5. `@name = arg`, with 'arg' being a method argument with a
     default value, type is inferred using rules 1, 2 and 3 from it
  6. `@name = uninitialized Type`, type is inferred to be Type
  7. `@name = LibSome.func`, and `LibSome` is a `lib`, type
     is inferred from that fun.
  8. `LibSome.func(out @name)`, and `LibSome` is a `lib`, type
     is inferred from that fun argument.

Other assignments have no effect on its type.

can't infer the type of instance variable '@name' of Hello

but I think @name should be String, is it right?

thanks~

That’s the line that prevents your code from working. Perhaps you should provide a default value for @name:

class Hello
  def initialize
    @name = "" # or outside #initialize
  end

  def go(name : Int32 | String = 32)
    name = "" if name.is_a?(Int32)
    @name = name
  end
end

That way the compiler knows what type @name is supposed to be from the get-go.

1 Like

hello @orangeSi

Do you want name to be a String or an Int32?

yes, it works! or use property name : String|Int32 = 3 also works~
It seems @ variable in class should be defined in initialize or property, I got it~
Thanks~

In fact, I want name be Int32|String when as argument of go method, but inside of go method I want name to be String.

Interesting!! Thanks I was just curious lol

Glad you got it working!

I tried this

class Hello
  alias KK = Int32|Int64|Array(String)
  property name2 : KK = 32
  def initialize()
  end
  def go()
	  @name2 = Array(String).new  if @name2.is_a?(Int32)
  end
end

p = Hello.new
p.go()

that works, but when I tried this:

class Hello
  alias KK = Int32|Int64|Array(String)
  property name2 : KK = 32
  def initialize()
  end
  def go()
    puts @name2 > 1  if @name2.is_a?(Int32)
  end
end

p = Hello.new
p.go()

got error:

In tmp6.cr:8:17

 8 | puts @name2 > 1  if @name2.is_a?(Int32)
                 ^
Error: no overload matches 'Array(String)#>' with type Int32

Overloads are:
 - Comparable(T)#>(other : T)

Since @name2 can be any type in Int32|Int64|Array(String), this error is catching that there isn’t a method defined that allows you to compare an Array(String) with an Int32.

You would have to check the type of @name2 before doing your comparison. For example https://play.crystal-lang.org/#/r/7o5a

class Hello 
  def initialize(@name2 : Int32 | Int64 | Array(String) = 32); end
  
  def go
    unless (name = @name2).is_a? Array(String)
      puts "Comparison: #{name > 1} - Type: #{typeof(name)}"
    else
      puts "Else: #{typeof(@name2)}"
    end
  end
end

Hello.new.go # => Comparison: true - Type: (Int32 | Int64)
Hello.new(["foo"]).go # => Else: (Array(String) | Int32 | Int64)

EDIT: See https://crystal-lang.org/reference/syntax_and_semantics/if_varis_a.html

1 Like

got it!
is_a do a little bit different thing with @variable and variable
Thanks a lot~