Skip to content

Fix JSX contextual types to not eagerly become apparent, use 2-pass i… #21749

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 1 commit into from
Feb 7, 2018
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
351 changes: 209 additions & 142 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3851,6 +3851,7 @@ namespace ts {
}

export const enum InferenceFlags {
None = 0, // No special inference behaviors
InferUnionTypes = 1 << 0, // Infer union types for disjoint candidates (otherwise unknownType)
NoDefault = 1 << 1, // Infer unknownType for no inferences (otherwise anyType or emptyObjectType)
AnyDefault = 1 << 2, // Infer anyType for no inferences (otherwise emptyObjectType)
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,7 @@ declare namespace ts {
isFixed: boolean;
}
enum InferenceFlags {
None = 0,
InferUnionTypes = 1,
NoDefault = 2,
AnyDefault = 4,
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,7 @@ declare namespace ts {
isFixed: boolean;
}
enum InferenceFlags {
None = 0,
InferUnionTypes = 1,
NoDefault = 2,
AnyDefault = 4,
Expand Down
20 changes: 10 additions & 10 deletions tests/baselines/reference/checkJsxChildrenProperty2.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ tests/cases/conformance/jsx/file.tsx(31,11): error TS2322: Type '{ children: (El
Type '(Element | ((name: string) => Element))[]' is not assignable to type 'string | Element'.
Type '(Element | ((name: string) => Element))[]' is not assignable to type 'Element'.
Property 'type' is missing in type '(Element | ((name: string) => Element))[]'.
tests/cases/conformance/jsx/file.tsx(37,11): error TS2322: Type '{ children: (Element | 1000000)[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & Prop'.
Type '{ children: (Element | 1000000)[]; a: number; b: string; }' is not assignable to type 'Prop'.
tests/cases/conformance/jsx/file.tsx(37,11): error TS2322: Type '{ children: (number | Element)[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & Prop'.
Type '{ children: (number | Element)[]; a: number; b: string; }' is not assignable to type 'Prop'.
Types of property 'children' are incompatible.
Type '(Element | 1000000)[]' is not assignable to type 'string | Element'.
Type '(Element | 1000000)[]' is not assignable to type 'Element'.
Property 'type' is missing in type '(Element | 1000000)[]'.
Type '(number | Element)[]' is not assignable to type 'string | Element'.
Type '(number | Element)[]' is not assignable to type 'Element'.
Property 'type' is missing in type '(number | Element)[]'.
tests/cases/conformance/jsx/file.tsx(43,11): error TS2322: Type '{ children: (string | Element)[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & Prop'.
Type '{ children: (string | Element)[]; a: number; b: string; }' is not assignable to type 'Prop'.
Types of property 'children' are incompatible.
Expand Down Expand Up @@ -80,12 +80,12 @@ tests/cases/conformance/jsx/file.tsx(49,11): error TS2322: Type '{ children: Ele
let k3 =
<Comp a={10} b="hi">
~~~~~~~~~~~~~
!!! error TS2322: Type '{ children: (Element | 1000000)[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & Prop'.
!!! error TS2322: Type '{ children: (Element | 1000000)[]; a: number; b: string; }' is not assignable to type 'Prop'.
!!! error TS2322: Type '{ children: (number | Element)[]; a: number; b: string; }' is not assignable to type 'IntrinsicAttributes & Prop'.
!!! error TS2322: Type '{ children: (number | Element)[]; a: number; b: string; }' is not assignable to type 'Prop'.
!!! error TS2322: Types of property 'children' are incompatible.
!!! error TS2322: Type '(Element | 1000000)[]' is not assignable to type 'string | Element'.
!!! error TS2322: Type '(Element | 1000000)[]' is not assignable to type 'Element'.
!!! error TS2322: Property 'type' is missing in type '(Element | 1000000)[]'.
!!! error TS2322: Type '(number | Element)[]' is not assignable to type 'string | Element'.
!!! error TS2322: Type '(number | Element)[]' is not assignable to type 'Element'.
!!! error TS2322: Property 'type' is missing in type '(number | Element)[]'.
<div> My Div </div>
{1000000}
</Comp>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
tests/cases/conformance/jsx/file.tsx(13,27): error TS2322: Type '{ initialValues: { x: string; }; nextValues: (a: { x: string; }) => string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<GenericComponent<{ initialValues: { x: string; }; nextValues: {}; }, { x: string; }>> & { initialValues: { x: string; }; nextValues: {}; } & BaseProps<{ x: string; }> & { children?: ReactNode; }'.
Type '{ initialValues: { x: string; }; nextValues: (a: { x: string; }) => string; }' is not assignable to type 'BaseProps<{ x: string; }>'.
Types of property 'nextValues' are incompatible.
Type '(a: { x: string; }) => string' is not assignable to type '(cur: { x: string; }) => { x: string; }'.
Type 'string' is not assignable to type '{ x: string; }'.


==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
import * as React from "react";
interface BaseProps<T> {
initialValues: T;
nextValues: (cur: T) => T;
}
declare class GenericComponent<Props = {}, Values = object> extends React.Component<Props & BaseProps<Values>, {}> {
iv: Values;
}

let a = <GenericComponent initialValues={{ x: "y" }} nextValues={a => a} />; // No error
let b = <GenericComponent initialValues={12} nextValues={a => a} />; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint)
let c = <GenericComponent initialValues={{ x: "y" }} nextValues={a => ({ x: a.x })} />; // No Error
let d = <GenericComponent initialValues={{ x: "y" }} nextValues={a => a.x} />; // Error - `string` is not assignable to `{x: string}`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type '{ initialValues: { x: string; }; nextValues: (a: { x: string; }) => string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<GenericComponent<{ initialValues: { x: string; }; nextValues: {}; }, { x: string; }>> & { initialValues: { x: string; }; nextValues: {}; } & BaseProps<{ x: string; }> & { children?: ReactNode; }'.
!!! error TS2322: Type '{ initialValues: { x: string; }; nextValues: (a: { x: string; }) => string; }' is not assignable to type 'BaseProps<{ x: string; }>'.
!!! error TS2322: Types of property 'nextValues' are incompatible.
!!! error TS2322: Type '(a: { x: string; }) => string' is not assignable to type '(cur: { x: string; }) => { x: string; }'.
!!! error TS2322: Type 'string' is not assignable to type '{ x: string; }'.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//// [file.tsx]
import * as React from "react";
interface BaseProps<T> {
initialValues: T;
nextValues: (cur: T) => T;
}
declare class GenericComponent<Props = {}, Values = object> extends React.Component<Props & BaseProps<Values>, {}> {
iv: Values;
}

let a = <GenericComponent initialValues={{ x: "y" }} nextValues={a => a} />; // No error
let b = <GenericComponent initialValues={12} nextValues={a => a} />; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint)
let c = <GenericComponent initialValues={{ x: "y" }} nextValues={a => ({ x: a.x })} />; // No Error
let d = <GenericComponent initialValues={{ x: "y" }} nextValues={a => a.x} />; // Error - `string` is not assignable to `{x: string}`

//// [file.jsx]
"use strict";
exports.__esModule = true;
var React = require("react");
var a = <GenericComponent initialValues={{ x: "y" }} nextValues={function (a) { return a; }}/>; // No error
var b = <GenericComponent initialValues={12} nextValues={function (a) { return a; }}/>; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint)
var c = <GenericComponent initialValues={{ x: "y" }} nextValues={function (a) { return ({ x: a.x }); }}/>; // No Error
var d = <GenericComponent initialValues={{ x: "y" }} nextValues={function (a) { return a.x; }}/>; // Error - `string` is not assignable to `{x: string}`
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
=== tests/cases/conformance/jsx/file.tsx ===
import * as React from "react";
>React : Symbol(React, Decl(file.tsx, 0, 6))

