Skip to content

Unify let, let!, use, use! LetOrUse AST representation.#18816

Closed
edgarfgp wants to merge 38 commits intodotnet:mainfrom
edgarfgp:unify-let-or-use
Closed

Unify let, let!, use, use! LetOrUse AST representation.#18816
edgarfgp wants to merge 38 commits intodotnet:mainfrom
edgarfgp:unify-let-or-use

Conversation

@edgarfgp
Copy link
Contributor

@edgarfgp edgarfgp commented Aug 4, 2025

Description

Fixes # (issue, if applicable)

Checklist

  • Test cases added
  • Release notes entry updated

@github-actions
Copy link
Contributor

github-actions bot commented Aug 4, 2025

❗ Release notes required

@edgarfgp,

Caution

No release notes found for the changed paths (see table below).

Please make sure to add an entry with an informative description of the change as well as link to this pull request, issue and language suggestion if applicable. Release notes for this repository are based on Keep A Changelog format.

The following format is recommended for this repository:

* <Informative description>. ([PR #XXXXX](https://github.com/dotnet/fsharp/pull/XXXXX))

See examples in the files, listed in the table below or in th full documentation at https://fsharp.github.io/fsharp-compiler-docs/release-notes/About.html.

If you believe that release notes are not necessary for this PR, please add NO_RELEASE_NOTES label to the pull request.

You can open this PR in browser to add release notes: open in github.dev

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/10.0.100.md No release notes found or release notes format is not correct

@edgarfgp
Copy link
Contributor Author

edgarfgp commented Aug 4, 2025

@auduchinok Can you please have a look at the AST changes.? Once we are happy with these will double check the type checking files.

@edgarfgp edgarfgp requested a review from Copilot August 5, 2025 09:56
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR unifies the AST representation for let, let!, use, and use! expressions by consolidating them into a single LetOrUse variant. Previously, F# had separate AST nodes for regular let/use expressions and bang-form let!/use! expressions (LetOrUseBang), which led to code duplication and complexity in handling these constructs.

  • Removes the SynExpr.LetOrUseBang AST variant entirely
  • Extends SynExpr.LetOrUse with additional flags (isFromSource and isComputed) to handle all cases
  • Updates all related parsing, checking, and analysis code to work with the unified representation

Reviewed Changes

Copilot reviewed 85 out of 85 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Compiler/SyntaxTree/SyntaxTree.fs Removes LetOrUseBang variant and extends LetOrUse with additional parameters
src/Compiler/SyntaxTree/ParseHelpers.fs Updates mkLetExpression to create unified LetOrUse nodes for all cases
src/Compiler/Checking/Expressions/CheckComputationExpressions.fs Major refactoring to work with unified AST representation
src/Compiler/Service/*.fs Updates various service modules to handle unified representation
tests/service/data/SyntaxTree/SynType/*.bsl Updates baseline test files to reflect new AST structure
tests/FSharp.Compiler.Service.Tests/*.bsl Updates surface area baselines for API changes

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.

@edgarfgp This is really good work 🙂

I think we can remove a lot of computed let handling from the tree traversal and similar utilities. I'd expect the normal let logic to work as expected there.

Comment on lines -959 to -967
| SynExpr.LetOrUseBang(rhs = synExpr1; andBangs = synExprAndBangs; body = synExpr2) ->
[
yield synExpr1
for SynBinding(expr = eAndBang) in synExprAndBangs do
yield eAndBang
yield synExpr2
]
|> List.tryPick walkExpr

Copy link
Member

Choose a reason for hiding this comment

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

Not a suggestion in this particular case, but we could in theory match

| SynExpr.LetOrUse(isComputed = true; bindings = synBindingList; body = synExpr) ->

to minimize the diff in cases like this.

Comment on lines +908 to +916
if isComputed then
[
for SynBinding(expr = bindingExpr) in synBindingList do
yield bindingExpr
yield synExpr
]
|> List.tryPick walkExpr
else
Option.orElse (List.tryPick walkBinding synBindingList) (walkExpr synExpr)
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if different logic is actually needed here. Would it work as expected if we just reuse the LetOrUse logic for the computed variants? It seems the tree traversal should be the same.

for SynBinding(debugPoint = andBangSpBind; expr = eAndBang) in andBangs do
yield! walkBindSeqPt andBangSpBind
yield! walkExpr true eAndBang
| SynExpr.LetOrUse(isComputed = true; bindings = bindings; body = bodyExpr) ->
Copy link
Member

Choose a reason for hiding this comment

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

Isn't it covered on line 700?

visit expr (fun exprNodes -> exprNodes @ List.collect visitSynType typeArgs |> continuation)
| SynExpr.LetOrUse(bindings = bindings; body = body) ->
visit body (fun nodes -> List.collect visitBinding bindings @ nodes |> continuation)
| SynExpr.LetOrUse(isComputed = isComputed; bindings = bindings; body = body) ->
Copy link
Member

Choose a reason for hiding this comment

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

Likewise, do we need to distinguish the computed variant here? I'd expect the logic from LetOrUse to be reused.

Comment on lines +1166 to +1167
true, // isFromSource is true for user-written code
false, // isComputed is false for let/use
Copy link
Member

Choose a reason for hiding this comment

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

Please move these comments to SyntaxTree.fsi.

@edgarfgp
Copy link
Contributor Author

edgarfgp commented Aug 5, 2025

@auduchinok Thanks for the review. I trying making this compile locally but as soon I removed the LetOrUseBang case from the SyntaxTree file and build it again using build.sh it just does not work. Not sure It seems like a compiler bootstrapping issue.

I would love to unify both constructs. but not luck so far.

Edit: This issue is when trying to build locally FsharpCore with the newly updated parser. @T-Gro have you seen this before by any chance ?

@T-Gro
Copy link
Member

T-Gro commented Aug 6, 2025

No, have not seen it.
Did you try with a clean folder state, deleting all artifacts from previous work?

@edgarfgp
Copy link
Contributor Author

edgarfgp commented Aug 6, 2025

No, have not seen it. Did you try with a clean folder state, deleting all artifacts from previous work?

Yes. usual stuff , but something seems to be happening to FSharpCore and I can not figure it out.

@majocha
Copy link
Contributor

majocha commented Aug 6, 2025

I tried to build the branch from scratch and the first error I got was

E:\repos\fsharp\src\FSharp.Core\async.fs(1531,48): error FS0001: This expression was expected to have type    'CancellationToken'    but here has type    'Async<Cancellat
ionToken>' [E:\repos\fsharp\src\FSharp.Core\FSharp.Core.fsproj::TargetFramework=netstandard2.0]

It looks like it encountered a let! but treated it as let. Normal LetOrUse matched before the isComputed match?

@edgarfgp
Copy link
Contributor Author

edgarfgp commented Aug 6, 2025

@majocha Thanks, You were on to something. It build now locally.

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

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants