-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: Improve type reductions with absorption laws #16386
Comments
Does this issue deserve a "suggestion" tag, or is something wrong with it? 😞 Thanks. |
Nothing's wrong with the proposal, we just have a lot of the team on vacation this summer so we haven't had a chance to look at it. I'm curious whether you've run into real code that would benefit from this proposal. |
I'd have to go back and look at what I was working on in June to answer definitively, but the problem surfaced for me in some confusing types reported by quickInfo (e.g., I have since come to understand that this might not play nicely with excess property checking on object literals ( |
The nominal typing workaround seems kind of hacky, perhaps there'd be some better way to do that. |
I figured this idea might merit trying in a branch to see how it'd work out, so I tried to think of what it means for two types to clash. My intuition is they match if they are assignable two each other in either direction, or are objects (non-primitives) and none of their properties/indices clash. In the latter case perhaps the reduction should be local though, e.g. Then again, can we really reduce to never? For LHS roles this seems okay, as you're just simplifying an impossible requirement. For RHS roles though, if I'm not sure I'm making sense here. Thoughts, @jcalz? |
So you're saying that the following: const b: boolean = Object.assign(1 as 1, 2 as 2); which currently errors ( I also don't like this: const x = 'x'
if (x !== 'x') {
const b: boolean = x; // no error
} which is current TS behavior. It's times like these I wish there were a difference between warnings and errors in TS (or there were a way to acknowledge and dismiss individual errors). When TS notices that you're shuttling const a: boolean = Object.assign(1 as 1, 2 as 2); // error: impossible type; are you sure?
const b: boolean = Object.assign(1 as 1, 2 as 2) as never; // no error I don't know if this warning-on- |
The problem is that function fn(): string {
switch(something) {
case A.X: return "";
default: return Debug.fail("nope"); // Debug.fail returns `never`
} There'd need to be some very strong distinguishing mechanism that still allows this but disallows some other usage. |
Could valid/bad cases be distinguished using say |
I have the vague sense that an explict |
@jcalz I think the spirit of this issue is fixed - would you agree? |
Is it? I think that there's definitely been some improvement here, but the basic issue, that something like type ShouldBeString = string & (string | number | boolean) expands to |
The display text shows this, but the behavior is the same as string because the conflicting-intersection types ( type ShouldBeString = string & (string | number | boolean);
// No error
var t: ShouldBeString = ""; |
Wasn't that also true when I originally filed this? When you get down to using actual values with concrete types, it's all good. I think some more aggressive reductions to One interesting development is that conditional types now allow someone to more closely express these operations like so: type AbsorbUnion<T, U> = T extends U ? U : U extends T ? T : T | U
type AbsorbIntersection<T, U> = T extends U ? T : U extends T ? U : T & U
type ShouldBeString = AbsorbIntersection<string, string | number | boolean> // string which is neat. |
It's sounding like there isn't anything concrete in terms of resulting behavior? The intermediate forms the types take during expansion/reduction is sort of an implementation detail as long as the resulting subtype relationships are correct. |
Yes, that's true. The benefit would mostly be in Intellisense quick info and any type-level manipulation that relies on absorption to work (which can probably be worked around with So what do you think? Fixed? Declined? |
Fixclined? |
@RyanCavanaugh Are you saying that the way a type is shown to the user doesn't matter, as long as the compiler evaluates expressions equivalently? I definitely don't want to be mentally reducing type expressions just to figure out what is acceptable in a given context. It doesn't seem to me that anything seen by a user is merely an implementation detail. If the compiler needs some extra time to simplify an expression, I suspect most users would need even more time :-) |
Of course it matters. It's a trade-off. If you write a type alias type T = some_type_expression; people will log bugs if a hover on For a type like |
@RyanCavanaugh subjective, but if |
@RyanCavanaugh I agree with you that the behavior seen is really ugly, but it seems this is a result of distributing over the components of PS: Shouldn't that whole type be |
Putting this another way, the debate should be between |
haha so I'm glad I misunderstood you, and it's really a matter of the much trickier question of finding the right 'simplification' to show the user. |
The type checker currently reduces/compares unions and intersections using the laws of idempotence (e.g.,
A & A
=A
), associativity (e.g.,(A | B) | C
=A | (B | C)
), commutativity (e.g.,A & B
=B & A
), and distributivity (e.g.,A | (B & C)
=(A | B) & (A | C)
).I think it would be great to introduce the absorption laws: That is,
A | (A & B)
andA & (A | B)
should reduce toA
.Related are laws of subtype collapsing; that is, if
B extends A
, thenA | B
should reduce toA
, andA & B
should reduce toB
. (Issue Spec Preview: Union types #805 implies that this already happens for unions, but I am not seeing the type checker doing an actual reduction.)Finally, maybe slightly off-topic, it would be nice to reduce intersections of known-disjoint types to
never
orundefined|null
depending on the strictness of null checks.I should note that currently the type checker does consider all such equivalent types mutually compatible (meaning that a value of type
A | (A & B)
can be assigned to a variable of typeA
and vice-versa), even though they don't reduce to the same type. This is good, but actual reductions for absorption laws would:string & (string | number) & (string | object) & (string | {ad: number, nauseum: string})
whenstring
would be better for everyone involved).I don't know how expensive it would be to perform such reduction rules in the compiler. Before I or anyone looks into that, though, I'd be interested in finding out if such a feature would even be considered useful. Thoughts?
Code examples! Everywhere below that produces the error
// Error, subsequent variable declarations must have the same type
should type-check:Cheers!
The text was updated successfully, but these errors were encountered: