Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0.400
FROM mcr.microsoft.com/dotnet/sdk:9.0.300

# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Some common use cases include:

<!-- Versions -->
<PropertyGroup>
<FCSCommitHash>e668b90e3c087e5fba8a855e502af60bf35be45e</FCSCommitHash>
<FCSCommitHash>20ff6a94300b5f2f5498d3d8ad1a44abaabc5c22</FCSCommitHash>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="FSharp.Core" Version="8.0.100"/>
<PackageVersion Include="FSharp.Core" Version="9.0.300"/>
Copy link
Contributor

Choose a reason for hiding this comment

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

I no longer remember how this stuff works, but can we stick to dotnet 8 SDK and versions? Or do you really need 9?

Copy link
Author

Choose a reason for hiding this comment

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

Due to the new nullness-related constructs, compiling the current compiler service requires 9.0 or higher for both fsc and FSharp.Core.
That's why I wrote above that this PR might be a draft one for quite some time.
I am assuming you might want to wait for the LTS SDK 10.
Regarding features I don't think there is a big pressure to move. The current Fantomas can deal with sources that contain nullness constructs or #warnon.
It is just that the changes accumulate. I naively just wanted to make the changes for "scoped nowarn" but found I had to deal with three other breaking changes also.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you have any more details on that? What exactly in FSharp.Core 9 does any of this depend on?

Copy link
Author

@Martin521 Martin521 Jun 4, 2025

Choose a reason for hiding this comment

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

Do you have any more details on that? What exactly in FSharp.Core 9 does any of this depend on?

It is all about nullness.
If I compile with SDK 9.0.300 and FSharp.Core 8.0.100, I get 85 errors in the Fantomas.FCS project, mostly about unknown objnull and NonNull.

