GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Generic spread expressions in object literals by ahejlsberg · Pull Request #28234 · microsoft/TypeScript

Conversation

@ahejlsberg

@ahejlsberg Anders Hejlsberg (ahejlsberg) commented Oct 30, 2018

Copy link
Copy Markdown
Member

With this PR we permit generic types in spread expressions within object literals. Object literals with generic spread expressions now produce intersection types, similar to the Object.assign function and JSX literals. For example:

function taggedObject<T, U extends string>(obj: T, tag: U) {
    return { ...obj, tag };  // T & { tag: U }
}

let x = taggedObject({ x: 10, y: 20 }, "point");  // { x: number, y: number } & { tag: "point" }

Property assignments and non-generic spread expressions are merged to the greatest extent possible on either side of a generic spread expression. For example:

function foo1<T>(t: T, obj1: { a: string }, obj2: { b: string }) {
    return { ...obj1, x: 1, ...t, ...obj2, y: 2 };  // { a: string, x: number } & T & { b: string, y: number }
}

Non-generic spread expressions continue to be processed as before: Call and construct signatures are stripped, only non-method properties are preserved, and for properties with the same name, the type of the rightmost property is used. This contrasts with intersection types which concatenate call and construct signatures, preserve all properties, and intersect the types of properties with the same name. Thus, spreads of the same types may produce different results when they are created through instantiation of generic types:

function spread<T, U>(t: T, u: U) {
    return { ...t, ...u };  // T & U
}

declare let x: { a: string, b: number };
declare let y: { b: string, c: boolean };

let s1 = { ...x, ...y };  // { a: string, b: string, c: boolean }
let s2 = spread(x, y);    // { a: string, b: number } & { b: string, c: boolean }
let b1 = s1.b;  // string
let b2 = s2.b;  // number & string

An alternative to using intersections would be to introduce a higher-order type operator { ...T, ...U } along the lines of what #10727 explores. While this appears attractive at first, it would take a substantial amount of work to implement this new type constructor and endow it with all the capabilities we already implement for intersection types, and it would produce little or no gain in precision for most scenarios. In particular, the differences really only matter when spread expressions involve objects with overlapping property names that have different types. Furthermore, for an unconstrained type parameter T, the type { ...T, ...T } wouldn't actually be assignable to T, which, while technically correct, would be pedantically annoying.

Having explored both options, and given that intersection types are already used for Object.assign and JSX literals, we feel that intersection types strike the best balance between accuracy and complexity for this feature.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

nscarcella, hatamov, lucasbasquerotto, apexskier, brainkim, j-oliveras, jemerald, KiaraGrouwstra, wanganjun, Jessidhia, and 35 more reacted with thumbs up emoji brainkim, deftomat, j-oliveras, KiaraGrouwstra, c69, takerukoushirou, niieani, zpdDG4gta8XKpMCd, saeedtabrizi, trotyl, and 12 more reacted with hooray emoji larsnystrom, j-oliveras, ulrichb, joslarson, saeedtabrizi, trotyl, cevek, fathyb, rchanaud, ksaldana1, and 7 more reacted with heart emoji
All reactions
  • 👍 45 reactions
  • 🎉 22 reactions
  • ❤️ 17 reactions
Anders Hejlsberg (ahejlsberg) added 4 commits October 29, 2018 16:02
@ahejlsberg
Produce intersection types for spreads with generic types
7a47248
@ahejlsberg
Accept new baselines
00c3015
@ahejlsberg
Add tests
e0d210d
@ahejlsberg
Accept new baselines
0f308f5
@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Oct 30, 2018
Add spread/rest higher-order types operator #10727
Open
@hatamov

Farkhad Khatamov (hatamov) commented Oct 30, 2018

Copy link
Copy Markdown

Awesome, this is my most anticipated feature! One thing I don't quite understand is how will this work with rest spread operator? For example what inferred return type this function will have?

function foo<T extends { x: string }>(obj: T) {
    const { x, ...rest } = obj;
    return rest;
}
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ajafff Klaus Meinhardt (ajafff) mentioned this pull request Oct 30, 2018
no-duplicate-spread-property: handle spread of type variables fimbullinter/wotan#447
Closed
@ahejlsberg
Anders Hejlsberg (ahejlsberg) requested review from Daniel Rosenwasser (DanielRosenwasser), Nathan Shively-Sanders (sandersn) and Wesley Wigham (weswigham) October 30, 2018 18:33
@ahejlsberg

Anders Hejlsberg (ahejlsberg) commented Oct 30, 2018

Copy link
Copy Markdown
Member Author

Farkhad Khatamov (@hatamov) This PR doesn't change the behavior of rest properties. However, I'm considering a separate PR that would use the Pick and Exclude predefined types to compute rest types:

