microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.48k stars 12.43k forks source link

Easier destructuring with type annotations on binding patterns #29526

Open Richiban opened 5 years ago

Richiban commented 5 years ago

Search Terms

type inference destructuring syntax conflict

Suggestion

It's currently not possible to destructure a value in Typescript and provide types for each of the values due to the syntax clash with destructuring and renaming at the same time. You can see exactly this issue in the Typescript FAQs at: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-cant-i-use-x-in-the-destructuring-function-f-x-number------

This is frustrating when programming in React where it's very common to see this pattern:

const MyComponent = ({ a, b }) => {
    // ...
}

But in Typescript a and b are untyped (inferred to have type any) and type annotation must be added (either to aid in type safety or to avoid compiler errors, depending on the state of the user's strict flags). To add the correct type annotation it feels natural to write:

const MyComponent = ({ a : string, b : number }) => {
    // ...
}

but that's not what the user thinks due to the aforementioned syntax clash. The only valid syntax in Typescript is actually this:

const MyComponent = ({ a, b } : { a : string, b : number }) => {
    // ...
}

Which is very strange to write and difficult to read when the object has more than two parameters or the parameters have longer names. Also the value names have been duplicated -- once in the destructuring and once in the type annotation.

I suggest we allow some other symbol (my current thinking is a double colon) to make the syntax unambiguous in this specific scenario:

const MyComponent = ({ a :: string, b :: number }) => {
    // ...
}

Although this is really the only place it would be used, for the sake of consistency, I think is should be allowed everywhere:

const a :: string = "";
const b :: number = 1;

Use Cases

It would allow for type-safe destructuring of values where the type cannot be inferred by the compiler (such as function parameters).

Examples

A good example of the sort of React components I'm talking about (and one of the first Google results for React Functional Components) can be found at https://hackernoon.com/react-stateless-functional-components-nine-wins-you-might-have-overlooked-997b0d933dbc. We can use my proposed syntax in the functional component definition:

import React from 'react'

const HelloWorld = ({name :: string}) => {
    const sayHi = (event) => {
        alert(`Hi ${name}`)
    }

    return (
        <div>
            <a href="#"
               onclick={sayHi}>Say Hi</a>
        </div>
    )
}

Checklist

My suggestion meets these guidelines:

j-oliveras commented 5 years ago

Duplicate/related to #7576, #29019

dragomirtitian commented 5 years ago

I know expression level syntax is a no-no, but how about:

const MyComponent = ({ ... } : { a : string, b : number }) => {
    // ...
}

With the ... meaning auto-spread all the rest of the properties in the object.

You can still have a traditional binding list if you want to remap any names:

const MyComponent = ({ a:aa, ... } : { a : string, b : number }) => {

}

There is a reason this issue keeps poping up. The current solution is painful.

RyanCavanaugh commented 5 years ago

I agree it's a duplicate but it really does suck. We should try again.

olmobrutall commented 5 years ago

With React hooks just released, class components are being... slowly deprecated in favor of functional components all the way.

This feature is now more important than ever.

The :: syntax sounds good to me.

scottmas commented 5 years ago

There are indeed multiple duplicates of this request. Heavyweights of the JS ecosystem have said they want it. Random dudes such as myself want it. Unscientific polls show that a large majority of JS developers use argument object destructuring (https://twitter.com/rauschma/status/987471929585143809). It seems to me that the time has come for some solution to be made :)

FWIW, the double colon syntax seems good to me.

TazmanianD commented 5 years ago

Yeah, this seems like the 3rd iteration of this issue with the previous two just being closed as being too complicated to implement but I suspect that people are going to continue to ask for it and I'll add my voice to the list. This is something that's actually making it harder for me to convince my teammates to switch to TypeScript because this type of destructuring in function calls is pretty common in our existing code.

gynet commented 5 years ago

The current syntax for this scenario does look bad... I wish ES6 could choose different syntax for renaming, instead of the colon, e.g 'as'; the 'as' keyword is more meaningful for renaming purpose :)

Anyway, although the proposal of double colon syntax does not look bad, it could ergonomically cause troubles for developers to understand what does it mean since people get used to using a single colon as the type annotation. I would prefer another way to address the problem. Actually, I like dragomirtitian's proposal better.

Richiban commented 5 years ago

While I also think @dragomirtitian 's solution is a reasonable one, I'd like something more in keeping with Typescript's philosophy of not introducing any new syntax other than type annotations. one of the reasons for Typescript's success has been its mantra of "It's just Javascript, but with types". It's being able to look at a declaration and immediately parse out what's TS and what's JS:

//          The Javascript bit
//               --------
const myFunction({ a, b } : SomeType) { ... }
//                        ----------
//                     The Typescript bit

If TS starts introducing its own expression / destructuring syntax I fear that starts us down a slope of TS being its own language, rather than a type system for JS.

dragomirtitian commented 5 years ago

Actually the more I think about :: the more I like it 😁, but with a slightly different meaning. Let's not consider :: as a new way to introduce a type annotation, but rather an empty rename.

// rename, no type annotations
const HelloWorld = ({name : newName }) => { 

}
// rename, with annotation 
const HelloWorld = ({name : newName : string }) => { 

}

// empty rename, with annotation 
const HelloWorld = ({name :: string }) => { 

}
// empty rename, with annotation, and default
const HelloWorld = ({name :: string = "default" }) => { 

}

I personally think it flows nicely. The first : always introduces a name binding, which can be empty, the second : always introduces a type annotation, and since a new : not valid here in ES it would not be ambiguous.

Would work decently with objects too:

type FooBar = { foo: string; bar: number }
const HelloWorld = ({ a, b:{ foo: fooLocal, bar: barLocal }: FooBar  }) => {

}

const HelloWorld = ({ a, b::FooBar  }) => {

}

const HelloWorld = ({ 
  a: number, 
  b: { 
    foo:fooLocal:string, 
    bar:barLocal:string
  }
}) => {

}
Richiban commented 5 years ago

@dragomirtitian

Let's not consider :: as a new way to introduce a type annotation, but rather an empty rename.

Works for me! πŸ‘

gynet commented 5 years ago

@dragomirtitian Good point!

danm commented 4 years ago

Were angle brackets considered? https://github.com/microsoft/TypeScript/issues/34748

fr0 commented 4 years ago

Has any progress been made on this? The current solution is not good since it is not DRY, e.g. ({foo, bar, baz}: {foo: string, bar: number, baz: boolean}) has a lot of duplicated information.

danm commented 4 years ago

DRY?

On 9 Dec 2019, at 21:03, Chris Frolik notifications@github.com wrote:

Has any progress been made on this? The current solution is not good since it is not DRY, e.g. ({foo, bar, baz}): {foo: string, bar: number, baz: boolean}) has a lot of duplicated information.

