diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 9d71d2504ca0c..237ef50b85dbc 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -497,8 +497,24 @@ func (c *Checker) elaborateObjectLiteral(node *ast.Node, source *Type, target *T if target.flags&(TypeFlagsPrimitive|TypeFlagsNever) != 0 { return false } + properties := node.Properties() + spreadNamesByIndex := make(map[int]collections.Set[string]) + for i, prop := range properties { + if !ast.IsSpreadAssignment(prop) { + continue + } + spreadType := c.getReducedType(c.checkExpressionEx(prop.Expression(), CheckModeNormal)) + if !c.isValidSpreadType(spreadType) { + continue + } + names := collections.Set[string]{} + for _, p := range c.getPropertiesOfType(spreadType) { + names.Add(p.Name) + } + spreadNamesByIndex[i] = names + } reportedError := false - for _, prop := range node.Properties() { + for i, prop := range properties { if ast.IsSpreadAssignment(prop) { continue } @@ -506,12 +522,27 @@ func (c *Checker) elaborateObjectLiteral(node *ast.Node, source *Type, target *T if nameType == nil || nameType.flags&TypeFlagsNever != 0 { continue } + anchor := prop.Name() + var next *ast.Node + if ast.IsPropertyAssignment(prop) { + next = prop.Initializer() + } + if isTypeUsableAsPropertyName(nameType) { + name := getPropertyNameFromType(nameType) + for j := i + 1; j < len(properties); j++ { + if names, ok := spreadNamesByIndex[j]; ok && names.Has(name) { + anchor = properties[j].Expression() + next = nil + break + } + } + } switch prop.Kind { case ast.KindSetAccessor, ast.KindGetAccessor, ast.KindMethodDeclaration, ast.KindShorthandPropertyAssignment: - reportedError = c.elaborateElement(source, target, relation, prop.Name(), nil, nameType, nil, nil, diagnosticOutput) || reportedError + reportedError = c.elaborateElement(source, target, relation, anchor, nil, nameType, nil, nil, diagnosticOutput) || reportedError case ast.KindPropertyAssignment: message := core.IfElse(ast.IsComputedNonLiteralName(prop.Name()), diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1, nil) - reportedError = c.elaborateElement(source, target, relation, prop.Name(), prop.Initializer(), nameType, message, nil, diagnosticOutput) || reportedError + reportedError = c.elaborateElement(source, target, relation, anchor, next, nameType, message, nil, diagnosticOutput) || reportedError } } return reportedError diff --git a/tsc/testdata/baselines/reference/compiler/spreadOverridingPropertyElaboratesOnSpread.errors.txt b/tsc/testdata/baselines/reference/compiler/spreadOverridingPropertyElaboratesOnSpread.errors.txt new file mode 100644 index 0000000000000..6dd30fe4778e2 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/spreadOverridingPropertyElaboratesOnSpread.errors.txt @@ -0,0 +1,33 @@ +spreadOverridingPropertyElaboratesOnSpread.ts(9,8): error TS2322: Type 'string | null' is not assignable to type 'string'. + Type 'null' is not assignable to type 'string'. +spreadOverridingPropertyElaboratesOnSpread.ts(18,5): error TS2322: Type 'null' is not assignable to type 'string'. + + +==== spreadOverridingPropertyElaboratesOnSpread.ts (2 errors) ==== + type Item = { + attribute: string; + }; + + declare const result: Partial>; + + const item: Item = { + attribute: "", + ...result, + ~~~~~~ +!!! error TS2322: Type 'string | null' is not assignable to type 'string'. +!!! error TS2322: Type 'null' is not assignable to type 'string'. +!!! related TS6500 spreadOverridingPropertyElaboratesOnSpread.ts:2:5: The expected type comes from property 'attribute' which is declared here on type 'Item' + }; + + const item2: Item = { + ...result, + attribute: "", + }; + + const item3: Item = { + attribute: null, + ~~~~~~~~~ +!!! error TS2322: Type 'null' is not assignable to type 'string'. +!!! related TS6500 spreadOverridingPropertyElaboratesOnSpread.ts:2:5: The expected type comes from property 'attribute' which is declared here on type 'Item' + }; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/spreadOverridingPropertyElaboratesOnSpread.symbols b/tsc/testdata/baselines/reference/compiler/spreadOverridingPropertyElaboratesOnSpread.symbols new file mode 100644 index 0000000000000..75fa81c8893a2 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/spreadOverridingPropertyElaboratesOnSpread.symbols @@ -0,0 +1,49 @@ +//// [tests/cases/compiler/spreadOverridingPropertyElaboratesOnSpread.ts] //// + +=== spreadOverridingPropertyElaboratesOnSpread.ts === +type Item = { +>Item : Symbol(Item, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 0, 0)) + + attribute: string; +>attribute : Symbol(attribute, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 0, 13)) + +}; + +declare const result: Partial>; +>result : Symbol(result, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 4, 13)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + +const item: Item = { +>item : Symbol(item, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 6, 5)) +>Item : Symbol(Item, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 0, 0)) + + attribute: "", +>attribute : Symbol(attribute, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 6, 20)) + + ...result, +>result : Symbol(result, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 4, 13)) + +}; + +const item2: Item = { +>item2 : Symbol(item2, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 11, 5)) +>Item : Symbol(Item, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 0, 0)) + + ...result, +>result : Symbol(result, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 4, 13)) + + attribute: "", +>attribute : Symbol(attribute, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 12, 14)) + +}; + +const item3: Item = { +>item3 : Symbol(item3, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 16, 5)) +>Item : Symbol(Item, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 0, 0)) + + attribute: null, +>attribute : Symbol(attribute, Decl(spreadOverridingPropertyElaboratesOnSpread.ts, 16, 21)) + +}; + diff --git a/tsc/testdata/baselines/reference/compiler/spreadOverridingPropertyElaboratesOnSpread.types b/tsc/testdata/baselines/reference/compiler/spreadOverridingPropertyElaboratesOnSpread.types new file mode 100644 index 0000000000000..b8ea404162d3f --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/spreadOverridingPropertyElaboratesOnSpread.types @@ -0,0 +1,49 @@ +//// [tests/cases/compiler/spreadOverridingPropertyElaboratesOnSpread.ts] //// + +=== spreadOverridingPropertyElaboratesOnSpread.ts === +type Item = { +>Item : Item + + attribute: string; +>attribute : string + +}; + +declare const result: Partial>; +>result : Partial> + +const item: Item = { +>item : Item +>{ attribute: "", ...result,} : { attribute: string | null; } + + attribute: "", +>attribute : string +>"" : "" + + ...result, +>result : Partial> + +}; + +const item2: Item = { +>item2 : Item +>{ ...result, attribute: "",} : { attribute: string; } + + ...result, +>result : Partial> + + attribute: "", +>attribute : string +>"" : "" + +}; + +const item3: Item = { +>item3 : Item +>{ attribute: null,} : { attribute: null; } + + attribute: null, +>attribute : null + +}; + diff --git a/tsc/testdata/tests/cases/compiler/spreadOverridingPropertyElaboratesOnSpread.ts b/tsc/testdata/tests/cases/compiler/spreadOverridingPropertyElaboratesOnSpread.ts new file mode 100644 index 0000000000000..1dc86696fa964 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/spreadOverridingPropertyElaboratesOnSpread.ts @@ -0,0 +1,22 @@ +// @strict: true +// @noEmit: true + +type Item = { + attribute: string; +}; + +declare const result: Partial>; + +const item: Item = { + attribute: "", + ...result, +}; + +const item2: Item = { + ...result, + attribute: "", +}; + +const item3: Item = { + attribute: null, +};