Why compile time type (typepof) is different in following case?

num2 : Int32 | Nil = 1
p! typeof(num2) # =>  (Int32 | Nil)
num2 = nil
num2 : Int32 | Nil
num2 = 1
p! typeof(num2) # =>  Int32
num2 = nil

Why runtime type of num2 is different?

I think it’s because there is no way to declare a variable w/o defining it. So the 2nd example is the same as:

num2 = 1
p! typeof(num2) # =>  Int32
num2 = nil

which the compiler is able to figure out it’ll only ever be an Int32. Whereas in the first one, you’ve explicitly gave a type to the local var, so that is reflected in typeof.

Oops, if that true, any uninitialized declare like following:

num2 : Int32 | Nil

Always no-op if type inference is happen later (e.g. assign a variable), right?
this seem like not make sense, your’s example, define a two variable, both of them name is num2, but, that not true for my second example.

Seems like it yea, as if you do

num : Int32 | Nil
pp typeof(num)

You get an error because you’re trying to read before assignment.

Your second example is the same as mine was, just without the num2 : Int32 | Nil line. So not sure what you mean.

What i means is,

num2 : Int32 | Nil

this line code told compiler, i declare a variable, it name is: num2, it type can be Int32 or nil, without this line (as example write by you), no this info told compiler, it should not same?

We can extend this example to a Array. (though, not match completely)

y : Array(Int32 | String | Char) # declare y here.
y = [1, "hello", 'A'] # => Ok
y = [1] # => Error: type must be Array(Char | Int32 | String), not Array(Int32)

So, i consider, my example, 1 and 2, should give same result.

Try assigning a different type in both cases. What happens?

Both case told me, Error: variable ‘num2’ already declared

num2 : Int32 | Nil = 1
p! typeof(num2) # =>  (Int32 | Nil)
num2 = nil
num2 : String
num2 : Int32 | Nil
num2 = 1
p! typeof(num2) # =>  Int32
num2 = nil
num2 : String

Oh, sorry, I meant assigning a value of a different type.

Check this:

num2 : Int32 | Nil = 1
num2 = nil
num2 = "hello" # You get an error

and…

num2 : Int32 | Nil
num2 = 1
num2 = "hello" # You get an error

In both cases the type must be that declared. The difference is that when you assign a specific value the compiler knows its type and does flow typing, which is reducing the type of the variable to the one the compiler knows about.

That said, it’s debatable whether when you do num : Int32 | Nil = 1 you’d want num to be Int32 or Int32 | Nil. The current behavior is probably a mistake. Feel free to report a bug for this.

2 Likes

Don’t ensure what is the mistake you means.

so, i will use original post create a issue.

BTW: if comment the last line, it get Int32

num2 : Int32 | Nil = 1
p! typeof(num2) # =>  Int32
# num2 = nil
1 Like

I think the current behavior is reasonable. Or perhaps I’m missing what would the alternative look like.

If the type is not restricted after setting a specific member of the union, then you’ll get a real weird behavior:

def an_int : Int32
  1
end

maybe_an_int : Int32? = nil
maybe_an_int = an_int # here, we know for sure there's an int there
p maybe_an_int + 1  # Should this fail? Why? We know there _is_ an int
1 Like

The current behaviour is technically consistent with def parameter restrictions: they restrict an expression’s type, but never perform any casts. If I want a cast, this is much clearer:

num2 = 1.as(Int32 | Nil)
typeof(num2) # => (Int32 | Nil)

The only use case for TypeDeclaration of a local variable I could think of is when one wants to trigger autocasting explicitly:

enum Foo
  Bar
end

x : Foo = :bar
x # => Bar

Otherwise I would go as far as saying TypeDeclarations of local variables are unidiomatic.

1 Like

I think we just made them work for completeness. There was a time they didn’t work.

One use case is to prevent the variable from taking an undesired type. But personally I find it rarely useful.