You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Union types and overloads acts a bit weirdly #33101
declareclassFoo{publicred(): this
publicred(text: string): string}declareclassBar{publicred(): this
publicred(text: string): string}constfoo: Foo|Bar=newFoo()consta=foo.red('text')
In the above code, I expect the variable a to be a string. However, it is string | Bar.
Now, things get even weird when I replace string with object in the return types of red method.
declareclassFoo{publicred(): this
publicred(text: string): object}declareclassBar{publicred(): this
publicred(text: string): object}constfoo: Foo|Bar=newFoo()consta=foo.red('text')
This isn't really ideal but the overloads are in the "wrong" order and the Foo | Bar type is subject to subtype resolution, so some strangeness is bound to happen. Fixing either of those problems independently is sufficient to make things behave as expected.
Some additional fun stuff from our gitter exploration:
Take the original example:
declareclassFoo{publicred(): this
publicred(text: string): string}declareclassBar{publicred(): this
publicred(text: string): string}declareconstfoo: Foo|Barconsta=foo.red('text')// string | Bar
Rearranging the overloads in Bar fixes a
Making foo: Bar | Foo makes a: string | Foo
Adding an identical class Baz (or more) and appending the type to foo's union also appends it to the type of a
I think that's all I had. But it does trigger one peeve of mine: conceptually, I don't expect the ordering within a union or intersection to matter. It's been pointed out that overloads are represented as intersections, so ordering matters there. So I think this may be the smallest example that shows ordering sensitivity in both a union and an intersection.
😠
Also Ryan Cavanaugh (@RyanCavanaugh) can you elaborate on the 'right' order of overloads since I don't know of it being documented anywhere? I was thinking that it makes sense to order from more specific to more generic. My first thought was that the nullary version was most specific, but then it occurred to me that 1 arg is more 'specified' than no args, so maybe I had it backwards. If you could help my intuition there that would be great.
This is fixed. In 3.7.0-dev.20190823, the original Foo | Bar example inferred a as string | Bar, so assigning it to string produced TS2322. 5.4.0-dev.20231130 still has that result, while 5.4.0-dev.20231201 and the current native 7.1.0-dev.20260902.1 compiler infer a as string.
The later variant with string and number return types also now infers string | number.
The change was #55447 (0a877615215980a1df2c4d54e2833acdaeb0bfd6), which prefers an exact signature match before a partial match when combining union call signatures. Closing as fixed.
Activity
fatcerberus commentedon Aug 27, 2019
More fun:
It picks one overload from each member of the union, and both are incompatible with the call site.
webstrand commentedon Aug 27, 2019
I can confirm this is an issue on ts version
3.7.0-dev.20190823.If
FooandBarare given conflicting return types for the second overload:ais set to an invalid typestringwhen it should bestring | number.AnyhowStep commentedon Aug 27, 2019
Playground
[-]Generics + overloads acts a bit weirdly[/-][+]Union types and overloads acts a bit weirdly[/+]RyanCavanaugh commentedon Aug 28, 2019
There are no generics in this example
RyanCavanaugh commentedon Aug 28, 2019
This isn't really ideal but the overloads are in the "wrong" order and the
Foo | Bartype is subject to subtype resolution, so some strangeness is bound to happen. Fixing either of those problems independently is sufficient to make things behave as expected.keithlayne commentedon Aug 29, 2019
Some additional fun stuff from our gitter exploration:
Take the original example:
Barfixesafoo: Bar | Foomakesa: string | FooBaz(or more) and appending the type tofoo's union also appends it to the type ofaI think that's all I had. But it does trigger one peeve of mine: conceptually, I don't expect the ordering within a union or intersection to matter. It's been pointed out that overloads are represented as intersections, so ordering matters there. So I think this may be the smallest example that shows ordering sensitivity in both a union and an intersection.
😠
Also Ryan Cavanaugh (@RyanCavanaugh) can you elaborate on the 'right' order of overloads since I don't know of it being documented anywhere? I was thinking that it makes sense to order from more specific to more generic. My first thought was that the nullary version was most specific, but then it occurred to me that 1 arg is more 'specified' than no args, so maybe I had it backwards. If you could help my intuition there that would be great.
thetutlage commentedon Feb 24, 2021
Its kind of sad that verified bugs are not getting fixed for years. :(
RyanCavanaugh commentedon Sep 2, 2026
This is fixed. In
3.7.0-dev.20190823, the originalFoo | Barexample inferredaasstring | Bar, so assigning it tostringproduced TS2322.5.4.0-dev.20231130still has that result, while5.4.0-dev.20231201and the current native7.1.0-dev.20260902.1compiler inferaasstring.The later variant with
stringandnumberreturn types also now infersstring | number.The change was #55447 (
0a877615215980a1df2c4d54e2833acdaeb0bfd6), which prefers an exact signature match before a partial match when combining union call signatures. Closing as fixed.