From 2547350bea1ea247bdac58bf86eccb4a75230365 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Tue, 26 May 2026 10:49:08 +0200
Subject: [PATCH 1/6] Add RED tests for issue #18841 (let _ = &expr triggers
 FS0421)

Five tests demonstrate the bug; two regression guards confirm
the named-binding and plain-discard paths still compile.
All RED tests currently fail with FS0421 as expected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 .../DataExpressions/AddressOf/AddressOf.fs    | 120 ++++++++++++++++++
 1 file changed, 120 insertions(+)

diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/DataExpressions/AddressOf/AddressOf.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/DataExpressions/AddressOf/AddressOf.fs
index 4f9ceb2067d..4005d80bf5d 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/DataExpressions/AddressOf/AddressOf.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/DataExpressions/AddressOf/AddressOf.fs
@@ -27,3 +27,123 @@ module AddressOf =
         |> shouldFail
         |> withErrorCode 431
         |> withDiagnosticMessageMatches "byref"
+
+    // Issue #18841: let _ = &expr should compile (parity with let x = &expr)
+
+    [<Fact>]
+    let ``Issue 18841 - let discard with address-of struct value compiles`` () =
+        Fsx """
+module Test
+
+type S =
+    struct
+        val Field: int
+    end
+
+let test () =
+    let s = S()
+    let _ = &s
+    let a = &s
+    ignore a
+        """
+        |> typecheck
+        |> shouldSucceed
+
+    [<Fact>]
+    let ``Issue 18841 - let discard with address-of mutable local compiles`` () =
+        Fsx """
+module Test
+let test () =
+    let mutable x = 42
+    let _ = &x
+    ()
+        """
+        |> typecheck
+        |> shouldSucceed
+
+    [<Fact>]
+    let ``Issue 18841 - let discard with address-of struct field compiles`` () =
+        Fsx """
+module Test
+
+[<Struct>]
+type S = { mutable Field: int }
+
+let test () =
+    let mutable s = { Field = 0 }
+    let _ = &s.Field
+    ()
+        """
+        |> typecheck
+        |> shouldSucceed
+
+    [<Fact>]
+    let ``Issue 18841 - let discard with parenthesized address-of compiles`` () =
+        Fsx """
+module Test
+
+type S =
+    struct
+        val Field: int
+    end
+
+let test () =
+    let s = S()
+    let _ = &(s)
+    ()
+        """
+        |> typecheck
+        |> shouldSucceed
+
+    [<Fact>]
+    let ``Issue 18841 - let discard with address-of inside function parameter compiles`` () =
+        Fsx """
+module Test
+
+type S =
+    struct
+        val Field: int
+    end
+
+let f (s: S) =
+    let _ = &s
+    ()
+        """
+        |> typecheck
+        |> shouldSucceed
+
+    [<Fact>]
+    let ``Issue 18841 - named binding let x = &s still compiles (regression guard)`` () =
+        Fsx """
+module Test
+
+type S =
+    struct
+        val Field: int
+    end
+
+let test () =
+    let s = S()
+    let a = &s
+    ignore a
+        """
+        |> typecheck
+        |> shouldSucceed
+
+    [<Fact>]
+    let ``Issue 18841 - plain let discard with no address-of still compiles`` () =
+        Fsx """
+module Test
+
+type S =
+    struct
+        val Field: int
+    end
+
+let test () =
+    let s = S()
+    let _ = s
+    ()
+        """
+        |> typecheck
+        |> shouldSucceed

From e0562a67cbbece607b55c997cda7fe6bffca9c3b Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Tue, 26 May 2026 11:06:53 +0200
Subject: [PATCH 2/6] Fix #18841: let _ = &expr now compiles like let x = &expr

