Skip to content
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

Type annotations inside destructuring #235

Closed
sebmck opened this issue Jan 27, 2015 · 114 comments
Closed

Type annotations inside destructuring #235

sebmck opened this issue Jan 27, 2015 · 114 comments

Comments

@sebmck
Copy link
Contributor

sebmck commented Jan 27, 2015

It seems like the following should be supported:

var [a: String, b: Number] = ["foo", 1];

function foo([a: String, b: Number]) {}
foo(["foo", 1]);

Is this a deliberate decision or was the scope of ES6 type annotations restricted until ES6 became more widespread?

Thanks!

@gabelevi
Copy link
Contributor

We've held off on adding inline type annotation for destructuring since there isn't an obvious place in the grammar for object destructuring (var {a: string, b: number} = x will bind x.a to string and x.b to number). Array destructuring doesn't have this problem, so we could definitely add it.

Also I don't think TypeScript supports these kinds of annotations, so it wasn't something we urgently needed to support. You're saying the 6to5 acorn fork has support?

@sebmck
Copy link
Contributor Author

sebmck commented Jan 27, 2015

@gabelevi Yeah, I was rewriting some of the flow parsing in acorn-6to5 and accidentally added it in. Only my examples with array destructuring work correctly because as you said, syntax ambiguity.

@RReverser
Copy link

Hmm, interesting how TypeScript / Flow are going to resolve this. Ideally, we could just do it during scope analyzing so if "property value" of object pattern is linked to type, then use it as type annotation and if not, use as destructuring target.

The only downside will be for cases like:

class A {}
var {a: A} = obj;

when you really want to override locally defined class and reassign A = obj.a, but I don't think this is an often case and it can be easily workarounded on developer side.

@sebmck
Copy link
Contributor Author

sebmck commented Feb 3, 2015

@RReverser You can just do:

class A {}
var { a }: { a: A } = obj;

@RReverser
Copy link

@sebmck That doesn't look good enough - variables and their types are split into different sides of operator. It works for regular object literals, but doesn't look so nice for bindings.

@gabelevi
Copy link
Contributor

gabelevi commented Feb 4, 2015

I've been thinking about this for awhile, and I think the : type paradigm just isn't going to fit into the grammar for object literals, patterns and types. I think if we're going to add inline type annotations then we need to switch paradigms. I've been thinking maybe we could do something like

// Works well for object types
var { number x; string y } = { x: 123, y: "hello" };
// Works well for object patterns
function foo({number a, string b}) {}

// Not so sure if object literals need this
var z = {
  ?string m: null,
  ?number n: null
};

// because soon Basil's new expression annotation syntax will land 
// which will help object literals
var z2 = {
  m: (null: ?string),
  n: (null: ?number)
}

The downsides include that this paradigm doesn't match the other type annotations and that there are certain reserved keywords in the modifier position (get, set, static).

@samwgoldman
Copy link
Member

@gabelevi, maybe I am being dense here, but why is var {a: string, b: number} = x not possible? Shouldn't the context be enough to disambiguate between destructuring-with-type-annotations and object literal syntax?

@samwgoldman
Copy link
Member

Oh, nevermind. I forgot about rebinding destructuring.

@rauschma
Copy link

TypeScript uses as as a coercion operator. How about the following?

var { x as number, y as string } = { x: 123, y: "hello" };

function foo({a as number, b as string}) {}

var z = {
  ?m: null as string,
  ?n: null as number,
};

Alternatively, one could also use “Basil's new expression annotation syntax” everywhere:

var { (x : number), (y : string) } = { x: 123, y: "hello" };

function foo({ (a : number), (b : string)}) {}

var z = {
  m: (null : ?string),
  n: (null : ?number)
}

But I like as, because then there is no overloading of the single colon (and less parens). A double colon (instead of as) would also be nice, but IIRC that is reserved by TC39 for guards.

@samwgoldman
Copy link
Member

Hmm, I actually like { (x : number) } = { x: 123 }. Seems totally unambiguous and unlikely to conflict with future ECMA-262 proposals. Any objections? /cc @gabelevi

@Daniel15
Copy link
Member

👍 to @rauschma's idea of using "as".

@ianobermiller
Copy link
Contributor

👍 to @rauschma's other idea of using "expression annotation syntax", or what I naïvely call "casting".

@xixixao
Copy link

xixixao commented Dec 6, 2015

This would be super useful for functional React components:

const Component = ({
  propA,
  propB,
}) =>
  ....

Currently, the best way is to create a type and access the props via field access (that way only prop. is repeated a lot, but not the names of the props).

