Flow: Maybe null type throws errors regardless of type of parameter passed.

Created on 13 Mar 2017  路  3Comments  路  Source: facebook/flow

I'm using flow 0.41.0.

I am trying to make some helpers around thefetch function that all have the same params and return type. In order to avoid boilerplate with regards to the types, I tried to use *, and typeof rather liberally to get Flow to do the work for me.

// @flow

import fetch from 'node-fetch'

export const post: typeof fetchJSON = (url: *, opts: *): * => fetchJSON(url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  ...opts
})

function fetchJSON(url: string | Request, opts?: RequestOptions = {}): Promise<Object> {
  return fetch(url, opts).then(r => r.json())
}

The above post function, will throw an error regardless of the params you pass to it. For example, with the current params above, I get the errors

x.js:5
  5: export const post: typeof fetchJSON = (url: *, opts: *): * => fetchJSON(url, {
                                                                                  ^ object literal
  6:   method: 'POST',
               ^^^^^^ string. This type is incompatible with
824:     method?: ?MethodType;
                  ^^^^^^^^^^^ null. See lib: /private/tmp/flow/flowlib_316468aa/bom.js:824

x.js:5
  5: export const post: typeof fetchJSON = (url: *, opts: *): * => fetchJSON(url, {
                                                                                  ^ object literal
  7:   headers: { 'Content-Type': 'application/json' },
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ object literal. This type is incompatible with
822:     headers?: ?HeadersInit;
                   ^^^^^^^^^^^^ null. See lib: /private/tmp/flow/flowlib_316468aa/bom.js:822

x.js:5
  5: export const post: typeof fetchJSON = (url: *, opts: *): * => fetchJSON(url, {
                                                                                  ^ object literal
  7:   headers: { 'Content-Type': 'application/json' },
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `Content-Type`. Property not found in
822:     headers?: ?HeadersInit;
                    ^^^^^^^^^^^ Headers. See lib: /private/tmp/flow/flowlib_316468aa/bom.js:822


Found 3 errors

Doing flow type-at-pos for the post function, gives me

(url: string | Request, opts?: {
  body?: ?(string | Blob | FormData | URLSearchParams),
  cache?: ?'default' | 'force-cache' | 'no-cache' | 'no-store' | 'only-if-cached' | 'reload',
  credentials?: ?'include' | 'omit' | 'same-origin',
  headers?: ?{[key: string]: string,} | Headers,
  integrity?: ?string,
  method?: ?MethodType,
  mode?: ?'cors' | 'no-cors' | 'same-origin',
  redirect?: ?'error' | 'follow' | 'manual',
  referrer?: ?string,
  referrerPolicy?: ?'' | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin-only' | 'origin-when-cross-origin' | 'unsafe-url'
}) => Promise <Object>

which seems correct to me. Is this a bug in Flow itself?

Most helpful comment

I was able to reproduce this with a much smaller subset of the bigger problem, and I think I understand the problem now because it's completely unrelated to fetch itself.

// @flow
const opts: RequestOptions = {}
const test = { method: 'POST', headers: { 'Content-Type': 'application/json' }, ...opts }

The above causes similar errors, and it's because spreading opts into the object might rewrite 'POST' to null, which isn't allowed because it has a string type. The solution is to spread into a new object, instead of the existing one. So

const test = { ...{ method: 'POST', headers: { 'Content-Type': 'application/json' } }, ...opts }

will work. My bad here there's no bug. Thanks @nmn and @mwalkerwells.

All 3 comments

I'm not sure but this sure looks like a problem because of property variance.

Objects are invariant & you can see that flow is inferring types that differ. You just need to be a bit more explicit here. For example { method: string } is actually a supertype of { method?: ?MethodType } and a function that would accept this (write only) would need to be marked as contravariant.

I was able to reproduce this with a much smaller subset of the bigger problem, and I think I understand the problem now because it's completely unrelated to fetch itself.

// @flow
const opts: RequestOptions = {}
const test = { method: 'POST', headers: { 'Content-Type': 'application/json' }, ...opts }

The above causes similar errors, and it's because spreading opts into the object might rewrite 'POST' to null, which isn't allowed because it has a string type. The solution is to spread into a new object, instead of the existing one. So

const test = { ...{ method: 'POST', headers: { 'Content-Type': 'application/json' } }, ...opts }

will work. My bad here there's no bug. Thanks @nmn and @mwalkerwells.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mmollaverdi picture mmollaverdi  路  3Comments

ghost picture ghost  路  3Comments

mjj2000 picture mjj2000  路  3Comments

tp picture tp  路  3Comments

cubika picture cubika  路  3Comments