interface BaseProps<T> {
>BaseProps : Symbol(BaseProps, Decl(file.tsx, 0, 31))
>T : Symbol(T, Decl(file.tsx, 1, 20))

initialValues: T;
>initialValues : Symbol(BaseProps.initialValues, Decl(file.tsx, 1, 24))
>T : Symbol(T, Decl(file.tsx, 1, 20))

nextValues: (cur: T) => T;
>nextValues : Symbol(BaseProps.nextValues, Decl(file.tsx, 2, 19))
>cur : Symbol(cur, Decl(file.tsx, 3, 15))
>T : Symbol(T, Decl(file.tsx, 1, 20))
>T : Symbol(T, Decl(file.tsx, 1, 20))
}
declare class GenericComponent<Props = {}, Values = object> extends React.Component<Props & BaseProps<Values>, {}> {
>GenericComponent : Symbol(GenericComponent, Decl(file.tsx, 4, 1))
>Props : Symbol(Props, Decl(file.tsx, 5, 31))
>Values : Symbol(Values, Decl(file.tsx, 5, 42))
>React.Component : Symbol(React.Component, Decl(react.d.ts, 158, 55), Decl(react.d.ts, 161, 66))
>React : Symbol(React, Decl(file.tsx, 0, 6))
>Component : Symbol(React.Component, Decl(react.d.ts, 158, 55), Decl(react.d.ts, 161, 66))
>Props : Symbol(Props, Decl(file.tsx, 5, 31))
>BaseProps : Symbol(BaseProps, Decl(file.tsx, 0, 31))
>Values : Symbol(Values, Decl(file.tsx, 5, 42))

iv: Values;
>iv : Symbol(GenericComponent.iv, Decl(file.tsx, 5, 116))
>Values : Symbol(Values, Decl(file.tsx, 5, 42))
}