function foo<T extends { a: string, b: string }>(obj: T) {
    let { a, b, ...rest } = obj;  // let rest: Pick<Exclude<T, 'a' | 'b'>>
}
vkrol, hatamov, KiaraGrouwstra, wanganjun, c69, ulrichb, joslarson, Cryrivers, zpdDG4gta8XKpMCd, jkillian, and 6 more reacted with thumbs up emoji vkrol, zpdDG4gta8XKpMCd, and jkillian reacted with hooray emoji
All reactions
  • 👍 16 reactions
  • 🎉 3 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ajafff

Klaus Meinhardt (ajafff) commented Oct 30, 2018

Copy link
Copy Markdown
Contributor

Anders Hejlsberg (@ahejlsberg) Would it be possible to use Pick and Exclude to more precisely construct the resulting type of spreading an instantiable type?

function spread<T, U>(t: T, u: U) {
    return { ...t, ...u };  // Pick<T, Exclude<keyof T, keyof U>> & U
}

Of course this needs to be a little bit more complex to correctly handle optional properties.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ahejlsberg

Anders Hejlsberg (ahejlsberg) commented Oct 30, 2018

Copy link
Copy Markdown
Member Author

Would it be possible to use Pick and Exclude to more precisely construct the resulting type of spreading an instantiable type?

It's possible, see for example my comment here. However, it ends up generating very complex types (and commensurately complex error messages), and even then we still lack accurate representation of which properties were declared optional and which properties are "own enumerable" properties.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

sandersn
Nathan Shively-Sanders (sandersn) reviewed Oct 30, 2018
View reviewed changes
Comment thread tests/baselines/reference/objectRestNegative.errors.txt Resolved

Uh oh!

There was an error while loading. Please reload this page.

sandersn
Nathan Shively-Sanders (sandersn) reviewed Oct 30, 2018
View reviewed changes
Comment thread tests/baselines/reference/objectSpreadNegative.errors.txt Resolved

Uh oh!

There was an error while loading. Please reload this page.

sandersn
Nathan Shively-Sanders (sandersn) reviewed Oct 30, 2018
View reviewed changes
Comment thread tests/baselines/reference/restInvalidArgumentType.errors.txt Resolved

Uh oh!

There was an error while loading. Please reload this page.

sandersn
Nathan Shively-Sanders (sandersn) reviewed Oct 30, 2018
View reviewed changes
Comment thread tests/baselines/reference/spreadInvalidArgumentType.errors.txt Resolved

Uh oh!

There was an error while loading. Please reload this page.

sandersn
Nathan Shively-Sanders (sandersn) requested changes Oct 30, 2018
View reviewed changes

@sandersn Nathan Shively-Sanders (sandersn) left a comment

Copy link
Copy Markdown
Member

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.

  1. Test cleanup
  2. Higher-order rest should be an error until we address it too.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Anders Hejlsberg (ahejlsberg) added 4 commits October 30, 2018 16:00
@ahejlsberg
Continue to error on rest from generic source type
9d5e8fe
@ahejlsberg
Accept new baselines
3c36339
@ahejlsberg
Update tests
2ed627a
@ahejlsberg
Accept new baselines
1577f94
@ahejlsberg

Anders Hejlsberg (ahejlsberg) commented Oct 30, 2018

Copy link
Copy Markdown
Member Author

Nathan Shively-Sanders (@sandersn) Your comments should be addressed now. In some cases I moved the tests, in others I just updated the comments.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sandersn Nathan Shively-Sanders (sandersn) self-assigned this Oct 31, 2018
sandersn
Nathan Shively-Sanders (sandersn) approved these changes Oct 31, 2018
View reviewed changes
@ahejlsberg
Anders Hejlsberg (ahejlsberg) merged commit 8e4b90d into master Oct 31, 2018
@ahejlsberg
Anders Hejlsberg (ahejlsberg) deleted the genericSpread branch October 31, 2018 19:52
@weswigham Wesley Wigham (weswigham) mentioned this pull request Nov 1, 2018
Compiler is forgetting the basetype of an extended type when that type is generic #28280
Closed
@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Nov 2, 2018
Generic object rest variables and parameters #28312
Merged
weswigham
Wesley Wigham (weswigham) reviewed Nov 2, 2018
View reviewed changes
Comment thread src/compiler/checker.ts

