feat(36908): add 'property overwritten by spread' error for jsx attributes. add related span for conflicting declaration in spread (#37329)

This commit is contained in:
Alexander T
2020-03-17 13:13:38 -07:00
committed by GitHub
parent b0450aed56
commit f1eb9898fb
10 changed files with 362 additions and 14 deletions
+20 -13
View File
@@ -22684,8 +22684,8 @@ namespace ts {
// Grammar checking
checkGrammarObjectLiteralExpression(node, inDestructuringPattern);
let propertiesTable: SymbolTable;
const allPropertiesTable = createSymbolTable();
const allPropertiesTable = strictNullChecks ? createSymbolTable() : undefined;
let propertiesTable = createSymbolTable();
let propertiesArray: Symbol[] = [];
let spread: Type = emptyObjectType;
@@ -22701,7 +22701,6 @@ namespace ts {
let patternWithComputedProperties = false;
let hasComputedStringProperty = false;
let hasComputedNumberProperty = false;
propertiesTable = createSymbolTable();
let offset = 0;
for (let i = 0; i < node.properties.length; i++) {
@@ -22767,7 +22766,7 @@ namespace ts {
prop.type = type;
prop.target = member;
member = prop;
allPropertiesTable.set(prop.escapedName, prop);
allPropertiesTable?.set(prop.escapedName, prop);
}
else if (memberDecl.kind === SyntaxKind.SpreadAssignment) {
if (languageVersion < ScriptTarget.ES2015) {
@@ -22785,16 +22784,9 @@ namespace ts {
error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types);
return errorType;
}
for (const right of getPropertiesOfType(type)) {
const rightType = getTypeOfSymbol(right);
const left = allPropertiesTable.get(right.escapedName);
if (strictNullChecks &&
left &&
!maybeTypeOfKind(rightType, TypeFlags.Nullable)) {
error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName));
}
if (allPropertiesTable) {
checkSpreadPropOverrides(type, allPropertiesTable, memberDecl);
}
spread = getSpreadType(spread, type, node.symbol, objectFlags, inConstContext);
offset = i + 1;
continue;
@@ -22965,6 +22957,7 @@ namespace ts {
*/
function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) {
const attributes = openingLikeElement.attributes;
const allAttributesTable = strictNullChecks ? createSymbolTable() : undefined;
let attributesTable = createSymbolTable();
let spread: Type = emptyJsxObjectType;
let hasSpreadAnyType = false;
@@ -22988,6 +22981,7 @@ namespace ts {
attributeSymbol.type = exprType;
attributeSymbol.target = member;
attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol);
if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
explicitlySpecifyChildrenAttribute = true;
}
@@ -23004,6 +22998,9 @@ namespace ts {
}
if (isValidSpreadType(exprType)) {
spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false);
if (allAttributesTable) {
checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl);
}
}
else {
typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType;
@@ -23088,6 +23085,16 @@ namespace ts {
return childrenTypes;
}
function checkSpreadPropOverrides(type: Type, props: SymbolTable, spread: SpreadAssignment | JsxSpreadAttribute) {
for (const right of getPropertiesOfType(type)) {
const left = props.get(right.escapedName);
if (left && !maybeTypeOfKind(getTypeOfSymbol(right), TypeFlags.Nullable)) {
const diagnostic = error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName));
addRelatedInfo(diagnostic, createDiagnosticForNode(spread, Diagnostics.This_spread_always_overwrites_this_property));
}
}
}
/**
* Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element.
* (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used)
+4
View File
@@ -2907,6 +2907,10 @@
"category": "Error",
"code": 2784
},
"This spread always overwrites this property.": {
"category": "Error",
"code": 2785
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
@@ -0,0 +1,62 @@
error TS2318: Cannot find global type 'CallableFunction'.
error TS2318: Cannot find global type 'NewableFunction'.
tests/cases/conformance/jsx/file.tsx(19,17): error TS2783: 'a' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/jsx/file.tsx(20,17): error TS2783: 'a' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/jsx/file.tsx(20,23): error TS2783: 'b' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/jsx/file.tsx(21,17): error TS2783: 'a' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/jsx/file.tsx(21,23): error TS2783: 'd' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/jsx/file.tsx(22,17): error TS2783: 'a' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/jsx/file.tsx(22,17): error TS2783: 'a' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/jsx/file.tsx(22,23): error TS2783: 'd' is specified more than once, so this usage will be overwritten.
!!! error TS2318: Cannot find global type 'CallableFunction'.
!!! error TS2318: Cannot find global type 'NewableFunction'.
==== tests/cases/conformance/jsx/file.tsx (8 errors) ====
import React = require('react');
interface Props {
a: number;
b: number;
c?: number;
d?: number;
}
const props: Props = { a: 1, b: 1 };
const Foo = (props: Props) => <div>{ props.a }</div>;
// ok
const a1 = <Foo {...props}></Foo>;
const a2 = <Foo d={1} {...props}></Foo>;
// error
const b1 = <Foo a={1} {...props}></Foo>;
~~~~~
!!! error TS2783: 'a' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/jsx/file.tsx:19:23: This spread always overwrites this property.
const b2 = <Foo a={1} b={2} {...props}></Foo>;
~~~~~
!!! error TS2783: 'a' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/jsx/file.tsx:20:29: This spread always overwrites this property.
~~~~~
!!! error TS2783: 'b' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/jsx/file.tsx:20:29: This spread always overwrites this property.
const b3 = <Foo a={1} d={1} {...props} {...{ d: 1 }}></Foo>;
~~~~~
!!! error TS2783: 'a' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/jsx/file.tsx:21:29: This spread always overwrites this property.
~~~~~
!!! error TS2783: 'd' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/jsx/file.tsx:21:40: This spread always overwrites this property.
const b4 = <Foo a={1} d={1} {...props} {...{ a: 1, d: 1 }}></Foo>;
~~~~~
!!! error TS2783: 'a' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/jsx/file.tsx:22:29: This spread always overwrites this property.
~~~~~
!!! error TS2783: 'a' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/jsx/file.tsx:22:40: This spread always overwrites this property.
~~~~~
!!! error TS2783: 'd' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/jsx/file.tsx:22:40: This spread always overwrites this property.
@@ -0,0 +1,39 @@
//// [file.tsx]
import React = require('react');
interface Props {
a: number;
b: number;
c?: number;
d?: number;
}
const props: Props = { a: 1, b: 1 };
const Foo = (props: Props) => <div>{ props.a }</div>;
// ok
const a1 = <Foo {...props}></Foo>;
const a2 = <Foo d={1} {...props}></Foo>;
// error
const b1 = <Foo a={1} {...props}></Foo>;
const b2 = <Foo a={1} b={2} {...props}></Foo>;
const b3 = <Foo a={1} d={1} {...props} {...{ d: 1 }}></Foo>;
const b4 = <Foo a={1} d={1} {...props} {...{ a: 1, d: 1 }}></Foo>;
//// [file.jsx]
"use strict";
exports.__esModule = true;
var React = require("react");
var props = { a: 1, b: 1 };
var Foo = function (props) { return <div>{props.a}</div>; };
// ok
var a1 = <Foo {...props}></Foo>;
var a2 = <Foo d={1} {...props}></Foo>;
// error
var b1 = <Foo a={1} {...props}></Foo>;
var b2 = <Foo a={1} b={2} {...props}></Foo>;
var b3 = <Foo a={1} d={1} {...props} {...{ d: 1 }}></Foo>;
var b4 = <Foo a={1} d={1} {...props} {...{ a: 1, d: 1 }}></Foo>;
@@ -0,0 +1,86 @@
=== tests/cases/conformance/jsx/file.tsx ===
import React = require('react');
>React : Symbol(React, Decl(file.tsx, 0, 0))
interface Props {
>Props : Symbol(Props, Decl(file.tsx, 0, 32))
a: number;
>a : Symbol(Props.a, Decl(file.tsx, 2, 17))
b: number;
>b : Symbol(Props.b, Decl(file.tsx, 3, 14))
c?: number;
>c : Symbol(Props.c, Decl(file.tsx, 4, 14))
d?: number;
>d : Symbol(Props.d, Decl(file.tsx, 5, 15))
}
const props: Props = { a: 1, b: 1 };
>props : Symbol(props, Decl(file.tsx, 10, 5))
>Props : Symbol(Props, Decl(file.tsx, 0, 32))
>a : Symbol(a, Decl(file.tsx, 10, 22))
>b : Symbol(b, Decl(file.tsx, 10, 28))
const Foo = (props: Props) => <div>{ props.a }</div>;
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
>props : Symbol(props, Decl(file.tsx, 11, 13))
>Props : Symbol(Props, Decl(file.tsx, 0, 32))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2400, 45))
>props.a : Symbol(Props.a, Decl(file.tsx, 2, 17))
>props : Symbol(props, Decl(file.tsx, 11, 13))
>a : Symbol(Props.a, Decl(file.tsx, 2, 17))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2400, 45))
// ok
const a1 = <Foo {...props}></Foo>;
>a1 : Symbol(a1, Decl(file.tsx, 14, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
>props : Symbol(props, Decl(file.tsx, 10, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
const a2 = <Foo d={1} {...props}></Foo>;
>a2 : Symbol(a2, Decl(file.tsx, 15, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
>d : Symbol(d, Decl(file.tsx, 15, 15))
>props : Symbol(props, Decl(file.tsx, 10, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
// error
const b1 = <Foo a={1} {...props}></Foo>;
>b1 : Symbol(b1, Decl(file.tsx, 18, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
>a : Symbol(a, Decl(file.tsx, 18, 15))
>props : Symbol(props, Decl(file.tsx, 10, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
const b2 = <Foo a={1} b={2} {...props}></Foo>;
>b2 : Symbol(b2, Decl(file.tsx, 19, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
>a : Symbol(a, Decl(file.tsx, 19, 15))
>b : Symbol(b, Decl(file.tsx, 19, 21))
>props : Symbol(props, Decl(file.tsx, 10, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
const b3 = <Foo a={1} d={1} {...props} {...{ d: 1 }}></Foo>;
>b3 : Symbol(b3, Decl(file.tsx, 20, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
>a : Symbol(a, Decl(file.tsx, 20, 15))
>d : Symbol(d, Decl(file.tsx, 20, 21))
>props : Symbol(props, Decl(file.tsx, 10, 5))
>d : Symbol(d, Decl(file.tsx, 20, 44))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
const b4 = <Foo a={1} d={1} {...props} {...{ a: 1, d: 1 }}></Foo>;
>b4 : Symbol(b4, Decl(file.tsx, 21, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
>a : Symbol(a, Decl(file.tsx, 21, 15))
>d : Symbol(d, Decl(file.tsx, 21, 21))
>props : Symbol(props, Decl(file.tsx, 10, 5))
>a : Symbol(a, Decl(file.tsx, 21, 44))
>d : Symbol(d, Decl(file.tsx, 21, 50))
>Foo : Symbol(Foo, Decl(file.tsx, 11, 5))
@@ -0,0 +1,106 @@
=== tests/cases/conformance/jsx/file.tsx ===
import React = require('react');
>React : typeof React
interface Props {
a: number;
>a : number
b: number;
>b : number
c?: number;
>c : number | undefined
d?: number;
>d : number | undefined
}
const props: Props = { a: 1, b: 1 };
>props : Props
>{ a: 1, b: 1 } : { a: number; b: number; }
>a : number
>1 : 1
>b : number
>1 : 1
const Foo = (props: Props) => <div>{ props.a }</div>;
>Foo : (props: Props) => JSX.Element
>(props: Props) => <div>{ props.a }</div> : (props: Props) => JSX.Element
>props : Props
><div>{ props.a }</div> : JSX.Element
>div : any
>props.a : number
>props : Props
>a : number
>div : any
// ok
const a1 = <Foo {...props}></Foo>;
>a1 : JSX.Element
><Foo {...props}></Foo> : JSX.Element
>Foo : (props: Props) => JSX.Element
>props : Props
>Foo : (props: Props) => JSX.Element
const a2 = <Foo d={1} {...props}></Foo>;
>a2 : JSX.Element
><Foo d={1} {...props}></Foo> : JSX.Element
>Foo : (props: Props) => JSX.Element
>d : number
>1 : 1
>props : Props
>Foo : (props: Props) => JSX.Element
// error
const b1 = <Foo a={1} {...props}></Foo>;
>b1 : JSX.Element
><Foo a={1} {...props}></Foo> : JSX.Element
>Foo : (props: Props) => JSX.Element
>a : number
>1 : 1
>props : Props
>Foo : (props: Props) => JSX.Element
const b2 = <Foo a={1} b={2} {...props}></Foo>;
>b2 : JSX.Element
><Foo a={1} b={2} {...props}></Foo> : JSX.Element
>Foo : (props: Props) => JSX.Element
>a : number
>1 : 1
>b : number
>2 : 2
>props : Props
>Foo : (props: Props) => JSX.Element
const b3 = <Foo a={1} d={1} {...props} {...{ d: 1 }}></Foo>;
>b3 : JSX.Element
><Foo a={1} d={1} {...props} {...{ d: 1 }}></Foo> : JSX.Element
>Foo : (props: Props) => JSX.Element
>a : number
>1 : 1
>d : number
>1 : 1
>props : Props
>{ d: 1 } : { d: number; }
>d : number
>1 : 1
>Foo : (props: Props) => JSX.Element
const b4 = <Foo a={1} d={1} {...props} {...{ a: 1, d: 1 }}></Foo>;
>b4 : JSX.Element
><Foo a={1} d={1} {...props} {...{ a: 1, d: 1 }}></Foo> : JSX.Element
>Foo : (props: Props) => JSX.Element
>a : number
>1 : 1
>d : number
>1 : 1
>props : Props
>{ a: 1, d: 1 } : { a: number; d: number; }
>a : number
>1 : 1
>d : number
>1 : 1
>Foo : (props: Props) => JSX.Element
@@ -10,6 +10,7 @@ tests/cases/conformance/types/spread/objectSpreadNegative.ts(28,36): error TS230
tests/cases/conformance/types/spread/objectSpreadNegative.ts(28,53): error TS2300: Duplicate identifier 'b'.
tests/cases/conformance/types/spread/objectSpreadNegative.ts(32,7): error TS2783: 'b' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/types/spread/objectSpreadNegative.ts(37,7): error TS2783: 'b' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/types/spread/objectSpreadNegative.ts(37,7): error TS2783: 'b' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/types/spread/objectSpreadNegative.ts(39,14): error TS2783: 'b' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/types/spread/objectSpreadNegative.ts(41,53): error TS2783: 'd' is specified more than once, so this usage will be overwritten.
tests/cases/conformance/types/spread/objectSpreadNegative.ts(43,7): error TS2783: 'a' is specified more than once, so this usage will be overwritten.
@@ -26,7 +27,7 @@ tests/cases/conformance/types/spread/objectSpreadNegative.ts(69,9): error TS2339
tests/cases/conformance/types/spread/objectSpreadNegative.ts(74,11): error TS2339: Property 'a' does not exist on type '{}'.
==== tests/cases/conformance/types/spread/objectSpreadNegative.ts (22 errors) ====
==== tests/cases/conformance/types/spread/objectSpreadNegative.ts (23 errors) ====
let o = { a: 1, b: 'no' }
/// private propagates
@@ -70,6 +71,7 @@ tests/cases/conformance/types/spread/objectSpreadNegative.ts(74,11): error TS233
let duplicated = { b: 'bad', ...o, b: 'bad', ...o2, b: 'bad' }
~~~~~~~~
!!! error TS2783: 'b' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/objectSpreadNegative.ts:28:30: This spread always overwrites this property.
~
!!! error TS2300: Duplicate identifier 'b'.
~
@@ -80,6 +82,7 @@ tests/cases/conformance/types/spread/objectSpreadNegative.ts(74,11): error TS233
{ b: 'ignored', ...o }
~~~~~~~~~~~~
!!! error TS2783: 'b' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/objectSpreadNegative.ts:32:21: This spread always overwrites this property.
let o3 = { a: 1, b: 'no' }
let o4 = { b: 'yes', c: true }
@@ -87,22 +90,30 @@ tests/cases/conformance/types/spread/objectSpreadNegative.ts(74,11): error TS233
{ b: 'ok', ...o3, ...o4 }
~~~~~~~
!!! error TS2783: 'b' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/objectSpreadNegative.ts:37:16: This spread always overwrites this property.
~~~~~~~
!!! error TS2783: 'b' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/objectSpreadNegative.ts:37:23: This spread always overwrites this property.
let combinedMid: { a: number, b: string, c: boolean } =
{ ...o3, b: 'ok', ...o4 }
~~~~~~~
!!! error TS2783: 'b' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/objectSpreadNegative.ts:39:23: This spread always overwrites this property.
let combinedNested: { a: number, b: boolean, c: string, d: string } =
{ ...{ a: 4, ...{ b: false, c: 'overriden' } }, d: 'actually new', ...{ a: 5, d: 'maybe new' } }
~~~~~~~~~~~~~~~~~
!!! error TS2783: 'd' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/objectSpreadNegative.ts:41:72: This spread always overwrites this property.
let changeTypeBefore: { a: number, b: string } =
{ a: 'wrong type?', ...o3 };
~~~~~~~~~~~~~~~~
!!! error TS2783: 'a' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/objectSpreadNegative.ts:43:25: This spread always overwrites this property.
let computedMiddle: { a: number, b: string, c: boolean, "in the middle": number } =
{ ...o3, ['in the middle']: 13, b: 'maybe?', ...o4 }
~~~~~~~~~~~
!!! error TS2783: 'b' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/objectSpreadNegative.ts:45:50: This spread always overwrites this property.
// primitives are not allowed, except for falsy ones
let spreadNum = { ...12 };
@@ -6,4 +6,5 @@ tests/cases/conformance/types/spread/objectSpreadSetonlyAccessor.ts(2,34): error
const o2: { foo: undefined } = { foo: 1, ... { set foo(_v: number) { } } }
~~~~~~
!!! error TS2783: 'foo' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/objectSpreadSetonlyAccessor.ts:2:42: This spread always overwrites this property.
@@ -9,6 +9,7 @@ tests/cases/conformance/types/spread/spreadOverwritesPropertyStrict.ts(24,14): e
var unused1 = { b: 1, ...ab } // error
~~~~
!!! error TS2783: 'b' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/spreadOverwritesPropertyStrict.ts:3:23: This spread always overwrites this property.
var unused2 = { ...ab, ...ab } // ok, overwritten error doesn't apply to spreads
var unused3 = { b: 1, ...abq } // ok, abq might have b: undefined
var unused4 = { ...ab, b: 1 } // ok, we don't care that b in ab is overwritten
@@ -23,6 +24,7 @@ tests/cases/conformance/types/spread/spreadOverwritesPropertyStrict.ts(24,14): e
return { x: 1, ...obj } // error
~~~~
!!! error TS2783: 'x' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/spreadOverwritesPropertyStrict.ts:15:20: This spread always overwrites this property.
}
function i(b: boolean, t: { command: string, ok: string }) {
return { command: "hi", ...(b ? t : {}) } // ok
@@ -34,5 +36,6 @@ tests/cases/conformance/types/spread/spreadOverwritesPropertyStrict.ts(24,14): e
return { command: "hi", ...{ spoiler: true }, spoiler2: true, ...t } // error
~~~~~~~~~~~~~
!!! error TS2783: 'command' is specified more than once, so this usage will be overwritten.
!!! related TS2785 tests/cases/conformance/types/spread/spreadOverwritesPropertyStrict.ts:24:67: This spread always overwrites this property.
}
@@ -0,0 +1,29 @@
// @filename: file.tsx
// @jsx: preserve
// @strict: true
// @noLib: true
// @skipLibCheck: true
// @libFiles: react.d.ts,lib.d.ts
import React = require('react');
interface Props {
a: number;
b: number;
c?: number;
d?: number;
}
const props: Props = { a: 1, b: 1 };
const Foo = (props: Props) => <div>{ props.a }</div>;
// ok
const a1 = <Foo {...props}></Foo>;
const a2 = <Foo d={1} {...props}></Foo>;
// error
const b1 = <Foo a={1} {...props}></Foo>;
const b2 = <Foo a={1} b={2} {...props}></Foo>;
const b3 = <Foo a={1} d={1} {...props} {...{ d: 1 }}></Foo>;
const b4 = <Foo a={1} d={1} {...props} {...{ a: 1, d: 1 }}></Foo>;