Flow: Add support for destructuring parameters + default values

Created on 10 Dec 2014  路  49Comments  路  Source: facebook/flow

I'm using ES6's new destructuring parameters with default values per the reminder from Brendan Eich (via 2ality.com).

So I would like to do this:

function rgba({ r, g, b, a = 1 }) {
    return [ r, g, b, a ];
}

Flow is throwing an Unexpected token = error at a = 1. Interestingly, JSHint has an issue with this pattern too.

ES2015+ destructuring incompleteness parsing

Most helpful comment

It's been more than two years since this issue was opened and it still doesn't work properly. What's the state of the issue? @samwgoldman did you improve on the partial fix?

All 49 comments

+1

Thank you for reporting this issue and appreciate your patience. We've notified the core team for an update on this issue. We're looking for a response within the next 30 days or the issue may be closed.

I have a question related to this issue: What's would be the expected behavior for typing params with default values?

My usage of default params tends to enforce strict types, e.g. this field is either 1 or some user-defined number, but it is always a number.

Considering the following:

/* @flow */
type params = { a: number }
function fn ({ a = 1 }: params): params {
  return { a }
}
fn({})

Would I get an error for not passing in an object that has a set to a number? It's optional, so I'm guessing it should probably be annotated as { a?: number } or { a: ?number }?

It could also infer the type from the default value? Making a?: number unnecessary to write (perhaps more obscure).

@cesarandreu It would be { a?: number }, which is the same as { a: void | number }. ?number is the same as void | null | number. null is not a real type annotation in Flow (edit: it is now), but the point remains.

Thanks!

@Eyyub You have some work on this. I looked over your branch and I think it makes sense to try to land the parsing support first, then the type checking support separately. If you don't have bandwidth to do this, would you mind if I tried to put a bow on what you have so far?

@samwgoldman Np I agree ! And yes sorry I don't have 'a bandwith' (nice expression :p) to do this actually, so do as you want and do not hesitate to ask me if something is weird on my branch !

Any updates for this issue?

I need diz !

Having to refactor

const { a, b = false } = obj

into

let { a, b } = obj;
if (b === undefined) b = false;

Just to please flow is very annoying :(

Would love to get a fix for this. Lack of support for such a nice language feature makes it feel like I'm getting handcuffed by flow =/

[Accidentally placed this comment in the wrong thread. Putting it here for posterity.]

I had a fair amount of WIP on this, but I tossed it out and haven't revisited.

My take is that this commit is too hacky and we shouldn't merge it. We need to separate parsing of objects and parsing of destructuring. Right now we do this weird thing where we parse an object, then transform it. Object syntax doesn't have defaults, so that method no longer works.

Ultimately this just confuses parsing support for assignment expressions ({ foo = bar } = obj). It's actually significantly simpler to just handle the case for params and variable declarations, which happens to be where destructuring+default is most often found.

I can still loop back on this in the coming weeks, but if someone is looking for something to do, please take it. If you do, I heartily recommend starting with the params/declaration cases and punting on assignment exprs. That's just me, though.

This is currently the biggest missing es2015 feature. Not being able to write the full es2015 spec makes it difficult to consider moving to flowtype. Therefore I hope this is a high priority missing feature.

Considering ES2015 parameter syntax is already implemented by all 3 major browsers, this is a sever limitation of Flow.

Yup, this has been on my plate for too long. I've been blocking this because I started working on it, then joined Facebook where I'm still getting used to things. Thanks for your patience. This feature is really high on my list of things to get out the door.

OK, so a partial fix to this landed just now. We've added support for defaults in destructuring declarations鈥攊.e., variable declarations and parameter lists for function declarations. What's missing is defaults for destructuring assignment expressions, e.g., var p; { p } = o;, which is definitely less common.

The fix will be in the next release, or you can build master and take it for a spin right now.

Here's a fun game:

function obj_prop_fun({p:{q=0}={q:true}}={p:{q:""}}) {
  // what is the type of `q`?
}

@samwgoldman great news!

So... I'd like to do something simpler, like:

let config = {
  someObject = {
   someArray: []
  }
}

Flowtype will say ^^ array element. Missing annotation

Is there any way to add an annotation to that array and still assign the default value to an empty array?

@svenanders The code you shared isn't a destructuring (and isn't valid syntax). Can you provide working code so I can reproduce what you're seeing?

@samwgoldman, is this feature now available in Flow 0.22.1 or should we still build from master?
Can't see info on destructuring in last changelogs (or in merged pull requests).

@mmazzarolo part of it is in 0.22, but an important bug fix related to annotations is not yet released, but is in master.

@samwgoldman, thank you, I'm just dumb. I thought this discussion was about type annotations on destructuring, defaults are working just fine.
Thank you anyway :+1:

[this.password, this.salt] = [password, salt];
flow - unsupported destructuring

Is the fix for this issue still in progress?

I'm having some trouble figuring out how to make this work. I'm writing a rule-parsing library, and want to offer some hooks to change default behavior. My code looks like this, hope the intent is clear:

type handlers = {
  onSuccess: Function,
  onFailure: Function,
  onComplete: Function,
};

function defaultOnSuccess(rule: rule): any {}
function defaultOnFailure(rule: rule): any {}
function defaultOnComplete() {}

// Here comes the bit I'm having a hard time to get both validated through flow, and parsed by babel (with transform-strip-flowtypes):

export default ({
  onSuccess = defaultOnSuccess,
  onFailure = defaultOnFailure,
  onComplete = defaultOnComplete,
}: ?handlers) => 

// The API I'm trying to land on is as follows:
const parse = createParser(); // all defaults assumed
const parse = createParser({ onSuccess: myOnSuccess }); // defaults, but own `onSuccess`

// I'd write that in ES6 as follows:

export default ({
  onSuccess = defaultOnSuccess,
  onFailure = defaultOnFailure,
  onComplete = defaultOnComplete,
} = {})

I tried this:

export default ({
  onSuccess = defaultOnSuccess,
  onFailure = defaultOnFailure,
  onComplete = defaultOnComplete,
}: ?handlers = {})

Which leads to Parsing error: binding rvalue in babel. I tried:

export default ({
  onSuccess = defaultOnSuccess,
  onFailure = defaultOnFailure,
  onComplete = defaultOnComplete,
}: ?handlers)

Which causes flow errors (because incorrect amount of arguments passed to createParser when I invoke without arguments). So I'm unsure how to express this with flow. Any thoughts?

The following syntax results in the dreaded Parsing error: binding rvalue in babel. I want to be able to provide a default object when no object is provided and provide default values for specific values for missing properties.

const myFunction = (someArray: Array<string>, {
  defaultValues = {},
}: {
  defaultValues: {[key: string]: string},
} = {
  defaultValues: {},
}) => ...;

@BartWaardenburg Flow parses that fine鈥攃an you open an issue with Babel?

// @flow
import React from 'react'

type SomeString = | 'this' | 'that' | 'maybe-this' | 'that-too'

const SomeComponent = ({ name = false }: { name: SomeString }) => {
  return <div>
    {name}
  </div>
}

This code type checks. I think flow should detect that the default parameter does not match the given type. Am I doing something wrong here?

@chrisui I'm using version 0.27.0

This is very strange, not sure what might be wrong on my local install. Also, the following code type checks in the repl, while I feel it should not:

import React from 'react'

type SomeString = | 'this' | 'that' | 'maybe-this' | 'that-too'

const SomeComponent = ({ name = 'thiss' }: { name: SomeString }) => {
  return <div>
    {name}
  </div>
}

See here

Hello, I read everything but didn't understand what's the state of the issue.
I use v0.30 and still have the problem!
thanks

I too have the error in code:

export const start = ({
  initialProgress = 15,
  valueIncreaseProgress = 3,
  stepDuration = 400,
}: {
  initialProgress: number,
  valueIncreaseProgress: number,
  stepDuration: number,
} = {}) => {

Error message:

ERROR in ./src/components/PageLoadProgressBar/reducer/index.js
Module build failed: SyntaxError: ./src/components/PageLoadProgressBar/reducer/index.js: Binding rvalue (33:22)
  31 |   };

  32 |
