Skip to content

Fix let-in scoping in light syntax via LexFilter change#19501

Open
Copilot wants to merge 9 commits intomainfrom
copilot/fix-9141961-29048891-3fc9276d-8a02-4a9e-b5d2-cc88eecabc17
Open

Fix let-in scoping in light syntax via LexFilter change#19501
Copilot wants to merge 9 commits intomainfrom
copilot/fix-9141961-29048891-3fc9276d-8a02-4a9e-b5d2-cc88eecabc17

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 26, 2026

Addresses the long-standing issue where let ... in with an explicit in keyword in indentation-sensitive F# code causes the parser to greedily capture all subsequent lines as part of the let body, leading to unexpected scoping behavior.

Problem

do
    let x = 1 in x + 1
    x  // incorrectly becomes part of the let body

The parser creates LetOrUse(let x = 1, Sequential(x + 1, x)) instead of the expected Sequential(LetOrUse(let x = 1, x + 1), x), causing x on the second line to resolve to the local binding rather than the outer scope.

Changes Made

  • New language feature: Added LanguageFeature.WarnOnLetInSequenceExpression in LanguageFeatures.fs/fsi, gated at F# 11.0
  • LexFilter change: In LexFilter.fs, when blockLet=true and an explicit IN token is encountered with the body expression starting on the same line as in, a new CtxtSeqBlock with AddBlockEnd is pushed. This creates an OBLOCKBEGIN/OBLOCKEND wrapper 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.
  • Intentional multi-line preserved: When the body starts on the next line after in (e.g., chained let ... in / let! ... in / and! patterns), standard behavior is preserved.
  • Regression tests: 4 tests in WarnExpressionTests.fs covering scoping in do blocks, function bodies, no change for single-line usage, and no change on older language versions
  • SyntaxTree baseline: 1 baseline updated for minor range change
  • Release notes: Documented in 11.0.100.md

Testing

  • ✅ All 4 regression tests pass
  • ✅ All 1042 ParseFile SyntaxTree tests pass (1 baseline updated)
  • ✅ All 2129 FSharp.Compiler.Service tests pass
  • ✅ All 524 ErrorMessages tests pass
  • ✅ All 630 BasicGrammarElements tests pass
  • ✅ All 776 Language tests pass
  • ✅ SurfaceAreaTest passes
  • ✅ CodeQL reports no security issues

💬 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.

@T-Gro
Copy link
Copy Markdown
Member

T-Gro commented Mar 27, 2026

@copilot : Try again

@T-Gro
Copy link
Copy Markdown
Member

T-Gro commented Mar 27, 2026

@copilot : Try again.

@abonie
Copy link
Copy Markdown
Member

abonie commented Mar 31, 2026

@copilot Try again

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 31, 2026

@copilot Try again

Implemented in 4c00982 and 6259b63. Added warning FS3885 (gated behind LanguageFeature.WarnOnLetInSequenceExpression at F# 11.0) that fires when let ... in with an explicit in keyword has a body that extends to subsequent lines — the common pitfall from issue #7091 where the parser greedily captures all subsequent lines as part of the let body.

Changes:

  • New LanguageFeature.WarnOnLetInSequenceExpression in LanguageFeatures.fs/fsi
  • Warning FS3885 message in FSComp.txt
  • Detection in CheckExpressions.fs: checks for explicit in keyword + Sequential body extending past the in line
  • 4 regression tests (warning in do block, warning in function body, no warning for single-line let-in, no warning without preview langversion)
  • Release notes

All 520 ErrorMessages tests and 630 BasicGrammar tests pass.

Copilot AI changed the title [WIP] Copilot Request Add warning FS3885 for let-in with multi-line body in sequence expressions Mar 31, 2026
Copilot AI requested a review from abonie March 31, 2026 15:20
@T-Gro T-Gro force-pushed the copilot/fix-9141961-29048891-3fc9276d-8a02-4a9e-b5d2-cc88eecabc17 branch from 6259b63 to e6a52d1 Compare April 9, 2026 11:34
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
LanguageFeatures.fsi docs/release-notes/.Language/preview.md

@T-Gro T-Gro force-pushed the copilot/fix-9141961-29048891-3fc9276d-8a02-4a9e-b5d2-cc88eecabc17 branch 2 times, most recently from b1c7950 to 10924f8 Compare April 9, 2026 14:26
Comment on lines +6047 to +6052
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))
| _ -> ()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@T-Gro Have you considered changing how this is handled by LexFilter? Can we detect a new line there and drop the let context?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member

@auduchinok auduchinok Apr 10, 2026

Choose a reason for hiding this comment

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

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 🙂

Copilot AI and others added 6 commits April 10, 2026 12:14
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>
The previous session referenced issue #7091 (Fix fsharp47 - test path fix)
but the correct issue is #7741 (Wrong let expression parsing) which
describes the let...in scoping problem this warning addresses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro force-pushed the copilot/fix-9141961-29048891-3fc9276d-8a02-4a9e-b5d2-cc88eecabc17 branch from a37539a to f37cbfc Compare April 10, 2026 10:14
@T-Gro T-Gro marked this pull request as ready for review April 10, 2026 13:21
@T-Gro T-Gro requested a review from a team as a code owner April 10, 2026 13:21
@T-Gro
Copy link
Copy Markdown
Member

T-Gro commented Apr 14, 2026

@copilot :

Lets try in a separate commit to implement a LexFilter change for let-in (as suggested by #19501 (comment) and by #19501 (comment) ). I.e. moving the logic from a type checking warning, to a lexfilter logical change and error.

I first want to see how it will look like.
You will need to run and adjust tests as well (I assume a lot of parser / SyntaxTree tests might need an update).

Do the work and the commit so that I can asses the impact.

@abonie
Copy link
Copy Markdown
Member

abonie commented Apr 14, 2026

@copilot :

Lets try in a separate commit to implement a LexFilter change for let-in (as suggested by #19501 (comment) and by #19501 (comment) ). I.e. moving the logic from a type checking warning, to a lexfilter logical change and error.

I first want to see how it will look like. You will need to run and adjust tests as well (I assume a lot of parser / SyntaxTree tests might need an update).

Do the work and the commit so that I can asses the impact.

@copilot try this again

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>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 14, 2026

@copilot :

Lets try in a separate commit to implement a LexFilter change for let-in (as suggested by #19501 (comment) a...

Implemented in eaa7a23. Moved the logic from a type-checking warning to a LexFilter change:

  • In LexFilter.fs, when blockLet=true and IN is encountered with the body starting on the same line as in, a new CtxtSeqBlock with AddBlockEnd is pushed. This limits the body scope via indentation, preventing the parser from greedily capturing subsequent lines.
  • When the body starts on the next line after in (e.g., let x = 1 in\n body), standard behavior is preserved — this is intentional multi-line usage.
  • Removed the type-checking warning from CheckExpressions.fs (the LanguageFeature.WarnOnLetInSequenceExpression and FSComp.txt entries are kept).

Test impact: Only 1 SyntaxTree baseline needed updating (minor range change in SynExprLetOrUseContainsTheRangeOfInKeyword.fs.bsl). All 1042 ParseFile tests, 2129 FCS tests, 524 ErrorMessages tests, 630 BasicGrammar tests, 776 Language tests pass.

Copilot AI changed the title Add warning FS3885 for let-in with multi-line body in sequence expressions Fix let-in scoping in light syntax via LexFilter change Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

4 participants