β€” You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/microsoft/TypeScript/issues/29526?email_source=notifications&email_token=ABQZHXW5ME4SAYR634QZ7STQX2XCVA5CNFSM4GRUGZRKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEGKWCEA#issuecomment-563437840, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABQZHXWA7OFF26Q7FGJFUTLQX2XCVANCNFSM4GRUGZRA.

fr0 commented 4 years ago

DRY?

https://en.wikipedia.org/wiki/Don%27t_repeat_yourself

tienpv222 commented 4 years ago

Not really related, but it'd be much more easier if object destructuring alias uses as instead of :, just like how import does.

dragomirtitian commented 4 years ago

@pynnl that ship sailed a long time ago unfortunately.

VanCoding commented 4 years ago

TypeScript really needs this! I can't think of a more annoying thing than this. Any syntax is okay, as long as it gets implemented. Please. This has been discussed for such a long time. There are a lot of options that don't collide with the ES spec.

mathieucaroff commented 4 years ago

I'd like to propose a variant of the multi-colon syntax:

{ x:: number }
{ x:: number = 123 }
{ xin: xout:: number }
{ xin: xout:: number = 123 }

The idea is to keep the double colon, even when remapping the name, so that the type is always identified by the colon-block (or colon-square) operator ::. The point of this subtlety is to make it easy for the reader to visually parse where the name ends and where the type begins. This is achieved through the use of a different operators rather than being purely syntax-based.

mathieucaroff commented 4 years ago

Here is another idea of syntax, which resembles the "Auto spread" proposition of @dragomirtitian: {...}: { x: number }. It actually extends the syntax of "Interface spread to variable" proposed by @antanas-arvasevicius in #14856, adding support for renaming variables:

let ...{ x: number } = v // x is now available
let ...{ x: number = 123 } = v
let ...{ xin xout: number } = v // xout is now available
let ...{ xin xout: number = 123 } = v
let ...{ left { data dataLeft: string }, right { data dataRight: string } } = v
// dataLeft and dataRight are now avaliable

Example with a function, a let together with the rest parameter:

const formatSidedData = (...{ left { data a: string }, right { data b: string } }) => `${a}<->${b}`

let ...{ person { name: string, age: number }, ...metadata: Record<string, string> } = getInfo()

Note: the ellipsis ... "type annotated destructuring-declaration" can only be used at the top level, either in a const, let or var list of variable declarations, or among the arguments of a function. I suggest a standard destructuring-declaration should not be allowed to use ... inside it to create a zone of "type annotated destructuring-declaration".

One of the unique nice properties only found in @dragomirtitian proposal, that of @antanas-arvasevicius and this one is that in the simple case --without renaming--, the syntax used is just the usual syntax for interfaces. This covers the use case of easily transforming a type annotated destructuring into an interface (by unedited copy-paste). This also applies to copy-pasting an interface to a "type annotated destructuring".

Below is an example to clarify.

Let there be a function magnitude, computing the length of a vector. Using my "Extended interface spread to variable" syntax, we can write this:

function magnitude(...{ x: number, y: number }) {
  return (x ** 2 + y ** 2) ** 0.5;
}

We then realize we want to name the interface. It is easy to just copy-paste it to a named type:

interface Point { x: number, y: number }

function magnitude({ x, y }: Point) {
  return (x ** 2 + y ** 2) ** 0.5;
}

If you use Prettier, it will automatically reformat the interface to this:

interface Point {
  x: number;
  y: number;
}

Doing the opposite change (going back) is also easy, though you may need to remove some semicolons and add commas:

function magnitude(...{ x: number, y: number }) {
  return (x ** 2 + y ** 2) ** 0.5;
}

This syntax can be used without any issue with the traditional ellipses for varargs functions:

function join(...{ begin: string, sep: string, end: string }, ...parts: string[]) {
    return begin + parts.join(sep) + end;
}
mathieucaroff commented 4 years ago

Content

Vocabulary

All the issues I could find

Note:

Reason(s) to decline the feature request (back in 2016)

@mhegazy

  • ultimately, every new syntax added is added complexity, for the user to learn and recognize, and for the compiler code base to maintain, so usually we try to limit complexity unless the value warrants it. and for destructuring, the syntax is already complex to start with, and we did not want to add yet another layer on top.

-- seconded by @DanielRosenwasser

Proposition(s) with unusable syntax

Here are a few propositions I found whose syntax cannot be used.

{ x: number }
{ xin: number = 123 }
{ xin as xout: number }
{ xin as xout: number = 123 }

The first two lines are valid JS and TS. This syntax is incompatible.

Formatting variants among valid syntaxes

(This is about formatting recommendations and auto-formatting)