let a = <GenericComponent initialValues={{ x: "y" }} nextValues={a => a} />; // No error
>a : Symbol(a, Decl(file.tsx, 9, 3))
>GenericComponent : Symbol(GenericComponent, Decl(file.tsx, 4, 1))
>initialValues : Symbol(initialValues, Decl(file.tsx, 9, 25))
>x : Symbol(x, Decl(file.tsx, 9, 42))
>nextValues : Symbol(nextValues, Decl(file.tsx, 9, 52))
>a : Symbol(a, Decl(file.tsx, 9, 65))
>a : Symbol(a, Decl(file.tsx, 9, 65))

let b = <GenericComponent initialValues={12} nextValues={a => a} />; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint)
>b : Symbol(b, Decl(file.tsx, 10, 3))
>GenericComponent : Symbol(GenericComponent, Decl(file.tsx, 4, 1))
>initialValues : Symbol(initialValues, Decl(file.tsx, 10, 25))
>nextValues : Symbol(nextValues, Decl(file.tsx, 10, 44))
>a : Symbol(a, Decl(file.tsx, 10, 57))
>a : Symbol(a, Decl(file.tsx, 10, 57))

let c = <GenericComponent initialValues={{ x: "y" }} nextValues={a => ({ x: a.x })} />; // No Error
>c : Symbol(c, Decl(file.tsx, 11, 3))
>GenericComponent : Symbol(GenericComponent, Decl(file.tsx, 4, 1))
>initialValues : Symbol(initialValues, Decl(file.tsx, 11, 25))
>x : Symbol(x, Decl(file.tsx, 11, 42))
>nextValues : Symbol(nextValues, Decl(file.tsx, 11, 52))
>a : Symbol(a, Decl(file.tsx, 11, 65))
>x : Symbol(x, Decl(file.tsx, 11, 72))
>a.x : Symbol(x, Decl(file.tsx, 11, 42))
>a : Symbol(a, Decl(file.tsx, 11, 65))
>x : Symbol(x, Decl(file.tsx, 11, 42))

let d = <GenericComponent initialValues={{ x: "y" }} nextValues={a => a.x} />; // Error - `string` is not assignable to `{x: string}`
>d : Symbol(d, Decl(file.tsx, 12, 3))
>GenericComponent : Symbol(GenericComponent, Decl(file.tsx, 4, 1))
>initialValues : Symbol(initialValues, Decl(file.tsx, 12, 25))
>x : Symbol(x, Decl(file.tsx, 12, 42))
>nextValues : Symbol(nextValues, Decl(file.tsx, 12, 52))
>a : Symbol(a, Decl(file.tsx, 12, 65))
>a.x : Symbol(x, Decl(file.tsx, 12, 42))
>a : Symbol(a, Decl(file.tsx, 12, 65))
>x : Symbol(x, Decl(file.tsx, 12, 42))

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
=== tests/cases/conformance/jsx/file.tsx ===
import * as React from "react";
>React : typeof React

