-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Object spread #11150
Object spread #11150
Conversation
@ahejlsberg can you take a look? |
1. SpreadElementExpression (existing, for arrays) -> SpreadExpression 2. SpreadElement (new for object literals) -> SpreadElementExpression
What's YOUR favourite thing about Mars, Beartato?
Previously, they worked when they came from a spread type but not when written in the object literal itself.
As elsewhere in the compiler code
Is |
What are the properties of number? I don't think there are any, so This is based on experimentation with V8's implementation of |
Well.... Usually when we think about the properties of However, you're right that the "correct" behavior will give you no properties because none of the properties of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some intermediate feedback.
@@ -4273,6 +4319,21 @@ namespace ts { | |||
setObjectTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); | |||
} | |||
|
|||
function resolveSpreadTypeMembers(type: SpreadType) { | |||
// The members and properties collections are empty for spread types. To get all properties of an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an -> a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this should be JSDoc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code no longer exists.
// spread type use getPropertiesOfType. | ||
let stringIndexInfo: IndexInfo = getIndexInfoOfSymbol(type.symbol, IndexKind.String); | ||
let numberIndexInfo: IndexInfo = getIndexInfoOfSymbol(type.symbol, IndexKind.Number); | ||
for (let i = type.types.length - 1; i > -1; i--) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
>= 0
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code no longer exists.
export interface IntersectionType extends UnionOrIntersectionType { } | ||
/* @internal */ | ||
export interface SpreadElementType extends ResolvedType { | ||
isDeclaredProperty?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add documentation about what this means?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type no longer exists. getSpreadType
acts on this information immediately instead of preserving it.
@@ -5589,15 +5729,67 @@ namespace ts { | |||
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: Node, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { | |||
const links = getNodeLinks(node); | |||
if (!links.resolvedType) { | |||
// Deferred resolution of members is handled by resolveObjectTypeMembers | |||
const type = createObjectType(TypeFlags.Anonymous, node.symbol); | |||
const isSpread = (node.kind === SyntaxKind.TypeLiteral && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasSpread
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an isTypeLiteral
s that you don't have to subsequently cast?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but it only saves a couple of casts anyway.
const isSpread = (node.kind === SyntaxKind.TypeLiteral && | ||
find((node as TypeLiteralNode).members, elt => elt.kind === SyntaxKind.SpreadTypeElement)); | ||
let type: ObjectType; | ||
if (isSpread) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider refactoring the entire body here into its own function to keep it clean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Done.
const type = createObjectType(TypeFlags.Anonymous, node.symbol); | ||
const isSpread = (node.kind === SyntaxKind.TypeLiteral && | ||
find((node as TypeLiteralNode).members, elt => elt.kind === SyntaxKind.SpreadTypeElement)); | ||
let type: ObjectType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You never use type
in the first branch, so do you really need to declare it up here?
@@ -5851,6 +5879,67 @@ namespace ts { | |||
return links.resolvedType; | |||
} | |||
|
|||
/** | |||
* Since the source of spread types are object literals and type literals, which are not binary, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not type literals anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
else if (!(rightProp.flags & SymbolFlags.Method && isFromSpread) && | ||
!(rightProp.flags & SymbolFlags.SetAccessor && !(rightProp.flags & SymbolFlags.GetAccessor))) { | ||
// skip methods from spreads and accessors with setters but no getters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why accessors with setters & no getters? Is this per spec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From offline, "hilariously yes"
.
let stringIndexInfo: IndexInfo; | ||
let numberIndexInfo: IndexInfo; | ||
if (left === emptyObjectType) { | ||
// for the first spread element, left === emptyObjectType, so take the right's string indexer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the right element is an empty object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's treated like any other object type. emptyObjectType
is only a marker of the start of the spread type because the checkObjectLiteral
uses getSpreadType
in a left fold manner, and emptyObjectType
is initial value of the fold.
|| leftProp.name in skippedPrivateMembers) { | ||
continue; | ||
} | ||
if (leftProp.name in members) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like members
should be renamed to rightProps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's the members of the resulting spread. If right
doesn't have a property that left
does, then members
will have leftProp
. So calling it rightProps
is not accurate.
result.isReadonly = isReadonly; | ||
result.type = containingType.flags & TypeFlags.Union ? getUnionType(propTypes) : getIntersectionType(propTypes); | ||
result.type = containingType.flags & TypeFlags.Intersection ? getIntersectionType(propTypes) : getUnionType(propTypes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert If you feel like it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
This avoids the need for a synthetic symbol and later code called from getTypeOfSymbol.
@@ -3564,6 +3571,14 @@ namespace ts { | |||
return unknownType; | |||
} | |||
|
|||
function getTypeOfSpreadProperty(symbol: Symbol) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there's a need for a new kind of symbol. We should just create regular SymbolFlags.Property
symbols and compute the type eagerly.
if (type.flags & TypeFlags.UnionOrIntersection) { | ||
return getPropertiesOfUnionOrIntersectionType(<UnionType>type); | ||
} | ||
return getPropertiesOfObjectType(type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change? I much prefer the existing terser functional style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With a separate spread type, I had more code in here that made the verbose style more readable. Restored.
result.containingType = containingType; | ||
result.hasNonUniformType = hasNonUniformType; | ||
result.isPartial = isPartial; | ||
result.declarations = declarations; | ||
if (declarations.length) { | ||
result.valueDeclaration = declarations[0]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this added? I don't think it is correct to say the first of an arbitrarily ordered list is the definitely value declaration. Plus, the declaration doesn't actually correctly reflect the type of the union property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking index constraints in checkIndexConstraints
relies on valueDeclaration
. It's not needed now that we don't create synthetic symbols for spread any more, so I removed it. But in general checkIndexConstraints
works on synthetic properties by mistake. If choosing an arbitrary valueDeclaration isn't right, the checkIndexConstraints
needs to stop relying on it.
@@ -5041,7 +5069,7 @@ namespace ts { | |||
const declaration = getIndexDeclarationOfSymbol(symbol, kind); | |||
if (declaration) { | |||
return createIndexInfo(declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, | |||
(getModifierFlags(declaration) & ModifierFlags.Readonly) !== 0, declaration); | |||
(getModifierFlags(declaration) & ModifierFlags.Readonly) !== 0, declaration); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We usually don't indent like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
* and right = the new element to be spread. | ||
*/ | ||
function getSpreadType(left: Type, right: Type, symbol: Symbol): ResolvedType | IntrinsicType { | ||
Debug.assert(!!(left.flags & (TypeFlags.Object | TypeFlags.Any)) && !!(right.flags & (TypeFlags.Object | TypeFlags.Any)), "Only object types may be spread."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you consider permitting intersection types as well? I understand how union types are harder and would require "lifting" to produce a resulting union type, but it seems like intersections would just work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did, and it would be simple to restore the code I had there earlier. The reason I decided against is that it gets harder to issue a correct, understandable error in checkObjectLiteral
because intersections that contain type parameters are still not allowed. And I assume that we'll have a good story for all kinds of type operators shortly after 2.1 ships, so a restriction on intersections won't have lasting problems.
result.declarations = declarations; | ||
if (declarations.length) { | ||
result.valueDeclaration = declarations[0]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, any reason you're setting the valueDeclaration
property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above. Removed.
result.valueDeclaration = declarations[0]; | ||
} | ||
result.isReadonly = isReadonlySymbol(leftProp) || isReadonlySymbol(rightProp); | ||
members[leftProp.name] = result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just compute the type here and set it directly on the new symbol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
checkIndexConstraints(type); | ||
checkTypeForDuplicateIndexSignatures(node); | ||
checkObjectTypeForDuplicateDeclarations(node); | ||
if (type.flags & TypeFlags.Object) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When would this not be an object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left over from when { ...T }
syntax would produce a spread type. Removed.
@@ -17140,7 +17270,7 @@ namespace ts { | |||
// perform property check if property or indexer is declared in 'type' | |||
// this allows to rule out cases when both property and indexer are inherited from the base class | |||
let errorNode: Node; | |||
if (prop.valueDeclaration.name.kind === SyntaxKind.ComputedPropertyName || prop.parent === containingType.symbol) { | |||
if (prop.valueDeclaration.name.kind === SyntaxKind.ComputedPropertyName || prop.parent === containingType.symbol) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore indentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
const name = prop.name; | ||
if (name.kind === SyntaxKind.ComputedPropertyName) { | ||
if (name && name.kind === SyntaxKind.ComputedPropertyName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can name
now be undefined where previously it couldn't?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not anymore. Removed.
Instead of getSpreadType. Also clean up special-case handling inside getSpreadType to be more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed PR comments
if (type.flags & TypeFlags.UnionOrIntersection) { | ||
return getPropertiesOfUnionOrIntersectionType(<UnionType>type); | ||
} | ||
return getPropertiesOfObjectType(type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With a separate spread type, I had more code in here that made the verbose style more readable. Restored.
@@ -5041,7 +5069,7 @@ namespace ts { | |||
const declaration = getIndexDeclarationOfSymbol(symbol, kind); | |||
if (declaration) { | |||
return createIndexInfo(declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, | |||
(getModifierFlags(declaration) & ModifierFlags.Readonly) !== 0, declaration); | |||
(getModifierFlags(declaration) & ModifierFlags.Readonly) !== 0, declaration); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
result.valueDeclaration = declarations[0]; | ||
} | ||
result.isReadonly = isReadonlySymbol(leftProp) || isReadonlySymbol(rightProp); | ||
members[leftProp.name] = result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
checkIndexConstraints(type); | ||
checkTypeForDuplicateIndexSignatures(node); | ||
checkObjectTypeForDuplicateDeclarations(node); | ||
if (type.flags & TypeFlags.Object) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left over from when { ...T }
syntax would produce a spread type. Removed.
result.containingType = containingType; | ||
result.hasNonUniformType = hasNonUniformType; | ||
result.isPartial = isPartial; | ||
result.declarations = declarations; | ||
if (declarations.length) { | ||
result.valueDeclaration = declarations[0]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking index constraints in checkIndexConstraints
relies on valueDeclaration
. It's not needed now that we don't create synthetic symbols for spread any more, so I removed it. But in general checkIndexConstraints
works on synthetic properties by mistake. If choosing an arbitrary valueDeclaration isn't right, the checkIndexConstraints
needs to stop relying on it.
result.declarations = declarations; | ||
if (declarations.length) { | ||
result.valueDeclaration = declarations[0]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above. Removed.
@@ -17140,7 +17270,7 @@ namespace ts { | |||
// perform property check if property or indexer is declared in 'type' | |||
// this allows to rule out cases when both property and indexer are inherited from the base class | |||
let errorNode: Node; | |||
if (prop.valueDeclaration.name.kind === SyntaxKind.ComputedPropertyName || prop.parent === containingType.symbol) { | |||
if (prop.valueDeclaration.name.kind === SyntaxKind.ComputedPropertyName || prop.parent === containingType.symbol) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
const name = prop.name; | ||
if (name.kind === SyntaxKind.ComputedPropertyName) { | ||
if (name && name.kind === SyntaxKind.ComputedPropertyName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not anymore. Removed.
I just noticed that I forgot to comment on not allowing intersections. Here's the comment copied out of the original thread since github's UI collapses that one.
|
Previously array destructuring inside an object destructuring with an object rest would downlevel the array destructuring to ES5. This breaks if the code that targets ES2015 is using iterators instead of arrays since iterators don't support [0] or .slice that the ES5 emit uses.
Now with object destructuring inside array destructuring inside object destructuring! Each with their own array/object rest! Also updates baselines.
@@ -4512,7 +4523,7 @@ namespace ts { | |||
t; | |||
} | |||
|
|||
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: string): Symbol { | |||
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove the return type annotation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reason. Fixed.
} | ||
if (leftProp.name in members) { | ||
const rightProp = members[leftProp.name]; | ||
if (rightProp.flags & SymbolFlags.Optional) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a more correct check here is whether the type of rightProp
includes undefined
. After all, that's how it works at runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I had to keep the optionality check for when strict null checks is off.
Is it possible to omit a property from object? |
@whitecolor no, compiler complains Thanks a lot for that feature, it at least saved my life \o/ |
you mean: const myObj = {a: 1, b:2, c:3};
const { a, ...myObjWithoutA} = myObj;
// myObjWithoutA has b and c but not a |
@alitaheri that is intresting btw, thanks What I meant a little bit different, though I probably do not refere to spread, I wanted to make it with only types without objects say from |
@whitecolor it isn't relative to spread, from what I understand now. I first understood your question was relative to smthg like that : interface FooBar {
foo: string
bar: string
}
let val = {foo: 'hello'};
let val2: FooBar = {...val}; In that configuration, compiler complains. |
I was looking to implement the emitted code from this patch into my project as a 'backport' utility until we can update to a released Typescript version that contains this operator, but I think I'm not understanding exactly what the patch does, because the way it seems to be implemented might have a bug? Basically, if we look at the emitted code in isolation, the idea of what it's supposed to do is fairly obvious.
Create new target object The only problem with this specifically is that javascript arrays are zero indexed, and the value of the expression
would result in the Unless I'm (likely) missing some essential context, it seems to me that there is an issue with the accepted solution. Thanks for your time. |
Leaves #10727 still open
Fixes #2103
Leaves #11100 still open
Important Update
The PR no longer contains a spread type, only spread syntax, which creates object types. I expect a spread type or something similar to be available in future versions of TypeScript.
Updates to proposed semantics: type variable assignability
A spread type containing spread elements whose types are type variables is assignable to another spread type also containing type variables if the non-type-variable properties and index signatures are assignable as above and the the type variables are the same variables in the same order.
For example:
{ a: number, ...T, b: number, ...U, ...x }
is assignable to{ b: number, a: number, ...x ...T, ...U}
and vice versa. But the similar{ a: number, ...T, b: number, ...U, ...x }
is not assignable to{ b: number, a: number, ...x ...U, ...T}
because the order ofT
andU
is reversed.This rule is not strictly safe, since a type parameter could override any properties that precede it (see the rule for
private
below), but it would be odd if{ ...T, ...U }
were not assignable to{ ...T, ...U }
. Instead, this rule assumes that the contents of a type variable will not conflict with other properties.Important Implementation Details
Spread types use a binary representation to make recursive transformations easier. This matches the proposal but the syntax remains close to spread's expression syntax.
getSpreadType
creates a spread only if the spread type contains a type parameter. Otherwise it will create an anonymous type, or union or intersection of anonymous types. The binary structure is left deep, so the left side of a spread type is either another spread (the recursive case) or an object type (the terminal case). The right side of spread type is either a type parameter or an object type. Adjacent object types are spread into a single object type, so the structure will never have two adjacent object types, only adjacent type parameters.Updated proposal
The spread part of #10727's updated proposal follows for convenience:
The spread type is a new type operator that types the TC39 stage 3 object spread operator. Its counterpart, the difference type, will type the proposed object rest destructuring operator. The spread type
{ ...A, ...B }
combines the properties, but not the call or construct signatures, of entities A and B.The original issue for spread/rest types is #2103. Note that this proposal deviates from the specification by keeping all properties except methods, not just own enumerable ones.
Proposal syntax
The type syntax in this proposal differs from the type syntax as implemented in order to treat spread as a binary operator. Three rules are needed to convert the
{ ...spread }
syntax to binary syntaxspread1 ... spread2
.{ ...spread }
becomes{} ... spread
.{ a, b, c, ...d}
becomes{a, b, c} ... d
{ a, b, c, ...d, ...e, f, g}
becomes{a, b, c} ... d ... e ... { f, g }
.Type Relationships
{} ... A
is equivalent to the properties ofA
.A ... A ... A
is equivalent toA ... A
andA ... A
is equivalent to{} ... A
.A ... B
is not equivalent toB ... A
. Properties ofB
overwrite properties ofA
with the same name.(A ... B) ... C
is equivalent toA ... (B ... C)
....
is right-associative.|
and&
, soA ... (B | C)
is equivalent toA ... B | A ... C
andA ... (B & C)
is equivalent toA ... B & A ... C
.Assignment compatibility
A ... B
is assignable toX
if the properties and index signatures ofA ... B
are assignable to those ofX
, andX
has no call or construct signatures.X
is assignable toA ... B
if the properties and index signatures ofX
are assignable to those ofA ... B
.Type variables
A spread type containing type parameters is assignable to another spread type if the the non-type-variable properties and index signatures are assignable as above and the type variables are the same variables in the same order.
For example:
{ a: number, b: number } ... T
is assignable to{ a: number } ...T
and toT ... { a: number }
. But the similarT ... U
is not assignable toU ... T
because the order ofT
andU
is reversed.Type inference
Type inference is allowed between an object type source and a spread type target with a type parameter as either the left or right side. If the type parameter is on the right, then its inferred type is the whole source type. If the type parameter is on the left, then its inferred type is the source type minus properties on the right side of the spread.
For example, if the target is
T ... { a: number }
and the source is{ a: number, b: string }
, the inferred type forT
is{ b: string }
. Because spread types remove call and construct signatures, if the source were{ a: number, b: string, (): void }
, the type inferred forT
would still be{ b: string }
.Properties and index signatures
In the following definitions, 'property' means either a property or a get accessor.
The type
A ... B
has a propertyP
ifA
has a propertyP
orB
has a propertyP
. In this case(A ... B).P
has the typeB.P
ifB.P
is not optional.A.P | B.P
ifB.P
is optional andA
has a propertyP
.A.P
otherwise.Index signatures spread the same way as properties.
private
,protected
andreadonly
behave the same way as optionality except that ifA.P
orB.P
isprivate
,protected
orreadonly
, then(A ...B).P
isprivate
,protected
orreadonly
, respectively.Call and Construct signatures
A ... B
has no call signatures and no construct signatures, since these are not properties.Precedence
Precedence of
...
is higher than&
and|
. Since the language syntax is that of object type literals, precedence doesn't matter since the braces act as boundaries of the spread type.Examples
Taken from the TC39 proposal and given types.
Shallow Clone (excluding prototype)
Merging Two Objects
Overriding Properties
Default Properties
Multiple Merges
Getters on the Object Initializer
Getters in the Spread Object
Setters Are Not Executed When They're Redefined
Null/Undefined Are Ignored
Updating Deep Immutable Object
Note: If
A = { name: string, address: { address, zipCode: string }, items: { title: string }[] }
, then the type of newVersion is equivalent toA