{ x:: number }
{ xin:: number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }
{ x :: number }
{ xin :: number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }
{ x::number }
{ xin::number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }
{ x::number }
{ xin::number = 123 }
{ xin:xout:number }
{ xin:xout:number = 123 }

Typescript 3.9.2 (current situation)

{ x }: { x: number }
{ xin = 123 }: { xin: number }
{ xin: xout }: { xin: number }
{ xin: xout = 123 }: { xin: number }
interface Prop { x: number }
{ x }: Prop

interface Prop { x: number }
{ xin = 123 }: Prop

interface Prop { xin: number }
{ xin: xout }: Prop

interface Prop { xin: number }
{ xin: xout = 123 }: Prop

All valid syntax propositions I could find

All these are proposals for "Complete type annotated destructuring".

(order attempts to group propositions sharing similar traits)

{ x as number }
{ xin: xout as number = 123 }
{ <number>x }
{ <number>xin: xout = 123 }
{ x<number> }
{ xin: xout<number> = 123 }
{...}: { x: number }
{ xin = 123 }: { xin: number? }
{ xin: xout }: { xin: number? }
{ xin: xout = 123 }: { xin: number? }
...{ x: number }
...{ xin xout: number = 123 }
{ x:: number}
{ xin: xout: number = 123 }
{ x:: number }
{ x:: number = 123 }
{ xin: xout:: number }
{ xin: xout:: number = 123 }
{ x number }
{ xin: xout number = 123 }
{ x number }
{ xin number: xout = 123 }
{ x: (number) }
{ xin: (xout: number) = 123 }

Pros and cons of each syntax

as_type

{ xin: xout as number = 123 }

Cons

Pros

angle_bracket_before

{ <number>xin: xout = 123 }

Cons

Pros

angle_bracket_after_remap

Cons

Pros

auto_spread

{ xin: xout = 123 }: { xin: number? }, but {...}: { x: number } in the simple case

Cons

Pros

spread_with_remap

Cons

Pros

double_colon

{ xin: xout: number = 123 }

Cons

Pros

@mhegazy

  • there is value in consistency that types always come after a :. adding another operator would increase complexity.
  • we have the : domain almost officially reserved for types.

colon_square

{ xin: xout:: number = 123 }

(disallowing spaces between the two colon)

Cons

Pros

concatenation_after_remap

{ xin: xout number = 123 }

Cons

Pros

concatenation_before_colon

{ xin number: xout = 123 }

Cons

Pros

parentheses

{ xin: (xout: number) = 123 }

Cons

Pros

mathieucaroff commented 4 years ago

Here is my order of preference on these 10 alternatives, from most wanted to least wanted:

My "approval threshold" separates the options I would vote for from those I would not in the event of a vote using approval voting.

I'm interested in your own preferences, especially if you believe debating can lead to a consensus. In case debating fails to lead to a consensus, or debating is not attempted, I believe a vote (using approval voting) would still be a satisfying outcome.

fleck commented 4 years ago

Would love to be able to type named parameters without having to duplicate the name of the parameter! In React code bases basically every component has a unique interface so you end up doing a lot of extra typing. I like the double colon, anybody who works in the TypeScript codebase have a feeling for how hard this would be to implement?

eezstreet commented 3 years ago

I think as_type is the best proposal here, as the "as" keyword is not used in structuring/destructuring, and doesn't produce ambiguous syntax. Not to mention, it produces a lot more readable code.

imhaeussler commented 3 years ago

Search Terms

type inference destructuring syntax conflict

Suggestion

It's currently not possible to destructure a value in Typescript and provide types for each of the values due to the syntax clash with destructuring and renaming at the same time. You can see exactly this issue in the Typescript FAQs at: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-cant-i-use-x-in-the-destructuring-function-f-x-number------

This is frustrating when programming in React where it's very common to see this pattern:

const MyComponent = ({ a, b }) => {
    // ...
}