> 33 | export const start = ({
     |                       ^
  34 |   initialProgress = 15,
  35 |   valueIncreaseProgress = 3,
  36 |   stepDuration = 400,
    at Parser.pp.raise (/Users/efrem/WebProjects/react-redux-universal/node_modules/babylon/lib/parser/location.js:22:13)
    at Parser.pp.checkLVal (/Users/efrem/WebProjects/react-redux-universal/node_modules/babylon/lib/parser/lval.js:335:12)
    at Parser.checkLVal (/Users/efrem/WebProjects/react-redux-universal/node_modules/babylon/lib/plugins/flow.js:245:22)
    at Parser.pp.checkLVal (/Users/efrem/WebProjects/react-redux-universal/node_modules/babylon/lib/parser/lval.js:326:12)
    at Parser.checkLVal (/Users/efrem/WebProjects/react-redux-universal/node_modules/babylon/lib/plugins/flow.js:245:22)
...

I use eslint ^2.0.0, babel-eslint ^6.0.0, flow ^0.30.0 and babel latest version.

I'd like to come back to @StevenLangbroek's example above (https://github.com/facebook/flow/issues/183#issuecomment-226938031). Shouldn't the following be possible?

type Data = {
  name: string;
  age: number;
};

const fn = ({ name = 'John Doe', age }: Data): Data => ({
  name,
  age,
});

fn({ age: 42 });
// Results in error:
// 6: const fn = ({ name = 'John Doe', age }: Data): Data => ({
//                                            ^ property `name`. Property not found in
// 11: fn({ age: 42 });
//        ^ object literal

With the latest version of Flow (0.31.0), this does not typecheck. However I think that it should. It all depends on whether the type annotation {鈥: Data is supposed to apply to the argument itself or to the argument including default values. The latter would make development easier, I guess.

In other words, Flow should be able to relax type checks (applied to the incoming value) according to the given default values, i.e. in this example, Data should be relaxed to this without having to declare it explicitly as such:

type Data = {
  name: string;
  age: number;
};

type Data_ = {
  name?: string;
  age: number;
};

const fn = ({ name = 'John Doe', age }: Data_): Data => ({
  name,
  age,
});

fn({ age: 42 });
// This works.

Any news here? @samwgoldman

At least some workarounds would be nice 馃槃

One more case: https://cl.ly/iSux

type Arg = { prop: string | number };
const func = ({ prop = 1 }: Arg) => prop;
4: const func = ({ prop = 1 }: Arg) => prop;
                   ^ number. This type is incompatible with
4: const func = ({ prop = 1 }: Arg) => prop;
                   ^ string
4: const func = ({ prop = 1 }: Arg) => prop;
                   ^ string. This type is incompatible with
4: const func = ({ prop = 1 }: Arg) => prop;
                   ^ number

Flow fails to parse this valid ES2015 code:

var href = 'https://github.com/facebook/flow/issues/183'
var HREF_REGEXP = /^([^#?]*)(\?([^#]*))?(#(.+))?$/;
var [, path='', , query='', , hash=''] = HREF_REGEXP.exec(href) || [];
//                ^ Unexpected token ,

(flow.org/try)

The parsing error is suppressed if we put some dummy variables in the holes _or_ if we remove the default assignments.

Shouldn't types from destructured params with default values be inferred from the default value as mentioned in @rauchg comment? It seems really redundant and verbose to do:

const flex = ({x = 'center', y = 'center'}: {x: string, y: string} = {}) => ({
  display: 'flex',
  justifyContent: x,
  alignItems: y,
});

seems like this should be enough:

const flex = ({x = 'center', y = 'center'} = {}) => ({
  display: 'flex',
  justifyContent: x,
  alignItems: y,
});

Sorry if there is already support for this already by my flow linter is lighting up

Same as reported by @alexfedoseev, but with optional type: http://bit.ly/2rYnuYg

type params = { a?: ?number }
function fn ({ a = null }: params): params {
  return { a }
}
fn({})

It's been more than two years since this issue was opened and it still doesn't work properly. What's the state of the issue? @samwgoldman did you improve on the partial fix?

Hi,

wondering whether this is related to an issue I'm having with default rest parameters and union types:

https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVAXAngBwKZgAqAFngLZ4ByAhhWALxgDkAlgHZRxNgA+zAzgFcAxsLz9+3PkwTUATm3YBzKcwAm1NkrxymAbnTZ8RUnUYBvVGDAthcNgC4w-DHOUAaK2Dvw5Tl25angC+Bpi4BCTkeAAKcnA4DCbRvMkUNBRhRgRxCfxJltYYpngA-E5RFLk4IWFQgmzCGCz2YABicHAAFOZgxSmMrBxcYMFO1fwAlGCFYKjBQA

Flow fails with this very simple example of specifying a maybe type with a default null value:

const foo = ({name = null}:{name: ?string}) => {
    console.log(name);
}

1: const foo = ({name = null}:{name: ?string}) => {
             ^ null or undefined. This type is incompatible with
1: const foo = ({name = null}:{name: ?string}) => {
                ^ string
1: const foo = ({name = null}:{name: ?string}) => {
                        ^ null. This type is incompatible with
1: const foo = ({name = null}:{name: ?string}) => {
                ^ string

https://flow.org/try/#0MYewdgzgLgBAZiEMC8MAUBvMBDAtgUxRjAFcAbMgXwC4s99qYB+aAJwEswBzSgShQB8MDACgAkKEggy+AHRkQXNDgK8A3CMpA

Is anybody from the flow team going to comment on this?

We are running into the exact problem that @lukemartin describes. Would be great to get this fixed or at least have someone from the team comment on it.

what is still failing?

@sibelius lukemartin has provided a good example. Do you have problems understanding it?

hey @sibelius - this throws errors in 0.76.0:

const foo = ({name = null}:{name: ?string}) => {
    console.log(name);
}

https://flow.org/try/#0MYewdgzgLgBAZiEMC8MAUBvMBDAtgUxRjAFcAbMgXwC4s99qYB+aAJwEswBzSgShQB8MDACgAkKEggy+AHRkQXNDgK8A3CMpA

Using $Subtype around the annotation seems to work, but I'm not sure if that's the right way to go in all situations.

const foo = ({name = null}: $Subtype<{name: ?string}>) => {
    console.log(name);
}

https://flow.org/try/#0MYewdgzgLgBAZiEMC8MAUBvMBDAtgUxRjAFcAbMgXwC4YASAZRICMoBPAB3wB4s99aAfmgAnAJZgA5pQB8AShQyYGAFABIUJBBl8AOjIhJaHATkBuFZRVA

const foo = ({name = true}: $Subtype<{name: ?string}>) => {
  //                 ^ flow does not check this
  console.log(name);
}

As proposed in SO

function foo(arg: {name: ?number}) {
  const { name = 42 } = arg;
  // the default value is only used for undefined values.
  console.log((name: (number | null)));
}

@Buggytheclown Not a big fan of changing code to accommodate flow. In our scenario (jetbrains) this would cause us to lose introspection as well.

Perhaps a separate issue, but I'm having a very similar problem with non-maybe types. I'm trying:

/* @flow */

type P = string | Array<string>;

function p(p: P = []) {}

type O = {p: P};

function o({p = []}: O) {}

And I get the following errors:

9: function o({p = []}: O) {}
               ^ array type [1] is incompatible with string [1].
References:
7: type O = {p: P};
                ^ [1]
9: function o({p = []}: O) {}
                   ^ empty array literal [1] is incompatible with string [2].
References:
9: function o({p = []}: O) {}
                   ^ [1]
7: type O = {p: P};
                ^ [2]

Interestingly, the errors go away if I simplify the type P to Array<string>. It's almost like Flow is expecting my default value to be _all_ of the unioned types, rather than just one of them.

Was this page helpful?
0 / 5 - 0 ratings