Skip to content

Record constructors (FS-1073)#19974

Open
charlesroddie wants to merge 13 commits into
dotnet:mainfrom
charlesroddie:feature/record-positional-ctor
Open

Record constructors (FS-1073)#19974
charlesroddie wants to merge 13 commits into
dotnet:mainfrom
charlesroddie:feature/record-positional-ctor

Conversation

@charlesroddie

@charlesroddie charlesroddie commented Jun 19, 2026

Copy link
Copy Markdown

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
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

Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Compiler-Output
Affects-Compiler-Output: adds record constructor resolution path in type checking/overload resolution

Generated by PR Tooling Safety Check · opus46 5.6M ·

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>
<Compile Include="ModuleReaderCancellationTests.fs" />
<Compile Include="EditorTests.fs" />
<Compile Include="Symbols.fs" />
<Compile Include="RecordConstructorTests.fs" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One more test angle please - can you assess the quoted representation and difference between {} and explicit .ctor call?

Can go on with https://github.com/dotnet/fsharp/blob/ae00c30e997ae54e8a68a095849e95efa0056bef/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ExpressionQuotations/QuotationRendering/QuotationRenderingTests.fs and regenerate corresponding quoted baseline.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

No change, tested in 23e39ee

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ 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

Status: New

Development

Successfully merging this pull request may close these issues.

2 participants