interface BaseProps<T> {
>BaseProps : BaseProps<T>
>T : T

initialValues: T;
>initialValues : T
>T : T

nextValues: (cur: T) => T;
>nextValues : (cur: T) => T
>cur : T
>T : T
>T : T
}
declare class GenericComponent<Props = {}, Values = object> extends React.Component<Props & BaseProps<Values>, {}> {
>GenericComponent : GenericComponent<Props, Values>
>Props : Props
>Values : Values
>React.Component : React.Component<Props & BaseProps<Values>, {}>
>React : typeof React
>Component : typeof React.Component
>Props : Props
>BaseProps : BaseProps<T>
>Values : Values

iv: Values;
>iv : Values
>Values : Values
}

let a = <GenericComponent initialValues={{ x: "y" }} nextValues={a => a} />; // No error
>a : JSX.Element
><GenericComponent initialValues={{ x: "y" }} nextValues={a => a} /> : JSX.Element
>GenericComponent : typeof GenericComponent
>initialValues : { x: string; }
>{ x: "y" } : { x: string; }
>x : string
>"y" : "y"
>nextValues : (a: { x: string; }) => { x: string; }
>a => a : (a: { x: string; }) => { x: string; }
>a : { x: string; }
>a : { x: string; }

let b = <GenericComponent initialValues={12} nextValues={a => a} />; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint)
>b : JSX.Element
><GenericComponent initialValues={12} nextValues={a => a} /> : JSX.Element
>GenericComponent : typeof GenericComponent
>initialValues : number
>12 : 12
>nextValues : (a: number) => number
>a => a : (a: number) => number
>a : number
>a : number

let c = <GenericComponent initialValues={{ x: "y" }} nextValues={a => ({ x: a.x })} />; // No Error
>c : JSX.Element
><GenericComponent initialValues={{ x: "y" }} nextValues={a => ({ x: a.x })} /> : JSX.Element
>GenericComponent : typeof GenericComponent
>initialValues : { x: string; }
>{ x: "y" } : { x: string; }
>x : string
>"y" : "y"
>nextValues : (a: { x: string; }) => { x: string; }
>a => ({ x: a.x }) : (a: { x: string; }) => { x: string; }
>a : { x: string; }
>({ x: a.x }) : { x: string; }
>{ x: a.x } : { x: string; }
>x : string
>a.x : string
>a : { x: string; }
>x : string

let d = <GenericComponent initialValues={{ x: "y" }} nextValues={a => a.x} />; // Error - `string` is not assignable to `{x: string}`
>d : JSX.Element
><GenericComponent initialValues={{ x: "y" }} nextValues={a => a.x} /> : JSX.Element
>GenericComponent : typeof GenericComponent
>initialValues : { x: string; }
>{ x: "y" } : { x: string; }
>x : string
>"y" : "y"
>nextValues : (a: { x: string; }) => string
>a => a.x : (a: { x: string; }) => string
>a : { x: string; }
>a.x : string
>a : { x: string; }
>x : string

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx(20,22): error TS2322: Type '{ prop: "x"; children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'.
Type '{ prop: "x"; children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; }' is not assignable to type 'LitProps<"x">'.
Types of property 'children' are incompatible.
Type '(p: IntrinsicAttributes & LitProps<"x">) => "y"' is not assignable to type '(x: LitProps<"x">) => "x"'.
Type '"y"' is not assignable to type '"x"'.
tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx(21,27): error TS2322: Type '{ children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; prop: "x"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x" | "y">'.
Type '{ children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; prop: "x"; }' is not assignable to type 'LitProps<"x" | "y">'.
Types of property 'children' are incompatible.
Type '(p: IntrinsicAttributes & LitProps<"x">) => "y"' is not assignable to type '(x: LitProps<"x" | "y">) => "x" | "y"'.
Types of parameters 'p' and 'x' are incompatible.
Type 'LitProps<"x" | "y">' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'.
Type 'LitProps<"x" | "y">' is not assignable to type 'LitProps<"x">'.
Types of property 'prop' are incompatible.
Type '"x" | "y"' is not assignable to type '"x"'.
Type '"y"' is not assignable to type '"x"'.
tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx(22,29): error TS2322: Type '{ children: () => number; prop: "x"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'.
Type '{ children: () => number; prop: "x"; }' is not assignable to type 'LitProps<"x">'.
Types of property 'children' are incompatible.
Type '() => number' is not assignable to type '(x: LitProps<"x">) => "x"'.
Type 'number' is not assignable to type '"x"'.


==== tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx (3 errors) ====
namespace JSX {
export interface Element {}
export interface ElementAttributesProperty { props: {}; }
export interface ElementChildrenAttribute { children: {}; }
export interface IntrinsicAttributes {}
export interface IntrinsicElements { [key: string]: Element }
}
const Elem = <T,U=never>(p: { prop: T, children: (t: T) => T }) => <div></div>;
Elem({prop: {a: "x"}, children: i => ({a: "z"})});
const q = <Elem prop={{a: "x"}} children={i => ({a: "z"})} />
const qq = <Elem prop={{a: "x"}}>{i => ({a: "z"})}</Elem>

interface LitProps<T> { prop: T, children: (x: this) => T }
const ElemLit = <T extends string>(p: LitProps<T>) => <div></div>;
ElemLit({prop: "x", children: () => "x"});
const j = <ElemLit prop="x" children={() => "x"} />
const jj = <ElemLit prop="x">{() => "x"}</ElemLit>

// Should error
const arg = <ElemLit prop="x" children={p => "y"} />
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type '{ prop: "x"; children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'.
!!! error TS2322: Type '{ prop: "x"; children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; }' is not assignable to type 'LitProps<"x">'.
!!! error TS2322: Types of property 'children' are incompatible.
!!! error TS2322: Type '(p: IntrinsicAttributes & LitProps<"x">) => "y"' is not assignable to type '(x: LitProps<"x">) => "x"'.
!!! error TS2322: Type '"y"' is not assignable to type '"x"'.
const argchild = <ElemLit prop="x">{p => "y"}</ElemLit>
~~~~~~~~
!!! error TS2322: Type '{ children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; prop: "x"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x" | "y">'.
!!! error TS2322: Type '{ children: (p: IntrinsicAttributes & LitProps<"x">) => "y"; prop: "x"; }' is not assignable to type 'LitProps<"x" | "y">'.
!!! error TS2322: Types of property 'children' are incompatible.
!!! error TS2322: Type '(p: IntrinsicAttributes & LitProps<"x">) => "y"' is not assignable to type '(x: LitProps<"x" | "y">) => "x" | "y"'.
!!! error TS2322: Types of parameters 'p' and 'x' are incompatible.
!!! error TS2322: Type 'LitProps<"x" | "y">' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'.
!!! error TS2322: Type 'LitProps<"x" | "y">' is not assignable to type 'LitProps<"x">'.
!!! error TS2322: Types of property 'prop' are incompatible.
!!! error TS2322: Type '"x" | "y"' is not assignable to type '"x"'.
!!! error TS2322: Type '"y"' is not assignable to type '"x"'.
const mismatched = <ElemLit prop="x">{() => 12}</ElemLit>
~~~~~~~~
!!! error TS2322: Type '{ children: () => number; prop: "x"; }' is not assignable to type 'IntrinsicAttributes & LitProps<"x">'.
!!! error TS2322: Type '{ children: () => number; prop: "x"; }' is not assignable to type 'LitProps<"x">'.
!!! error TS2322: Types of property 'children' are incompatible.
!!! error TS2322: Type '() => number' is not assignable to type '(x: LitProps<"x">) => "x"'.
!!! error TS2322: Type 'number' is not assignable to type '"x"'.
Loading