<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
<PackageVersion Include="System.Memory" Version="4.6.0" />
Expand Down
4 changes: 4 additions & 0 deletions build.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ pipeline "Init" {
"src/Compiler/SyntaxTree/SyntaxTree.fs"
"src/Compiler/SyntaxTree/SyntaxTreeOps.fsi"
"src/Compiler/SyntaxTree/SyntaxTreeOps.fs"
"src/Compiler/SyntaxTree/WarnScopes.fsi"
"src/Compiler/SyntaxTree/WarnScopes.fs"
"src/Compiler/SyntaxTree/LexerStore.fsi"
"src/Compiler/SyntaxTree/LexerStore.fs"
"src/Compiler/SyntaxTree/ParseHelpers.fsi"
"src/Compiler/SyntaxTree/ParseHelpers.fs"
"src/Compiler/SyntaxTree/LexHelpers.fsi"
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.400",
"version": "9.0.300",
"rollForward": "latestPatch"
}
}
10 changes: 5 additions & 5 deletions src/Fantomas.Benchmarks/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
},
"FSharp.Core": {
"type": "Direct",
"requested": "[8.0.100, )",
"resolved": "8.0.100",
"contentHash": "ZOVZ/o+jI3ormTZOa28Wh0tSRoyle1f7lKFcUN61sPiXI7eDZu8eSveFybgTeyIEyW0ujjp31cp7GOglDgsNEg=="
"requested": "[9.0.300, )",
"resolved": "9.0.300",
"contentHash": "TVt2J7RCE1KCS2IaONF+p8/KIZ1eHNbW+7qmKF6hGoD4tXl+o07ja1mPtFjMqRa5uHMFaTrGTPn/m945WnDLiQ=="
},
"G-Research.FSharp.Analyzers": {
"type": "Direct",
Expand Down Expand Up @@ -272,14 +272,14 @@
"fantomas.core": {
"type": "Project",
"dependencies": {
"FSharp.Core": "[8.0.100, )",
"FSharp.Core": "[9.0.300, )",
"Fantomas.FCS": "[1.0.0, )"
}
},
"fantomas.fcs": {
"type": "Project",
"dependencies": {
"FSharp.Core": "[8.0.100, )",
"FSharp.Core": "[9.0.300, )",
"System.Collections.Immutable": "[8.0.0, )",
"System.Diagnostics.DiagnosticSource": "[8.0.1, )",
"System.Memory": "[4.6.0, )",
Expand Down
8 changes: 4 additions & 4 deletions src/Fantomas.Client.Tests/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
},
"FSharp.Core": {
"type": "Direct",
"requested": "[8.0.100, )",
"resolved": "8.0.100",
"contentHash": "ZOVZ/o+jI3ormTZOa28Wh0tSRoyle1f7lKFcUN61sPiXI7eDZu8eSveFybgTeyIEyW0ujjp31cp7GOglDgsNEg=="
"requested": "[9.0.300, )",
"resolved": "9.0.300",
"contentHash": "TVt2J7RCE1KCS2IaONF+p8/KIZ1eHNbW+7qmKF6hGoD4tXl+o07ja1mPtFjMqRa5uHMFaTrGTPn/m945WnDLiQ=="
},
"G-Research.FSharp.Analyzers": {
"type": "Direct",
Expand Down Expand Up @@ -137,7 +137,7 @@
"fantomas.client": {
"type": "Project",
"dependencies": {
"FSharp.Core": "[8.0.100, )",
"FSharp.Core": "[9.0.300, )",
"SemanticVersioning": "[2.0.2, )",
"StreamJsonRpc": "[2.20.20, )"
}
Expand Down
6 changes: 3 additions & 3 deletions src/Fantomas.Client/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
},
"FSharp.Core": {
"type": "Direct",
"requested": "[8.0.100, )",
"resolved": "8.0.100",
"contentHash": "ZOVZ/o+jI3ormTZOa28Wh0tSRoyle1f7lKFcUN61sPiXI7eDZu8eSveFybgTeyIEyW0ujjp31cp7GOglDgsNEg=="
"requested": "[9.0.300, )",
"resolved": "9.0.300",
"contentHash": "TVt2J7RCE1KCS2IaONF+p8/KIZ1eHNbW+7qmKF6hGoD4tXl+o07ja1mPtFjMqRa5uHMFaTrGTPn/m945WnDLiQ=="
},
"G-Research.FSharp.Analyzers": {
"type": "Direct",
Expand Down
4 changes: 1 addition & 3 deletions src/Fantomas.Core.Tests/ASTTransformerTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ let ``avoid stack-overflow in long array/list, 2485`` () =
true,
QualifiedNameOfFile(Ident("", Range.Zero)),
[],
[],
[ SynModuleOrNamespace(
[],
false,
Expand All @@ -53,8 +52,7 @@ let ``avoid stack-overflow in long array/list, 2485`` () =
{ LeadingKeyword = SynModuleOrNamespaceLeadingKeyword.None }
) ],
(false, false),
{ ConditionalDirectives = []
CodeComments = [] },
ParsedInputTrivia.Empty,
Set.empty
)
)
Expand Down
2 changes: 1 addition & 1 deletion src/Fantomas.Core.Tests/AutoPropertiesTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type X =
member internal Y: int with public get, private set
"""

[<Test>]
[<Test; Ignore "no longer supported (see #17802)">]
let ``abstract member with public get, private set`` () =
formatSignatureString
"""
Expand Down
5 changes: 5 additions & 0 deletions src/Fantomas.Core.Tests/CommentTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ open FsUnit
open Fantomas.Core.Tests.TestHelpers
open Fantomas.Core

[<Test>]
let ``should keep sticky-to-the-left comments after #if directives`` () =
formatSourceString "#if DEBUG // debug only\n#endif\n" config
|> should equal "#if DEBUG // debug only\n#endif\n"

[<Test>]
let ``should keep sticky-to-the-left comments after nowarn directives`` () =
formatSourceString """#nowarn "51" // address-of operator can occur in the code""" config
Expand Down
3 changes: 2 additions & 1 deletion src/Fantomas.Core.Tests/HashDirectiveTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ let ``#help without string`` () =
#help List.map
"""

// As of F# 10.0, warn directives are treated as trivia like #if, so argruments are not formatted
[<Test>]
let ``#nowarn with integer`` () =
formatSourceString
Expand All @@ -259,5 +260,5 @@ let ``#nowarn with integer`` () =
|> should
equal
"""
#nowarn 1182
#nowarn 1182
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, seems like a step back, though.
What AST does this give?

Copy link
Author

Choose a reason for hiding this comment

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

Warn directives are now handled exactly like conditional directives as whole-line trivia.
https://github.com/dotnet/fsharp/blob/main/tests/service/data/SyntaxTree/WarnScope/WarnScope.fs.bsl

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, yes, but you don't have the 1182 in the AST?

Copy link
Author

@Martin521 Martin521 Jun 4, 2025

Choose a reason for hiding this comment

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

I had the numbers actually at some point (as int only, as they are no longer parsed by the parser). But when I saw how the conditional directives are treated, I went for the same simple whole-line-trivia approach. After all, both conditional and warn directives are preprocessor directives that are not really part of the proper syntax tree.

Copy link
Contributor

Choose a reason for hiding this comment

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

I can't say I really follow here, looking at this example, the AST does have proper nodes for the expression.
Your nowarn and warn only have the range?

Copy link
Author

@Martin521 Martin521 Jun 4, 2025

Choose a reason for hiding this comment

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

Yes, but I believe the expressions are only used to direct the computation of the different trees.
If I understand it correctly (with my limited knowledge of Fantomas), they are not used in formatting.
(EDIT: if the question is based on my mentioning "proper syntax tree" - that was just a remark from a theoretical point of view.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, I assumed wrongly that we were using the AST node to actually print the hash directive expressions, looks like we indeed took a shortcut and took the original source. We would look at the nodes honestly.

Anyway, circling back to the new thing, how are you getting the values of the nowarns later in the filtering?

Copy link
Author

@Martin521 Martin521 Jun 4, 2025

Choose a reason for hiding this comment

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

Long story :-).
For the "warn scopes" functionality to make sense, #nowarn and #warnon must be allowed on any line (also like in the middle of an expression).
Therefore, we can no longer parse them in the parser. We parse them rather during lexing and store them first temporarily in a lexbuf BufferLocalStore (similar to #ifdef and xml comments) and later in DiagnosticOptions, which is available where the diagnostic filtering takes place. So, all outside of compilation proper (parsing, syntax trees (except for the trivia), type checking, code gen etc).

Copy link
Author

Choose a reason for hiding this comment

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

I added the WarnDirectives entry in the AST only for Fantomas. (Again, like the ConditionalDirectives entry.)

"""
4 changes: 1 addition & 3 deletions src/Fantomas.Core.Tests/SynLongIdentTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,6 @@ let ``backticks can be added from AST only scenarios`` () =
true,
QualifiedNameOfFile testIdent,
[],
[],
[ SynModuleOrNamespace(
[ testIdent ],
false,
Expand All @@ -377,8 +376,7 @@ let ``backticks can be added from AST only scenarios`` () =
{ LeadingKeyword = SynModuleOrNamespaceLeadingKeyword.None }
) ],
(true, false),
{ ConditionalDirectives = []
CodeComments = [] },
ParsedInputTrivia.Empty,
Set.empty
)
)
Expand Down
10 changes: 5 additions & 5 deletions src/Fantomas.Core.Tests/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
},
"FSharp.Core": {
"type": "Direct",
"requested": "[8.0.100, )",
"resolved": "8.0.100",
"contentHash": "ZOVZ/o+jI3ormTZOa28Wh0tSRoyle1f7lKFcUN61sPiXI7eDZu8eSveFybgTeyIEyW0ujjp31cp7GOglDgsNEg=="
"requested": "[9.0.300, )",
"resolved": "9.0.300",
"contentHash": "TVt2J7RCE1KCS2IaONF+p8/KIZ1eHNbW+7qmKF6hGoD4tXl+o07ja1mPtFjMqRa5uHMFaTrGTPn/m945WnDLiQ=="
},
"FsUnit": {
"type": "Direct",
Expand Down Expand Up @@ -138,14 +138,14 @@
"fantomas.core": {
"type": "Project",
"dependencies": {
"FSharp.Core": "[8.0.100, )",
"FSharp.Core": "[9.0.300, )",
"Fantomas.FCS": "[1.0.0, )"
}
},
"fantomas.fcs": {
"type": "Project",
"dependencies": {
"FSharp.Core": "[8.0.100, )",
"FSharp.Core": "[9.0.300, )",
"System.Collections.Immutable": "[8.0.0, )",
"System.Diagnostics.DiagnosticSource": "[8.0.1, )",
"System.Memory": "[4.6.0, )",
Expand Down
63 changes: 35 additions & 28 deletions src/Fantomas.Core/ASTTransformer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -966,23 +966,23 @@ let mkExpr (creationAide: CreationAide) (e: SynExpr) : Expr =
| SynExpr.AddressOf(false, e, _, StartRange 2 (ampersandToken, _range)) ->
ExprSingleNode(stn "&&" ampersandToken, false, false, mkExpr creationAide e, exprRange)
|> Expr.Single
| SynExpr.YieldOrReturn((true, _), e, StartRange 5 (yieldKeyword, _range)) ->
ExprSingleNode(stn "yield" yieldKeyword, true, true, mkExpr creationAide e, exprRange)
| SynExpr.YieldOrReturn((true, _), e, _range, trivia) ->
ExprSingleNode(stn "yield" trivia.YieldOrReturnKeyword, true, true, mkExpr creationAide e, exprRange)
|> Expr.Single
| SynExpr.YieldOrReturn((false, _), e, StartRange 6 (returnKeyword, _range)) ->
ExprSingleNode(stn "return" returnKeyword, true, true, mkExpr creationAide e, exprRange)
| SynExpr.YieldOrReturn((false, _), e, _range, trivia) ->
ExprSingleNode(stn "return" trivia.YieldOrReturnKeyword, true, true, mkExpr creationAide e, exprRange)
|> Expr.Single
| SynExpr.YieldOrReturnFrom((true, _), e, StartRange 6 (yieldBangKeyword, _range)) ->
ExprSingleNode(stn "yield!" yieldBangKeyword, true, true, mkExpr creationAide e, exprRange)
| SynExpr.YieldOrReturnFrom((true, _), e, _range, trivia) ->
ExprSingleNode(stn "yield!" trivia.YieldOrReturnFromKeyword, true, true, mkExpr creationAide e, exprRange)
|> Expr.Single
| SynExpr.YieldOrReturnFrom((false, _), e, StartRange 7 (returnBangKeyword, _range)) ->
ExprSingleNode(stn "return!" returnBangKeyword, true, true, mkExpr creationAide e, exprRange)
| SynExpr.YieldOrReturnFrom((false, _), e, _range, trivia) ->
ExprSingleNode(stn "return!" trivia.YieldOrReturnFromKeyword, true, true, mkExpr creationAide e, exprRange)
|> Expr.Single
| SynExpr.Do(e, StartRange 2 (doKeyword, _range)) ->
ExprSingleNode(stn "do" doKeyword, true, true, mkExpr creationAide e, exprRange)
|> Expr.Single
| SynExpr.DoBang(e, StartRange 3 (doBangKeyword, _range)) ->
ExprSingleNode(stn "do!" doBangKeyword, true, true, mkExpr creationAide e, exprRange)
| SynExpr.DoBang(e, _range, trivia) ->
ExprSingleNode(stn "do!" trivia.DoBangKeyword, true, true, mkExpr creationAide e, exprRange)
|> Expr.Single
| SynExpr.Fixed(e, StartRange 5 (fixedKeyword, _range)) ->
ExprSingleNode(stn "fixed" fixedKeyword, true, false, mkExpr creationAide e, exprRange)
Expand Down Expand Up @@ -1029,9 +1029,7 @@ let mkExpr (creationAide: CreationAide) (e: SynExpr) : Expr =
let fieldNodes =
recordFields
|> List.choose (function
| SynExprRecordField((fieldName, _), Some mEq, Some expr, _) ->
let m = unionRanges fieldName.Range expr.Range

| SynExprRecordField((fieldName, _), Some mEq, Some expr, m, _) ->
Some(
RecordFieldNode(mkSynLongIdent creationAide fieldName, stn "=" mEq, mkExpr creationAide expr, m)
)
Expand Down Expand Up @@ -1144,7 +1142,7 @@ let mkExpr (creationAide: CreationAide) (e: SynExpr) : Expr =
_,
pat,
e1,
SynExpr.YieldOrReturn((true, _), e2, _),
SynExpr.YieldOrReturn((true, _), e2, _, _),
StartRange 3 (mFor, _)) ->
ExprForEachNode(
stn "for" mFor,
Expand Down Expand Up @@ -2809,8 +2807,8 @@ let mkMemberDefn (creationAide: CreationAide) (md: SynMemberDefn) =
let memberDefinitionRange = md.Range

match md with
| SynMemberDefn.ImplicitInherit(t, e, _, StartRange 7 (mInherit, _)) ->
mkInheritConstructor creationAide t e mInherit memberDefinitionRange
| SynMemberDefn.ImplicitInherit(t, e, _, _, trivia) ->
mkInheritConstructor creationAide t e trivia.InheritKeyword memberDefinitionRange
|> MemberDefn.ImplicitInherit

// Transforms: `member this.Y with get() = "meh"` into `member this.Y = "meh"`
Expand Down Expand Up @@ -2870,9 +2868,16 @@ let mkMemberDefn (creationAide: CreationAide) (md: SynMemberDefn) =
)
|> MemberDefn.ExplicitCtor
| SynMemberDefn.Member(memberDefn, _) -> mkBinding creationAide memberDefn |> MemberDefn.Member
| SynMemberDefn.Inherit(baseType, _, StartRange 7 (mInherit, _)) ->
MemberDefnInheritNode(stn "inherit" mInherit, mkType creationAide baseType, memberDefinitionRange)
|> MemberDefn.Inherit
| SynMemberDefn.Inherit(baseTypeOpt, _, _isInline, trivia) ->
match baseTypeOpt with
| Some baseType ->
MemberDefnInheritNode(
stn "inherit" trivia.InheritKeyword,
mkType creationAide baseType,
memberDefinitionRange
)
|> MemberDefn.Inherit
| None -> failwith "successful parse shouldn't have any unfinished inherit"
| SynMemberDefn.ValField(f, _) -> mkSynField creationAide f |> MemberDefn.ValField
| SynMemberDefn.LetBindings(
bindings = [ SynBinding(trivia = { LeadingKeyword = SynLeadingKeyword.Extern _ }) as binding ]) ->
Expand Down Expand Up @@ -3598,25 +3603,27 @@ let mkSigFile
let mds = List.map (mkModuleOrNamespaceSig creationAide) contents
Oak(phds, mds, m)

let includeTrivia
(baseRange: range)
(comments: CommentTrivia list)
(conditionDirectives: ConditionalDirectiveTrivia list)
: range =
let includeTrivia (baseRange: range) (trivia: ParsedInputTrivia) : range =
let ranges =
[ yield!
List.map
(function
| CommentTrivia.LineComment m
| CommentTrivia.BlockComment m -> m)
comments
trivia.CodeComments
yield!
List.map
(function
| ConditionalDirectiveTrivia.If(range = range)
| ConditionalDirectiveTrivia.Else(range = range)
| ConditionalDirectiveTrivia.EndIf(range = range) -> range)
conditionDirectives ]
trivia.ConditionalDirectives
yield!
List.map
(function
| WarnDirectiveTrivia.Nowarn range
| WarnDirectiveTrivia.Warnon range -> range)
trivia.WarnDirectives ]

(baseRange, ranges)
||> List.fold (fun acc triviaRange ->
Expand Down Expand Up @@ -3668,7 +3675,7 @@ let mkFullTreeRange ast =
| Some lastModule -> mkSynModuleOrNamespaceFullRange lastModule

let astRange = unionRanges startPos endPos
includeTrivia astRange trivia.CodeComments trivia.ConditionalDirectives
includeTrivia astRange trivia

| ParsedInput.SigFile(ParsedSigFileInput(hashDirectives = directives; contents = modules; trivia = trivia)) ->
let startPos =
Expand All @@ -3688,7 +3695,7 @@ let mkFullTreeRange ast =
| Some lastModule -> mkSynModuleOrNamespaceSigFullRange lastModule

let astRange = unionRanges startPos endPos
includeTrivia astRange trivia.CodeComments trivia.ConditionalDirectives
includeTrivia astRange trivia

let mkOak (sourceText: ISourceText option) (ast: ParsedInput) =
let creationAide = { SourceText = sourceText }
Expand Down
Loading
Loading