facebook / facebook/flow

Flow does not refine type parameter based on type being parametrized

Open
#4,455 6 comments 0 reactions 0 assignees View on GitHub
Typing: refinements
Dominant language
Rust
Stars
22.3k
Forks
1.9k
PR merge metrics
No merged PRs in 30d

Description

I'm not sure what is right correct terminology to describe this limitation, but it a major limitation that prevents flow users from splitting data & functions operating on it in favor of types with methods. While it's fine in general it becomes pretty painful when data needs to be send across thread boundaries. Here is [an example code that does not work](https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVAXAngBwKZgByArgLYBGeATgCJ4DGcAJtQDwCGAXGAHZmVUAfGAC8YAN6owYANYBLHk24AiPhWrLUAX3TZ8YALLsslOoxZUOAGjDluAfnbCxk6fMUrSxysqtSwLObU3GbMbE7aurgEoRYczkT81LHhwgA+ht54KZYgNhGojDwAzhgBDGGiYKxwxBiCABQKOHXcpVQKAOY2gWFUIRVxtfUAlNzDosKuYMUIchj0ABZgDb0WAHTuTCMS-tL07MUEqklUytzT0vtwJWUAbuwwxARiOOxURwBi8OwYTTwtDAjPZXORQFYkdRUdZyYqfBTzPANB5PPAjHaXK7SKh4DDEKg8MAo54grHAYBYsAAPV4p3WYAAKotYWA9AQWQpGKQ3hg5OQYAQ5hhlsKCHgAB74egYPBMMA4vEE1nRUlXcmU4pwUjsnhcnl8gVgBSldg8Xm-OQ3MBwcEAA2GttVWjAeBgR12lKuwqoiDAAFEqD6qA1bYQ4GV2LSoWAACTiZp1LS24GenRYtNXA7u5ReEx4c4eylFUpEx7PKprJEJjA9QbUdaVqgpylglbEnUm3V4G3+wNwJuFz0K-GEvgwGCq6TqrE0scwelMllso3FI26rX6-mC+YixZiyUMGVy4dK5c2ydgadXTXatd6i1bted828q09+11R2pl1ugiYrEnoS7ZOiCGbSCwUDsMQMAYBcqp3HAchyqsdb9C63LYM2WLer6AZBiGACqPAyDwiCEo2sgKEwyagf4Og6EAA):

```js
/* @flow */

type NumberDecoder = {
kind: "number"
}

type MaybeDecoder = {
kind: "maybe",
decoder: Decoder
}

type Decoder = NumberDecoder | MaybeDecoder<*, a>

const decode = (input: string, decoder: Decoder): out => {
switch (decoder.kind) {
case "number": {
const value = parseFloat(input)
if (Number.isFinite(value)) {
return value
// ^ number. This type is incompatible with the expected return type
// some incompatible instantiation of `out`
} else {
throw Error(`Not a number ${input}`)
}
}
case "maybe": {
const value = decode(input, decoder.decoder)
if (value instanceof Error) {
return null
// ^ null. This type is incompatible with the expected return type of
// some incompatible instantiation of `out`
} else {
return value
}
}
default: {
void (decoder: empty)
throw Error(`Unknown decoder kind`)
}
}
}
```

- First error reports that `number` is incompatible with some instantiation of `out`, but that should not matter as long as it's compatible with the given instantiation of `out` and since `NumberDecoder` requires `out:number` number should be compatible with given instigation of `out`.

- Second error reports that `null` is incompatible with some instantiation of `out`, but here as well it should not matter as passed `decoder` is narrowed down to `MaybeDecoder<*, out>` and given the `MaybeDecoder` it should be clear that `out` is compatible with `null`.

Now there is a way to avoid these issues [by using class based approach](https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVBLAdgFwKYBOUAhgMZ5gAiepcAJoQDzEB8YA3qmGA7QwBTYADgFccALgDOOAtgDmASnHEwqAL7pSMYpMlgAciIC2AI0LU+TVmAxGhMPEby49F+kyzGzBNp24BrbDpxMAAiT1NCUK4eGndBLFEJaVksRXEI7w4Y7losaTAAN2IYEQoAXjAhYgJJPAAxeGIcBKSFHJsoMH5DSIIAOgxJeuwMfH5i0rwFBWzuefmCPBwRAiwikrLUAEg1MDwYOrmFsBwACwJEMABRAkuCfgADfTgcMBVMwjAAEnZhMTUj3aCw03A0GlQWh0egAssQAJ5mNwMAjMNi2eyOZw4VxxFGMAD81j8YECWGCYSMCLM0W4vHcBBCyKsLBi9IE-2SMnkSiJx1ycHyb0mZTAlXZeFaYgANKczkN+hKCMD5hguhNNhRsNJiFhyHAurd7rMSQslis1mBPDAYB09gcjqbFstVusRXg7TFwehNIKChKxWBGHAxCwpRIwCl5LKlUy8UwQzgWEowImxay6fGBhLw+0gA):

```js
/* @flow */

interface Decoder {
decode(input:string):a
}

class NumberDecoder implements Decoder {
kind: "number"
decode(input:string):number {
const value = parseFloat(input)
if (Number.isFinite(value)) {
return value
} else {
throw Error(`Not a number ${input}`)
}
}
}

class MaybeDecoder implements Decoder {
kind: "maybe"
decoder: Decoder

decode(input:string):?a {
const value = decode(input, this.decoder)
if (value instanceof Error) {
return null
} else {
return value
}
}
}

const decode = (input: string, decoder: Decoder): out =>
decoder.decode(input)
```

It would be really nice if there as a way to achieve the same result as in last example but without having to attach decode implementation to a variant of the decoder.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.