mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-14 19:16:17 -06:00
Infer tuples for jsx children if contextually typed by a tuple (#27409)
This commit is contained in:
parent
deeb40129d
commit
e1d346ea53
@ -17189,31 +17189,14 @@ namespace ts {
|
||||
const minLength = elementCount - (hasRestElement ? 1 : 0);
|
||||
// If array literal is actually a destructuring pattern, mark it as an implied type. We do this such
|
||||
// that we get the same behavior for "var [x, y] = []" and "[x, y] = []".
|
||||
let tupleResult: Type | undefined;
|
||||
if (inDestructuringPattern && minLength > 0) {
|
||||
const type = cloneTypeReference(<TypeReference>createTupleType(elementTypes, minLength, hasRestElement));
|
||||
type.pattern = node;
|
||||
return type;
|
||||
}
|
||||
if (contextualType && contextualTypeIsTupleLikeType(contextualType)) {
|
||||
const pattern = contextualType.pattern;
|
||||
// If array literal is contextually typed by a binding pattern or an assignment pattern, pad the resulting
|
||||
// tuple type with the corresponding binding or assignment element types to make the lengths equal.
|
||||
if (!hasRestElement && pattern && (pattern.kind === SyntaxKind.ArrayBindingPattern || pattern.kind === SyntaxKind.ArrayLiteralExpression)) {
|
||||
const patternElements = (<BindingPattern | ArrayLiteralExpression>pattern).elements;
|
||||
for (let i = elementCount; i < patternElements.length; i++) {
|
||||
const e = patternElements[i];
|
||||
if (hasDefaultValue(e)) {
|
||||
elementTypes.push((<TypeReference>contextualType).typeArguments![i]);
|
||||
}
|
||||
else if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && (<BindingElement>e).dotDotDotToken || e.kind === SyntaxKind.SpreadElement)) {
|
||||
if (e.kind !== SyntaxKind.OmittedExpression) {
|
||||
error(e, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
|
||||
}
|
||||
elementTypes.push(strictNullChecks ? implicitNeverType : undefinedWideningType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return createTupleType(elementTypes, minLength, hasRestElement);
|
||||
else if (tupleResult = getArrayLiteralTupleTypeIfApplicable(elementTypes, contextualType, hasRestElement, elementCount)) {
|
||||
return tupleResult;
|
||||
}
|
||||
else if (forceTuple) {
|
||||
return createTupleType(elementTypes, minLength, hasRestElement);
|
||||
@ -17222,6 +17205,31 @@ namespace ts {
|
||||
return getArrayLiteralType(elementTypes, UnionReduction.Subtype);
|
||||
}
|
||||
|
||||
function getArrayLiteralTupleTypeIfApplicable(elementTypes: Type[], contextualType: Type | undefined, hasRestElement: boolean, elementCount = elementTypes.length) {
|
||||
if (contextualType && contextualTypeIsTupleLikeType(contextualType)) {
|
||||
const minLength = elementCount - (hasRestElement ? 1 : 0);
|
||||
const pattern = contextualType.pattern;
|
||||
// If array literal is contextually typed by a binding pattern or an assignment pattern, pad the resulting
|
||||
// tuple type with the corresponding binding or assignment element types to make the lengths equal.
|
||||
if (!hasRestElement && pattern && (pattern.kind === SyntaxKind.ArrayBindingPattern || pattern.kind === SyntaxKind.ArrayLiteralExpression)) {
|
||||
const patternElements = (<BindingPattern | ArrayLiteralExpression>pattern).elements;
|
||||
for (let i = elementCount; i < patternElements.length; i++) {
|
||||
const e = patternElements[i];
|
||||
if (hasDefaultValue(e)) {
|
||||
elementTypes.push((<TypeReference>contextualType).typeArguments![i]);
|
||||
}
|
||||
else if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && (<BindingElement>e).dotDotDotToken || e.kind === SyntaxKind.SpreadElement)) {
|
||||
if (e.kind !== SyntaxKind.OmittedExpression) {
|
||||
error(e, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
|
||||
}
|
||||
elementTypes.push(strictNullChecks ? implicitNeverType : undefinedWideningType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return createTupleType(elementTypes, minLength, hasRestElement);
|
||||
}
|
||||
}
|
||||
|
||||
function getArrayLiteralType(elementTypes: Type[], unionReduction = UnionReduction.Literal) {
|
||||
return createArrayType(elementTypes.length ?
|
||||
getUnionType(elementTypes, unionReduction) :
|
||||
@ -17637,11 +17645,13 @@ namespace ts {
|
||||
error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName));
|
||||
}
|
||||
|
||||
const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes);
|
||||
const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName);
|
||||
// If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process
|
||||
const childrenPropSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, jsxChildrenPropertyName);
|
||||
childrenPropSymbol.type = childrenTypes.length === 1 ?
|
||||
childrenTypes[0] :
|
||||
createArrayType(getUnionType(childrenTypes));
|
||||
(getArrayLiteralTupleTypeIfApplicable(childrenTypes, childrenContextualType, /*hasRestElement*/ false) || createArrayType(getUnionType(childrenTypes)));
|
||||
const childPropMap = createSymbolTable();
|
||||
childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol);
|
||||
spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined),
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
tests/cases/conformance/jsx/checkJsxChildrenCanBeTupleType.tsx(17,18): error TS2322: Type '{ children: [Element, Element, Element]; }' is not assignable to type 'Readonly<ResizablePanelProps>'.
|
||||
Types of property 'children' are incompatible.
|
||||
Type '[Element, Element, Element]' is not assignable to type '[ReactNode, ReactNode]'.
|
||||
Types of property 'length' are incompatible.
|
||||
Type '3' is not assignable to type '2'.
|
||||
|
||||
|
||||
==== tests/cases/conformance/jsx/checkJsxChildrenCanBeTupleType.tsx (1 errors) ====
|
||||
/// <reference path="/.lib/react16.d.ts" />
|
||||
|
||||
import React from 'react'
|
||||
|
||||
interface ResizablePanelProps {
|
||||
children: [React.ReactNode, React.ReactNode]
|
||||
}
|
||||
|
||||
class ResizablePanel extends React.Component<
|
||||
ResizablePanelProps, any> {}
|
||||
|
||||
const test = <ResizablePanel>
|
||||
<div />
|
||||
<div />
|
||||
</ResizablePanel>
|
||||
|
||||
const testErr = <ResizablePanel>
|
||||
~~~~~~~~~~~~~~
|
||||
!!! error TS2322: Type '{ children: [Element, Element, Element]; }' is not assignable to type 'Readonly<ResizablePanelProps>'.
|
||||
!!! error TS2322: Types of property 'children' are incompatible.
|
||||
!!! error TS2322: Type '[Element, Element, Element]' is not assignable to type '[ReactNode, ReactNode]'.
|
||||
!!! error TS2322: Types of property 'length' are incompatible.
|
||||
!!! error TS2322: Type '3' is not assignable to type '2'.
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</ResizablePanel>
|
||||
58
tests/baselines/reference/checkJsxChildrenCanBeTupleType.js
Normal file
58
tests/baselines/reference/checkJsxChildrenCanBeTupleType.js
Normal file
@ -0,0 +1,58 @@
|
||||
//// [checkJsxChildrenCanBeTupleType.tsx]
|
||||
/// <reference path="/.lib/react16.d.ts" />
|
||||
|
||||
import React from 'react'
|
||||
|
||||
interface ResizablePanelProps {
|
||||
children: [React.ReactNode, React.ReactNode]
|
||||
}
|
||||
|
||||
class ResizablePanel extends React.Component<
|
||||
ResizablePanelProps, any> {}
|
||||
|
||||
const test = <ResizablePanel>
|
||||
<div />
|
||||
<div />
|
||||
</ResizablePanel>
|
||||
|
||||
const testErr = <ResizablePanel>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</ResizablePanel>
|
||||
|
||||
//// [checkJsxChildrenCanBeTupleType.js]
|
||||
"use strict";
|
||||
/// <reference path="react16.d.ts" />
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
}
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
exports.__esModule = true;
|
||||
var react_1 = __importDefault(require("react"));
|
||||
var ResizablePanel = /** @class */ (function (_super) {
|
||||
__extends(ResizablePanel, _super);
|
||||
function ResizablePanel() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
return ResizablePanel;
|
||||
}(react_1["default"].Component));
|
||||
var test = react_1["default"].createElement(ResizablePanel, null,
|
||||
react_1["default"].createElement("div", null),
|
||||
react_1["default"].createElement("div", null));
|
||||
var testErr = react_1["default"].createElement(ResizablePanel, null,
|
||||
react_1["default"].createElement("div", null),
|
||||
react_1["default"].createElement("div", null),
|
||||
react_1["default"].createElement("div", null));
|
||||
@ -0,0 +1,55 @@
|
||||
=== tests/cases/conformance/jsx/checkJsxChildrenCanBeTupleType.tsx ===
|
||||
/// <reference path="react16.d.ts" />
|
||||
|
||||
import React from 'react'
|
||||
>React : Symbol(React, Decl(checkJsxChildrenCanBeTupleType.tsx, 2, 6))
|
||||
|
||||
interface ResizablePanelProps {
|
||||
>ResizablePanelProps : Symbol(ResizablePanelProps, Decl(checkJsxChildrenCanBeTupleType.tsx, 2, 25))
|
||||
|
||||
children: [React.ReactNode, React.ReactNode]
|
||||
>children : Symbol(ResizablePanelProps.children, Decl(checkJsxChildrenCanBeTupleType.tsx, 4, 31))
|
||||
>React : Symbol(React, Decl(checkJsxChildrenCanBeTupleType.tsx, 2, 6))
|
||||
>ReactNode : Symbol(React.ReactNode, Decl(react16.d.ts, 216, 49))
|
||||
>React : Symbol(React, Decl(checkJsxChildrenCanBeTupleType.tsx, 2, 6))
|
||||
>ReactNode : Symbol(React.ReactNode, Decl(react16.d.ts, 216, 49))
|
||||
}
|
||||
|
||||
class ResizablePanel extends React.Component<
|
||||
>ResizablePanel : Symbol(ResizablePanel, Decl(checkJsxChildrenCanBeTupleType.tsx, 6, 1))
|
||||
>React.Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
|
||||
>React : Symbol(React, Decl(checkJsxChildrenCanBeTupleType.tsx, 2, 6))
|
||||
>Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
|
||||
|
||||
ResizablePanelProps, any> {}
|
||||
>ResizablePanelProps : Symbol(ResizablePanelProps, Decl(checkJsxChildrenCanBeTupleType.tsx, 2, 25))
|
||||
|
||||
const test = <ResizablePanel>
|
||||
>test : Symbol(test, Decl(checkJsxChildrenCanBeTupleType.tsx, 11, 5))
|
||||
>ResizablePanel : Symbol(ResizablePanel, Decl(checkJsxChildrenCanBeTupleType.tsx, 6, 1))
|
||||
|
||||
<div />
|
||||
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
|
||||
|
||||
<div />
|
||||
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
|
||||
|
||||
</ResizablePanel>
|
||||
>ResizablePanel : Symbol(ResizablePanel, Decl(checkJsxChildrenCanBeTupleType.tsx, 6, 1))
|
||||
|
||||
const testErr = <ResizablePanel>
|
||||
>testErr : Symbol(testErr, Decl(checkJsxChildrenCanBeTupleType.tsx, 16, 5))
|
||||
>ResizablePanel : Symbol(ResizablePanel, Decl(checkJsxChildrenCanBeTupleType.tsx, 6, 1))
|
||||
|
||||
<div />
|
||||
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
|
||||
|
||||
<div />
|
||||
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
|
||||
|
||||
<div />
|
||||
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
|
||||
|
||||
</ResizablePanel>
|
||||
>ResizablePanel : Symbol(ResizablePanel, Decl(checkJsxChildrenCanBeTupleType.tsx, 6, 1))
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
=== tests/cases/conformance/jsx/checkJsxChildrenCanBeTupleType.tsx ===
|
||||
/// <reference path="react16.d.ts" />
|
||||
|
||||
import React from 'react'
|
||||
>React : typeof React
|
||||
|
||||
interface ResizablePanelProps {
|
||||
children: [React.ReactNode, React.ReactNode]
|
||||
>children : [React.ReactNode, React.ReactNode]
|
||||
>React : any
|
||||
>React : any
|
||||
}
|
||||
|
||||
class ResizablePanel extends React.Component<
|
||||
>ResizablePanel : ResizablePanel
|
||||
>React.Component : React.Component<ResizablePanelProps, any, any>
|
||||
>React : typeof React
|
||||
>Component : typeof React.Component
|
||||
|
||||
ResizablePanelProps, any> {}
|
||||
|
||||
const test = <ResizablePanel>
|
||||
>test : JSX.Element
|
||||
><ResizablePanel> <div /> <div /></ResizablePanel> : JSX.Element
|
||||
>ResizablePanel : typeof ResizablePanel
|
||||
|
||||
<div />
|
||||
><div /> : JSX.Element
|
||||
>div : any
|
||||
|
||||
<div />
|
||||
><div /> : JSX.Element
|
||||
>div : any
|
||||
|
||||
</ResizablePanel>
|
||||
>ResizablePanel : typeof ResizablePanel
|
||||
|
||||
const testErr = <ResizablePanel>
|
||||
>testErr : JSX.Element
|
||||
><ResizablePanel> <div /> <div /> <div /></ResizablePanel> : JSX.Element
|
||||
>ResizablePanel : typeof ResizablePanel
|
||||
|
||||
<div />
|
||||
><div /> : JSX.Element
|
||||
>div : any
|
||||
|
||||
<div />
|
||||
><div /> : JSX.Element
|
||||
>div : any
|
||||
|
||||
<div />
|
||||
><div /> : JSX.Element
|
||||
>div : any
|
||||
|
||||
</ResizablePanel>
|
||||
>ResizablePanel : typeof ResizablePanel
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
// @jsx: react
|
||||
// @strict: true
|
||||
// @esModuleInterop: true
|
||||
/// <reference path="/.lib/react16.d.ts" />
|
||||
|
||||
import React from 'react'
|
||||
|
||||
interface ResizablePanelProps {
|
||||
children: [React.ReactNode, React.ReactNode]
|
||||
}
|
||||
|
||||
class ResizablePanel extends React.Component<
|
||||
ResizablePanelProps, any> {}
|
||||
|
||||
const test = <ResizablePanel>
|
||||
<div />
|
||||
<div />
|
||||
</ResizablePanel>
|
||||
|
||||
const testErr = <ResizablePanel>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</ResizablePanel>
|
||||
Loading…
x
Reference in New Issue
Block a user