19226, "stonea", "Funny behavior when initializing variable with built-in generic type and contradicting split initialization.", "2022-02-09T00:41:17Z"
If I try and compile and run the following:
use IO;
var r = read(int);
var x : numeric;
if(r > 2) {
x = 2;
} else {
x = 3.0i;
}
writeln(x.type : string);
I'll get this error:
foo.chpl:6: error: cannot initialize 'x' of type 'imag(64)' from '2'
.
To which I'm tempted to say: "but I told you x
is a numeric
not a imag(64)
!"
So I assume when type inference sees something given a built in generic type like "numeric" it then looks at subsequent assignments to see if it can "lock in" some concrete type. This seems fair enough (Chapel is a strongly and statically typed language), but there are a couple things I find odd about this:
(1) It errors out in the x=2
statement in the if
which is lexically before the assignment in the else
. I guess type inference or whatever analysis is driving this steps into the 'else' block first. This could get especially confusing if that "first" assignment to x
weren't so evident but rather buried in a mountain of code somewhere (maybe assigned to the result of a function call or passed to a function as a ref argument).
(2) If I do the following and use split initialization with an (initially) untyped variable I get a different error message. Actually I get two error messages, but I kind of like it because it tells me what lines the contradicting initializations are on:
use IO;
var r = read(int);
var x;
if(r > 2) {
x = 2;
} else {
x = 3.0i;
}
writeln(x.type : string);
foo.chpl:8: error: Split initialization uses multiple types; another initialization has type int(64) but this initialization has type imag(64)
foo.chpl:6: error: cannot initialize 'x' of type 'imag(64)' from '2'
Anyway, why bring this up? There are a few possible improvements I could see:
- When trying to come up with a type for
x
could we switch things to analyze the 'if' body first? Maybe there's a good reason why we do things in that order? - Could we change the error message to print out the location of the contradicting assignments?
- Do we explain this behavior in the doc somewhere (that if you declare a variable with a built-in generic type, once you assign it to some value with a concrete type that type is "locked" in). I came up with this example because I was reading the doc on types, got to the third sentence in the section about Primitive types (Types — Chapel Documentation 1.25), which linked to the section on "Built-in generic types" (Generics — Chapel Documentation 1.25), started reading that and my first thought was "hey, are these types generic at runtime?". So maybe we'd want to add something there.