Skip to content

Record constructors (FS-1073) - #19974

Merged
T-Gro merged 26 commits into
dotnet:mainfrom
charlesroddie:feature/record-positional-ctor
Aug 7, 2026
Merged

Record constructors (FS-1073)#19974
T-Gro merged 26 commits into
dotnet:mainfrom
charlesroddie:feature/record-positional-ctor

Conversation

@charlesroddie

@charlesroddie charlesroddie commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Implements FS-1073. F# records compile to a class whose all-fields constructor is callable from C# (new MyRecord(a, b)) but not from F#, which only permits { Field = … }. This adds a RecordConstructorSyntax preview language feature that surfaces that constructor to F#, with positional and named arguments.

Notes:

  • Accessibility mirrors { } construction, so a private/internal record's representation is not bypassed (unlike C#'s public IL constructor).
  • An existing same-named value binding takes precedence over the constructor (FS-1073 scope rule).
  • Behind --langversion:preview.
  • Pattern matching is out of scope.

Records compile to a class whose all-fields constructor is callable from C#
(new MyRecord(a, b)) but not from F#, which only allows { Field = ... } syntax.
This adds a RecordConstructorSyntax preview feature that surfaces that constructor
to F# too, supporting positional and named arguments.

Implemented via a new MethInfo.RecdAllFieldsCtor case surfaced by InfoReader for
record tycons; it elaborates through the existing mkRecordExpr path, so there is
no codegen or overload-resolution change. Accessibility mirrors { } construction
(the ctor is no more accessible than the record's representation/fields), so the
C# behaviour of a public IL constructor bypassing a private record is not inherited.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

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


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`src/Compiler` docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
`src/Compiler/Facilities/LanguageFeatures.fsi` docs/release-notes/.Language/preview.md

charlesroddie and others added 2 commits June 19, 2026 11:58
Shorter name; a record has exactly one synthesized constructor, so the
'AllFields' qualifier is not needed to disambiguate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files label Jun 19, 2026
Comment thread src/Compiler/Checking/InfoReader.fs
Comment thread src/Compiler/Facilities/LanguageFeatures.fs
The feature surfaces only the all-fields constructor. A struct record's
zero-init default and a [<CLIMutable>] record's IL parameterless .ctor must
remain non-callable from F#; both 'Point()' and 'R()' are rejected (FS0501).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread src/Compiler/Checking/InfoReader.fs
charlesroddie and others added 3 commits June 22, 2026 10:46
…terless ctor

A struct record's 'Point()' is default (zero) initialization, not a real
constructor; only [<CLIMutable>] emits an actual parameterless .ctor. Name the
two tests accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rangeOfMethInfo had no RecdCtor case, so it fell through to ArbitraryValRef
(None) and GoToDefinition on a positional record-constructor call navigated
nowhere. Add a RecdCtor arm returning the record type's range, mirroring
DefaultStructCtor.

Adds FSharp.Compiler.Service.Tests smoke tests: go-to-definition lands on the
record type, the tooltip mentions the type, and find-all-references links the
constructor call to the type declaration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regression guard: the positional constructor has no field labels, so a
[<RequireQualifiedAccess>] record must construct without a spurious diagnostic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the ⚠️ Affects-Compiler-Output Tooling check: PR touches IL emission or codegen label Jun 22, 2026
@github-actions

This comment has been minimized.

charlesroddie and others added 2 commits June 22, 2026 15:53
…073)

Library exposes a record and an inline constructor function; the app constructs
records positionally and via the inline function. The new syntax is gated behind
RECORD_CTOR_FEATURE / --langversion:preview for local builds, with a classic { }
fallback so SDK-compiler scenarios still build. Both branches elaborate to the
same record-allocation node, so the pickled representation is unchanged and the
matrix exercises both directions (feature-enabled consumer, older consumer).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The constructor is not a declared member, so it rides on the record's
representation visibility through a signature: available when the .fsi exposes
the representation, rejected (FS1133, like { }) when the .fsi hides it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj Outdated
Comment thread tests/projects/CompilerCompat/CompilerCompatApp/Program.fs Outdated
…ents

Per review: rename the per-feature RECORD_CTOR_FEATURE define to the reusable
USES_PREVIEW_COMPILER, and remove the explanatory comments so the addition is
compact. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
charlesroddie and others added 3 commits June 29, 2026 16:57
A positional record constructor quotes as NewRecord (R, Value (1), Value (2)),
identical to { A = 1; B = 2 } - both lower to the same node before quotation
translation, so no constructor call appears in the quotation. Uses
Console.WriteLine + Expr.ToString() to avoid sprintf/printf.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tional-ctor

# Conflicts:
#	src/Compiler/Facilities/LanguageFeatures.fs
#	src/Compiler/Facilities/LanguageFeatures.fsi
main added a CSharpExtensionTypeDisplay parameter to layoutMethInfoCSharpStyle
and updated its callers; the RecdCtor arm (new in this branch) was auto-merged
with the old call. Pass extTypeDisplay to match the sibling arms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@charlesroddie