The wildcard short-circuit in TcLetBinding rewrote `let _ = e` as a Sequential, which caused PostInferenceChecks to evaluate `e` with PermitByRefExpr.No and emit FS0421 for address-of expressions. Skip the short-circuit when the RHS has a byref-like type so the binding keeps its real `Expr.Let` shape and the byref-permitting context.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 src/Compiler/Checking/Expressions/CheckExpressions.fs | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs
index 6636901e460..c1f50c94256 100644
--- a/src/Compiler/Checking/Expressions/CheckExpressions.fs
+++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs
@@ -11863,9 +11863,14 @@ and TcLetBinding (cenv: cenv) isUse env containerInfo declKind tpenv (synBinds,
         let rhsExpr = mkTypeLambda m generalizedTypars (rhsExpr, tauTy)
 
         match checkedPat with
-        // Don't introduce temporary or 'let' for 'match against wild' or 'match against unit'
-
-        | TPat_wild _ | TPat_const (Const.Unit, _) when not isUse && not isFixed && isNil generalizedTypars ->
+        // Don't introduce temporary or 'let' for 'match against wild' or 'match against unit',
+        // unless the RHS is a byref-like value (e.g. `let _ = &s`). For byref-like RHS we
+        // must keep a real `Expr.Let` so that PostInferenceChecks treats the binding as
+        // permitting byref expressions, matching the behaviour of `let v = &s`.
+        // See issue dotnet/fsharp#18841.
+
+        | TPat_wild _ | TPat_const (Const.Unit, _)
+            when not isUse && not isFixed && isNil generalizedTypars && not (isByrefLikeTy g m tauTy) ->
             let mkSequentialBind (tm, tmty) = mkSequential m rhsExpr tm, tmty
             (buildExpr >> mkSequentialBind, env, tpenv)
         | _ ->

From 908cc5aa5389b9cb2228923ae00da9b2ca9931d9 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Tue, 26 May 2026 11:44:12 +0200
Subject: [PATCH 3/6] Apply remaining changes

---
 .executor-pid | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 .executor-pid

diff --git a/.executor-pid b/.executor-pid
new file mode 100644
index 00000000000..634f4111a99
--- /dev/null
+++ b/.executor-pid
@@ -0,0 +1 @@
+25504
\ No newline at end of file

From 2ca5a186f8e59a58fadc6b1ccb95761be05b63e3 Mon Sep 17 00:00:00 2001
From: Copilot <copilot@github.com>
Date: Tue, 26 May 2026 13:12:50 +0200
Subject: [PATCH 4/6] Add release notes entry for #18841 fix

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
index 7d4162f804e..ef15b8a73f0 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
@@ -1,5 +1,6 @@
 ### Fixed
 
+* Fix FS0421 "The address of the variable cannot be used at this point" incorrectly raised for the discard pattern `let _ = &expr` when `let x = &expr` compiles. ([Issue #18841](https://github.com/dotnet/fsharp/issues/18841), [PR #19811](https://github.com/dotnet/fsharp/pull/19811))
 * Honor `--nowarn` and `--warnaserror` for warnings emitted during command-line option parsing ([Issue #19576](https://github.com/dotnet/fsharp/issues/19576), [PR #19776](https://github.com/dotnet/fsharp/pull/19776))
 * Fix `[<return: X>]` prefix attributes being silently dropped on class members, and fix false-positive `AllowMultiple=false` errors when `[<X>]` and `[<return: X>]` are applied to the same binding. ([Issue #17904](https://github.com/dotnet/fsharp/issues/17904), [Issue #19020](https://github.com/dotnet/fsharp/issues/19020), [PR #19738](https://github.com/dotnet/fsharp/pull/19738))
 * Fix attributes on return type of unparenthesized tuple methods being silently dropped from IL. ([Issue #462](https://github.com/dotnet/fsharp/issues/462), [PR #19714](https://github.com/dotnet/fsharp/pull/19714))

From 9a12c42edf59e783a392f6060d1e3fef6c0253f7 Mon Sep 17 00:00:00 2001
From: Copilot <copilot@github.com>
Date: Tue, 26 May 2026 13:20:11 +0200
Subject: [PATCH 5/6] Apply remaining changes

---
 .copilot-prompt.txt | 16 ++++++++++++++++
 .executor-pid       |  1 -
 2 files changed, 16 insertions(+), 1 deletion(-)
 create mode 100644 .copilot-prompt.txt
 delete mode 100644 .executor-pid

diff --git a/.copilot-prompt.txt b/.copilot-prompt.txt
new file mode 100644
index 00000000000..172f3350161
--- /dev/null
+++ b/.copilot-prompt.txt
@@ -0,0 +1,16 @@
+Create a pull request for the current branch against main in dotnet/fsharp.
+
+1. Run `git log origin/main..HEAD --oneline` and `git diff origin/main --stat` to understand the changes
+2. Create the PR with `gh pr create`:
+   - Title: concise, max 72 chars, describes what changed
+   - Body: starts with "Fixes #18841" on its own line, then a blank line, then 1-2 sentences of commentary
+   - Target repo: dotnet/fsharp
+
+Run exactly:
+```
+gh pr create --repo dotnet/fsharp --title "YOUR TITLE" --body "Fixes #18841
+
+YOUR BRIEF DESCRIPTION" --head fix/issue-18841
+```
+
+3. After creating, output the PR URL on its own line.
\ No newline at end of file
diff --git a/.executor-pid b/.executor-pid
deleted file mode 100644
index 634f4111a99..00000000000
--- a/.executor-pid
+++ /dev/null
@@ -1 +0,0 @@
-25504
\ No newline at end of file

From e2f39ba598db7aae4d6101f6b126dd03c19db138 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Tue, 26 May 2026 14:30:34 +0200
Subject: [PATCH 6/6] Remove executor artifact

---
 .copilot-prompt.txt | 16 ----------------
 1 file changed, 16 deletions(-)
 delete mode 100644 .copilot-prompt.txt

diff --git a/.copilot-prompt.txt b/.copilot-prompt.txt
deleted file mode 100644
index 172f3350161..00000000000
--- a/.copilot-prompt.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-Create a pull request for the current branch against main in dotnet/fsharp.
-
-1. Run `git log origin/main..HEAD --oneline` and `git diff origin/main --stat` to understand the changes
-2. Create the PR with `gh pr create`:
-   - Title: concise, max 72 chars, describes what changed
-   - Body: starts with "Fixes #18841" on its own line, then a blank line, then 1-2 sentences of commentary
-   - Target repo: dotnet/fsharp
-
-Run exactly:
-```
-gh pr create --repo dotnet/fsharp --title "YOUR TITLE" --body "Fixes #18841
-
-YOUR BRIEF DESCRIPTION" --head fix/issue-18841
-```
-
-3. After creating, output the PR URL on its own line.
\ No newline at end of file