function isValidSpreadType(type: Type): boolean {
return !!(type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.NonPrimitive) ||
return !!(type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) ||

@weswigham Wesley Wigham (weswigham) Nov 2, 2018

Copy link
Copy Markdown
Member

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.

In this InstantiableNonPrimitive case, shouldn't this be checking that the constraint of type, if it exists, isValidSpreadType?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) mentioned this pull request Nov 3, 2018
Design Meeting Notes, 11/2/2018 #28326
Closed
@sandersn Nathan Shively-Sanders (sandersn) mentioned this pull request Nov 19, 2018
Object.assign return assigned type instead merged #28553
Closed
@jack-williams

Jack Williams (jack-williams) commented Nov 23, 2018

Copy link
Copy Markdown
Collaborator

Thus, spreads of the same types may produce different results when they are created through instantiation of generic types

Is the following behaviour also grouped under this description?

function ripoff<T>(augmented: T): T {
  const { ...original } = augmented;
  return original;
}

interface Foo {
  x: number;
  (y: string): number;
}

const foo = (x: string) => x.length;
foo.x = 42;
const x: Foo = ripoff(foo);
x("string") // TypeError: x is not a function

Did the team consider returning the type Pick<T, keyof T> instead?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sandersn

Nathan Shively-Sanders (sandersn) commented Nov 26, 2018

Copy link
Copy Markdown
Member

Jack Williams (@jack-williams) Yes, except that's rest, not spread. The principle is the same though. We think that pattern of spreading a callable object is rare, especially in generic functions like ripoff. (Non-generic uses are already correct.)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jack-williams

Jack Williams (jack-williams) commented Nov 26, 2018

Copy link
Copy Markdown
Collaborator

Nathan Shively-Sanders (@sandersn)

Yes, except that's rest, not spread.

Yep sorry, silly mistake.

We think that pattern of spreading a callable object is rare, especially in generic functions like ripoff.

I agree that the callable case is rare (and contrived) but there are other object invariants someone might expect to be preserved by a function of type <X>(x: X) => X. The current choice seems to be invested in either having rest preserve all these invariants (current and future new object features) or having the violation of these invariants being suitable rare or low-impact as to not matter.

I expect my concerns are unfounded, it just seems that types like Pick are exactly what you want in this situation.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lsagetlethias Lilian Saget-Lethias (lsagetlethias) mentioned this pull request Dec 20, 2018
Support Typescript last version or support choosing local typescript bin TypeStrong/typedoc#924
Closed
@JasonKleban

JasonKleban commented Jan 8, 2019

Copy link
Copy Markdown

Is this expected? Playground

function func2<T extends { a: string, b: string }>(param: T)
    : { a: string, b: string } {
    const { a, ...rest } = param;
    const x = rest.b; // Property 'b' does not exist on type 'Pick<T, Exclude<keyof T, "a">>'.
    return { a, ...rest }; // Property 'b' is missing in type '{ a: string; } & Pick<T, Exclude<keyof T, "a">>' 
                           // but required in type '{ a: string; b: string; }'.
}
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ahejlsberg

Anders Hejlsberg (ahejlsberg) commented Jan 8, 2019

Copy link
Copy Markdown
Member Author

JasonKleban That was fixed by #29121, give it a try with typescript@next.

JasonKleban and styfle reacted with heart emoji
All reactions
  • ❤️ 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Porges

George Pollard (Porges) commented Jun 18, 2019

Copy link
Copy Markdown
Member

A bit of an aside to Anders' comment:

Would it be possible to use Pick and Exclude to more precisely construct the resulting type of spreading an instantiable type?

It's possible, see for example my comment here. However, it ends up generating very complex types (and commensurately complex error messages), and even then we still lack accurate representation of which properties were declared optional and which properties are "own enumerable" properties.

I was looking at typing Object.assign better, and there's a few modifications that could be made to
Omit make it generate (somewhat) nicer-looking types, which helps with intellisense inspection of inferred types, etc.

Given:

let x_1 = { x: 1 };
let x_true = { x: true };
let x_string = { x: "hello" };

If we wanted to type assign 'more precisely' (but not 'most precisely'?) we could write:

const assign3 =
    <T, U, V> (first: T, second: U, third: V)
        : (Omit<T, keyof (U&V)> & Omit<U, keyof V> & V) =>
        Object.assign(first, second, third) as any;

The library Omit type is:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

The generated result type looks pretty bad:

image

To improve it, we can handle the "exclude everything" case like so:

type Omit<T, K extends keyof any> =
    Exclude<keyof T, K> extends never
    ? {}
    : Pick<T, Exclude<keyof T, K>>;

This greatly simplifies the generated type:
image

However, if we add back more properties...

let x_1 = { x: 1, j: 2 };
let x_true = { x: true, k: 2 };
let x_string = { x: "hello", l: 2 };

We are again in "scary type" territory:
image

To improve this we can inline Pick into the definition:

type Omit<T, K extends keyof any> =
    Exclude<keyof T, K> extends never
    ? {}
    : { [k in Exclude<keyof T, K>]: T[k] };

And we end up with something a bit nicer (although there seems to be no way to force & to reduce):
image

I'm not sure if this is worth doing to Omit or if there should be some attribute that can be applied to types like Pick saying that they are "transparent" and should always be fully expanded, never shown.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jcalz Joe Calzaretta (jcalz) mentioned this pull request Jun 21, 2019
missing properties after applying spread operator on instance of abstract type #32022
Closed
@karlhorky Karl Horky (karlhorky) mentioned this pull request Jul 4, 2019
Full Handbook Page on Soundness microsoft/TypeScript-Handbook#1059
Open
@saulshanabrook Saul Shanabrook (saulshanabrook) mentioned this pull request Sep 24, 2019
Destructure and spread on generics not typing #33579
Closed
@syy1125 Jack Sun (syy1125) mentioned this pull request Oct 4, 2019
Inferred return type of a generic function with destructure incorrectly retains generics #33823
Closed
@jnv Jan Vlnas (jnv) mentioned this pull request Feb 28, 2020
docs(README.md): update info about type spread operator in TS niieani/typescript-vs-flowtype#69
Merged
@RyanCavanaugh Ryan Cavanaugh (RyanCavanaugh) mentioned this pull request Apr 14, 2020
Type inference issue with spread and generics #37934
Closed
@WilliamPeralta

WilliamPeralta commented Oct 13, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
interface InputProps {
    name: string
    value: any
}


interface SelectProps extends InputProps{
    items: any[]
}

export const Input =  (props : InputProps) => {
    return <div>Sono un input</div>
}

export const Select =  (props : SelectProps) => {
    return <div>Sono un Select</div>
}



type PropsOf<T> = T extends React.ComponentType<infer Props> ? Props : object

// type BPropsOf<T> = PropsOf<BaseFieldProps<T>>

type BaseFieldProps<T> = {
    field1: string
    field2: boolean
    component: React.ComponentType<PropsOf<T>>
} & {
    [Property in keyof PropsOf<T>]: PropsOf<T>[Property]
}


// type FieldProps = BaseFieldProps<InputProps>  // & PropsOf<BaseFieldProps<InputProps>['component']>

//  & PropsOf<FieldProps['component']> 
function Field < C >(props : BaseFieldProps<C>){
    return <div>Sono un field</div>
}


export const Home =  () => {
    return (
        <div>
            <h2>I am body</h2>
            <Field<typeof Select>
                field1="field1"
                field2    
                component={Select}
               {/* Here all the properties of the component */}
            />
        </div>
    )
}

There is a way to avoid having to write the generic "typeof Select" near "Field" and extract it from the prop "component"??

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@MajorLift Jongsun (MajorLift) mentioned this pull request Feb 22, 2024
[base-controller] Fix any usage in BaseControllerV1 MetaMask/core#3959
Merged
3 tasks done
Jongsun (MajorLift) added a commit to MetaMask/core that referenced this pull request Feb 26, 2024
@MajorLift
Fix code written based on outdated TypeScript limitation
538c5c1
- Generic spread expressions for object literals are supported by TypeScript: microsoft/TypeScript#28234
Jongsun (MajorLift) added a commit to MetaMask/core that referenced this pull request Feb 27, 2024
@MajorLift
[base-controller] Fix any usage in BaseControllerV1 (#3959)
eda5b9d
## Explanation

- For runtime property assignment, use `as unknown as` instead of `as
any`.
- Change the types for `BaseControllerV1` class fields `initialConfig`,
`initialState` from `C`, `S` to `Partial<C>`, `Partial<S>`.
- Initial user-supplied constructor options do not need to be complete
`C`, `S` objects, since `internal{Config,State}` will be populated with
`default{Config,State}`.
- For empty objects, prefer no type assertions or `as never` (`never` is
assignable to all types).
- Fix code written based on outdated TypeScript limitation.
- Generic spread expressions for object literals are supported by
TypeScript: microsoft/TypeScript#28234

## References

- Closes #3715 

## Changelog

###
[`@metamask/base-controller`](https://github.com/MetaMask/core/pull/3959/files#diff-a8212838da15b445582e5622bd4cc8195e4c52bcf87210af8074555f806706a9)

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've highlighted breaking changes using the "BREAKING" category
above as appropriate
@microsoft Microsoft (microsoft) locked as resolved and limited conversation to collaborators Oct 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@weswigham Wesley Wigham (weswigham) weswigham left review comments
@sandersn Nathan Shively-Sanders (sandersn) sandersn approved these changes
@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) Awaiting requested review from DanielRosenwasser

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@sandersn Nathan Shively-Sanders (sandersn)

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

9 participants

@ahejlsberg @hatamov @ajafff @jack-williams @sandersn @JasonKleban @Porges @WilliamPeralta @weswigham
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.