Skip to content

Include type parameter defaults in contextual typing #50994

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

Merged
merged 4 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27532,9 +27532,10 @@ namespace ts {
function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags: ContextFlags | undefined): Type | undefined {
if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) {
const inferenceContext = getInferenceContext(node);
// If no inferences have been made, nothing is gained from instantiating as type parameters
// would just be replaced with their defaults similar to the apparent type.
if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidates)) {
// If no inferences have been made, and none of the type parameters for which we are inferring
// specify default types, nothing is gained from instantiating as type parameters would just be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this part of the comment here still relevant? with the code change in this PR this now reads weirdly to me, or there is some additional context missing here - like what kind of default type parameters should be ignored and what kind of default parameters might be useful. Or perhaps this comment was never about defaults and it should mention base constraints?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, that should say constraints, not defaults.

// replaced with their constraints similar to the apparent type.
if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also have an additional fix touching this very line. Since this is being touched now, perhaps you could take a look if it looks any good? :p

https://github.com/microsoft/TypeScript/pull/48838/files#diff-d9ab6589e714c71e657f601cf30ff51dfc607fc98419bf72e04f6b0fa92cc4b8R27537

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure what that fix is trying to accomplish. Either way I don't think it is related to the issue being solved in this PR, so I think it's better to keep it separate.

// For contextual signatures we incorporate all inferences made so far, e.g. from return
// types as well as arguments to the left in a function call.
return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
Expand Down Expand Up @@ -35145,6 +35146,10 @@ namespace ts {
return !!(info.candidates || info.contraCandidates);
}

function hasInferenceCandidatesOrDefault(info: InferenceInfo) {
return !!(info.candidates || info.contraCandidates || hasTypeParameterDefault(info.typeParameter));
}

function hasOverlappingInferences(a: InferenceInfo[], b: InferenceInfo[]) {
for (let i = 0; i < a.length; i++) {
if (hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i])) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
=== tests/cases/compiler/contextualSignatureConditionalTypeInstantiationUsingDefault.ts ===
// repro #46310

export interface TypegenDisabled {
>TypegenDisabled : Symbol(TypegenDisabled, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 0, 0))

"@@xstate/typegen": false;
>"@@xstate/typegen" : Symbol(TypegenDisabled["@@xstate/typegen"], Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 2, 34))
}
export interface TypegenEnabled {
>TypegenEnabled : Symbol(TypegenEnabled, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 4, 1))

"@@xstate/typegen": true;
>"@@xstate/typegen" : Symbol(TypegenEnabled["@@xstate/typegen"], Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 5, 33))
}

type ActionFunction<TEvent extends { type: string }> = (event: TEvent) => void;
>ActionFunction : Symbol(ActionFunction, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 7, 1))
>TEvent : Symbol(TEvent, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 9, 20))
>type : Symbol(type, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 9, 36))
>event : Symbol(event, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 9, 56))
>TEvent : Symbol(TEvent, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 9, 20))

declare function createMachine<
>createMachine : Symbol(createMachine, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 9, 79))

TTypesMeta extends TypegenEnabled | TypegenDisabled = TypegenDisabled
>TTypesMeta : Symbol(TTypesMeta, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 11, 31))
>TypegenEnabled : Symbol(TypegenEnabled, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 4, 1))
>TypegenDisabled : Symbol(TypegenDisabled, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 0, 0))
>TypegenDisabled : Symbol(TypegenDisabled, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 0, 0))

>(
config: {
>config : Symbol(config, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 13, 2))

types?: TTypesMeta;
>types : Symbol(types, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 14, 11))
>TTypesMeta : Symbol(TTypesMeta, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 11, 31))

},
implementations: TTypesMeta extends TypegenEnabled
>implementations : Symbol(implementations, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 16, 4))
>TTypesMeta : Symbol(TTypesMeta, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 11, 31))
>TypegenEnabled : Symbol(TypegenEnabled, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 4, 1))

? ActionFunction<{ type: "test" }>
>ActionFunction : Symbol(ActionFunction, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 7, 1))
>type : Symbol(type, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 18, 22))

