Repository navigation
Intersect interfaces instead of union #33070
Description
Activity
MartinJohns commented
on Aug 24, 2019 ContributorMore actionsBut that would produce invalid results. If
selectedFuncwould be an intersection, then it would be appear as an overloaded function, even tho it's not.For example if you manually force an intersection:
const someCondition = false interface Func1Options { name: string } interface Func2Options { age: number } function Func1 (options: Func1Options) { console.log(options.name) } function Func2 (options: Func2Options) { console.log(options.age) } type BothFunc = typeof Func1 & typeof Func2 const selectedFunc = (someCondition ? Func1 : Func2) as BothFunc const obj: Func1Options = { name: 'Bob' } selectedFunc(obj)The compiler will assume it calls
Func1, because that's what theobjparameter matches to. But it actually callsFunc2which gets an invalid object passed along.Is this issue saying that
selectedFuncshould be of typetypeof Func1because the condition in the ternary is statically known to betrue? If so, that is working as intended as per #14206. As far as I know, the type ofx ? y : zis always (equivalent to)typeof y | typeof zregardless of whetherxis known to be truthy or falsy.
Or is this issue saying that
selectedFuncshould be of typetypeof Func1 | typeof Func2, but that you should be able to call it with aFunc1Options | Func2Optionsargument? If so, then this is working as intended, as implemented in #29011, which is a partial fix for #7294. It is not safe for a union of functions to accept a union of arguments, because function arguments are contravariant. Consider the following code:function Func1(options: Func1Options) { options.name.toUpperCase(); } function Func2(options: Func2Options) { options.age.toFixed(); } const selectedFunc = Math.random() < 0.99 ? Func1 : Func2; selectedFunc({ age: 20 }); // 99% guarantee of kaboom
At runtime,
selectedFuncmay turn out to beFunc1. Inside theselectedFunc({age: 20})call,options.name.toUpperCase()will end up trying to call a method onundefined, and you will get a runtime error. It's not safe. Unless you can narrowselectedFuncto eithertypeof Func1ortypeof Func2, the only safe thing you can pass it as an argument isFunc1Options & Func2Options. That way you know no matter which function it turns out to be, it will have an argument that meets its needs.Reacted by Bruce Pascoe, Max Rukomoynikov and Ryan Cavanaughjack-williams commented
on Aug 24, 2019 CollaboratorMore actionsMartin Johns (@MartinJohns)’s comment shows why the current behaviour is the correct one.
More details can be found here: #29011
Reacted by Martin Johns and Max RukomoynikovRukomoynikov commented
on Aug 25, 2019 AuthorMore actionsIs this issue saying that
selectedFuncshould be of typetypeof Func1because the condition in the ternary is statically known to betrue? If so, that is working as intended as per #14206. As far as I know, the type ofx ? y : zis always (equivalent to)typeof y | typeof zregardless of whetherxis known to be truthy or falsy.Or is this issue saying that
selectedFuncshould be of typetypeof Func1 | typeof Func2, but that you should be able to call it with aFunc1Options | Func2Optionsargument? If so, then this is working as intended, as implemented in #29011, which is a partial fix for #7294. It is not safe for a union of functions to accept a union of arguments, because function arguments are contravariant. Consider the following code:function Func1(options: Func1Options) { options.name.toUpperCase(); } function Func2(options: Func2Options) { options.age.toFixed(); } const selectedFunc = Math.random() < 0.99 ? Func1 : Func2; selectedFunc({ age: 20 }); // 99% guarantee of kaboom
At runtime,
selectedFuncmay turn out to beFunc1. Inside theselectedFunc({age: 20})call,options.name.toUpperCase()will end up trying to call a method onundefined, and you will get a runtime error. It's not safe. Unless you can narrowselectedFuncto eithertypeof Func1ortypeof Func2, the only safe thing you can pass it as an argument isFunc1Options & Func2Options. That way you know no matter which function it turns out to be, it will have an argument that meets its needs.Thank you, for description. Now I understand kind of philosophy of ts,
- locked as resolved and limited conversation to collaborators
on Oct 21, 2025
TypeScript Version: 3.4.0-dev.201xxxxx
Search Terms: Intersection interfaces, Ternary operator, Intersect interfaces instead of union
Code
Expected behavior:
selectedFuncacceptsFunc1OptionsActual behavior:
selectedFuncacceptsFunc1Options & Func2OptionsPlayground Link: Playground