Skip to content

Error: Property is overwritten by later spread #27645

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

Closed
wants to merge 4 commits into from
Closed
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
37 changes: 17 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9806,7 +9806,7 @@ namespace ts {
* this function should be called in a left folding style, with left = previous result of getSpreadType
* and right = the new element to be spread.
*/
function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, typeFlags: TypeFlags, objectFlags: ObjectFlags): Type {
function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, typeFlags: TypeFlags, objectFlags: ObjectFlags, isUnion?: boolean): Type {
if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) {
return anyType;
}
Expand All @@ -9820,10 +9820,10 @@ namespace ts {
return left;
}
if (left.flags & TypeFlags.Union) {
return mapType(left, t => getSpreadType(t, right, symbol, typeFlags, objectFlags));
return mapType(left, t => getSpreadType(t, right, symbol, typeFlags, objectFlags, isUnion));
}
if (right.flags & TypeFlags.Union) {
return mapType(right, t => getSpreadType(left, t, symbol, typeFlags, objectFlags));
return mapType(right, t => getSpreadType(left, t, symbol, typeFlags, objectFlags, true));
}
if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) {
return left;
Expand Down Expand Up @@ -9870,6 +9870,14 @@ namespace ts {
result.nameType = leftProp.nameType;
members.set(leftProp.escapedName, result);
}
else if (strictNullChecks &&
!isUnion &&
symbol &&
shouldCheckAsExcessProperty(leftProp, symbol) &&
!shouldCheckAsExcessProperty(rightProp, symbol) &&
!(getFalsyFlags(rightType) & TypeFlags.Nullable)) {
error(leftProp.valueDeclaration, Diagnostics._0_is_overwritten_by_a_later_spread, unescapeLeadingUnderscores(leftProp.escapedName));
}
}
else {
members.set(leftProp.escapedName, getNonReadonlySymbol(leftProp));
Expand Down Expand Up @@ -11562,7 +11570,7 @@ namespace ts {
return hasExcessProperties(source, discriminant, /*discriminant*/ undefined, reportErrors);
}
for (const prop of getPropertiesOfObjectType(source)) {
if (!isPropertyFromSpread(prop, source.symbol) && !isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) {
if (shouldCheckAsExcessProperty(prop, source.symbol) && !isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) {
if (reportErrors) {
// We know *exactly* where things went wrong when comparing the types.
// Use this property as the error node as this will be more helpful in
Expand Down Expand Up @@ -11606,10 +11614,6 @@ namespace ts {
return false;
}

function isPropertyFromSpread(prop: Symbol, container: Symbol) {
return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent !== container.valueDeclaration;
}

function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary {
let result = Ternary.True;
const sourceTypes = source.types;
Expand Down Expand Up @@ -12522,6 +12526,10 @@ namespace ts {
return match || defaultValue;
}

function shouldCheckAsExcessProperty(prop: Symbol, container: Symbol) {
return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration;
}

/**
* A type is 'weak' if it is an object type with at least one optional property
* and no required properties, call/construct signatures or index signatures
Expand Down Expand Up @@ -17652,7 +17660,6 @@ namespace ts {
let spread: Type = emptyObjectType;
let hasSpreadAnyType = false;
let typeToIntersect: Type | undefined;
let explicitlySpecifyChildrenAttribute = false;
let propagatingFlags: TypeFlags = 0;
const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement));

Expand All @@ -17671,9 +17678,6 @@ namespace ts {
attributeSymbol.type = exprType;
attributeSymbol.target = member;
attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
explicitlySpecifyChildrenAttribute = true;
}
}
else {
Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute);
Expand Down Expand Up @@ -17707,13 +17711,6 @@ namespace ts {
const childrenTypes: Type[] = checkJsxChildren(parent, checkMode);

if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
// Error if there is a attribute named "children" explicitly specified and children element.
// This is because children element will overwrite the value from attributes.
// Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread.
if (explicitlySpecifyChildrenAttribute) {
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
Expand Down Expand Up @@ -20062,7 +20059,7 @@ namespace ts {
if (node.arguments.length === 1 && isTypeAssertion(first(node.arguments))) {
const text = getSourceFileOfNode(node).text;
if (isLineBreak(text.charCodeAt(skipTrivia(text, node.expression.end, /* stopAfterLineBreak */ true) - 1))) {
relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.It_is_highly_likely_that_you_are_missing_a_semicolon);
relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.Are_you_missing_a_semicolon_here);
}
}
invocationError(node, apparentType, SignatureKind.Call, relatedInformation);
Expand Down
10 changes: 9 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2485,10 +2485,18 @@
"category": "Error",
"code": 2732
},
"It is highly likely that you are missing a semicolon.": {
"Index '{0}' is out-of-bounds in tuple of length {1}.": {
"category": "Error",
"code": 2733
},
"Are you missing a semicolon here?": {
"category": "Error",
"code": 2734
},
"'{0}' is overwritten by a later spread.": {
"category": "Error",
"code": 2735
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ tests/cases/compiler/betterErrorForAccidentallyCallingTypeAssertionExpressions.t
(1 as number).toString();
~~~~~~~~~~~~~
!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'String' has no compatible call signatures.
!!! related TS2734 tests/cases/compiler/betterErrorForAccidentallyCallingTypeAssertionExpressions.ts:7:1: It is highly likely that you are missing a semicolon.
!!! related TS2734 tests/cases/compiler/betterErrorForAccidentallyCallingTypeAssertionExpressions.ts:7:1: Are you missing a semicolon here?

foo()
~~~~~~~~
(1 as number).toString();
~~~~~~~~~~~~~~~~~
!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'String' has no compatible call signatures.
!!! related TS2734 tests/cases/compiler/betterErrorForAccidentallyCallingTypeAssertionExpressions.ts:10:1: It is highly likely that you are missing a semicolon.
!!! related TS2734 tests/cases/compiler/betterErrorForAccidentallyCallingTypeAssertionExpressions.ts:10:1: Are you missing a semicolon here?

foo()
~~~~~~~~
(<number>1).toString();
~~~~~~~~~~~~~~~
!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'String' has no compatible call signatures.
!!! related TS2734 tests/cases/compiler/betterErrorForAccidentallyCallingTypeAssertionExpressions.ts:13:1: It is highly likely that you are missing a semicolon.
!!! related TS2734 tests/cases/compiler/betterErrorForAccidentallyCallingTypeAssertionExpressions.ts:13:1: Are you missing a semicolon here?

33 changes: 0 additions & 33 deletions tests/baselines/reference/checkJsxChildrenProperty13.errors.txt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
tests/cases/conformance/jsx/file.tsx(14,10): error TS2322: Type '{ a: number; b: string; }' is not assignable to type 'Prop'.
Property 'children' is missing in type '{ a: number; b: string; }'.
tests/cases/conformance/jsx/file.tsx(17,11): error TS2710: 'children' are specified twice. The attribute named 'children' will be overwritten.
tests/cases/conformance/jsx/file.tsx(31,6): error TS2322: Type '{ children: (Element | ((name: string) => Element))[]; a: number; b: string; }' is not assignable to type 'Prop'.
Types of property 'children' are incompatible.
Type '(Element | ((name: string) => Element))[]' is not assignable to type 'string | Element'.
Expand All @@ -23,7 +22,7 @@ tests/cases/conformance/jsx/file.tsx(49,6): error TS2322: Type '{ children: Elem
Property 'type' is missing in type 'Element[]'.


==== tests/cases/conformance/jsx/file.tsx (6 errors) ====
==== tests/cases/conformance/jsx/file.tsx (5 errors) ====
import React = require('react');

interface Prop {
Expand All @@ -44,8 +43,6 @@ tests/cases/conformance/jsx/file.tsx(49,6): error TS2322: Type '{ children: Elem

let k0 =
<Comp a={10} b="hi" children="Random" >
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2710: 'children' are specified twice. The attribute named 'children' will be overwritten.
hi hi hi!
</Comp>;

Expand Down
30 changes: 0 additions & 30 deletions tests/baselines/reference/excessPropertyCheckWithSpread.errors.txt

This file was deleted.

24 changes: 2 additions & 22 deletions tests/baselines/reference/objectSpread.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,14 @@ let addAfter: { a: number, b: string, c: boolean } =
{ ...o, c: false }
let addBefore: { a: number, b: string, c: boolean } =
{ c: false, ...o }
// Note: ignore still changes the order that properties are printed
let ignore: { a: number, b: string } =
{ b: 'ignored', ...o }
let override: { a: number, b: string } =
{ ...o, b: 'override' }
let nested: { a: number, b: boolean, c: string } =
{ ...{ a: 3, ...{ b: false, c: 'overriden' } }, c: 'whatever' }
let combined: { a: number, b: string, c: boolean } =
{ ...o, ...o2 }
let combinedBefore: { a: number, b: string, c: boolean } =
{ b: 'ok', ...o, ...o2 }
let combinedMid: { a: number, b: string, c: boolean } =
{ ...o, b: 'ok', ...o2 }
let combinedAfter: { a: number, b: string, c: boolean } =
{ ...o, ...o2, b: 'ok' }
let combinedNested: { a: number, b: boolean, c: string, d: string } =
{ ...{ a: 4, ...{ b: false, c: 'overriden' } }, d: 'actually new', ...{ a: 5, d: 'maybe new' } }
let combinedNestedChangeType: { a: number, b: boolean, c: number } =
{ ...{ a: 1, ...{ b: false, c: 'overriden' } }, c: -1 }
let propertyNested: { a: { a: number, b: string } } =
Expand Down Expand Up @@ -91,8 +82,6 @@ cplus.plus();
// new field's type conflicting with existing field is OK
let changeTypeAfter: { a: string, b: string } =
{ ...o, a: 'wrong type?' }
let changeTypeBefore: { a: number, b: string } =
{ a: 'wrong type?', ...o };
let changeTypeBoth: { a: string, b: number } =
{ ...o, ...swap };

Expand All @@ -109,8 +98,6 @@ function container(
// computed property
let computedFirst: { a: number, b: string, "before everything": number } =
{ ['before everything']: 12, ...o, b: 'yes' }
let computedMiddle: { a: number, b: string, c: boolean, "in the middle": number } =
{ ...o, ['in the middle']: 13, b: 'maybe?', ...o2 }
let computedAfter: { a: number, b: string, "at the end": number } =
{ ...o, b: 'yeah', ['at the end']: 14 }
}
Expand Down Expand Up @@ -138,15 +125,10 @@ var o2 = { b: 'yes', c: true };
var swap = { a: 'yes', b: -1 };
var addAfter = __assign({}, o, { c: false });
var addBefore = __assign({ c: false }, o);
// Note: ignore still changes the order that properties are printed
var ignore = __assign({ b: 'ignored' }, o);
var override = __assign({}, o, { b: 'override' });
var nested = __assign({}, __assign({ a: 3 }, { b: false, c: 'overriden' }), { c: 'whatever' });
var combined = __assign({}, o, o2);
var combinedBefore = __assign({ b: 'ok' }, o, o2);
var combinedMid = __assign({}, o, { b: 'ok' }, o2);
var combinedAfter = __assign({}, o, o2, { b: 'ok' });
var combinedNested = __assign({}, __assign({ a: 4 }, { b: false, c: 'overriden' }), { d: 'actually new' }, { a: 5, d: 'maybe new' });
var combinedNestedChangeType = __assign({}, __assign({ a: 1 }, { b: false, c: 'overriden' }), { c: -1 });
var propertyNested = { a: __assign({}, o) };
// accessors don't copy the descriptor
Expand Down Expand Up @@ -196,18 +178,16 @@ var cplus = __assign({}, c, { plus: function () { return this.p + 1; } });
cplus.plus();
// new field's type conflicting with existing field is OK
var changeTypeAfter = __assign({}, o, { a: 'wrong type?' });
var changeTypeBefore = __assign({ a: 'wrong type?' }, o);
var changeTypeBoth = __assign({}, o, swap);
// optional
function container(definiteBoolean, definiteString, optionalString, optionalNumber) {
var _a, _b, _c;
var _a, _b;
var optionalUnionStops = __assign({}, definiteBoolean, definiteString, optionalNumber);
var optionalUnionDuplicates = __assign({}, definiteBoolean, definiteString, optionalString, optionalNumber);
var allOptional = __assign({}, optionalString, optionalNumber);
// computed property
var computedFirst = __assign((_a = {}, _a['before everything'] = 12, _a), o, { b: 'yes' });
var computedMiddle = __assign({}, o, (_b = {}, _b['in the middle'] = 13, _b.b = 'maybe?', _b), o2);
var computedAfter = __assign({}, o, (_c = { b: 'yeah' }, _c['at the end'] = 14, _c));
var computedAfter = __assign({}, o, (_b = { b: 'yeah' }, _b['at the end'] = 14, _b));
}
// shortcut syntax
var a = 12;
Expand Down
Loading