: ActionFunction<{ type: string }>
>ActionFunction : Symbol(ActionFunction, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 7, 1))
>type : Symbol(type, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 19, 22))

): void;

createMachine({}, (ev) => {
>createMachine : Symbol(createMachine, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 9, 79))
>ev : Symbol(ev, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 22, 19))

ev.type; // should be `string`
>ev.type : Symbol(type, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 19, 22))
>ev : Symbol(ev, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 22, 19))
>type : Symbol(type, Decl(contextualSignatureConditionalTypeInstantiationUsingDefault.ts, 19, 22))

});

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
=== tests/cases/compiler/contextualSignatureConditionalTypeInstantiationUsingDefault.ts ===
// repro #46310

export interface TypegenDisabled {
"@@xstate/typegen": false;
>"@@xstate/typegen" : false
>false : false
}
export interface TypegenEnabled {
"@@xstate/typegen": true;
>"@@xstate/typegen" : true
>true : true
}

type ActionFunction<TEvent extends { type: string }> = (event: TEvent) => void;
>ActionFunction : ActionFunction<TEvent>
>type : string
>event : TEvent

declare function createMachine<
>createMachine : <TTypesMeta extends TypegenDisabled | TypegenEnabled = TypegenDisabled>(config: { types?: TTypesMeta;}, implementations: TTypesMeta extends TypegenEnabled ? ActionFunction<{ type: "test";}> : ActionFunction<{ type: string;}>) => void

TTypesMeta extends TypegenEnabled | TypegenDisabled = TypegenDisabled
>(
config: {
>config : { types?: TTypesMeta | undefined; }

types?: TTypesMeta;
>types : TTypesMeta | undefined

},
implementations: TTypesMeta extends TypegenEnabled
>implementations : TTypesMeta extends TypegenEnabled ? ActionFunction<{ type: "test"; }> : ActionFunction<{ type: string; }>

? ActionFunction<{ type: "test" }>
>type : "test"

: ActionFunction<{ type: string }>
>type : string

): void;

createMachine({}, (ev) => {
>createMachine({}, (ev) => { ev.type; // should be `string`}) : void
>createMachine : <TTypesMeta extends TypegenDisabled | TypegenEnabled = TypegenDisabled>(config: { types?: TTypesMeta | undefined; }, implementations: TTypesMeta extends TypegenEnabled ? ActionFunction<{ type: "test"; }> : ActionFunction<{ type: string; }>) => void
>{} : {}
>(ev) => { ev.type; // should be `string`} : (ev: { type: string; }) => void
>ev : { type: string; }

ev.type; // should be `string`
>ev.type : string
>ev : { type: string; }
>type : string

});

21 changes: 21 additions & 0 deletions tests/baselines/reference/genericInferenceDefaultTypeParameter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//// [genericInferenceDefaultTypeParameter.ts]
// Repro from #50858

type Type = {
a: (e: string) => void;
b: (e: number) => void;
}

declare function f1<T extends keyof Type = "a">(props: Type[T]): void;

f1(event => { });
f1<"a">(event => { });
f1<"b">(event => { });


//// [genericInferenceDefaultTypeParameter.js]
"use strict";
// Repro from #50858
f1(function (event) { });
f1(function (event) { });
f1(function (event) { });
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
=== tests/cases/compiler/genericInferenceDefaultTypeParameter.ts ===
// Repro from #50858

type Type = {
>Type : Symbol(Type, Decl(genericInferenceDefaultTypeParameter.ts, 0, 0))

a: (e: string) => void;
>a : Symbol(a, Decl(genericInferenceDefaultTypeParameter.ts, 2, 13))
>e : Symbol(e, Decl(genericInferenceDefaultTypeParameter.ts, 3, 8))

b: (e: number) => void;
>b : Symbol(b, Decl(genericInferenceDefaultTypeParameter.ts, 3, 27))
>e : Symbol(e, Decl(genericInferenceDefaultTypeParameter.ts, 4, 8))
}

declare function f1<T extends keyof Type = "a">(props: Type[T]): void;
>f1 : Symbol(f1, Decl(genericInferenceDefaultTypeParameter.ts, 5, 1))
>T : Symbol(T, Decl(genericInferenceDefaultTypeParameter.ts, 7, 20))
>Type : Symbol(Type, Decl(genericInferenceDefaultTypeParameter.ts, 0, 0))
>props : Symbol(props, Decl(genericInferenceDefaultTypeParameter.ts, 7, 48))
>Type : Symbol(Type, Decl(genericInferenceDefaultTypeParameter.ts, 0, 0))
>T : Symbol(T, Decl(genericInferenceDefaultTypeParameter.ts, 7, 20))

f1(event => { });
>f1 : Symbol(f1, Decl(genericInferenceDefaultTypeParameter.ts, 5, 1))
>event : Symbol(event, Decl(genericInferenceDefaultTypeParameter.ts, 9, 3))

f1<"a">(event => { });
>f1 : Symbol(f1, Decl(genericInferenceDefaultTypeParameter.ts, 5, 1))
>event : Symbol(event, Decl(genericInferenceDefaultTypeParameter.ts, 10, 8))

f1<"b">(event => { });
>f1 : Symbol(f1, Decl(genericInferenceDefaultTypeParameter.ts, 5, 1))
>event : Symbol(event, Decl(genericInferenceDefaultTypeParameter.ts, 11, 8))

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
=== tests/cases/compiler/genericInferenceDefaultTypeParameter.ts ===
// Repro from #50858

type Type = {
>Type : { a: (e: string) => void; b: (e: number) => void; }

a: (e: string) => void;
>a : (e: string) => void
>e : string

b: (e: number) => void;
>b : (e: number) => void
>e : number
}

declare function f1<T extends keyof Type = "a">(props: Type[T]): void;
>f1 : <T extends keyof Type = "a">(props: Type[T]) => void
>props : Type[T]

f1(event => { });
>f1(event => { }) : void
>f1 : <T extends keyof Type = "a">(props: Type[T]) => void
>event => { } : (event: string) => void
>event : string

f1<"a">(event => { });
>f1<"a">(event => { }) : void
>f1 : <T extends keyof Type = "a">(props: Type[T]) => void
>event => { } : (event: string) => void
>event : string

f1<"b">(event => { });
>f1<"b">(event => { }) : void
>f1 : <T extends keyof Type = "a">(props: Type[T]) => void
>event => { } : (event: number) => void
>event : number

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//// [genericInferenceDefaultTypeParameterJsxReact.tsx]
/// <reference path="/.lib/react16.d.ts" />

// Repro from #50858

import React, { ComponentPropsWithRef, ElementType, ReactNode } from 'react';

type ButtonBaseProps<T extends ElementType> = ComponentPropsWithRef<T> & { children?: ReactNode };

function Component<T extends ElementType = 'span'>(props: ButtonBaseProps<T>) {
return <></>;
}

const v1 = <Component onClick={e => e.preventDefault()} />;


//// [genericInferenceDefaultTypeParameterJsxReact.js]
"use strict";
/// <reference path="react16.d.ts" />
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
// Repro from #50858
var react_1 = __importDefault(require("react"));
function Component(props) {
return react_1["default"].createElement(react_1["default"].Fragment, null);
}
var v1 = react_1["default"].createElement(Component, { onClick: function (e) { return e.preventDefault(); } });
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
=== tests/cases/compiler/genericInferenceDefaultTypeParameterJsxReact.tsx ===
/// <reference path="react16.d.ts" />

// Repro from #50858

import React, { ComponentPropsWithRef, ElementType, ReactNode } from 'react';
>React : Symbol(React, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 4, 6))
>ComponentPropsWithRef : Symbol(ComponentPropsWithRef, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 4, 15))
>ElementType : Symbol(ElementType, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 4, 38))
>ReactNode : Symbol(ReactNode, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 4, 51))

