-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Inference not working for union without discriminant properties #34590
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
Comments
As a workaround of your first code (add a optional property type AElement = HTMLAnchorElement;
type BElement = HTMLButtonElement;
interface A {
a: 'a';
onClick(el: AElement): void;
}
interface B {
a?: undefined;
onClick(el: BElement): void;
}
type Props = A | B;
const props: Props = {
a: 'a',
onClick: el => {}
}; Playgorund (tested with 3.7-beta, 3.6.3 and nightly). |
I don't understand the intended use of this code. When some code that receives a |
declare const aElement: AElement;
declare const bElement: BElement;
props.a === 'a' ? props.onClick(aElement) : props.onClick(bElement); Although as I just discovered, this won't work.
Is that because |
In the context of usage, this is used for polymorphic components in React (where the most popular use case is rendering buttons vs anchors that visually look the same). If there is a // ignore the fact that HTML allows `href` to be optional for anchors (if we pretend)
type ButtonProps = JSX.IntrinsicElements['button']
type AnchorProps = JSX.IntrinsicElements['a'] & { href: string }
declare function PolymorphicButton(props: ButtonProps | AnchorProps): JSX.Element
// I want `e` to be `MouseEvent<HTMLButtonElement>`
const myButton = <PolymorphicButton onClick={e => e.target} />
// I want `e` to be `MouseEvent<HTMLAnchorElement>`
const myAnchor = <PolymorphicButton onClick={e => e.target} href='https://typescriptlang.org' /> |
We could use solid discriminants or Just like in: https://twitter.com/WrocTypeScript/status/1122952350178643968 |
The problem with a nonpresent discriminant is that it's subject to aliasing effects: type AElement = HTMLAnchorElement;
type BElement = HTMLButtonElement;
interface A {
a: 'a';
onClick(el: AElement): void;
}
interface B {
onClick(el: BElement): void;
}
type Props = A | B;
function fail(x: A | B) {
if (x.a === "a") {
x.onClick(someAnchor);
}
}
// Clearly OK, by definition
let tricky1 = { a: "a", onClick: (el: BElement) => e.buttonThing() };
// Clearly OK - 'a' property is aliased away, 'onClick' property matches
let tricky2: B = tricky1;
// Clearly OK by the definition of union types
let tricky3: A | B = tricky2;
// Crashes
fail(tricky3); This is why you need an |
For use cases of polymorphic components, overload signatures will work fine but they will break in cases where a conditional type attempts to infer its props, so I was hoping this would be a solution but your reasoning makes sense to me. |
Blocking this (w/o having to manually sprinkle import { Union } from "ts-toolbelt";
const tricky4 = { a: "a", onClick: (el: BElement) => el.checkValidity() };
const tricky5: Union.Strict<A | B> = tricky4; // ts(2322) |
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
TypeScript Version: 3.6.3
Search Terms:
Code
If we add a common discriminant property, it does work:
It will also work if the union members do not share the same property:
Expected behavior:
Actual behavior:
Playground Link:
Related Issues:
The text was updated successfully, but these errors were encountered: