Skip to content

Conversation

@edgarfgp
Copy link
Contributor

Description

Continuation of #18899

Checklist

  • Test cases added
  • Release notes entry updated

@github-actions
Copy link
Contributor

github-actions bot commented Sep 19, 2025

⚠️ Release notes required, but author opted out

Warning

Author opted out of release notes, check is disabled for this pull request.
cc @dotnet/fsharp-team-msft

@edgarfgp edgarfgp added the NO_RELEASE_NOTES Label for pull requests which signals, that user opted-out of providing release notes label Sep 19, 2025
Copy link
Member

@auduchinok auduchinok left a comment

Choose a reason for hiding this comment

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

There are many incorrect nodes in the test tree dumps. @edgarfgp Could you please fix the implementation, so the tests produce correct data?

@github-project-automation github-project-automation bot moved this from New to In Progress in F# Compiler and Tooling Sep 19, 2025
@edgarfgp edgarfgp force-pushed the capture-block-separators branch from 6eea60c to 9f9c0ec Compare September 19, 2025 21:13
@edgarfgp edgarfgp force-pushed the capture-block-separators branch from a2f929b to fcc7b41 Compare September 20, 2025 14:18
Copy link
Member

@auduchinok auduchinok left a comment

Choose a reason for hiding this comment

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

This is really good now. It's now possible to rely on the separator info when processing the tree, and all the cases with fake tree nodes are cleared up. Thanks @edgarfgp!

@edgarfgp edgarfgp force-pushed the capture-block-separators branch 2 times, most recently from d19dc1c to 8315a15 Compare September 21, 2025 16:37
"let r = {AAA = 5}"
"let b = {r with }"
]
AssertCtrlSpaceCompleteContains code "{r " [] ["AAA"]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Humm. This will enabled completion before the with keyword. To fix this we can do some range calculations in ServiceParseTreeWalk.fs and update the completion context for records or capture the with range in the tree and then use it.

@edgarfgp edgarfgp force-pushed the capture-block-separators branch from 8315a15 to 7a291f2 Compare September 22, 2025 08:36
Comment on lines 5746 to 5747
(Some($1, BlockSeparator.Offside(rhs parseState 2, None)), l) }
(Some($1, $5), l) }
Copy link
Member

Choose a reason for hiding this comment

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

This change looks suspicious. Previously we would capture the with range and store it as a separator. That didn't look correct because of how the separator field was reused for a different thing, but it could be the place where the code completion relied on. We can add a new field to the trivia and store the with range there.

Copy link
Member

Choose a reason for hiding this comment

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

This is where we've started assuming that with was an offside separator: https://github.com/dotnet/fsharp/pull/18899/files#diff-e4c6a418b5f63918e99d3b27f209771a966fb6c7dbadf25cf5d3bb4b7b7d7bd7L5746 and then in this PR we've stopped keeping all non-semi and non-comma ones. A simpler possible fix is to rollback the copyInfo changes in the records and keep a with range there to use it from the code completion.

Copy link
Member

@auduchinok auduchinok Sep 23, 2025

Choose a reason for hiding this comment

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

Why is this comment marked as resolved? We still capture a different thing here, compared to pre-#18899, don't we?

Copy link
Contributor Author

@edgarfgp edgarfgp Sep 24, 2025

Choose a reason for hiding this comment

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

Yes, everything that was BlockSeparator.Offside now should be None.

Copy link
Member

Choose a reason for hiding this comment

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

But isn't it exactly the thing that the code completion relied on?

Copy link
Member

@auduchinok auduchinok Sep 24, 2025

Choose a reason for hiding this comment

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

It clearly wasn't a separator in the new terminology but it was named that way before this terminology was introduced in the tree. It captured the with keyword range and other places could use it, so it was a different kind of separator between the expression and the field updates, and that's where the name came from.

It just was a mistake to start storing with range as if it was a semicolon/comma in the recent PRs, which happened because of the unfortunate naming clash.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So your point is that we should not capture this as separator at all in this case ?

Copy link
Member

@auduchinok auduchinok Sep 24, 2025

Choose a reason for hiding this comment

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

My point is it seems it was captured for a reason before and now it's lost. Even though it was also called a separator, it served a different purpose: it wasn't an offside separator, but the range of with keyword (that now we can store in the record expression trivia), and it seems it's exactly what the code completion could rely on.

Comment on lines +1932 to +1939
let notHasRecordField (fieldName:string) (symbolUses: FSharpSymbolUse list) =
symbolUses
|> List.exists (fun symbolUse ->
match symbolUse.Symbol with
| :? FSharpField as field -> field.DisplayName = fieldName
| _ -> false
)
|> fun exists -> Assert.False(exists, $"Field {fieldName} should not be found.")
Copy link
Member

Choose a reason for hiding this comment

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

Please use hasRecordField and not.

let rangeOfBlockSeparator (id: Ident) =
let idEnd = id.idRange.End
let blockSeparatorStartCol = idEnd.Column
let blockSeparatorEndCol = blockSeparatorStartCol + 4
Copy link
Member

Choose a reason for hiding this comment

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

Where does 4 magic constant come from? If we assume that a separator is ; or , then it can't be 4 characters long.

If this is a with keyword range, it should not be calculated from the id, it's job of the parser to preserve it correctly, like it was before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was how it was originally. See https://github.com/dotnet/fsharp/pull/18899/files#diff-1e790bba80026c421a964cb1d8be0297aff37a0a3dbe7cbf954b54f6e3544e40L93-L100. But I think in this case we do not have a separator we can use range0 ?

@edgarfgp edgarfgp force-pushed the capture-block-separators branch from 7a291f2 to aa7bd57 Compare September 23, 2025 07:40
@edgarfgp edgarfgp force-pushed the capture-block-separators branch from acefecd to ffdaa6e Compare September 23, 2025 21:11
{ let l = List.rev $4
let l = rebindRanges $3 l $5
(Some($1, $5), l) }
(Some($1, None), l) }
Copy link
Member

Choose a reason for hiding this comment

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

The captured info is still different here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. this should be None as previously was BlockSeparator.Offside

Comment on lines -561 to +553
copyInfo: (SynExpr * BlockSeparator) option *
copyInfo: (SynExpr * BlockSeparator option) option *
Copy link
Member

Choose a reason for hiding this comment

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

This place still looks very wrong. We've stored the with keyword range before #18899 here, and now we store an optional semicolon or comma. Why do we expect a ;/, separator instead of the with in the tree? Where do we expect them to come from? Do we allow some new syntax? Or is it just dead code now? Why don't we update all the service code accordingly and try to process semicolons in places where they can never be at all where previously we processed with keywords?

@auduchinok
Copy link
Member

auduchinok commented Sep 25, 2025

This PR seems to add lots of new logic on top of mistakes made in #18899, most importantly treating with as if it was a an implicit semicolon, and with each commit we only diverge more from the original implementation.

The previous implementation would call with a separator in record expressions and capture its range, so the tooling could use it. With the current implementation only ; and , are considered separators. The problem is the old code which checked with range in record expressions now ignores with completely and tries to process semicolons that can't even be there. This leads us to regressions in features like code completion.

@edgarfgp @T-Gro I propose we revert #18899, so this work could be started from scratch with better understanding of what the original code did instead of covering the mistakes with more new code. I believe that would allow us to have less diff as well. We can also use blame.ignoreRevsFile to hide #18899 and the revert from git blame

If not reverting, let's make a fresh branch starting from pre-#18899 and modeling the tree with the minimal diff, clearly stating what we consider a separator and updating the service code accordingly, and only then process with subsequent changes.

@edgarfgp edgarfgp closed this Sep 25, 2025
@github-project-automation github-project-automation bot moved this from In Progress to Done in F# Compiler and Tooling Sep 25, 2025
@edgarfgp
Copy link
Contributor Author

@auduchinok Yeah let’s revert the previous PR and leave things as were before. Feel free to raise a new PR with your suggested changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NO_RELEASE_NOTES Label for pull requests which signals, that user opted-out of providing release notes

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants