Skip to content

Commit 0c36266

Browse files
authored
Obey the excludeArgument parameter when checking JSX signature validity (#28002)
* Obey the excludeArgument parameter when checking JSX signature validity * Fix conditional type extending any contextual types and accept baselines * use flag check to also drop unknown from comparison for the same reason * Slight refinement - make an intersection to ensure parameter constraints flow through contextual types when instantiated * Format ternary more nicely
1 parent f701daf commit 0c36266

11 files changed

+272
-21
lines changed

src/compiler/checker.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7083,7 +7083,11 @@ namespace ts {
70837083
function getDefaultConstraintOfConditionalType(type: ConditionalType) {
70847084
if (!type.resolvedDefaultConstraint) {
70857085
const rootTrueType = type.root.trueType;
7086-
const rootTrueConstraint = rootTrueType.flags & TypeFlags.Substitution ? (<SubstitutionType>rootTrueType).substitute : rootTrueType;
7086+
const rootTrueConstraint = !(rootTrueType.flags & TypeFlags.Substitution)
7087+
? rootTrueType
7088+
: ((<SubstitutionType>rootTrueType).substitute).flags & TypeFlags.AnyOrUnknown
7089+
? (<SubstitutionType>rootTrueType).typeVariable
7090+
: getIntersectionType([(<SubstitutionType>rootTrueType).substitute, (<SubstitutionType>rootTrueType).typeVariable]);
70877091
type.resolvedDefaultConstraint = getUnionType([instantiateType(rootTrueConstraint, type.combinedMapper || type.mapper), getFalseTypeFromConditionalType(type)]);
70887092
}
70897093
return type.resolvedDefaultConstraint;
@@ -19163,12 +19167,12 @@ namespace ts {
1916319167
* @param relation a relationship to check parameter and argument type
1916419168
* @param excludeArgument
1916519169
*/
19166-
function checkApplicableSignatureForJsxOpeningLikeElement(node: JsxOpeningLikeElement, signature: Signature, relation: Map<RelationComparisonResult>, reportErrors: boolean) {
19170+
function checkApplicableSignatureForJsxOpeningLikeElement(node: JsxOpeningLikeElement, signature: Signature, relation: Map<RelationComparisonResult>, excludeArgument: boolean[] | undefined, reportErrors: boolean) {
1916719171
// Stateless function components can have maximum of three arguments: "props", "context", and "updater".
1916819172
// However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props,
1916919173
// can be specified by users through attributes property.
1917019174
const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node);
19171-
const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*contextualMapper*/ undefined);
19175+
const attributesType = checkExpressionWithContextualType(node.attributes, paramType, excludeArgument && excludeArgument[0] ? identityMapper : undefined);
1917219176
return checkTypeRelatedToAndOptionallyElaborate(attributesType, paramType, relation, reportErrors ? node.tagName : undefined, node.attributes);
1917319177
}
1917419178

@@ -19180,7 +19184,7 @@ namespace ts {
1918019184
excludeArgument: boolean[] | undefined,
1918119185
reportErrors: boolean) {
1918219186
if (isJsxOpeningLikeElement(node)) {
19183-
return checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, reportErrors);
19187+
return checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, excludeArgument, reportErrors);
1918419188
}
1918519189
const thisType = getThisTypeOfSignature(signature);
1918619190
if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) {

tests/baselines/reference/checkJsxGenericTagHasCorrectInferences.errors.txt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
tests/cases/conformance/jsx/file.tsx(13,71): error TS2322: Type 'string' is not assignable to type '{ x: string; }'.
1+
tests/cases/conformance/jsx/file.tsx(13,54): error TS2322: Type '(a: { x: string; }) => string' is not assignable to type '((a: { x: string; }) => string) & ((cur: { x: string; }) => { x: string; })'.
2+
Type '(a: { x: string; }) => string' is not assignable to type '(cur: { x: string; }) => { x: string; }'.
3+
Type 'string' is not assignable to type '{ x: string; }'.
24

35

46
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
@@ -15,6 +17,8 @@ tests/cases/conformance/jsx/file.tsx(13,71): error TS2322: Type 'string' is not
1517
let b = <GenericComponent initialValues={12} nextValues={a => a} />; // No error - Values should be reinstantiated with `number` (since `object` is a default, not a constraint)
1618
let c = <GenericComponent initialValues={{ x: "y" }} nextValues={a => ({ x: a.x })} />; // No Error
1719
let d = <GenericComponent initialValues={{ x: "y" }} nextValues={a => a.x} />; // Error - `string` is not assignable to `{x: string}`
18-
~~~
19-
!!! error TS2322: Type 'string' is not assignable to type '{ x: string; }'.
20-
!!! related TS6502 tests/cases/conformance/jsx/file.tsx:4:15: The expected type comes from the return type of this signature.
20+
~~~~~~~~~~
21+
!!! error TS2322: Type '(a: { x: string; }) => string' is not assignable to type '((a: { x: string; }) => string) & ((cur: { x: string; }) => { x: string; })'.
22+
!!! error TS2322: Type '(a: { x: string; }) => string' is not assignable to type '(cur: { x: string; }) => { x: string; }'.
23+
!!! error TS2322: Type 'string' is not assignable to type '{ x: string; }'.
24+
!!! related TS6500 tests/cases/conformance/jsx/file.tsx:13:54: The expected type comes from property 'nextValues' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes<GenericComponent<{ initialValues: { x: string; }; nextValues: (a: { x: string; }) => string; }, { x: string; }>> & { initialValues: { x: string; }; nextValues: (a: { x: string; }) => string; } & BaseProps<{ x: string; }> & { children?: ReactNode; }'
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//// [checkJsxSubtleSkipContextSensitiveBug.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
import * as React from "react";
4+
5+
interface ErrorResult { error: true }
6+
7+
interface AsyncLoaderProps<TResult> {
8+
readonly prop1: () => Promise<TResult>;
9+
10+
readonly prop2: (result: Exclude<TResult, ErrorResult>) => any;
11+
}
12+
13+
class AsyncLoader<TResult> extends React.Component<AsyncLoaderProps<TResult>> {
14+
render() { return null; }
15+
}
16+
17+
async function load(): Promise<{ success: true } | ErrorResult> {
18+
return { success: true };
19+
}
20+
21+
const loader = <AsyncLoader
22+
prop1={load}
23+
prop2={result => result}
24+
/>;
25+
26+
27+
//// [checkJsxSubtleSkipContextSensitiveBug.js]
28+
"use strict";
29+
var __extends = (this && this.__extends) || (function () {
30+
var extendStatics = function (d, b) {
31+
extendStatics = Object.setPrototypeOf ||
32+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
33+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
34+
return extendStatics(d, b);
35+
};
36+
return function (d, b) {
37+
extendStatics(d, b);
38+
function __() { this.constructor = d; }
39+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
40+
};
41+
})();
42+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
43+
return new (P || (P = Promise))(function (resolve, reject) {
44+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
45+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
46+
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
47+
step((generator = generator.apply(thisArg, _arguments || [])).next());
48+
});
49+
};
50+
var __generator = (this && this.__generator) || function (thisArg, body) {
51+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
52+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
53+
function verb(n) { return function (v) { return step([n, v]); }; }
54+
function step(op) {
55+
if (f) throw new TypeError("Generator is already executing.");
56+
while (_) try {
57+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
58+
if (y = 0, t) op = [op[0] & 2, t.value];
59+
switch (op[0]) {
60+
case 0: case 1: t = op; break;
61+
case 4: _.label++; return { value: op[1], done: false };
62+
case 5: _.label++; y = op[1]; op = [0]; continue;
63+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
64+
default:
65+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
66+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
67+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
68+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
69+
if (t[2]) _.ops.pop();
70+
_.trys.pop(); continue;
71+
}
72+
op = body.call(thisArg, _);
73+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
74+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
75+
}
76+
};
77+
exports.__esModule = true;
78+
/// <reference path="react16.d.ts" />
79+
var React = require("react");
80+
var AsyncLoader = /** @class */ (function (_super) {
81+
__extends(AsyncLoader, _super);
82+
function AsyncLoader() {
83+
return _super !== null && _super.apply(this, arguments) || this;
84+
}
85+
AsyncLoader.prototype.render = function () { return null; };
86+
return AsyncLoader;
87+
}(React.Component));
88+
function load() {
89+
return __awaiter(this, void 0, void 0, function () {
90+
return __generator(this, function (_a) {
91+
return [2 /*return*/, { success: true }];
92+
});
93+
});
94+
}
95+
var loader = React.createElement(AsyncLoader, { prop1: load, prop2: function (result) { return result; } });
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
=== tests/cases/conformance/jsx/checkJsxSubtleSkipContextSensitiveBug.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import * as React from "react";
4+
>React : Symbol(React, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 1, 6))
5+
6+
interface ErrorResult { error: true }
7+
>ErrorResult : Symbol(ErrorResult, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 1, 31))
8+
>error : Symbol(ErrorResult.error, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 3, 23))
9+
10+
interface AsyncLoaderProps<TResult> {
11+
>AsyncLoaderProps : Symbol(AsyncLoaderProps, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 3, 37))
12+
>TResult : Symbol(TResult, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 5, 27))
13+
14+
readonly prop1: () => Promise<TResult>;
15+
>prop1 : Symbol(AsyncLoaderProps.prop1, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 5, 37))
16+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
17+
>TResult : Symbol(TResult, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 5, 27))
18+
19+
readonly prop2: (result: Exclude<TResult, ErrorResult>) => any;
20+
>prop2 : Symbol(AsyncLoaderProps.prop2, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 6, 43))
21+
>result : Symbol(result, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 8, 21))
22+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
23+
>TResult : Symbol(TResult, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 5, 27))
24+
>ErrorResult : Symbol(ErrorResult, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 1, 31))
25+
}
26+
27+
class AsyncLoader<TResult> extends React.Component<AsyncLoaderProps<TResult>> {
28+
>AsyncLoader : Symbol(AsyncLoader, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 9, 1))
29+
>TResult : Symbol(TResult, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 11, 18))
30+
>React.Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
31+
>React : Symbol(React, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 1, 6))
32+
>Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
33+
>AsyncLoaderProps : Symbol(AsyncLoaderProps, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 3, 37))
34+
>TResult : Symbol(TResult, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 11, 18))
35+
36+
render() { return null; }
37+
>render : Symbol(AsyncLoader.render, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 11, 79))
38+
}
39+
40+
async function load(): Promise<{ success: true } | ErrorResult> {
41+
>load : Symbol(load, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 13, 1))
42+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
43+
>success : Symbol(success, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 15, 32))
44+
>ErrorResult : Symbol(ErrorResult, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 1, 31))
45+
46+
return { success: true };
47+
>success : Symbol(success, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 16, 12))
48+
}
49+
50+
const loader = <AsyncLoader
51+
>loader : Symbol(loader, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 19, 5))
52+
>AsyncLoader : Symbol(AsyncLoader, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 9, 1))
53+
54+
prop1={load}
55+
>prop1 : Symbol(prop1, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 19, 27))
56+
>load : Symbol(load, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 13, 1))
57+
58+
prop2={result => result}
59+
>prop2 : Symbol(prop2, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 20, 16))
60+
>result : Symbol(result, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 21, 11))
61+
>result : Symbol(result, Decl(checkJsxSubtleSkipContextSensitiveBug.tsx, 21, 11))
62+
63+
/>;
64+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
=== tests/cases/conformance/jsx/checkJsxSubtleSkipContextSensitiveBug.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import * as React from "react";
4+
>React : typeof React
5+
6+
interface ErrorResult { error: true }
7+
>error : true
8+
>true : true
9+
10+
interface AsyncLoaderProps<TResult> {
11+
readonly prop1: () => Promise<TResult>;
12+
>prop1 : () => Promise<TResult>
13+
14+
readonly prop2: (result: Exclude<TResult, ErrorResult>) => any;
15+
>prop2 : (result: Exclude<TResult, ErrorResult>) => any
16+
>result : Exclude<TResult, ErrorResult>
17+
}
18+
19+
class AsyncLoader<TResult> extends React.Component<AsyncLoaderProps<TResult>> {
20+
>AsyncLoader : AsyncLoader<TResult>
21+
>React.Component : React.Component<AsyncLoaderProps<TResult>, {}, any>
22+
>React : typeof React
23+
>Component : typeof React.Component
24+
25+
render() { return null; }
26+
>render : () => null
27+
>null : null
28+
}
29+
30+
async function load(): Promise<{ success: true } | ErrorResult> {
31+
>load : () => Promise<ErrorResult | { success: true; }>
32+
>success : true
33+
>true : true
34+
35+
return { success: true };
36+
>{ success: true } : { success: true; }
37+
>success : true
38+
>true : true
39+
}
40+
41+
const loader = <AsyncLoader
42+
>loader : JSX.Element
43+
><AsyncLoader prop1={load} prop2={result => result}/> : JSX.Element
44+
>AsyncLoader : typeof AsyncLoader
45+
46+
prop1={load}
47+
>prop1 : () => Promise<ErrorResult | { success: true; }>
48+
>load : () => Promise<ErrorResult | { success: true; }>
49+
50+
prop2={result => result}
51+
>prop2 : (result: { success: true; }) => { success: true; }
52+
>result => result : (result: { success: true; }) => { success: true; }
53+
>result : { success: true; }
54+
>result : { success: true; }
55+
56+
/>;
57+

tests/baselines/reference/contextuallyTypedStringLiteralsInJsxAttributes02.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
tests/cases/conformance/types/contextualTypes/jsxAttributes/file.tsx(27,13): error TS2322: Type '{ extra: true; onClick: (k: "left" | "right") => void; }' is not assignable to type 'IntrinsicAttributes & LinkProps'.
22
Property 'extra' does not exist on type 'IntrinsicAttributes & LinkProps'.
3-
tests/cases/conformance/types/contextualTypes/jsxAttributes/file.tsx(28,13): error TS2322: Type '{ onClick: (k: "left" | "right") => void; extra: true; }' is not assignable to type 'IntrinsicAttributes & LinkProps'.
3+
tests/cases/conformance/types/contextualTypes/jsxAttributes/file.tsx(28,13): error TS2322: Type '{ onClick: (k: any) => void; extra: true; }' is not assignable to type 'IntrinsicAttributes & LinkProps'.
44
Property 'onClick' does not exist on type 'IntrinsicAttributes & LinkProps'.
55
tests/cases/conformance/types/contextualTypes/jsxAttributes/file.tsx(29,13): error TS2322: Type '{ extra: true; goTo: "home"; }' is not assignable to type 'IntrinsicAttributes & LinkProps'.
66
Property 'extra' does not exist on type 'IntrinsicAttributes & LinkProps'.
@@ -45,7 +45,7 @@ tests/cases/conformance/types/contextualTypes/jsxAttributes/file.tsx(36,13): err
4545
!!! error TS2322: Property 'extra' does not exist on type 'IntrinsicAttributes & LinkProps'.
4646
const b2 = <MainButton onClick={(k)=>{console.log(k)}} extra />; // k has type "left" | "right"
4747
~~~~~~~~~~
48-
!!! error TS2322: Type '{ onClick: (k: "left" | "right") => void; extra: true; }' is not assignable to type 'IntrinsicAttributes & LinkProps'.
48+
!!! error TS2322: Type '{ onClick: (k: any) => void; extra: true; }' is not assignable to type 'IntrinsicAttributes & LinkProps'.
4949
!!! error TS2322: Property 'onClick' does not exist on type 'IntrinsicAttributes & LinkProps'.
5050
const b3 = <MainButton {...{goTo:"home"}} extra />; // goTo has type"home" | "contact"
5151
~~~~~~~~~~

tests/baselines/reference/contextuallyTypedStringLiteralsInJsxAttributes02.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ const b2 = <MainButton onClick={(k)=>{console.log(k)}} extra />; // k has type
8181
>b2 : JSX.Element
8282
><MainButton onClick={(k)=>{console.log(k)}} extra /> : JSX.Element
8383
>MainButton : { (buttonProps: ButtonProps): JSX.Element; (linkProps: LinkProps): JSX.Element; }
84-
>onClick : (k: "left" | "right") => void
85-
>(k)=>{console.log(k)} : (k: "left" | "right") => void
86-
>k : "left" | "right"
84+
>onClick : (k: any) => void
85+
>(k)=>{console.log(k)} : (k: any) => void
86+
>k : any
8787
>console.log(k) : void
8888
>console.log : (message?: any, ...optionalParams: any[]) => void
8989
>console : Console
9090
>log : (message?: any, ...optionalParams: any[]) => void
91-
>k : "left" | "right"
91+
>k : any
9292
>extra : true
9393

9494
const b3 = <MainButton {...{goTo:"home"}} extra />; // goTo has type"home" | "contact"

0 commit comments

Comments
 (0)