diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index da3dac4d60504..6b05a28b66529 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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); } /** diff --git a/tests/baselines/reference/contextualTypingOfOptionalMembers.errors.txt b/tests/baselines/reference/contextualTypingOfOptionalMembers.errors.txt new file mode 100644 index 0000000000000..02c94400e2167 --- /dev/null +++ b/tests/baselines/reference/contextualTypingOfOptionalMembers.errors.txt @@ -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 { + [prop: string]: (state: State) => State; + } + + interface Options { + state?: State; + view?: (state: State, actions: Actions) => any; + actions: string | Actions; + } + + declare function app>(obj: Options): 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(x: string | T): T; + + const y = foo({ + bar(x) { // Should be typed number => void + } + }); + + interface Options2 { + state?: State; + view?: (state: State, actions: Actions) => any; + actions?: Actions; + } + + declare function app2>(obj: Options2): void; + + app2({ + state: 100, + actions: { + foo: s => s // Should be typed number => number + }, + view: (s, a) => undefined as any, + }); + + + type ActionsArray = ((state: State) => State)[]; + + declare function app3>(obj: Options): 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 { + [prop: string]: ((state: State) => State) | State; + } + + declare function App4>(props: Options["actions"] & { state: State }): JSX.Element; + + const a = s} />; // TODO: should be number => number, but JSX resolution is missing an inferential pass + ~ +!!! error TS7006: Parameter 's' implicitly has an 'any' type. + \ No newline at end of file diff --git a/tests/baselines/reference/contextualTypingOfOptionalMembers.js b/tests/baselines/reference/contextualTypingOfOptionalMembers.js new file mode 100644 index 0000000000000..289d64546e83d --- /dev/null +++ b/tests/baselines/reference/contextualTypingOfOptionalMembers.js @@ -0,0 +1,103 @@ +//// [index.tsx] +interface ActionsObject { + [prop: string]: (state: State) => State; +} + +interface Options { + state?: State; + view?: (state: State, actions: Actions) => any; + actions: string | Actions; +} + +declare function app>(obj: Options): 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(x: string | T): T; + +const y = foo({ + bar(x) { // Should be typed number => void + } +}); + +interface Options2 { + state?: State; + view?: (state: State, actions: Actions) => any; + actions?: Actions; +} + +declare function app2>(obj: Options2): void; + +app2({ + state: 100, + actions: { + foo: s => s // Should be typed number => number + }, + view: (s, a) => undefined as any, +}); + + +type ActionsArray = ((state: State) => State)[]; + +declare function app3>(obj: Options): 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 { + [prop: string]: ((state: State) => State) | State; +} + +declare function App4>(props: Options["actions"] & { state: State }): JSX.Element; + +const a = 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 = ; // TODO: should be number => number, but JSX resolution is missing an inferential pass diff --git a/tests/baselines/reference/contextualTypingOfOptionalMembers.symbols b/tests/baselines/reference/contextualTypingOfOptionalMembers.symbols new file mode 100644 index 0000000000000..14d8ef41b3af0 --- /dev/null +++ b/tests/baselines/reference/contextualTypingOfOptionalMembers.symbols @@ -0,0 +1,235 @@ +=== tests/cases/compiler/index.tsx === +interface ActionsObject { +>ActionsObject : Symbol(ActionsObject, Decl(index.tsx, 0, 0)) +>State : Symbol(State, Decl(index.tsx, 0, 24)) + + [prop: string]: (state: State) => State; +>prop : Symbol(prop, Decl(index.tsx, 1, 5)) +>state : Symbol(state, Decl(index.tsx, 1, 21)) +>State : Symbol(State, Decl(index.tsx, 0, 24)) +>State : Symbol(State, Decl(index.tsx, 0, 24)) +} + +interface Options { +>Options : Symbol(Options, Decl(index.tsx, 2, 1)) +>State : Symbol(State, Decl(index.tsx, 4, 18)) +>Actions : Symbol(Actions, Decl(index.tsx, 4, 24)) + + state?: State; +>state : Symbol(Options.state, Decl(index.tsx, 4, 35)) +>State : Symbol(State, Decl(index.tsx, 4, 18)) + + view?: (state: State, actions: Actions) => any; +>view : Symbol(Options.view, Decl(index.tsx, 5, 18)) +>state : Symbol(state, Decl(index.tsx, 6, 12)) +>State : Symbol(State, Decl(index.tsx, 4, 18)) +>actions : Symbol(actions, Decl(index.tsx, 6, 25)) +>Actions : Symbol(Actions, Decl(index.tsx, 4, 24)) + + actions: string | Actions; +>actions : Symbol(Options.actions, Decl(index.tsx, 6, 51)) +>Actions : Symbol(Actions, Decl(index.tsx, 4, 24)) +} + +declare function app>(obj: Options): void; +>app : Symbol(app, Decl(index.tsx, 8, 1)) +>State : Symbol(State, Decl(index.tsx, 10, 21)) +>Actions : Symbol(Actions, Decl(index.tsx, 10, 27)) +>ActionsObject : Symbol(ActionsObject, Decl(index.tsx, 0, 0)) +>State : Symbol(State, Decl(index.tsx, 10, 21)) +>obj : Symbol(obj, Decl(index.tsx, 10, 66)) +>Options : Symbol(Options, Decl(index.tsx, 2, 1)) +>State : Symbol(State, Decl(index.tsx, 10, 21)) +>Actions : Symbol(Actions, Decl(index.tsx, 10, 27)) + +app({ +>app : Symbol(app, Decl(index.tsx, 8, 1)) + + state: 100, +>state : Symbol(state, Decl(index.tsx, 12, 5)) + + actions: { +>actions : Symbol(actions, Decl(index.tsx, 13, 15)) + + foo: s => s // Should be typed number => number +>foo : Symbol(foo, Decl(index.tsx, 14, 14)) +>s : Symbol(s, Decl(index.tsx, 15, 12)) +>s : Symbol(s, Decl(index.tsx, 15, 12)) + + }, + view: (s, a) => undefined as any, +>view : Symbol(view, Decl(index.tsx, 16, 6)) +>s : Symbol(s, Decl(index.tsx, 17, 11)) +>a : Symbol(a, Decl(index.tsx, 17, 13)) +>undefined : Symbol(undefined) + +}); + + +interface Bar { +>Bar : Symbol(Bar, Decl(index.tsx, 18, 3)) + + bar: (a: number) => void; +>bar : Symbol(Bar.bar, Decl(index.tsx, 21, 15)) +>a : Symbol(a, Decl(index.tsx, 22, 10)) +} + +declare function foo(x: string | T): T; +>foo : Symbol(foo, Decl(index.tsx, 23, 1)) +>T : Symbol(T, Decl(index.tsx, 25, 21)) +>Bar : Symbol(Bar, Decl(index.tsx, 18, 3)) +>x : Symbol(x, Decl(index.tsx, 25, 36)) +>T : Symbol(T, Decl(index.tsx, 25, 21)) +>T : Symbol(T, Decl(index.tsx, 25, 21)) + +const y = foo({ +>y : Symbol(y, Decl(index.tsx, 27, 5)) +>foo : Symbol(foo, Decl(index.tsx, 23, 1)) + + bar(x) { // Should be typed number => void +>bar : Symbol(bar, Decl(index.tsx, 27, 15)) +>x : Symbol(x, Decl(index.tsx, 28, 8)) + } +}); + +interface Options2 { +>Options2 : Symbol(Options2, Decl(index.tsx, 30, 3)) +>State : Symbol(State, Decl(index.tsx, 32, 19)) +>Actions : Symbol(Actions, Decl(index.tsx, 32, 25)) + + state?: State; +>state : Symbol(Options2.state, Decl(index.tsx, 32, 36)) +>State : Symbol(State, Decl(index.tsx, 32, 19)) + + view?: (state: State, actions: Actions) => any; +>view : Symbol(Options2.view, Decl(index.tsx, 33, 18)) +>state : Symbol(state, Decl(index.tsx, 34, 12)) +>State : Symbol(State, Decl(index.tsx, 32, 19)) +>actions : Symbol(actions, Decl(index.tsx, 34, 25)) +>Actions : Symbol(Actions, Decl(index.tsx, 32, 25)) + + actions?: Actions; +>actions : Symbol(Options2.actions, Decl(index.tsx, 34, 51)) +>Actions : Symbol(Actions, Decl(index.tsx, 32, 25)) +} + +declare function app2>(obj: Options2): void; +>app2 : Symbol(app2, Decl(index.tsx, 36, 1)) +>State : Symbol(State, Decl(index.tsx, 38, 22)) +>Actions : Symbol(Actions, Decl(index.tsx, 38, 28)) +>ActionsObject : Symbol(ActionsObject, Decl(index.tsx, 0, 0)) +>State : Symbol(State, Decl(index.tsx, 38, 22)) +>obj : Symbol(obj, Decl(index.tsx, 38, 67)) +>Options2 : Symbol(Options2, Decl(index.tsx, 30, 3)) +>State : Symbol(State, Decl(index.tsx, 38, 22)) +>Actions : Symbol(Actions, Decl(index.tsx, 38, 28)) + +app2({ +>app2 : Symbol(app2, Decl(index.tsx, 36, 1)) + + state: 100, +>state : Symbol(state, Decl(index.tsx, 40, 6)) + + actions: { +>actions : Symbol(actions, Decl(index.tsx, 41, 15)) + + foo: s => s // Should be typed number => number +>foo : Symbol(foo, Decl(index.tsx, 42, 14)) +>s : Symbol(s, Decl(index.tsx, 43, 12)) +>s : Symbol(s, Decl(index.tsx, 43, 12)) + + }, + view: (s, a) => undefined as any, +>view : Symbol(view, Decl(index.tsx, 44, 6)) +>s : Symbol(s, Decl(index.tsx, 45, 11)) +>a : Symbol(a, Decl(index.tsx, 45, 13)) +>undefined : Symbol(undefined) + +}); + + +type ActionsArray = ((state: State) => State)[]; +>ActionsArray : Symbol(ActionsArray, Decl(index.tsx, 46, 3)) +>State : Symbol(State, Decl(index.tsx, 49, 18)) +>state : Symbol(state, Decl(index.tsx, 49, 29)) +>State : Symbol(State, Decl(index.tsx, 49, 18)) +>State : Symbol(State, Decl(index.tsx, 49, 18)) + +declare function app3>(obj: Options): void; +>app3 : Symbol(app3, Decl(index.tsx, 49, 55)) +>State : Symbol(State, Decl(index.tsx, 51, 22)) +>Actions : Symbol(Actions, Decl(index.tsx, 51, 28)) +>ActionsArray : Symbol(ActionsArray, Decl(index.tsx, 46, 3)) +>State : Symbol(State, Decl(index.tsx, 51, 22)) +>obj : Symbol(obj, Decl(index.tsx, 51, 66)) +>Options : Symbol(Options, Decl(index.tsx, 2, 1)) +>State : Symbol(State, Decl(index.tsx, 51, 22)) +>Actions : Symbol(Actions, Decl(index.tsx, 51, 28)) + +app3({ +>app3 : Symbol(app3, Decl(index.tsx, 49, 55)) + + state: 100, +>state : Symbol(state, Decl(index.tsx, 53, 6)) + + actions: [ +>actions : Symbol(actions, Decl(index.tsx, 54, 15)) + + s => s // Should be typed number => number +>s : Symbol(s, Decl(index.tsx, 55, 14)) +>s : Symbol(s, Decl(index.tsx, 55, 14)) + + ], + view: (s, a) => undefined as any, +>view : Symbol(view, Decl(index.tsx, 57, 6)) +>s : Symbol(s, Decl(index.tsx, 58, 11)) +>a : Symbol(a, Decl(index.tsx, 58, 13)) +>undefined : Symbol(undefined) + +}); + +namespace JSX { +>JSX : Symbol(JSX, Decl(index.tsx, 59, 3)) + + export interface Element {} +>Element : Symbol(Element, Decl(index.tsx, 61, 15)) + + export interface IntrinsicElements {} +>IntrinsicElements : Symbol(IntrinsicElements, Decl(index.tsx, 62, 31)) +} + +interface ActionsObjectOr { +>ActionsObjectOr : Symbol(ActionsObjectOr, Decl(index.tsx, 64, 1)) +>State : Symbol(State, Decl(index.tsx, 66, 26)) + + [prop: string]: ((state: State) => State) | State; +>prop : Symbol(prop, Decl(index.tsx, 67, 5)) +>state : Symbol(state, Decl(index.tsx, 67, 22)) +>State : Symbol(State, Decl(index.tsx, 66, 26)) +>State : Symbol(State, Decl(index.tsx, 66, 26)) +>State : Symbol(State, Decl(index.tsx, 66, 26)) +} + +declare function App4>(props: Options["actions"] & { state: State }): JSX.Element; +>App4 : Symbol(App4, Decl(index.tsx, 68, 1)) +>State : Symbol(State, Decl(index.tsx, 70, 22)) +>Actions : Symbol(Actions, Decl(index.tsx, 70, 28)) +>ActionsObjectOr : Symbol(ActionsObjectOr, Decl(index.tsx, 64, 1)) +>State : Symbol(State, Decl(index.tsx, 70, 22)) +>props : Symbol(props, Decl(index.tsx, 70, 69)) +>Options : Symbol(Options, Decl(index.tsx, 2, 1)) +>State : Symbol(State, Decl(index.tsx, 70, 22)) +>Actions : Symbol(Actions, Decl(index.tsx, 70, 28)) +>state : Symbol(state, Decl(index.tsx, 70, 114)) +>State : Symbol(State, Decl(index.tsx, 70, 22)) +>JSX : Symbol(JSX, Decl(index.tsx, 59, 3)) +>Element : Symbol(JSX.Element, Decl(index.tsx, 61, 15)) + +const a = s} />; // TODO: should be number => number, but JSX resolution is missing an inferential pass +>a : Symbol(a, Decl(index.tsx, 72, 5)) +>App4 : Symbol(App4, Decl(index.tsx, 68, 1)) +>state : Symbol(state, Decl(index.tsx, 72, 15)) +>foo : Symbol(foo, Decl(index.tsx, 72, 27)) +>s : Symbol(s, Decl(index.tsx, 72, 33)) +>s : Symbol(s, Decl(index.tsx, 72, 33)) + diff --git a/tests/baselines/reference/contextualTypingOfOptionalMembers.types b/tests/baselines/reference/contextualTypingOfOptionalMembers.types new file mode 100644 index 0000000000000..515685f5c534d --- /dev/null +++ b/tests/baselines/reference/contextualTypingOfOptionalMembers.types @@ -0,0 +1,261 @@ +=== tests/cases/compiler/index.tsx === +interface ActionsObject { +>ActionsObject : ActionsObject +>State : State + + [prop: string]: (state: State) => State; +>prop : string +>state : State +>State : State +>State : State +} + +interface Options { +>Options : Options +>State : State +>Actions : Actions + + state?: State; +>state : State | undefined +>State : State + + view?: (state: State, actions: Actions) => any; +>view : ((state: State, actions: Actions) => any) | undefined +>state : State +>State : State +>actions : Actions +>Actions : Actions + + actions: string | Actions; +>actions : string | Actions +>Actions : Actions +} + +declare function app>(obj: Options): void; +>app : >(obj: Options) => void +>State : State +>Actions : Actions +>ActionsObject : ActionsObject +>State : State +>obj : Options +>Options : Options +>State : State +>Actions : Actions + +app({ +>app({ state: 100, actions: { foo: s => s // Should be typed number => number }, view: (s, a) => undefined as any,}) : void +>app : >(obj: Options) => void +>{ state: 100, actions: { foo: s => s // Should be typed number => number }, view: (s, a) => undefined as any,} : { state: number; actions: { foo: (s: number) => number; }; view: (s: number, a: ActionsObject) => any; } + + state: 100, +>state : number +>100 : 100 + + actions: { +>actions : { foo: (s: number) => number; } +>{ foo: s => s // Should be typed number => number } : { foo: (s: number) => number; } + + foo: s => s // Should be typed number => number +>foo : (s: number) => number +>s => s : (s: number) => number +>s : number +>s : number + + }, + view: (s, a) => undefined as any, +>view : (s: number, a: ActionsObject) => any +>(s, a) => undefined as any : (s: number, a: ActionsObject) => any +>s : number +>a : ActionsObject +>undefined as any : any +>undefined : undefined + +}); + + +interface Bar { +>Bar : Bar + + bar: (a: number) => void; +>bar : (a: number) => void +>a : number +} + +declare function foo(x: string | T): T; +>foo : (x: string | T) => T +>T : T +>Bar : Bar +>x : string | T +>T : T +>T : T + +const y = foo({ +>y : { bar(x: number): void; } +>foo({ bar(x) { // Should be typed number => void }}) : { bar(x: number): void; } +>foo : (x: string | T) => T +>{ bar(x) { // Should be typed number => void }} : { bar(x: number): void; } + + bar(x) { // Should be typed number => void +>bar : (x: number) => void +>x : number + } +}); + +interface Options2 { +>Options2 : Options2 +>State : State +>Actions : Actions + + state?: State; +>state : State | undefined +>State : State + + view?: (state: State, actions: Actions) => any; +>view : ((state: State, actions: Actions) => any) | undefined +>state : State +>State : State +>actions : Actions +>Actions : Actions + + actions?: Actions; +>actions : Actions | undefined +>Actions : Actions +} + +declare function app2>(obj: Options2): void; +>app2 : >(obj: Options2) => void +>State : State +>Actions : Actions +>ActionsObject : ActionsObject +>State : State +>obj : Options2 +>Options2 : Options2 +>State : State +>Actions : Actions + +app2({ +>app2({ state: 100, actions: { foo: s => s // Should be typed number => number }, view: (s, a) => undefined as any,}) : void +>app2 : >(obj: Options2) => void +>{ state: 100, actions: { foo: s => s // Should be typed number => number }, view: (s, a) => undefined as any,} : { state: number; actions: { foo: (s: number) => number; }; view: (s: number, a: ActionsObject) => any; } + + state: 100, +>state : number +>100 : 100 + + actions: { +>actions : { foo: (s: number) => number; } +>{ foo: s => s // Should be typed number => number } : { foo: (s: number) => number; } + + foo: s => s // Should be typed number => number +>foo : (s: number) => number +>s => s : (s: number) => number +>s : number +>s : number + + }, + view: (s, a) => undefined as any, +>view : (s: number, a: ActionsObject) => any +>(s, a) => undefined as any : (s: number, a: ActionsObject) => any +>s : number +>a : ActionsObject +>undefined as any : any +>undefined : undefined + +}); + + +type ActionsArray = ((state: State) => State)[]; +>ActionsArray : ((state: State) => State)[] +>State : State +>state : State +>State : State +>State : State + +declare function app3>(obj: Options): void; +>app3 : State)[]>(obj: Options) => void +>State : State +>Actions : Actions +>ActionsArray : ((state: State) => State)[] +>State : State +>obj : Options +>Options : Options +>State : State +>Actions : Actions + +app3({ +>app3({ state: 100, actions: [ s => s // Should be typed number => number ], view: (s, a) => undefined as any,}) : void +>app3 : State)[]>(obj: Options) => void +>{ state: 100, actions: [ s => s // Should be typed number => number ], view: (s, a) => undefined as any,} : { state: number; actions: ((s: number) => number)[]; view: (s: number, a: ((state: number) => number)[]) => any; } + + state: 100, +>state : number +>100 : 100 + + actions: [ +>actions : ((s: number) => number)[] +>[ s => s // Should be typed number => number ] : ((s: number) => number)[] + + s => s // Should be typed number => number +>s => s : (s: number) => number +>s : number +>s : number + + ], + view: (s, a) => undefined as any, +>view : (s: number, a: ((state: number) => number)[]) => any +>(s, a) => undefined as any : (s: number, a: ((state: number) => number)[]) => any +>s : number +>a : ((state: number) => number)[] +>undefined as any : any +>undefined : undefined + +}); + +namespace JSX { +>JSX : any + + export interface Element {} +>Element : Element + + export interface IntrinsicElements {} +>IntrinsicElements : IntrinsicElements +} + +interface ActionsObjectOr { +>ActionsObjectOr : ActionsObjectOr +>State : State + + [prop: string]: ((state: State) => State) | State; +>prop : string +>state : State +>State : State +>State : State +>State : State +} + +declare function App4>(props: Options["actions"] & { state: State }): JSX.Element; +>App4 : >(props: (string & { state: State; }) | (Actions & { state: State; })) => JSX.Element +>State : State +>Actions : Actions +>ActionsObjectOr : ActionsObjectOr +>State : State +>props : (string & { state: State; }) | (Actions & { state: State; }) +>Options : Options +>State : State +>Actions : Actions +>state : State +>State : State +>JSX : any +>Element : JSX.Element + +const a = s} />; // TODO: should be number => number, but JSX resolution is missing an inferential pass +>a : JSX.Element +> s} /> : JSX.Element +>App4 : >(props: (string & { state: State; }) | (Actions & { state: State; })) => JSX.Element +>state : number +>100 : 100 +>foo : (s: any) => any +>s => s : (s: any) => any +>s : any +>s : any + diff --git a/tests/cases/compiler/contextualTypingOfOptionalMembers.tsx b/tests/cases/compiler/contextualTypingOfOptionalMembers.tsx new file mode 100644 index 0000000000000..5e5a9c70c9b46 --- /dev/null +++ b/tests/cases/compiler/contextualTypingOfOptionalMembers.tsx @@ -0,0 +1,77 @@ +// @noImplicitAny: true +// @strictNullChecks: true +// @jsx: preserve +// @filename: index.tsx +interface ActionsObject { + [prop: string]: (state: State) => State; +} + +interface Options { + state?: State; + view?: (state: State, actions: Actions) => any; + actions: string | Actions; +} + +declare function app>(obj: Options): 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(x: string | T): T; + +const y = foo({ + bar(x) { // Should be typed number => void + } +}); + +interface Options2 { + state?: State; + view?: (state: State, actions: Actions) => any; + actions?: Actions; +} + +declare function app2>(obj: Options2): void; + +app2({ + state: 100, + actions: { + foo: s => s // Should be typed number => number + }, + view: (s, a) => undefined as any, +}); + + +type ActionsArray = ((state: State) => State)[]; + +declare function app3>(obj: Options): 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 { + [prop: string]: ((state: State) => State) | State; +} + +declare function App4>(props: Options["actions"] & { state: State }): JSX.Element; + +const a = s} />; // TODO: should be number => number, but JSX resolution is missing an inferential pass