TypeScript Version: 3.6.0-dev.20190713
Search Terms: TS2339, union type object, union type property does not exist
And github suggestions while I was writing the title.
Code
type Test = {
someKey: false
} | {
someKey: {
someKeyInside?: number
},
someKeyThatExistOnlyIfOtherKeyIsNotFalse: string;
};
declare const test: Test;
if (typeof test.someKey === 'object') {
console.log(test.someKeyThatExistOnlyIfOtherKeyIsNotFalse);
}
Expected behavior:
String in someKeyThatExistOnlyIfOtherKeyIsNotFalse should be logged
Actual behavior:
error TS2339: Property 'someKeyThatExistOnlyIfSomeKeyIsNotFalse' does not exist on type 'Test2'.
Property 'someKeyThatExistOnlyIfSomeKeyIsNotFalse' does not exist on type '{ someKey: false; }'.
29 console.log(test2.someKeyThatExistOnlyIfSomeKeyIsNotFalse); // TS2339: Property 'someKeyThatExistOnlyIfSomeKeyIsNotFalse' does not exist on type '{ someKey: false; }'.
Playground Link:
https://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAKhDOwCMUC8UDeAoKurwHsBbCAaQhAC4oAzAQwBt4IsBfKAH0xz0JPKrc8w-MTIUAkgDt4ASwAmEalICuRAEYQATj1ysANLtH8KMABZ1gAUQAesxAHkpDEBJoPgZ7QInwAcgTAAGKMzNSIWrJSAOYA3GzxWIoAxgx0WtDJBDLAUMAIyNRwiEiJsjRQABT5JQB0fOIgAJRCeFkyBAwQtQwE0dUFSPViAuaWtvbATi5uHl5aPv6BIUwQTbFQAPSbUA6kAIRsWFigkLAFAExorbgNAtT0q2ycN8aN1NgieNuwXlDZLn+Umg8nKNG0ECkyWgNC0xDy4GgxWQ+jePhkCkydCkUE0UBUUkUNCiEHkQLRFCMwjukgxigA-Mo1JodMIDEYaSAxtY7I5nK53J5vJIlsFQkp8MBIjF4qxEik0hkoO1EHlLkVLmUKtVEQQKjVgBdhiYQGhUOgAOQEdQAKwgyWAFpanza2UIXR6fQGiCNnO5Ez5M0F80WATFq3WWx2MAAyhcAMzxgCc1AACnDIFpQFALX6LDzJtMBXNha5RStmBaoPICAgoFJAlAILzctkEWcLRgKYJHswNqwLbU2EA
Take a look at type Test1. The only one difference from type Test2 is someKeyInside should be always defined.
Related Issues:
-
I'm not sure typeof has ever triggered narrowing by discriminant.
Just chiming in to say I would love this fixed! Our repro is something like the following:
interface A { x: string, y: string };
interface B { x: number, y: number };
type X = A | B;
declare var bar: X;
if (typeof bar.x === 'string') {
let y = bar.y; // string | number, but really should be string.
}
Essentially we have some methods we'd like to return union of object types from. The object types are mostly distinct with a little overlap. Ideally users would just check the field that they want is present, and TS would do the math and present a proper type inside the if block.
We ran in to this because we were using a tagged union pattern which was working fine, but then attempted to generalize it, and found the narrowing stopped working.
Looks interesting. I would have a try on this.
Hi, if I define
interface Foo1 {
key: number|undefined;
f1: number;
}
interface Foo2 {
key: string|undefined;
f2: number;
}
type U = Foo1 | Foo2
function f1(u: U) {
if (typeof u.key !== 'number' && typeof u.key !== 'undefined') {
u; // What is this?
u.key; // What is this?
}
}
What type of u and u.key should be?
Should it be Foo2? If so, could some guy give me some advice about how to change the flow?
Maybe it should have comparable behavior to the literal type case?
interface Foo1 {
key: "number" | "undefined";
f1: number;
}
interface Foo2 {
key: "string" | "undefined";
f2: number;
}
type U = Foo1 | Foo2
function f1(u: U) {
if (u.key !== 'number' && u.key !== 'undefined') {
u; // U
u.key; // 'string'
}
}
I ran across this after seeing @jack-williams's comment here: https://github.com/microsoft/TypeScript/issues/30622#issuecomment-477398183
I was trying to "entangle" the refinements on two types by sticking them in an object together and was surprised that it didn't work:
function f(x: string | number, y: string | number) {
let o;
if (typeof x === 'string' && typeof y === 'string') {
o = { x, y };
} else if (typeof x === 'number' && typeof y === 'number') {
o = { x, y };
} else {
throw new Error(`Can't have mixed types: ${typeof x} and ${typeof y}`);
}
o; // type is { x: string; y: string; } |
// { x: number; y: number; }
o.x; // type is string | number
if (typeof o.x === 'string') {
o.x; // type is string
o; // type is the same as above
o.y; // type is string | number :(
}
}
([playground])
This would be useful to have since the improved tuple types in 4.0 will make overload implementations that use tuples even more common.
function Example(a: number, b?: number): number;
function Example(a: string, b?: string): string;
function Example(...args: [a: number, b?: number] | [a: string, b?: string]): number | string {
if (typeof args[0] === 'number') {
// doesn't narrow
} else {
// doesn't narrow
}
}
Most helpful comment
Just chiming in to say I would love this fixed! Our repro is something like the following:
Essentially we have some methods we'd like to return union of object types from. The object types are mostly distinct with a little overlap. Ideally users would just check the field that they want is present, and TS would do the math and present a proper type inside the
ifblock.We ran in to this because we were using a tagged union pattern which was working fine, but then attempted to generalize it, and found the narrowing stopped working.