Skip to content

Conversation

brianrourkeboll
Copy link
Contributor

@brianrourkeboll brianrourkeboll commented Sep 22, 2025

Description

Add support for the spread operator ... in record types and expressions (nominal and anonymous).

type R1      = { A : int; B : int }
type R2      = { C : int; D : int }
type R3      = { ...R1; ...R2; E : int } // { A : int; B : int; C : int; D : int; E : int }

let r1       = { A = 1; B = 2 }
let r2       = { C = 3; D = 4 }
let r3       = { ...r1; ...r2; E = 5 }   // { A = 1; B = 2; C = 3; D = 4; E = 5 }

let r1' : R1 = { ...r3; B = 99 }         // { A = 1; B = 99 }
let r2'      = {| ...r2 |}               // {| C = 3; D = 4 |}
let r3'      = { ...r1'; ...r2' }        // { A = 1; B = 99; C = 3; D = 4 }
let r3''     = {| ...r1; ...r2 |}        // {| A = 1; B = 2; C = 3; D = 4 |}
let r3'''    = {| ...r1'; ...r2' |}      // {| A = 1; B = 99; C = 3; D = 4 |}

This PR is meant to begin probing the "spread operator for objects" space (especially the set algebra and associated mechanics) while leaving room for implementing more of the scenarios outlined in fsharp/fslang-suggestions#1253 later. For example, should this prove viable, I would expect one of the next additions to be support for spreading non-records into records, i.e., mapping regular class/struct/interface properties/fields to record fields; this PR explicitly disallows that to ensure that we are free to add it later.

Checklist

Copy link
Contributor

❗ Release notes required

@brianrourkeboll,

Caution

No release notes found for the changed paths (see table below).

Please make sure to add an entry with an informative description of the change as well as link to this pull request, issue and language suggestion if applicable. Release notes for this repository are based on Keep A Changelog format.

The following format is recommended for this repository:

* <Informative description>. ([PR #XXXXX](https://github.com/dotnet/fsharp/pull/XXXXX))

See examples in the files, listed in the table below or in th full documentation at https://fsharp.github.io/fsharp-compiler-docs/release-notes/About.html.

If you believe that release notes are not necessary for this PR, please add NO_RELEASE_NOTES label to the pull request.

You can open this PR in browser to add release notes: open in github.dev

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/10.0.100.md No release notes found or release notes format is not correct
LanguageFeatures.fsi docs/release-notes/.Language/preview.md No release notes found or release notes format is not correct

@nojaf
Copy link
Contributor

nojaf commented Sep 22, 2025

Amazing work! I was literally asking @edgarfgp a few days ago if we had something like type R3 = { ...R1; ...R2; E : int } in F#!


// F# preview (still preview in 10.0)
LanguageFeature.FromEndSlicing, previewVersion // Unfinished features --- needs work
LanguageFeature.RecordSpreads, previewVersion
Copy link
Member

@T-Gro T-Gro Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see two options on how to integrate all forms of spreads:

  • 1: Have a dedicate feature/spreading branch and have PRs merging incremental additions to it. This is how bigger features have been done in the past.

  • 2: Add the features to main (make us of the fact that NET10 development is close now, and within short time main will mean net11 already -> plenty of time) via dedicate feature switches. And prior to major release time, decide on the feature set to bring in.

Option 1 has advantages in terms of overall feature marketing and explain-ability to F# users.
Option 2 would give us options to dogfood selected pieces via preview SDK, and gather feedback sooner. (with the usual preview disclaimer of those bits being subject to potential change).

In this PR , Record spreads IMO form a coherent addition to the language and could be integrated standalone (= I would vote for option 2 here, i.e. integrate to main once main means net11)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be a bit afraid that a feature branch would take a lot of effort to keep conflict-free. But yes, I understand the desire to avoid a situation like from-end slicing where the feature is never enabled but the extra code complexity sticks around (someday I will revive my slice branch that will address that particular problem, though 🙂).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am with you on merging it to main as long as we can imagine final (e.g. at major .NET release time) user communication for that specific subset.

(sensible cuts I could imagine being separately release-able as of now basically follow the sections of the RFC Discussion: LanguageFeature.RecordSpread, ObjectIntoRecordSpread, InterfaceImplementationSpread, SpreadingPattern )

@dsyme
Copy link
Contributor

dsyme commented Sep 22, 2025

@brianrourkeboll Great to see work starting in this direction!


/// Resolve a long identifier representing a record field
let ResolveFieldPrim sink (ncenv: NameResolver) nenv ad ty (mp, id: Ident) allFields =
let ResolveFieldPrim sink (ncenv: NameResolver) nenv ad ty (mp, id: Ident) isFromSpread allFields =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's early (and it's a draft), but I wonder if instead of boolean flag, it can be represented by the struct DU? Intention will be a bit more readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize you probably meant something like type FromSpreadOrNah = FromSpread | Nah, but since we technically already had that information in scope anyway...

c61dbf4

It got a bit invasive, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: New
Development

Successfully merging this pull request may close these issues.

5 participants