Copy link
Copy Markdown
Contributor Author

This PR OK @T-Gro ? Seems straightforward to me.

@github-actions

This comment has been minimized.

…tional-ctor

# Conflicts:
#	src/Compiler/Facilities/LanguageFeatures.fs
#	src/Compiler/Facilities/LanguageFeatures.fsi
@charlesroddie

Copy link
Copy Markdown
Contributor Author

PR updated @abonie

…tional-ctor

# Conflicts:
#	src/Compiler/xlf/FSComp.txt.cs.xlf
#	src/Compiler/xlf/FSComp.txt.de.xlf
#	src/Compiler/xlf/FSComp.txt.es.xlf
#	src/Compiler/xlf/FSComp.txt.fr.xlf
#	src/Compiler/xlf/FSComp.txt.it.xlf
#	src/Compiler/xlf/FSComp.txt.ja.xlf
#	src/Compiler/xlf/FSComp.txt.ko.xlf
#	src/Compiler/xlf/FSComp.txt.pl.xlf
#	src/Compiler/xlf/FSComp.txt.pt-BR.xlf
#	src/Compiler/xlf/FSComp.txt.ru.xlf
#	src/Compiler/xlf/FSComp.txt.tr.xlf
#	src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
#	src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
@T-Gro

T-Gro commented Aug 4, 2026

Copy link
Copy Markdown
Member

🤖🕵️
@charlesroddie Re the synthesized record constructor surfacing: it's emitted unconditionally, so it leaks into signature-file generation (SigGenerationRoundTripTests fail with non-compiling .fsi — FS0193/FS0501) and flips FS1133→FS0501 under the default langversion. Exclude RecdCtor from .fsi/NicePrint output and gate its surfacing behind the RecordConstructorSyntax langversion, not just the call site.

@github-actions github-actions Bot added the ⚠️ Affects-Build-Infra Tooling check: PR touches build infrastructure label Aug 4, 2026
@github-actions

This comment has been minimized.

charlesroddie and others added 2 commits August 4, 2026 19:25
The synthesized all-fields constructor was surfaced unconditionally, leaking
into signature-file generation (SigGenerationRoundTripTests produced
non-compiling .fsi) and changing name resolution on released langversions.

Gate the surfacing on the RecordConstructorSyntax langversion in InfoReader
(not just at the call site), exclude RecdCtor from NicePrint/.fsi output, and
drop the now-redundant call-site feature check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tional-ctor

# Conflicts:
#	src/Compiler/Facilities/LanguageFeatures.fs
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Build-Infra, Affects-Compiler-Output
Affects-Build-Infra: CompilerCompat .fsproj files add PropertyGroup with LangVersion/DefineConstants
Affects-Compiler-Output: Modifies compiler checking/name resolution to add record constructor syntax

Generated by PR Tooling Safety Check · opus46 3.9M ·

charlesroddie and others added 3 commits August 4, 2026 20:06
…eLine

- infos.fs: build the single tupled parameter group with List.singleton
  instead of a two-line list literal
- NicePrint.fs: split the constructor filter one conjunct per line
- CompilerCompat Program.fs: use Console.WriteLine in the record-constructor
  block (printn is not yet available)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- InfoReader.fs: match ValueNone rather than a wildcard in the RecdCtor
  XML-doc-signature arm
- Trim the RecdCtor comments in InfoReader/NicePrint/MethodCalls to the
  non-obvious rationale

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@charlesroddie

Copy link
Copy Markdown
Contributor Author

🤖🕵️ @charlesroddie Re the synthesized record constructor surfacing: it's emitted unconditionally, so it leaks into signature-file generation (SigGenerationRoundTripTests fail with non-compiling .fsi — FS0193/FS0501) and flips FS1133→FS0501 under the default langversion. Exclude RecdCtor from .fsi/NicePrint output and gate its surfacing behind the RecordConstructorSyntax langversion, not just the call site.

Thanks for the advice! Was a bit stuck on this. Done now.

@T-Gro
T-Gro enabled auto-merge (squash) August 6, 2026 13:35
@T-Gro
T-Gro requested review from T-Gro and abonie August 6, 2026 13:35
auto-merge was automatically disabled August 7, 2026 04:07

Head branch was pushed to by a user without write access

@charlesroddie

Copy link
Copy Markdown
Contributor Author

Fixed spacing so needs approval again @T-Gro @abonie thanks!

@T-Gro
T-Gro merged commit 7e2cbf1 into dotnet:main Aug 7, 2026
48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ Affects-Build-Infra Tooling check: PR touches build infrastructure ⚠️ Affects-Compiler-Output Tooling check: PR touches IL emission or codegen AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants