Skip to content

Make getContextualTypeOfApparentType mapType over unions #17668

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
2 changes: 1 addition & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13528,7 +13528,7 @@ namespace ts {
// be "pushed" onto a node using the contextualType property.
function getApparentTypeOfContextualType(node: Expression): Type {
const type = getContextualType(node);
return type && getApparentType(type);
return type && mapType(type, getApparentType);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
tests/cases/compiler/index.tsx(73,34): error TS7006: Parameter 's' implicitly has an 'any' type.


==== tests/cases/compiler/index.tsx (1 errors) ====
interface ActionsObject<State> {
[prop: string]: (state: State) => State;
}

interface Options<State, Actions> {
state?: State;
view?: (state: State, actions: Actions) => any;
actions: string | Actions;
}

declare function app<State, Actions extends ActionsObject<State>>(obj: Options<State, Actions>): void;

app({
state: 100,
actions: {
foo: s => s // Should be typed number => number
},
view: (s, a) => undefined as any,
});


interface Bar {
bar: (a: number) => void;
}

declare function foo<T extends Bar>(x: string | T): T;

const y = foo({
bar(x) { // Should be typed number => void
}
});

interface Options2<State, Actions> {
state?: State;
view?: (state: State, actions: Actions) => any;
actions?: Actions;
}

declare function app2<State, Actions extends ActionsObject<State>>(obj: Options2<State, Actions>): void;

app2({
state: 100,
actions: {
foo: s => s // Should be typed number => number
},
view: (s, a) => undefined as any,
});


type ActionsArray<State> = ((state: State) => State)[];

declare function app3<State, Actions extends ActionsArray<State>>(obj: Options<State, Actions>): void;

app3({
state: 100,
actions: [
s => s // Should be typed number => number
],
view: (s, a) => undefined as any,
});

namespace JSX {
export interface Element {}
export interface IntrinsicElements {}
}

interface ActionsObjectOr<State> {
[prop: string]: ((state: State) => State) | State;
}

declare function App4<State, Actions extends ActionsObjectOr<State>>(props: Options<State, Actions>["actions"] & { state: State }): JSX.Element;

const a = <App4 state={100} foo={s => s} />; // TODO: should be number => number, but JSX resolution is missing an inferential pass
~
!!! error TS7006: Parameter 's' implicitly has an 'any' type.

103 changes: 103 additions & 0 deletions tests/baselines/reference/contextualTypingOfOptionalMembers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//// [index.tsx]
interface ActionsObject<State> {
[prop: string]: (state: State) => State;
}

interface Options<State, Actions> {
state?: State;
view?: (state: State, actions: Actions) => any;
actions: string | Actions;
}

declare function app<State, Actions extends ActionsObject<State>>(obj: Options<State, Actions>): void;

app({
state: 100,
actions: {
foo: s => s // Should be typed number => number
},
view: (s, a) => undefined as any,
});


interface Bar {
bar: (a: number) => void;
}

declare function foo<T extends Bar>(x: string | T): T;

const y = foo({
bar(x) { // Should be typed number => void
}
});

interface Options2<State, Actions> {
state?: State;
view?: (state: State, actions: Actions) => any;
actions?: Actions;
}

declare function app2<State, Actions extends ActionsObject<State>>(obj: Options2<State, Actions>): void;

app2({
state: 100,
actions: {
foo: s => s // Should be typed number => number
},
view: (s, a) => undefined as any,
});


type ActionsArray<State> = ((state: State) => State)[];

declare function app3<State, Actions extends ActionsArray<State>>(obj: Options<State, Actions>): void;

app3({
state: 100,
actions: [
s => s // Should be typed number => number
],
view: (s, a) => undefined as any,
});

namespace JSX {
export interface Element {}
export interface IntrinsicElements {}
}

interface ActionsObjectOr<State> {
[prop: string]: ((state: State) => State) | State;
}

declare function App4<State, Actions extends ActionsObjectOr<State>>(props: Options<State, Actions>["actions"] & { state: State }): JSX.Element;

const a = <App4 state={100} foo={s => s} />; // TODO: should be number => number, but JSX resolution is missing an inferential pass


//// [index.jsx]
app({
state: 100,
actions: {
foo: function (s) { return s; } // Should be typed number => number
},
view: function (s, a) { return undefined; }
});
var y = foo({
bar: function (x) {
}
});
app2({
state: 100,
actions: {
foo: function (s) { return s; } // Should be typed number => number
},
view: function (s, a) { return undefined; }
});
app3({
state: 100,
actions: [
function (s) { return s; } // Should be typed number => number
],
view: function (s, a) { return undefined; }
});
var a = <App4 state={100} foo={function (s) { return s; }}/>; // TODO: should be number => number, but JSX resolution is missing an inferential pass
Loading