Fix let-in scoping in light syntax via LexFilter change#19501
Fix let-in scoping in light syntax via LexFilter change#19501
Conversation
|
@copilot : Try again |
|
@copilot : Try again. |
|
@copilot Try again |
Implemented in 4c00982 and 6259b63. Added warning FS3885 (gated behind Changes:
All 520 ErrorMessages tests and 630 BasicGrammar tests pass. |
6259b63 to
e6a52d1
Compare
❗ Release notes required
|
b1c7950 to
10924f8
Compare
| match letOrUse with | ||
| | { Trivia = { InKeyword = Some inRange }; Body = SynExpr.Sequential(expr2 = expr2) } | ||
| when g.langVersion.SupportsFeature LanguageFeature.WarnOnLetInSequenceExpression | ||
| && expr2.Range.StartLine > inRange.StartLine -> | ||
| warning(Error(FSComp.SR.tcLetExpressionWithInHasMultiLineBody(), inRange)) | ||
| | _ -> () |
There was a problem hiding this comment.
@T-Gro Have you considered changing how this is handled by LexFilter? Can we detect a new line there and drop the let context?
There was a problem hiding this comment.
I think I would not be able to avoid breaking changes? A warning can still be silenced, a different decision in LexFilter would affect rest of the compilation process as well.
OR you think this can be done safely?
There was a problem hiding this comment.
I'd vote for a breaking change here. Similar to various cases in fsharp/fslang-suggestions#1273, I think there should be not many cases where such code was written intentionally and could be compiled successfully.
The initially reported issue comes from brute-force checking of various cases to test, not from real code 🙂
a37539a to
f37cbfc
Compare
|
@copilot : Lets try in a separate commit to implement a LexFilter change for I first want to see how it will look like. Do the work and the commit so that I can asses the impact. |
| module Test | ||
| let f () = | ||
| let x = 1 in printfn "a" | ||
| printfn "b" |
There was a problem hiding this comment.
@T-Gro Could you also add it as a syntax tree test, please, so we could be sure what tree structure is produced?
auduchinok
left a comment
There was a problem hiding this comment.
Thanks a lot for fixing the module member ranges!
The current behavior is according to the spec, so it is not a bug. |
Please back up this claim with a link to the place in the spec that allows such special case with different indents inside sequence expressions.
They are equivalent due to a bug in the parser. See my previous reply: #7741 (comment)
We should not change the spec, we only need to fix the bug in the parser. |
That is a different animal, but also correctly handled today. |
The spec says: "When a token occurs directly on the offside line of a SeqBlock on the second or subsequent lines of the block, the $sep token is inserted. This token plays the same role as ; in the grammar rules." (§15.1.7) |
|
As far I understand it, it applies for cases where the indentation matches. The example there doesn't include the 15.1.9 (exceptions to the rules) doesn't include the case that is being fixed here. |
I think the spec is very clear here. The rule applies to SeqBlocks and that is what we have here. And of course it does not need to be listed under exceptions because it is no exception. Beyond the spec discussion, I believe this PR would introduce a gross inconsistency as shown in the comment above and should be rejected even on this reason alone. |
Hm, then maybe starting a |
It is not an inconsistency. A "fix" would create an inconsistency. For good reasons the spec and the current compiler don't start a SeqBlock at |
33b741e to
08e543a
Compare
…sions Agent-Logs-Url: https://github.com/dotnet/fsharp/sessions/740bb677-5347-48ad-9001-278dfc1631e3 Co-authored-by: abonie <20281641+abonie@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/fsharp/sessions/740bb677-5347-48ad-9001-278dfc1631e3 Co-authored-by: abonie <20281641+abonie@users.noreply.github.com>
…tries - Remove explicit 'in' keyword in HashMultiMap.fs and fsi.fs that triggered the new FS3885 warning (treated as error in CI builds) - Add missing XLF translation entries for featureWarnOnLetInSequenceExpression and tcLetExpressionWithInHasMultiLineBody to all 13 locale files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ests.fs triggering FS3885, fix PR number in release notes, add .Language/preview.md entry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When an explicit 'in' keyword is used with a block-scoped 'let' in light syntax and the body starts on the same line as 'in', push a new seq block context to limit the body scope based on indentation. This prevents the parser from greedily capturing subsequent lines as part of the let body. - Remove type-checking warning from CheckExpressions.fs - Add LexFilter logic: when blockLet=true and body is on same line as 'in', push CtxtSeqBlock with AddBlockEnd to scope the body - Update WarnExpressionTests to reflect new behavior (no FS3885, different parse tree produces different W20 ranges) - Update SyntaxTree baseline for minor range change Agent-Logs-Url: https://github.com/dotnet/fsharp/sessions/1c48edbc-5796-4d9b-a80d-3a3d423c2e3f Co-authored-by: abonie <20281641+abonie@users.noreply.github.com>
- Add 7 SyntaxTree tests (LetIn 01-07) verifying let-in body scoping after the LexFilter change: module-level, semicolons, nested let-in, triple-nested, if-then-else, if-else with nested let-in, and named module counterpart to the anonymous module InKeyword test - Remove dead FS3885 tcLetExpressionWithInHasMultiLineBody warning from FSComp.txt and all xlf files (no code references it after LexFilter change) - Update feature description to reflect LexFilter behavior Note on SynExprLetOrUseContainsTheRangeOfInKeyword.fs.bsl (2,0--3,0): The AnonModule range extends to (3,0) due to the OBLOCKEND token from the let-in scope block being positioned at EOF. This ONLY affects the SynModuleOrNamespace range — the LetOrUse range stays correct at (2,0--2,15). Named modules are unaffected (see LetIn 07.fs.bsl where the same code produces module range (1,0--3,15), ending at content). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rename WarnOnLetInSequenceExpression → LetInBodyScoping (no longer a warning) - Move release notes from Added to Fixed, describe parser behavior change - Fix AnonModule range in pars.fsy: use last declaration's Range.End instead of rhs (which includes OBLOCKEND at EOF position). This is the same pattern as PR #12585/#15369 for fixing block-end-derived ranges. Fixes the (2,0--3,0) regression to correct (2,0--2,15). - Add LetIn 08 (do block scoping from WarnExpressionTests) - Add LetIn 09 (function body scoping from WarnExpressionTests) - Add LetIn 10 (indentation continuation: c aligns with b, d is separate) - Update 204 anonymous module baselines (all consistent: end at last content character instead of extending to next-line col 0) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r scoping change The LexFilter LetInBodyScoping change correctly scopes 'let x = 1 in x + 1' to just that line. The 'x' on the next line is now in the outer scope, so Warning 20 targets (6,5)-(6,6) instead of the old (5,5)-(6,6). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4ce0417 to
8a9ab43
Compare
The approach changed from a type-checking warning (FS3885) to a LexFilter scoping change, so the 'Added' entries about FS3885 are no longer relevant. The 'Fixed' entry already correctly describes the LexFilter change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The body scope extends to continuation lines at the same or greater indentation, not just the single line. Updated comment to reflect this. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The AnonModule range change (ending at lastDecl.Range.End instead of OBLOCKEND/EOF position) broke IDE completions at end-of-file positions. This caused failures in EditorTests (Find function from var), CompletionTests (AllowObsolete), and Warning 988 position changes (CustomEquality01_fs, W_ImplIComparable_fs). Revert the pars.fsy change and restore SyntaxTree baselines. The AnonModule range redesign can be done in a separate PR with proper service-layer adjustments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…a9e-b5d2-cc88eecabc17
|
I do not want to rush this for sure. The original issue #7741 is in my view very legit - it simply is not what is expected by the casual reader. I did start with a warning first for the specific coding pattern in this PR (i.e. not a parser change, but a typechecking detection). You are right with the behavior w.r.t to the spec. At the same time, the code shown at #7741 is something not to be encouraged - I would definitely not like keeping the status quo here. |
Addresses the long-standing issue where
let ... inwith an explicitinkeyword in indentation-sensitive F# code causes the parser to greedily capture all subsequent lines as part of theletbody, leading to unexpected scoping behavior.Problem
The parser creates
LetOrUse(let x = 1, Sequential(x + 1, x))instead of the expectedSequential(LetOrUse(let x = 1, x + 1), x), causingxon the second line to resolve to the local binding rather than the outer scope.Changes Made
LanguageFeature.WarnOnLetInSequenceExpressioninLanguageFeatures.fs/fsi, gated at F# 11.0LexFilter.fs, whenblockLet=trueand an explicitINtoken is encountered with the body expression starting on the same line asin, a newCtxtSeqBlockwithAddBlockEndis pushed. This creates anOBLOCKBEGIN/OBLOCKENDwrapper around the body, limiting its scope via indentation — tokens on the next line at a lower column are offside and close the block, preventing greedy capture.in(e.g., chainedlet ... in/let! ... in/and!patterns), standard behavior is preserved.WarnExpressionTests.fscovering scoping indoblocks, function bodies, no change for single-line usage, and no change on older language versions11.0.100.mdTesting
💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.