F# in .NET 11 Preview 4 - Release Notes
May 12, 2026 · View on GitHub
Here's a summary of what's new in F# in this Preview 4 release:
- Improved diagnostic messages
- Codegen correctness — fewer runtime crashes from valid F# code
- Signature generation roundtrips cleanly
- Missing implementations for C#
abstract overrideare now caught at compile time - Faster IDE responsiveness with generative type providers
- Bug fixes and other improvements
Improved diagnostic messages
A batch of long-standing usability issues with compiler diagnostics has been addressed. The focus is clearer wording, actionable hints, and catching common beginner mistakes that previously produced opaque error text (dotnet/fsharp #19398).
Highlights:
- New informational warning FS3886 for a single tuple in a list literal —
[1, 2, 3]is almost always a typo for[1; 2; 3]. Suppressible via#nowarn "3886". - New error FS3885 when
let!/use!is used as the final expression in a computation expression, replacing the confusingUnexpected symbol }. - FS0670 (the "code is not sufficiently generic" error) now suggests concrete fixes: add a type annotation, convert to a function, or mark the binding
inline. - FS3082 lists the available static parameter names (truncated at 5) when an unknown one is supplied.
- FS0072 ("Lookup on object of indeterminate type...") rewritten in plain English with an example annotation.
- Tuple-shaped errors now say "is a tuple of type" / "given a tuple of type" when the actual type is a tuple, and the parser hints at unintended commas instead of reporting a generic missing argument.
- Elif chains without a final
elsenow report "missing else branch" instead of a confusing type-mismatch error. - FS0597 reworked with a concrete example:
printfn "%s" (arg.Trim()). - SRTP errors on function or tuple types now append the hint "or the operator may not be in scope".
let xs = [1, 2, 3]
// warning FS3886: This list contains a single tuple element. Did you mean to use ';' instead of ','?
let compute x =
async {
let! result = doWorkAsync x
}
// error FS3885: 'let!' / 'use!' cannot be the final construct in a computation expression.
Codegen correctness — fewer runtime crashes from valid F# code
Several long-standing IL-emit bugs that produced TypeLoadException, InvalidProgramException, or stack corruption at runtime for otherwise valid F# programs are now fixed (dotnet/fsharp #19338). This continues the codegen-correctness work started in Preview 3, which dropped the F# compiler's ILVerify error count from 551 to 56.
Fixes in this preview:
voidptrin delegate generic arguments and interface instantiations is now correctly encoded asnativeintin closure metadata.- Tail calls are now suppressed in methods that use
NativePtr.stackalloc/localloc, where they were unsafe. - Generic type parameter constraints are no longer stripped from
Specialize<>closure methods. - The
boxinstruction is no longer emitted spuriously for literal upcasts (including default parameter values) or for decimal literals in debug builds.
Signature generation roundtrips cleanly
FSharpCheckFileResults.GenerateSignature and fsc --sig now produce signatures that roundtrip-compile against their implementations across a much wider range of inputs (dotnet/fsharp #19586, dotnet/fsharp #19609). A corpus-wide sweep of 1,483 standalone test files was used to drive out the remaining bugs.
Fixes include:
- Recursive module
dobindings no longer leak compiler-generatedvals into the signature. - Literal values in attribute arguments are preserved as identifiers instead of being reduced to constants.
- Struct types now carry the correct
[<NoComparison>]/[<NoEquality>]attributes in display. - Backtick escaping is correct for identifiers that themselves contain backticks.
- The
privatekeyword is placed correctly in prefix-style type abbreviation signatures. - Types without visible constructors now emit the
[<Class>]attribute. namespace globalis detected on thefsc --sigpath, not just through the FCS API.
The on-disk pickle format is unchanged — old compilers read new DLLs identically. Display improvements also flow into IDE tooltips.
Missing implementations for C# abstract override are now caught at compile time
When a non-abstract F# class inherited from a C# class containing abstract override members (for example public abstract override string ToString()), the F# compiler did not flag the missing implementation, and the program failed at runtime with TypeLoadException. The compiler now correctly reports FS0365 at compile time (dotnet/fsharp #19503).
// C#
public abstract class Base {
public abstract override string ToString();
}
// F#
type Derived() =
inherit Base()
// error FS0365: No implementation was given for 'Base.ToString() : string'
Faster IDE responsiveness with generative type providers
Solutions using generative type providers no longer pin the IDE language service at high CPU. TypeStructure.GetHashCode previously computed an O(n) hash over the underlying TypeToken[] on every cache lookup, and the type-subsumption cache rehashed all entries on each rebuildStore. The hash is now precomputed once at construction (O(1) lookup), with a fast-path equality check that compares cached hashes before token arrays (dotnet/fsharp #19369).
The reported scenario — a multi-project solution referencing a generative type provider with ~15 provided types — saw sustained ~150% CPU on the F# language service that no longer reproduces. Thank you @OnurGumus for this contribution!
Bug fixes and other improvements
Computation expressions and SRTP
- Pattern-based extension method lookups for computation-expression members (
Bind,Delay,Run, etc.) and for theDisposepattern inusebindings now honor accessibility and type-compatibility checks. Previously they could resolve to private extension methods or methods on incompatible types (dotnet/fsharp #19536). Thank you @evgTSV! - Fixed
seq { }try/withrunning the handler body twice when the handler itself yielded (dotnet/fsharp #19660). - Fixed a
builder@variable leak in resumable state machines introduced by the Preview 2 task-builder optimization (dotnet/fsharp #19630). Thank you @majocha! - Fixed SRTP trait call overload resolution returning the wrong typed value across multiple FSI submissions (dotnet/fsharp #19471).
- Fixed a
NullReferenceExceptionwhen calling virtualObjectmethods on value types through inline SRTP code (dotnet/fsharp #8098).
Symbols API, tooltips, and completion
CLIEventproperties are now reported as events through the Symbols API:IsEventreturnstrue, the XML doc signature uses theE:prefix, and tooltips renderevent EventName(dotnet/fsharp #18584).- Methods are now tagged as
Methodrather thanMemberin tooltips, fixing a regression that affected colorization and classification (dotnet/fsharp #19507). - Compiler-generated auto-property symbols (the
vsetter parameter andFoo@backing field formember val Foo = 123 with get, set) are no longer surfaced throughGetAllUsesOfAllSymbolsInFile(dotnet/fsharp #19498). - Completion now consistently filters obsolete fields and events (dotnet/fsharp #13512).
IlxGennow emits theCompilationMappingattribute for generic values (dotnet/fsharp #19643). Thank you @auduchinok!
Compiler correctness
AttributeUsage.AllowMultipleis now correctly inherited through C#-defined attribute hierarchies — the inheritance walk previously only consulted F# augmentation data and missed IL-imported supertypes (dotnet/fsharp #19315). Thank you @edgarfgp!- Type definitions no longer emit duplicate or incorrect metadata: DU case names that match IWSAM member names no longer produce duplicate property entries,
DefaultAugmentation(false)no longer causes duplicate method-table entries, and abstract eventadd_/remove_accessors are now emitted with theSpecialNameflag (dotnet/fsharp #19341). - Fixed FS0452 in quotations that pattern-match an empty string (
match s with "" -> ...) — the empty-string optimization tomkNonNullTesthad no quotation translator handler (dotnet/fsharp #19532). - Fixed an incorrect range on warning 20 inside sequential expressions (dotnet/fsharp #5735).
Community contributors
Thank you contributors! ❤️
F# updates: