Skip to content

Named Type Arguments & Partial Type Argument Inference - #23696

Closed
Wesley Wigham (weswigham) wants to merge 14 commits into
microsoft:masterfrom
weswigham:named-type-arguments-beta
Closed

Named Type Arguments & Partial Type Argument Inference#23696
Wesley Wigham (weswigham) wants to merge 14 commits into
microsoft:masterfrom
weswigham:named-type-arguments-beta

Conversation

@weswigham

Copy link
Copy Markdown
Member

With this PR, we allow named type arguments, of the form Identifier = Type anywhere a type argument is expected. This looks like so:

const instance1 = new Foo<T = number, U = string>(0, "");
const result1 = foo<T = number, U = string>(0, "");
const tagged1 = tag<T = number, U = string>`tags ${12} ${""}`;
const jsx1 = <Component<T = number, U = string> x={12} y="" cb={props => void (props.x.toFixed() + props.y.toUpperCase())} />;
type A = Foo<T = number, U = string>;

These arguments do not need to come in any particular order, but must come after all positional type arguments. When you've used a named type argument, you may elide any other type arguments you wish. When you do so, the missing arguments will be inferred (and will not cause an error to be issued even if they do not have a default)

const instance4 = new Foo<U = string>(0, "");
const result4 = foo<U = string>(0, "");
const tagged4 = tag<U = string>`tags ${12} ${""}`;
const jsx4 = <Component<U = string> x={12} y="" cb={props => void (props.x.toFixed() + props.y.toUpperCase())} />;

(This is not valid for typespace references such as type references - those still have strict arity checks as there is no inference source)

Fixes #22631
Fixes #20122
Fixes #10571

🚲 🏠: Should named type argument assignments use a = (as they do in this PR now), or a : (as #22631 proposed)?

@kpdonn

Kevin Donnelly (kpdonn) commented Apr 26, 2018

Copy link
Copy Markdown
Contributor

First question

(This is not valid for typespace references such as type references - those still have strict arity checks as there is no inference source)

I assume what you mean here is that something like the following isn't allowed:

type Example<T, U> = { t: T, u: U }
type NotAllowed = Example<U = string> // not allowed because nowhere to infer T from

Would that be allowed though if the missing type arguments already have defaults specified? Example:

type ExampleDefaults<T = any, U = any> = { t: T, u: U }
type IsThisAllowed = ExampleDefaults<U = string> // allowed?

Second question

When you do so, the missing arguments will be inferred (and will not cause an error to be issued even if they do not have a default)

Does that mean the skipped type arguments will still be inferred even if they have a default? Example:

declare function test<A = any, B = any>(arg: { a?: A, b?: B }): {a: A, b: B}

const r1 = test<string>({ b: "foo" })
const r2 = test<A = string>({ b: "foo" })

What are the types of r1 and r2 in those cases? Today the type for r1 is unfortunately { a: string, b: any} because explicitly passing one type argument causes type inference to be skipped for all of them.

@weswigham

Copy link
Copy Markdown
Member Author

Would that be allowed though if the missing type arguments already have defaults specified?

Yes, that should be fine. Though you've reminded me of a class of error I probably need to add a test and error for:

type Two<A, B = number> = [A, B]
type Bug = Two<B = string> // should error, A was not provided

Does that mean the skipped type arguments will still be inferred even if they have a default?

Yes, and if inference fails (ie, there are no inference sites) it still falls back to the default (which is always the case when inferring normal type parameters).

As for your example, in the first call your result is { a: string, b: any }, as no inference currently takes place unless there's a named type argument (though we may change that). In the second, the result should be { a: any, b: string}, as there's no inference drawn for A, so it falls back to its default.

@kpdonn

Kevin Donnelly (kpdonn) commented Apr 26, 2018