type ButtonBaseProps<T extends ElementType> = ComponentPropsWithRef<T> & { children?: ReactNode };
>ButtonBaseProps : Symbol(ButtonBaseProps, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 4, 77))
>T : Symbol(T, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 6, 21))
>ElementType : Symbol(ElementType, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 4, 38))
>ComponentPropsWithRef : Symbol(ComponentPropsWithRef, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 4, 15))
>T : Symbol(T, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 6, 21))
>children : Symbol(children, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 6, 74))
>ReactNode : Symbol(ReactNode, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 4, 51))

function Component<T extends ElementType = 'span'>(props: ButtonBaseProps<T>) {
>Component : Symbol(Component, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 6, 98))
>T : Symbol(T, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 8, 19))
>ElementType : Symbol(ElementType, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 4, 38))
>props : Symbol(props, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 8, 51))
>ButtonBaseProps : Symbol(ButtonBaseProps, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 4, 77))
>T : Symbol(T, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 8, 19))

return <></>;
}

const v1 = <Component onClick={e => e.preventDefault()} />;
>v1 : Symbol(v1, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 12, 5))
>Component : Symbol(Component, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 6, 98))
>onClick : Symbol(onClick, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 12, 21))
>e : Symbol(e, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 12, 31))
>e.preventDefault : Symbol(React.SyntheticEvent.preventDefault, Decl(react16.d.ts, 642, 31))
>e : Symbol(e, Decl(genericInferenceDefaultTypeParameterJsxReact.tsx, 12, 31))
>preventDefault : Symbol(React.SyntheticEvent.preventDefault, Decl(react16.d.ts, 642, 31))

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
=== tests/cases/compiler/genericInferenceDefaultTypeParameterJsxReact.tsx ===
/// <reference path="react16.d.ts" />

// Repro from #50858

import React, { ComponentPropsWithRef, ElementType, ReactNode } from 'react';
>React : typeof React
>ComponentPropsWithRef : any
>ElementType : any
>ReactNode : any

type ButtonBaseProps<T extends ElementType> = ComponentPropsWithRef<T> & { children?: ReactNode };
>ButtonBaseProps : ButtonBaseProps<T>
>children : React.ReactNode

function Component<T extends ElementType = 'span'>(props: ButtonBaseProps<T>) {
>Component : <T extends React.ElementType<any> = "span">(props: ButtonBaseProps<T>) => JSX.Element
>props : ButtonBaseProps<T>

return <></>;
><></> : JSX.Element
}

const v1 = <Component onClick={e => e.preventDefault()} />;
>v1 : JSX.Element
><Component onClick={e => e.preventDefault()} /> : JSX.Element
>Component : <T extends React.ElementType<any> = "span">(props: ButtonBaseProps<T>) => JSX.Element
>onClick : (e: React.MouseEvent<HTMLSpanElement>) => void
>e => e.preventDefault() : (e: React.MouseEvent<HTMLSpanElement>) => void
>e : React.MouseEvent<HTMLSpanElement>
>e.preventDefault() : void
>e.preventDefault : () => void
>e : React.MouseEvent<HTMLSpanElement>
>preventDefault : () => void

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @strict: true
// @noEmit: true

// repro #46310

export interface TypegenDisabled {
"@@xstate/typegen": false;
}
export interface TypegenEnabled {
"@@xstate/typegen": true;
}

type ActionFunction<TEvent extends { type: string }> = (event: TEvent) => void;

declare function createMachine<
TTypesMeta extends TypegenEnabled | TypegenDisabled = TypegenDisabled
>(
config: {
types?: TTypesMeta;
},
implementations: TTypesMeta extends TypegenEnabled
? ActionFunction<{ type: "test" }>
: ActionFunction<{ type: string }>
): void;

createMachine({}, (ev) => {
ev.type; // should be `string`
});
14 changes: 14 additions & 0 deletions tests/cases/compiler/genericInferenceDefaultTypeParameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @strict: true

// Repro from #50858

type Type = {
a: (e: string) => void;
b: (e: number) => void;
}

declare function f1<T extends keyof Type = "a">(props: Type[T]): void;

f1(event => { });
f1<"a">(event => { });
f1<"b">(event => { });
Loading