-
Notifications
You must be signed in to change notification settings - Fork 13k
Description
I don't really know how to word the title for this one or explain it well...but here's what I'm thinking: it would be nice to allow function overloads to unify, provided only one parameter and possibly the return type are different across each overload. Additionally, when narrowing the returned type, it should be able to inductively narrow the overloaded parameter's type accordingly within the same block. To hopefully explain this a little better, here's what I mean:
enum Types { Foo, Bar }
class Foo { foo: number; }
class Bar { bar: number; }
declare function foo(type: Types.Foo, length: number): Foo;
declare function foo(type: Types.Bar, length: number): Bar;
// This should be okay, with `result` inferred as `Foo | Bar`
const type: Types.Foo | Types.Bar = getType();
const result = foo(type, 1);
if (result instanceof Foo) {
// str is inferred as Types.Foo here
} else {
// str is inferred as Types.Bar here
}
In this case, if result
is a Foo
, str
can only possibly be a "foo"
through induction, and similarly result
being a Bar
and str
being a "bar"
.
With #12883 also, this would also permit assertions to do compile-time type narrowing, without adding any new syntax or special casing of any particular identifier, hence fixing #12825 and #8655 simultaneously while remaining much more flexible:
declare function assert(cond: true, message: string): void;
declare function assert(cond: false, message: string): never;
I know this would likely be really hard to implement, but it would pay off. It helps that boolean
is equivalent to true | false
, "foo"
is a subtype of string
, E
is equivalent to E.A | E.B | E.C
where enum E {A, B, C}
, etc., so much of the structural narrowing would allow this to apply to several other areas.
Note that this would specifically not allow more than one varying type to be unified, because it would be an M⨯N type implication, which would be unrealistic to infer in practice (you would already need length
to be similarly guarded in theory):
declare function foo(type: "foo", length: 1): Foo;
declare function foo(type: "bar", length: 2): Foo;
let type: "foo" | "bar" = someType;
let length: 1 | 2 = someLength;
let result = foo(type, length); // Fail
Sorry if I did a really poor job explaining it (I don't really know the correct technical term for this, hence the detailed examples).