Open iskiselev opened 8 years ago
let second : A|B;
second.f = n; // Ops, no error! But it should be at least string&number here
second.f = str;// Ops, no error! But it should be at least string&number here
n = second.f; // error, typescript prefer to use first signature string. It should be string|number
str = second.f;// Ops, no error, typescript prefer to use first signature string. It should be string|number
second.f
initially has type string | number
so a value of type string
, number
, string | number
, or string & number
would be valid.
let first : A&B;
first.f = n; // error, as f has type number&string. Probably OK, but probably it should be number|string, to be symmetric with set_f function.
How would the language know that set_f
is related to f
?
n = first.get_f(); // error, typescript prefer to use first signature () => string. Shouldn't it be string&number.
The spec indicates that this behavior is correct, but I find it very counter-intuitive.
As an implementation of get_f
could not differ in behavior based on its parameters (since it has none) it must theoretically return a value which satisfies both string
and number
which would be string & number
. Instead what we end up with is 2 call signatures. Since the first call signature takes precedence, the signature returning number
cannot be called.
Intuitively, I would expect the call the signatures to be combined into a single signature having the union of their parameter types and the intersection of their return types. But that is not what the spec says.
I understand, that current behavior is correct according to spec, but in given cases it's cont-intuitive and gives you an opportunity to make an error that type-system could find.
Let's look: A|B < A < A&B
; A|B < B < A&B
(according to ability for reference assignment).
As we know, function is contravariance to it's arguments and covariance to it's result type. Let's look on the sample once more with this statements:
first.set_f(n);
first.set_f(str);
Typescript found two overloads here. Really, it behaves same, as if it has signature (string|number)=>void
, and it is great: string|number < string
, string|number < number
and A&B>A
, A&B>B
. So, argument is really contravariant here, and it is great!
second.set_f(n); //error, lack a call signature. It's really good, but better if it would be (string&number) => void
second.set_f(str); //error, lack a call signature.
It will be really hard to calculate correct sum of all overrides, and it's OK that it's not working. But let's try to calculate it in this simplest case: A|B < A
, A|B < B
. The only valid combination here may be string&number > string
, string&number > number
.
Now let's look on result type:
var r:number|string = second.get_f();
TypeScript greatly infer correct result type here, as A|B < A
, A|B < B
and string|number < string
, string|number < number
.
n = first.get_f(); // error, typescript prefer to use first signature () => string. Shouldn't it be string&number.
str = first.get_f();
Technically it's not correct: as A&B > A
, A&B > B
it should be string&number > string
, string&number > number
.
Now let's look on properties. Compiler for sure can't have any knowledge that f
relates to get_f
and set_f
, but we understand it. It means that specification should be written with this knowledge in mind. For it, we should calculate property type different: it should be covariant when it is used as R-Value and contraviriant when it is used as L-Value. If it will be implemented, property could walk absolutely same as we'd want get
and set
methods work.
I think that the way that property types are inferred in an intersection is intuitive, but that the way that nullary function types are inferred is not. The problem is, as you say,
It will be really hard to calculate correct sum of all overrides
However, when intersecting the types of nullary functions the overload set produced is really counter intuitive because they are distinguished solely by return type. So the type A & B
would have a single get_f
with the type () => string & number
, just as the type A | B
currently has a single get_f
with the type () => string | number
.
We're continuing to look for a good algorithm that can produce "correct" signatures from intersection types.
Hi there! Any progress here?
Hey all - I just wanted to add a simplified example of where this is currently failing for me as well.
Consider next example:
With #9167 additional to previous was added preserving readonly flag for intersection. Should't readonly flag be reset if any of intersection member has no such flag? As intersection should extend possibilities of type, and never narrow down it.