But in Typescript a and b are untyped (inferred to have type any) and type annotation must be added (either to aid in type safety or to avoid compiler errors, depending on the state of the user's strict flags). To add the correct type annotation it feels natural to write:

const MyComponent = ({ a : string, b : number }) => {
    // ...
}

but that's not what the user thinks due to the aforementioned syntax clash. The only valid syntax in Typescript is actually this:

const MyComponent = ({ a, b } : { a : string, b : number }) => {
    // ...
}

Which is very strange to write and difficult to read when the object has more than two parameters or the parameters have longer names. Also the value names have been duplicated -- once in the destructuring and once in the type annotation.

I suggest we allow some other symbol (my current thinking is a double colon) to make the syntax unambiguous in this specific scenario:

const MyComponent = ({ a :: string, b :: number }) => {
  // ...
}

Although this is really the only place it would be used, for the sake of consistency, I think is should be allowed everywhere:

const a :: string = "";
const b :: number = 1;

Use Cases

It would allow for type-safe destructuring of values where the type cannot be inferred by the compiler (such as function parameters).

Examples

A good example of the sort of React components I'm talking about (and one of the first Google results for React Functional Components) can be found at https://hackernoon.com/react-stateless-functional-components-nine-wins-you-might-have-overlooked-997b0d933dbc. We can use my proposed syntax in the functional component definition:

import React from 'react'

const HelloWorld = ({name :: string}) => {
  const sayHi = (event) => {
      alert(`Hi ${name}`)
  }

  return (
      <div>
          <a href="#"
             onclick={sayHi}>Say Hi</a>
      </div>
  )
}

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • [x] This feature would agree with the rest of TypeScript's Design Goals.

I vote for @Richiban's double colon syntax

randfur commented 3 years ago

Would auto_spread work with interface types e.g. {...}: InterfaceType? Seems a bit much to have a function's local variables be defined arbitrarily far away.

mathieucaroff commented 3 years ago

As I imagine it, {...}: InterfaceType would be an invalid syntax. auto_spread would be a rigid syntax with four braces in it: {...}: {}.

mathieucaroff commented 3 years ago

angle_bracket_after_remap

{ x<number> }
{ xin: xout<number> = 123 }
mathieucaroff commented 3 years ago

angle_bracket_before

{ <number>x }
{ <number>xin: xout = 123 }
mathieucaroff commented 3 years ago

as_type

{ x as number }
{ xin: xout as number = 123 }
mathieucaroff commented 3 years ago

auto_spread

{...}: { x: number }
{ xin: xout = 123 }: { xin: number? }
mathieucaroff commented 3 years ago

double_colon

{ x:: number}
{ xin: xout: number = 123 }
mathieucaroff commented 3 years ago

colon_square

{ x:: number }
{ xin: xout:: number = 123 }
mathieucaroff commented 3 years ago

concatenation_after_remap

{ x number }
{ xin: xout number = 123 }
mathieucaroff commented 3 years ago

concatenation_before_colon

{ x number }
{ xin number: xout = 123 }
mathieucaroff commented 3 years ago

parentheses

{ x: (number) }
{ xin: (xout: number) = 123 }
mathieucaroff commented 3 years ago

spread_with_remap

...{ x: number }
...{ xin xout: number = 123 }
mathieucaroff commented 3 years ago

I've posted each of the 10 valid syntax proposals in separate messages. In 80 days (two months and month and 19 days), on Sunday the 6th of June, I will count and post in this issue the number of THUMBS_UP reaction on each alternative.


/!\ Also check out the late contesting proposals linked below /!\

rettgerst commented 3 years ago

I love typescript and I love react, but trying to write my functional react components with typed props is currently the biggest pain point. This problem needs a solution.

A lot of the proposals above have flaws which I believe is a result of trying to combine the features of interfaces (name, type) and destructuring (name, remap, default) into syntax as terse as possible.

Most react developers don't particularly care that our props come to us as an object; that's just how the react api gives them to us. We don't need all of the features of objects, and we only use destructuring syntax in order to emulate named parameters.

Given that, in order to solve this problem, I think we could find a more elegant solution if we think about this a little differently - we don't want to extend destructuring syntax, we want (something which emulates) named parameters. Meaning an alternative function signature syntax would be a better solution for react developers, especially if it doesn't need to support remapping.


One approach could be borrowing the syntax from declaring class members:

function FooBar({ foo: string; bar: string = 'baz'; }) {
    return <span>{foo} {bar}<span>;
}

This may look similar to existing destructuring syntax but is subtly different, in that it uses semicolons to delimit fields instead of commas - a win-win as it should be instantly familiar to developers, as well as sufficiently unique enough to not cause a syntax collision.


A more radical alternative could be the addition of a named keyword to the function signature, which would imply that the function accepts only one argument, which is an object with the specified fields, in the same way that the async keyword implies that the return value will always be wrapped in a promise.

function FooBar named (foo: string, bar: string = 'baz') {
    return <span>{foo} {bar}<span>;
}

These would both likely be considered out-of-scope and too big of a departure from existing ES syntax, but this is a common enough use case that I think some special consideration is warranted.

7ze commented 3 years ago

Any update on this?

VanCoding commented 3 years ago

After giving it some thought, I think auto spread would be a pretty good solution to the problem! When looking at the use case, it makes perfect sense. When you don't want specify the type, but infer it from the spreading, then you always want to access all of the properties of the resulting type. Then, why not just autospread them all anyway. This way we wouldn't even have to introduce a new method to "construct" types.

I would be fine with other solutions as well, though. I just think it would be a quick, easy and reasonable solution to this problem.

mjwach commented 3 years ago

parentheses

{ x: (number) }
{ xin: (xout: number) = 123 }

I quite like this one in the simpler case. But why aren't the parentheses around "number" in the renaming case too? Like this:

{ xin: xout: (number) = 123 }

Would that clash with something else somehow? I want it that way, anyhow. It feels unnatural to me to put parentheses around the new name, whereas putting them around the type feels like a perfectly normal way to refer to the type. Because, I mean, it IS that. You can do this, after all:

type NumberOrString = number | string;
let n: (((((((((NumberOrString))))))))) = x < 3 ? 3 : "three";

Of course, the "xin: xout" part of it would look weird to a Typescript programmer, but whatever - that's just a consequence of choosing the colon as a type annotation operator. The colon is used for renaming and type annotation both, and solving that problem is outside the scope of this issue. So I think we should accept the weirdness of "xin: xout: (number)". Unless it causes some other unacceptable problem I haven't thought of, I mean.

Come on, vote for this one! Unless you like something else better. (I voted for double_colon above as well!)

mathieucaroff commented 3 years ago

@mjwach, I'm adding your proposal to the contest.

mathieucaroff commented 3 years ago

parentheses-around-type

{ x: (number) }
{ xin: xout: (number) = 123 }
charles-allen commented 3 years ago

Think of the n00bs

I already spend my day trying to coax JS devs to adopt TS. IMO, some of these syntaxes are quite obstructive to TS adoption; it's very painful to teach ": here, but :: there", or ": here, but <...> there". These edge cases might be acceptable for someone who's already a TS professional, but I wouldn't wish it on a new learner. Please think of the n00bs!

Optional Properties

I think there should be consideration for how the proposals deal with optional properties (which surely isn't so rare). Here's my best guesses (and opinion):

// angle_bracket_after_remap => 🟑 doesn't look like TS
{ x<number?> }
{ xin: xout<number?> = 123 }

// angle_bracket_before => 🟑 doesn't look like TS
{ <number?>x }
{ <number?>xin: xout = 123 }

// as_type => 🟑 doesn't look like TS
{ x as number? }
{ xin: xout as number? = 123 }

// auto_spread => βœ… intuitive
{...}: { x?: number }
{ xin: xout = 123 }: { xin?: number }

// double_colon => ❌ slightly ambiguous
{ x?:: number}
{ xin: xout?: number = 123 }
// ...or...
{ x:?: number}
{ xin: xout?: number = 123 }

// colon_square => 🟑 more new syntax
{ x?:: number }
{ xin: xout?:: number = 123 }

// concatenation_after_remap => 🟑 doesn't look like TS
{ x number? }
{ xin: xout number? = 123 }

// concatenation_before_colon => ❌ confusing
{ x number? }
{ xin number?: xout = 123 }

// parentheses => ❌ ambiguous
{ x: (number?) }
{ xin: (xout: number?) = 123 }
// ...or...
{ x?: (number) }
{ xin: (xout?: number) = 123 }

// parentheses-around-type => ❌ ambiguous
{ x: (number?) }
{ xin: xout: (number?) = 123 }
// ...or...
{ x?: (number) }
{ xin: xout?: (number) = 123 }

// spread_with_remap => βœ… intuitive
...{ x?: number }
...{ xin xout?: number = 123 }

Pre-Existing Types

Are pre-existing types out of scope (for now)? Either way I think the syntax choice should be future-proof:

// ❌ Not sure how...
// angle_bracket_after_remap
// angle_bracket_before => doesn't look like TS
// as_type => doesn't look like TS
// double_colon => awkward
// colon_square => awkward
// concatenation_after_remap => doesn't look like TS
// concatenation_before_colon => confusing
// parentheses => ambiguous
// parentheses-around-type => ambiguous
???

// auto_spread => βœ… intuitive
{...}: MyInterface
{ xin: xout = 123 }: MyInterface

// spread_with_remap => 🟑 not sure how to rename
...MyInterface
??? for renaming

Partial Renames

Finally, if pre-existing types are supported, the auto_spread syntax looks capable of partial renames:

// auto_spread => βœ… awesome
{...}: MyInterface
{ xin: xout = 123 }: MyInterface
{ xin: xout = 123, ... }: MyInterface
{ xin: xout = 123, ...rest }: MyInterface
richardaum commented 3 years ago

Isn't there any external tool/plugin/transformer which enables any of these suggestions?

soylomass commented 3 years ago

+1 for the spread_with_remap option

I like that I can imagine what it does at first sight, unlike the other options (with the exception of auto_spread, which is similar, but more verbose)

mathieucaroff commented 3 years ago

About 80 days ago, I said:

I've posted each of the 10 valid syntax proposals in separate messages. In 80 days (two months and month and 19 days), on Sunday the 6th of June, I will count and post in this issue the number of THUMBS_UP reaction on each alternative.

Here is are the counts, in alphabetical order:

2 angle_bracket_after_remap
0 angle_bracket_before
10 as_type
9 auto_spread
18 colon_square
0 concatenation_after_remap
0 concatenation_before_colon
13 double_colon
1 parentheses
3 parentheses_around_type
6 spread_with_remap

The top 4 proposals are the following:

Here are their descriptions:

colon_square (18)

{ x:: number }
{ x:: number = 123 }
{ xin: xout:: number }
{ xin: xout:: number = 123 }

double_colon (13)

{ x:: number }
{ x:: number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }

as_type (10)
{ x as number }
{ x as number = 123 }
{ xin: xout as number }
{ xin: xout as number = 123 }

auto_spread (9)
{...}: { x: number }
{ x = 123 }: { x: number }
{ xin: xout }: { xin: number }
{ xin: xout = 123 }: { xin: number }
soylomass commented 3 years ago

I understand, however, in the case that vote is not binding, I want to emphasize that @charles-allen comment should be taken into account. intuitiveness is important, and if we can use syntax we are already used to instead of having to learn a new one (double colon), it would be better.

Kingwl commented 3 years ago

IMO