type Props = {
  propA: SomeType,
  propB: SomeType,
}
const Component = (props: Props) =>
  ....

Clearly the destructuring and typing syntaxes clash :(. This makes sense to me, but is super ugly:

const Component = ({...}: {
  propA: SomeType,
  propB: SomeType,
}) =>

(since empty {} destructuring seems to be going through the committee, and wouldn't be obvious anyway).
This is probably currently illegal, but I have no reasoning for it:

const Component = ({{
  propA: SomeType,
  propB: SomeType,
}}) =>

What I'm looking for is a solution that lets us easily go from typed params to named typed params:

f = (a: X, b: Y) => ...
f = ({{a: X, b: Y}}) => ...

@theshock
Copy link

What about this? I like "as" operator too - it is short, simple and evident. This example is good:

function foo({ a as number, b as string }) {}

@leebyron
Copy link
Contributor

Just bumped into this myself. I also support { (x : number) } = { x: 123 } since it aligns with type annotations elsewhere. I don't think we need to add a new keyword.

For some reason I thought { x: (localX: number) } = { x: 123 } would work, but perhaps it also should work.

@theshock
Copy link

This syntax is confuzy - too easy to make mistake.

@jeffmo
Copy link
Contributor

jeffmo commented Jan 26, 2016

I was on a long plane ride this weekend so I figured I'd toy around with the paren-grouping syntax (i.e. let { (x:number) } = {x:123}) because it's simple, it composes well with the rest of of our lval type annotations, and when paired with object literals it unlocks the ability to type a property slot (rather than having to type the value and inferring the property slot from that). On objects, this would unlock the ability to understand deferred initialization of a property similar to how we can with let/var (i.e. let foo:number; foo = 42; --> let a = { (someNum:number) }; a.someNum = 42;)

On syntax: I went with what matches the most with our other annotation positions. TBH I don't find the syntax beautiful, but I don't think we're going to find something that everyone agrees on there. Additionally, after playing a bit I don't find it prohibitively inexcusable.

My branch is linked below. It doesn't wire up enforcement of the destructuring annotations just yet -- but it shows that we can if we wanted to pursue this route:

https://github.com/jeffmo/flow/tree/pattern_lval_annotations

@rauschma
Copy link

Did you get in contact with the TypeScript team? Ideally, a syntactic solution would be adopted by both Flow and TypeScript.

@jeffmo
Copy link
Contributor

jeffmo commented Jan 26, 2016

@rauschma: I haven't on this, no (this was just plane-ride hackery) -- I'm happy to reach out.

EDIT: I guess this isn't really casting, so maybe casting syntax consistency doesn't matter too much

@RReverser
Copy link

@jeffmo It's not about casting consistency, but rather would be nice to have as much syntax shared as possible. I'm pretty sure they were also exploring options for typing destructuring, so collaboration could help in both short-term and long-term perspectives.

@theshock
Copy link

Also similar syntax in TS and Flow is good for IDE.

@jeffmo
Copy link
Contributor

jeffmo commented Jan 26, 2016

Indeed. I will be sure we follow up with them before moving forward with anything here

@rauschma
Copy link

👍

@jtremback
Copy link

I know that you probably can't switch the whole syntax over now, but it's too bad that we can't have this:

Regular function call

function (
  number id,
  string name
) {
  // function body
}

Object destructuring

function ({
  number id,
  string name
}) {
  // function body
}

Array destructuring

function ([
  number id,
  string name
]) {
  // function body
}

One of the most useful places to have types is in function signatures, so it's too bad that Flow doesn't support a cohesive and readable syntax there.

@alexeygolev
Copy link

@jtremback It's not too bad at all. Flow does indeed support a cohesive and readable syntax. Destructuring aside (I'll address it below), your

function (
  number id,
  string name
) {
  // function body
}

is just (by the way a function is an arrow type, you don't have a full function type there) a personal preference over

function (
  id: number,
  name: string
): any {
  // function body
}

I prefer the latter, you prefer the former. It's not too bad.

Destructuring actually doesn't mean that your function's argument is what you wrote it is. I'm talking about the object destructuring. Your array destructuring is actually a tuple type which is [id, name]: [number, string].
The main point is — you argument is an object or a tuple. It has a specific type. And this type would be the argument type. Just because you're destructuring it the type doesn't change. So I actually prefer to declare my type elsewhere (normally you would use it in several places anyway) and then just do { id, name }: MyType. Flow will figure the rest up. The trick is to use Flow's impressive mechanisms not to try to annotate every single thing in your code.
But it's just my opinion. However the presence of one invalidates your too bad argument.

@zaynv
Copy link

zaynv commented Mar 12, 2018

Hey @avikchaudhuri thanks for taking the time to reply! I was wondering if the Flow team would consider using GitHub to discuss progress/plans/etc for Flow. It seems like a lot of it is happening through internal groups at Facebook, and I think it would be great for the Flow community if they could be a part of it.

@ESRogs
Copy link

ESRogs commented Mar 15, 2018

@gabelevi says:

We've held off on adding inline type annotation for destructuring since there isn't an obvious place in the grammar for object destructuring (var {a: string, b: number} = x will bind x.a to string and x.b to number). Array destructuring doesn't have this problem...

@samwgoldman says:

@gabelevi, maybe I am being dense here, but why is var {a: string, b: number} = x not possible? Shouldn't the context be enough to disambiguate between destructuring-with-type-annotations and object literal syntax?

Oh, nevermind. I forgot about rebinding destructuring.

I had the same question, and I still don't get it. What's the problem with var {a: string, b: number} = x ?

Why would it be bad to bind x.a to string and x.b to number? (With this code, that sounds like exactly what I'd be trying to do -- make sure that x.a is a string and x.b is a number.)

@Macil
Copy link
Contributor

Macil commented Mar 15, 2018

@ESRogs var {a: string, b: number} = x is already valid javascript equivalent to var string = x.a; var number = x.b;. (It's a type of destructuring assignment. The logic of its design is easier to get with silly symmetrical examples like var {p: foo, q: bar} = {p: 42, q: true};.)

@zeorin
Copy link

zeorin commented Apr 19, 2018

It should be noted that proposal A may be confusing if the as destructuring patterns Stage 0 ECMAScript proposal is accepted.

@Extarys
Copy link

Extarys commented Apr 29, 2018

What is the current working solution if I don't want a type defined externally to my function?

@sethwklein
Copy link

@Extarys, var f = function({x}: {x: T}) { /* ... */ } is working for me.

I can't really find documentation for it, although https://flow.org/en/docs/types/objects/#toc-exact-object-types and https://flow.org/en/docs/lang/refinements/ imply it.

@thisissami
Copy link

As somebody late to the world of flow and this party of a thread... what's the likelihood that something along the lines of proposal B from @erykpiast's comment back in Nov 2017 will be integrated in to flow? That makes so much sense and instills DRY principles - the current implementation certainly has me wanting to just use {} or Object when faced with somewhat involved object destructuring.

@gnprice
Copy link
Contributor

gnprice commented Sep 27, 2018

@thisissami (and others finding this thread, as it's gotten pretty long): the status was summarized by @avikchaudhuri in a comment about 6 months ago:

Hi, proposal B is the most likely one the Flow team would support, but it continues to be very low on our priority list. Happy to take PRs.

So, the likelihood of it happening is up to you! And me, and "us" -- whoever wants to have some fun with the Flow implementation to work up a PR.

@nickshanks
Copy link

It's been nearly three years since the solution was first mentioned and it still doesn't work. How is this not facebook's top priority with flow? I'd been using it for less than a day when I ran into this.

@ermik
Copy link

ermik commented Oct 10, 2018

Facebook’s priorities changed in November 2016, as you might remember.

Sent with GitHawk

@koko236
Copy link

koko236 commented Oct 10, 2018 via email

@Fzzr
Copy link

Fzzr commented Oct 10, 2018

What does it mean? Is flow no longer meant to be used for js type declarations?

That was a joke, it's implying that political issues are distracting Facebook from tech as a priority. It's not actually clear if the events of the last few years have made flow less important.

@jsardev
Copy link

jsardev commented Oct 10, 2018

Why is it so hard for everybody to understand that Facebook's main priority is for sure not their open-source software? Why can't just everybody be grateful for what they got and stop complaining all the time? It's so annoying 🙄 If you need this feature so much - fork it and contribute. That's how open-source works.

@chaimleib
Copy link

How does this look? It incorporates current syntax like B, but it doesn't require pairs of parens. Also, the parser can't confuse it with destructuring.

function f({ a: string:, b: number: })
function f({ a: string: aAlias, b: number: bAlias })
// extrapolating ...
function f({ a: string: aAlias = 'a', b: number: bAlias = 1 })

@theshock
Copy link

theshock commented Feb 26, 2019

How does this look? It incorporates current syntax like B, but it doesn't require pairs of parens. Also, the parser can't confuse it with destructuring.

function f({ a: string:, b: number: })

@chaimleib Parser CAN be confused, like here:

function f({ a: MyClass; })

Is this destructing or typing?

@chaimleib
Copy link

No, in my syntax, that would be function f({ a: MyClass: }), with a colon, not a semicolon. I've never seen semicolons inside of curly braces like that before, what does it do?

@chaimleib
Copy link

Try Flow gives parse errors on both

function f({ a: MyClass: }) {}

and

function f({ a: MyClass; }) {}

Both syntaxes seem to be available. But between these, I still prefer the one with colons.

@lll000111
Copy link
Contributor

lll000111 commented Mar 9, 2019

@chaimleib AFAICS types for function parameter destructuring already work just fine. if you use the appropriate Flow syntax. This thread is about variable assignment, where there is no solution.

The difference is what you show is only destructuring of an existing object. The problem is creating new variables from an existing object, i.e. type-annotating those new variables.

That said, looking at the original very first comment at the top, it has code that actually works just fine now. The function parameter destructuring of the array parameter can be type-annotated. Here is the variable-creating assignment from that comment.

....

Aaaactually, even destructuring object assignment with new variable creation (rebinding destructuring) works now?

So... what exactly is the issue now? And I'm asking for "I don't like that particular syntax" answers, it's a given that a number of people won't like whatever is chosen.

@chaimleib
Copy link

chaimleib commented Mar 10, 2019

So... what exactly is the issue now? And I'm asking for "I don't like that particular syntax" answers, it's a given that a number of people won't like whatever is chosen.

@lll000111 Currently, we have to do things like this to get object destructuring with type annotations:

const MyComponent = ({
  aProp,
  someOther,
  zethIs,
  onClick,
  onSubmit
}: {
  aProp: string,
  someOther: number,
  zethIs: Object,
  onClick: (event: Event) => void,
  onSubmit: (event: Event) => void,
}) =>

If we need to rename, add or remove a parameter, we have to modify two different lines to make sure the type annotations and the destructured fields stay in sync. And this line-doubling gets really annoying when you are destructuring lots of fields at once.

We can't combine the destructuring with the annotations to get the effect we want:

const MyComponent = ({
  aProp: string, // actually, would rename aProp to string
  someOther: number,
  zethIs: Object,
  onClick: (event: Event) => void, // invalid syntax
  onSubmit: (event: Event) => void,
}) =>

So we are discussing alternate syntaxes that would let us stop repeating ourselves. My syntax proposal follows a nice summary post of other syntaxes above.

@dimaqq
Copy link

dimaqq commented Jul 5, 2019

The UX is pretty bad, it's like either flow or ES6..ES2019 😞
Please think of something, o enlightened ones! 🙏

@sheerlox
Copy link

sheerlox commented Jul 17, 2019

Actually we can't even rename a variable during destructuring, which is not ok at all:

const { SecretString: dbSecret }: { dbSecret: string } = await secretsManager.getSecretValue()

Gives the following error:

property `SecretString` is missing in  object type [1].

I can't believe this issue has been opened more than 4 years ago and is still not addressed as it should.

@acusti
Copy link
Contributor

acusti commented Jul 17, 2019

@SherloxFR You can rename a variable during destructuring. That’s the primary reason why this issue hasn’t yet been addressed: to avoid conflicting with that syntax. In your example, you need to annotate the original object that you get from secretsManager.getSecretValue(), not the destructured and renamed variable:

const { SecretString: dbSecret }: { SecretString: string } = await secretsManager.getSecretValue()

@ancms2600
Copy link

ancms2600 commented Aug 8, 2019

one could utilize cast-through-any here (unsafe!)

    for (const [,{name, value}]/*:[string,{name:string,value:mixed}]*/ of (Object.entries(data)/*:any*/)) {
        const _name = name.replace(/\[]$/, '');

see also: https://stackoverflow.com/a/45068255

or, as in my case above, you could realize:

  1. if you aren't using both, use Object.keys or Object.values instead.
  2. if you are walking an object's keys, you can only expect each value type to be predictable if the object is a Map (ie. { [string]: number }. Otherwise, we must expect the value type to be different in each item of the resulting array! Which means we must handle it at runtime with validation, casting, or switching:
    for (const item of Object.values(data)) {
        const name = (''+ _.get(item, 'name')).replace(/\[]$/, '');

@SamChou19815
Copy link
Contributor

We banned this completely last year, since it's confusing.

@SamChou19815 SamChou19815 closed this as not planned Won't fix, can't repro, duplicate, stale Jan 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests