TypeScript: typeof union.membername errors.
TypeScript Version: 2.8.0-dev.20180308
Search Terms: typeof union parameter undefined.
Code
declare var value: string | Date;
if (typeof value.now /*error!*/ !== "undefined") {}
Expected behavior: No error and appropriate type narrowing.
Actual behavior:
[ts]
Property 'now' does not exist on type 'string | Date'.
Property 'now' does not exist on type 'string'.
NOTE I don’t like having to make a new function to properly narrow types for every union I create, is there another way around this (like manually overriding the variable’s type within the scope of a code block without declaring a new variable?).
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 1
- Comments: 20 (12 by maintainers)
You can use
if ("queryname" in matchingoptions) {
which narrows and doesn’t error. One advantage of using it in a union position is that if you misspelll the left operand, the right operand isn’t narrowed (which hopefully clues you in).@Griffork No? The point is that when you ask for the type of
value.now
whenvalue
is typestring | typeof Date
, sincenow
only exists on one union member, nothing is constraining its’ type on the other member, meaning you could have potentially assigned a value of a conflicting type to it in the past. For example,So while property access won’t immediately throw, it is not safe to use (and using it would likely be a code smell), since its type is indeterminate!
You should probably just cast inside your
typeof
. Alternatively, augment theString
interface with an explicit property stating the type of thenow
member (undefined
), to make the fields mutually exclusive (and prevent you from ever assigning something with a definednow
type to astring
):I definitely don’t think this is bug (it’s a feature request) because this is definitely how union type property accesses are intended to work (properties only exist if they’re in all union members, and that’s how they’ve worked forever)… however allowing accesses of nonexistant union member properties for the purposes of checking for their existence seems reasonableish? Only -ish, though… I think union excess property checking will catch most of the obvious problems that could have cropped up from allowing it nowadays, so… it may be fine? I can’t convince myself it definitely is… Like, here’s the deal, if I say I have
type Union = {kind: "a", Foo: number} | {kind: "b", Foo: string} | {kind: "c"}
, and a variablex
of typeUnion
, I cannot confidently say thatx.Foo
isstring | number | undefined
, because someone could have used{kind: "c", Foo: true}
as a perfectly valid value for that variable; all I can say isany
, really, which doesn’t help. Unions aren’t actually closed to subtypes, which muddles this - discrimination is only possible for discriminable unions precisely because a field exists on all union members and whose types give that field a finite domain of possible values into which each possible union member can be bucketed (which is also why we only discriminate on unit types).I think this is a bug. I guess the rule is that for
typeof expr.id
, ifexpr
is a union type andid
exists on at least one member, permit the check and narrow based on the property.@nomcopter, in #22094 you did something similar.
Thanks! I agree the docs need some love.
https://github.com/Microsoft/TypeScript/wiki/What’s-new-in-TypeScript#type-guards-inferred-from--in-operator
We could have at most one consistency.
The
in
operator is used for all kinds of dynamic tests and we never had a rule in place to disallow any left operand of it, so we blessed it with the narrowing behavior because why not. There’s currently no special casing for property access anywhere.