-
-
Notifications
You must be signed in to change notification settings - Fork 84
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
Add branch awareness to TypeScript types (strict) #438
base: master
Are you sure you want to change the base?
Conversation
This comment has been minimized.
This comment has been minimized.
A solution that was found for #457 has the potential of unblocking this PR. In particular, it is possible to overload functions in such a way that TypeScript chooses the correct overload based on the "direction" that function arguments are supplied in: Lines 144 to 148 in 8eef14a
I believe that this allows me to fix the issue mentioned at the bottom of my PR comment. 🎉 |
I rebased this on #457 and pushed a commit that shows typings for |
A certain Future is a Future whose branch is certain. In TypeScript, this is represented by having 'never' as the type for one of the branches. This change also applies to ConcurrentFutures.
An uncertain Future is one that might reject or resolve.
3b7922b
to
7072818
Compare
This is an alternative to #435 that doesn't include auto-expansion of generic types.
A quick recap of auto-expansion:
Unstrict: With auto-expansion (behaviour on
master
forchain
andchainRej
)chain (_ => reject ('b')) (reject ('a'))
gets typeFuture<string, never>
: The types align.chain (_ => reject ('a')) (reject (1))
gets typeFuture<number | string, never>
: The rejection type was expanded.chain (_ => reject ('a')) (resolve (1))
gets typeFuture<string, number>
: The types were expanded withnever
, which leaves them intact.Strict: Without auto-expansion (behaviour on
master
for everything else)chain (_ => reject ('b')) (reject ('a'))
gets typeFuture<string, never>
: The types align.chain (_ => reject ('a')) (reject (1))
produces a type error: The types don't align. This is desirable behaviour, because it is more true to the behaviour in Haskell, where the rejection branch cannot change type because the monad instance is created for the unary constructor that is left after providing the first type variable.chain (_ => reject ('a')) (resolve (1))
produces a type error: TypeScript doesn't want to assignnever
to another type. Forchain
andchainRej
this was fixed in Improve TypeScript type inference #374 by making those functions unstrict.In #435, I had given up on the strict approach (following the trend created in #374), because I thought there's no way to get the strict behaviour while retaining the ability to
chain
(or otherwise combine) futures where the branch types don't technically conflict because the variable on one of the sides was untouched (like in examples3
above).The goal of this pull request is to:
chain
andchainRej
using the strict approach, getting rid of the problem highlighted by example2
.3
from the strict approach, because we're no longer asking TypeScript to assignnever
to anything: Our overloads take care of any occurrence ofnever
with special treatment.This approach brings new problems, however. In particular, TypeScript loses type information when these functions are called indirectly, such as by
pipe
. I think this happens because TypeScript cannot infer types from overloaded functions (microsoft/TypeScript#32418 (comment)).So where this approach works perfectly for statements like
and (resolve (42)) (reject ('abc'))
, it produces someunknown
type variables when refactoring that toreject ('abc') .pipe (and (resolve (42)))
, becauseand (resolve (42))
creates an overloaded lambda, from whichpipe
cannot infer any type information.If anyone can help with this new problem, it'll be much appreciated!