Update FCS - #3400
Merged
Merged
Conversation
Bump the vendored compiler sources from ab1f6cea to 9487d36e776a5c7315f04a6ddf2887f92ab60d49, spanning three upstream commits that touch src/Compiler/SyntaxTree: 0abb33dc Address signature generation bugs (#19586) cd1d1804 Address additional signature generation roundtrip bugs (#19609) 9487d36e Fix #17904 and #19020 (#19738) The last of those makes mkSynBinding move a `[<return: ...>]` attribute written in front of a binding out of the binding's attribute list and into the arity information. Fantomas reads attributes from SynBinding only, so those attributes were parsed and then never printed, silently deleting them from the formatted output. Partial active patterns marked `[<return: Struct>]` are the common case, and the Fantomas code base itself relies on them. The existing tests only covered the return type annotation form, `let f x : [<return: Attribute>] int = x`, so the whole suite stayed green while source was being dropped. restoreRotatedReturnAttributes puts those attributes back in the attribute list they were written in. Attributes written on a return type annotation end up in the arity information as well, and are left alone there, because the return type node already prints them.
Bump the vendored compiler sources to f4b785f189aedc4a0f1ec22182e3653a0b9dd142 and support the record spread syntax it introduces, RFC FS-1151, dotnet/fsharp#18927. A spread reaches the syntax tree in three places, and each one changed shape: SynTypeDefnSimpleRepr.Record now holds SynFieldOrSpread SynExpr.Record now holds SynExprRecordFieldOrSpread SynExpr.AnonRecd now holds SynExprAnonRecordFieldOrSpread Oak mirrors that with two unions, ExprRecordFieldOrSpread for the expression forms and TypeDefnRecordFieldOrSpread for the record representation of a type definition, carrying ExprSpreadNode and TypeSpreadNode. Nominal and anonymous record items keep sharing RecordFieldNode, which upstream splits in two, and the block separator is left behind since the printer decides separators itself. The spread source is an arbitrary expression, the same grammar as the right-hand side of a field, so it can be multiline. Nothing may come between the dots and the source, which means a spread has no break to decide and prints as the dots followed by the expression. That keeps the existing cramped and aligned field printers untouched. ExprRecordBaseNode.HasFields becomes HasItems, since a record holding only a spread has no fields but is not empty, and the value decides whether a separator follows an inherit or copy-from clause. Also use the range the parser now provides on SynExprAnonRecordField instead of reconstructing it from the field name and the value. Add RecordSpreadTests.fs covering the three positions in implementation and signature files, spreads before, between and after fields, multiple spreads, record and anonymous record literals, applications and property gets as sources, struct anonymous records, Stroustrup, and comments attached to a spread.
Bump the vendored compiler sources to d3403caee0e62ae3a64964f19fc93f20a50d6aae, dotnet/fsharp#19971. The second field of SynInterpolatedStringPart.FillExpr changed from `qualifiers: Ident option` to a SynInterpolationFormatting, which models the alignment of `{x,10}` and the specifier of `%d{x}` separately instead of leaving them inside the fill expression and the preceding string part. Nothing is lost from the tree, it carries more than before, but Fantomas read the old locations. Absorb the change in ASTTransformer so the printer and the Oak model stay as they are, and so the formatted output does not move. The alignment is folded back into the expression as the tuple the parser used to hand us, which is why `{x, 10}` keeps its existing space. The comma has no node of its own, its range is the gap between the expression and the alignment. A printf specifier no longer appears in the text of the preceding string part, though that part's range still covers it. The source text path is therefore unaffected, but the fallback used when there is no source text has to look ahead and re-append the specifier, otherwise `%d{x}` prints as `{x}` when formatting from an AST. Negative alignment, `$"{value,-10}"`, previously failed to parse and now formats. Add tests for alignment in string interpolation, which nothing covered, including one that goes through the AST rather than the source text so the reconstructed comma is exercised.
Bump the vendored compiler sources to ea778bb414648883fca4219bf7a69286bf82dd6b, dotnet/fsharp#20106. Non-strict indentation is gone from the lexer, so the `strictIndentation` parameter disappears from the lexbuf constructors in UnicodeLexing, ParseHelpers and prim-lexing, along with its language feature flag. Drop the argument at the one call site. Formatting is unaffected. Fantomas already passed `Some true` there, so it had opted into strict indentation, which is exactly the mode that survives upstream.
Bump the vendored compiler sources to 5260b68c6db2a56b973a0fba0d614cadf3599682, dotnet/fsharp#19186. The commit adds SyntaxTree/XmlDocIncludeExpander.fs and its signature, and XmlDoc.fs now calls into them, so both vendored file lists need the pair. Neither list globs, so a new upstream file has to be added by hand to the Init download list and to the Compile items, in the same position upstream compiles it, between UnicodeLexing and XmlDoc. Formatting is unaffected. XmlDoc gained an expansion of <include> tags used while validating a doc comment, and that validation reads files from disk, but Fantomas asks for the doc comment without checking it, so the expander is never invoked.
Bump the vendored compiler sources from 5260b68c to
afe45bfeda02e5eab2b9e8a959174a95fdf9ae5e, spanning two upstream commits
that touch src/Compiler/SyntaxTree:
34a3053b Allow closing '>' of multiline nested type arguments to align
with the opener (#20003)
afe45bfe Remove always-on WildCardInForLoop language feature flag
(#20221)
The first makes the lexer treat a closing '>' as a sequence block element
continuator, so a type argument list may close on a line aligned with its
opener. That widens what parses and changes nothing about what Fantomas
prints. The vendored compiler runs ahead of the compiler in a published
SDK, so output that only the newer one accepts would not compile for
users. Adopting the layout is a separate decision for later.
The second drops a language feature flag that was already always on, which
removes the parseState parameter from idOfPat and makes its wildcard case
unconditional. Both ParseHelpers and the pars.fsy call site are vendored,
so they move together.
Add a test for the wildcard identifier in a for loop, `for _ = 1 to 10`.
It is the syntax the second commit is about and nothing in the suite
covered it, so it was passing on luck rather than on a guarantee.
Bump the vendored compiler sources from afe45bfe to d05075e098278aedcea3379159504d664628a495, spanning three upstream commits that touch src/Compiler/SyntaxTree: 208b7b4b Add symbol and type highlighting to F# diagnostics (#20097) d26c842f Remove always-on RelaxWhitespace language feature flag (#20226) d05075e0 Parser: recover on missing 'when' conditions (#20071) The first of those makes diagnostic messages carry classified text, so every SR accessor generated from FSComp.txt returns RichText instead of string. The task that generates them, FSharpEmbedResourceText, ships in the .NET SDK and is older than the sources we vendor, so it cannot emit the classified accessors and the compiler no longer builds against it. Upstream avoids this by bootstrapping its own FSharp.Build. Vendor that one task file instead and compile it in a new Fantomas.FCS.BuildTasks project. FSComp.txt moves out of the EmbeddedText item group into one of our own, which keeps the SDK's generator away from it, and a target mirroring the SDK's GenerateFSharpTextResources runs the vendored task over it. RichText and the sources it needs are marked CompileFirst so they precede the generated file, matching what upstream does in its own project. The namespace rewrite that already turns FSharp.Compiler into Fantomas.FCS on download also rewrites the namespace the task emits, so the generated SR opens Fantomas.FCS.Text. The task itself is renamed to Fantomas.FCS.Build so it cannot be confused with the one in the SDK. Parse.fs reads the text back out of the diagnostics it maps to Fantomas diagnostics, since those are reported as plain strings. Only numbered diagnostics became rich, unnumbered messages are still strings. Formatting is unaffected. Layout, indentation, when clauses and the text of parse errors were all checked against the previous hash.
nojaf
marked this pull request as ready for review
August 21, 2026 13:42
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Bump the vendored compiler sources from
ab1f6ceatod05075e098278aedcea3379159504d664628a495, walking one upstream commit at a time rather than jumping, so a broken build is attributable to a single change.Nineteen upstream commits touching
src/Compiler/SyntaxTreeare crossed. Most were clean bumps. Four needed real work.[<return: Struct>]was being silently deleteddotnet/fsharp#19738 made
mkSynBindingmove a[<return: ...>]attribute written in front of a binding out ofSynBinding.attributesand into the arity information. Fantomas only reads the binding's attribute list, so those attributes were parsed and then never printed.Partial active patterns marked
[<return: Struct>]are the common case, and the Fantomas code base itself has 34 of them, so self-formatting would have removed every one. The existing tests only covered the return type annotation form,let f x : [<return: Attribute>] int = x, so the entire suite stayed green while source was being dropped.restoreRotatedReturnAttributesputs them back in the list they were written in, leaving annotation attributes alone since the return type node already prints those.I raised the layering question upstream on dotnet/fsharp#19738: the rotation used to live in the type checker and patched a local copy, so the parse tree stayed faithful to the source. Moving it into the parser makes the untyped tree report no attributes for source that visibly has one, and it is lossy,
[<return: A; return: B>]and[<return: A>][<return: B>]now produce identical trees.Record spreads
dotnet/fsharp#18927, RFC FS-1151. New syntax in three tree positions, so Oak and
CodePrinterneeded real support:Oak mirrors the upstream shape with two unions,
ExprRecordFieldOrSpreadandTypeDefnRecordFieldOrSpread, carryingExprSpreadNodeandTypeSpreadNode. Simplified where we can: nominal and anonymous record items keep sharingRecordFieldNode, which upstream splits in two, and the block separator is dropped since the printer decides separators itself.ExprRecordBaseNode.HasFieldsbecameHasItems, because a record holding only a spread has no fields but is not empty.52 tests in a new
RecordSpreadTests.fscover the three positions in implementation and signature files, spreads before, between and after fields, multiple spreads, literals and applications and property gets as sources,structanonymous records, Stroustrup, and comments attached to a spread.Interpolated string alignment
dotnet/fsharp#19971 replaced
FillExpr'squalifiers: Ident optionwith aSynInterpolationFormattingthat models alignment and printf specifiers separately.{x,10}used to arrive as a tuple inside the fill expression, which is why Fantomas prints{x, 10}.Absorbed entirely in
ASTTransformerso the printer and the formatted output do not move: the alignment is folded back into the expression as the tuple the parser used to hand us. A printf specifier no longer appears in the preceding string part's text, so the fallback used when there is no source text re-appends it, otherwise%d{x}prints as{x}when formatting from an AST.Alignment had no test coverage at all, which is what would have made this silent. There are four now, including one through the AST path.
$"{value,-10}"previously failed to parse and now formats.The SR generator had to be vendored
dotnet/fsharp#20097 made diagnostic messages carry classified text, so every
SRaccessor generated fromFSComp.txtreturnsRichTextinstead ofstring. The task that generates them,FSharpEmbedResourceText, ships in the .NET SDK and is older than the sources we vendor, so it cannot emit the classified accessors and the compiler no longer builds against it. Upstream avoids this by bootstrapping its ownFSharp.Build.We vendor that one task file instead and compile it in a new
Fantomas.FCS.BuildTasksproject.FSComp.txtmoves out ofEmbeddedTextinto an item of our own, which keeps the SDK's generator away from it, and a target mirroring the SDK'sGenerateFSharpTextResourcesruns the vendored task over it. The namespace rewrite that already turnsFSharp.CompilerintoFantomas.FCSon download also rewrites the namespace the task emits, so the generatedSRopensFantomas.FCS.Textwithout any extra machinery.This is build tooling only. The published packages are unchanged.
Also
Each step was probed by hand with
scripts/format.fsx, not just run against the suite, because the failure mode of a parser shape change is silently dropped source that no existing assertion notices. Two commits made the parser accept a layout it previously rejected, and Fantomas deliberately does not adopt either, since the vendored compiler runs ahead of the compiler in a published SDK and output only the newer one accepts would not compile for users..claude/commands/update-fcs.mddocuments the walk itself, including the traps found along the way.