-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Description
This is a proposal for using property access as another form of type guards (see #900) to narrow union types. While we're investigating expanding the power of type guards (#1007) this feature would support the natural style that JavaScript programmers have in their code today.
Using property access to narrow union types
var x: string|number;
if (x.length) { // no error even though number lacks a length property
var r = x.charAt(3); // x is of type string inside this block
}
var r2 = x.length || x * x; // no error, x narrowed to number on right hand side, r3 is number
var y: string[]|number[]|string;
if (y.length && y.push) {
// y is string[]|number[] now
var first = y[0]; // first is string|number
if (first.length) {
// first is string in here
} else {
// first is number in here
}
}
We do not expand the situations in which types are narrowed, but we do expand the known type guard patterns to include basic property access. In these narrowing contexts it would be an error to access a property that does not exist in at least one of the constituent types of a union type (as it is today). However, it would now be valid to access a property that exists in at least one, but not all, constituent types. Any such property access will then narrow the type of the operand to only those constituent types in the union which do contain the accessed property. In any other context property access is unchanged.
Invalid property access
var x: string|number;
var r = x.length; // error, not a type guard, normal property access rules apply
if (x.len) { } // error, len does not exist on type string|number
var r3 = !x.len && x.length; // error, len does not exist on type string|number
var r4 = x.length && x.len; // error, len does not exist on type string
Issues/Questions
- Should the language service behave differently in these type guard contexts? Should dotting off a union type in a type guard list all members on all types rather than only those that exist on all of them?
- Need to understand performance implications
I have a sample implementation and tests in a branch here: https://github.com/Microsoft/TypeScript/tree/typeGuardsViaPropertyAccess. There're a couple bugs remaining but examples like the above all work and no existing behavior was changed.
Activity
DanielRosenwasser commentedon Nov 26, 2014
👍 for this.
For the existing issues/questions:
I assume that solely in these typeguard contexts, you're allowing a property access for properties that exist on any of the constituent types. If so, I'd think it makes sense to extend this sort of logic to the language service. After all, I assume we'll still give errors in the appropriate contexts. For instance,
is fine, but as soon as you have something like
the LS will complain that
bar
does not exist on typeA | B
, and hopefully thatbizzle
is a ridiculous property name. The biggest problem is that that's not a terribly helpful error message, which we could work on.Shouldn't be terrible if user-defined type tags supposedly won't affect perf either, but it'll be good to keep an eye on it.
AbubakerB commentedon Jul 4, 2015
Any update on this?
if i can weigh in:
In addition to what Daniel said, what would be cool is when
getCompletion
is called, intellisense shows an info/warning symbol near the properties that don't belong to the union type and maybe some text indicating that this property exists in only TypeA or TypeA | TypeB or TypeC etc...Also, in the case of
if bar doesn't exist in the union type, maybe
if (foo.bar.bizzle)
could be emitted to
if (foo.bar && foo.bar.bizzle)
and that way, we could allow completion.
.
) #4297mykohsu commentedon Jan 29, 2016
Since this is still open, it seems that the original example here is no longer valid?
The code above now errors at x.length and seems this is the case per https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Advanced%20Types.md
Can anyone confirm?
14 remaining items
AlCalzone commentedon Jan 15, 2020
Since this is tagged "Awaiting more feedback", I'll just link to #36194 as a potential use case.
jsejcksn commentedon Jan 10, 2021
Just curious: Is this simply a yet-to-be-implemented feature request or has it not been implemented due to some kind of technical cost or differing opinion?
RyanCavanaugh commentedon Jan 12, 2021
"Awaiting More Feedback" generally means that this makes sense, and would presumably be OK to add to the language, but the quantity and quality of feedback heard so far has not indicated that this is what we should spend churn, complexity, and risk on.
This particular change request is very problematic from a soundness perspective, especially compared to the much clearer intent of using
in
(which already works), so the bar is set rather high in that regard.thorn0 commentedon Jan 14, 2021
Feedback for you. Prettier is a formatter that works with ASTs that come from different parsers. In the case of JS-based languages, these AST formats are all based on the same ESTree base format, but also they have differences. It'd be great to be able to use a union type for this, and it looks like it might work (simplified code for two parsers only):
but in practice the resulting
Node
type is a pain to work with.E.g., this already quite complex condition
needs to be rewritten this way to make TS happy:
The added
in
check doesn't make code any clearer and only bloats it. The entire code base needs adding a lot of such checks, which is whyany
is used almost everywhere instead ofNode
.jsejcksn commentedon Jul 7, 2021
@RyanCavanaugh Can you link me to an explainer that includes this and other information about how the TS team uses labels and other meta-features for issues in this repo? I'd like to be able to link other people who haven't been informed yet, and I imagine it will keep other similar replies DRYer.
therynamo commentedon Sep 17, 2021
Just wanted to check in and see if there were any updates on this - this would be a fantastic feature! Let me know if there is anything I can do to help.
in
operator as type guard which asserts property existence #21732Telokis commentedon Oct 23, 2022
The example from prettier in this comment is very representative of real-life use-cases.
From my understanding, one of the goals of TypeScript is to embrace current JavaScript practices and checking non-existing properties in
if
s is an often used idiom. I know it's not as safe as usingin
but it's so much more convenient that it ends up being the default solution most of the time.This issue is now even more relevant when we consider Optional chaining because using
in
becomes a nightmare in conditions and really bloats the code (like in the Prettier example above).One of the solutions I'm considering is to virtually add all properties to all members of my unions and set some of them to
prop?: undefined;
. This will let my users migrate from JavaScript to TypeScript in a way more effective and comfortable way.Example:
Instead of the following code
I will do something equivalent to the following:
In my opinion, it's a tradeoff. I know this will reduce the type-safety but since it's easier to use and reason with, it will lead to more people moving from JS to TS without having to change their code too much and in a too annoying way which actually increases type-safety.
Throughout the years, I've come to this issue several times and this, in my opinion, is a proof that this is a really missing feature.
Edit: Wrapper helping with the UX issue
I've created a type to help solve the problem. Here is the things you need:
Here is an example usage:
Typescript playground
forivall commentedon Nov 18, 2023
The
BetterUxWrapper
has also been described asExclusifyUnion
in this StackOverflow answer, and I called itUnionExpand
when i independently figured it out (before finding that StackOverflow answer)richard-ling commentedon Mar 12, 2025
Here's another simplified version of a real-life problem I have.
If
animal.breed
is aDogBreed
, the only possible type foranimal
isDog
.DogBreed
andCatBreed
are not literal constants (this example works when they are), but givenDogBreed | CatBreed
would be a discriminated union,Animal
should be a discriminated union itself.this comment (from 2016) proposes something very similar. The 2 comments after it suggest it may cause a performance problem, but no followup work has been noted.
Other actioned tickets have gradually expanded the scope of what forms a discriminant property:
null or not-null: #24193 / #27695
Another similar open issue, more recent: #30506