Skip to content

Commit

Permalink
Include type parameter defaults in contextual typing (#50994)
Browse files Browse the repository at this point in the history
* Include type parameter defaults in contextual typing

* Add tests

* Add additional an test for instantiating contextual signature using default type param (#51002)

* Update comment

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
  • Loading branch information
ahejlsberg and Andarist committed Sep 30, 2022
1 parent 0d0a793 commit 96894db
Show file tree
Hide file tree
Showing 12 changed files with 387 additions and 3 deletions.
11 changes: 8 additions & 3 deletions src/compiler/checker.ts
Expand Up @@ -27596,9 +27596,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
// replaced with their constraints similar to the apparent type.
if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)) {
// 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 @@ -35209,6 +35210,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
@@ -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))

});

@@ -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
@@ -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) { });
@@ -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))

@@ -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

@@ -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(); } });
@@ -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))

@@ -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

@@ -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
@@ -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 => { });

0 comments on commit 96894db

Please sign in to comment.