Two orthogonal problems with empty intersection types:
never. It should be undefined when strictNullChecks is disabled.I thought filing one issue might make less work for the TypeScript team. If you agree with the proposed changes, this should be an easy pull request (I've already looked at the code involved) and I'll be happy to submit it.
TypeScript Version: master (d9ed917)
Search Terms: empty intersection unit type undefined union never strictNullChecks
Code
type T = "foo" & "bar"; // "foo" & "bar" (expected undefined)
type TT = T | T; // never (expected undefined)
let x: T;
let xx: TT;
let u: undefined;
x = u;
u = x; // error (expected OK)
u = xx;
x = xx;
xx = u; // error (expected OK)
xx = x; // error (expected OK)
Expected behavior: x, xx have the same type, and (assuming strictNullChecks is disabled) that type is undefined.
Actual behavior: As marked.
Playground Link: link
Related Issues: None found
I may have missed something, but where are you getting that "foo" & "bar" should become "undefined"? I've never seen that mentioned anywhere.
I think you meant never. undefined is a non-vacuous type it has one value undefined. never is the empty type. so invalid intersections reduce to never.
The inconsistency here is because intersections are only reduced when used in a union. so "foo" & "bar" will stay that type unless it is part of a union. that is why x is not assignable to u.
We have found that keeping the intersections longer gives better error messages to users, since never can does not tell you where the type originated from. more over, there is something to say about user typing a type as such, what the intent is..
I think you meant
never.undefinedis a non-vacuous type it has one value undefined.neveris the empty type. so invalid intersections reduce tonever.
When strictNullChecks is off, "foo" really means "foo" | null | undefined. Since null and undefined belong to both "foo" and "bar", they should belong to the intersection "foo" & "bar". So if "foo" & "bar" | "foo" & "bar" simplifies at all, it should simplify to something containing null and undefined. I suggested undefined; null would be equivalent.
We have found that keeping the intersections longer gives better error messages to users ...
OK re this part. Thanks for taking the time to explain.
When strictNullChecks is off,
"foo"really means"foo" | null | undefined. Sincenullandundefinedbelong to both"foo"and"bar", they should belong to the intersection"foo" & "bar". So if"foo" & "bar" | "foo" & "bar"simplifies at all, it should simplify to something containingnullandundefined. I suggestedundefined;nullwould be equivalent.
we consider null and undefined to be implicitly part of the domain of other types, instead of considering every type to be T | null | undefined. this changes the calculus a bit. so (number & string) | never is never and not null | undefined.
this changes the calculus a bit. so
(number & string) | neverisneverand notnull | undefined.
I confess I don't understand how you come to this conclusion. If #12825 is implemented, couldn't this lead (under admittedly unlikely circumstances) to a call to a generic function unexpectedly stopping the control flow when the function was supposed to return undefined? But if you say so, feel free to close the issue.
We did not have union types, and we did not have manifest types for null or undefined. they both were always widened to any. We added union types, and a mode to enable checking for null and undefined. to limit disruptions and breaks, the non --strictNullChecks mode had to stay where it was. and null | undefined being in the domain of any type was how it was.
@mhegazy
The inconsistency here is because intersections are only reduced when used in a union. so "foo" & "bar" will stay that type unless it is part of a union. that is why x is not assignable to u.
Were there any other related discussions?
It's very counterintuitive that T extends never and (T|T) extends never can be different.