Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/9.0.200.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* Shim/file system: fix leaks of the shim [PR #18144](https://github.com/dotnet/fsharp/pull/18144)
* fsi: fix auto-loading of script file inside NuGet package ([PR #18177](https://github.com/dotnet/fsharp/pull/18177))
* Fix for `Obsolete` attribute warning/error not taken into account when used with a unit of measure [PR #18182](https://github.com/dotnet/fsharp/pull/18182)
* Fix incorrect type reported for RHS of pattern binding when there's a type annotation on the pattern [PR #19284](https://github.com/dotnet/fsharp/pull/19284)

### Added

Expand Down
13 changes: 13 additions & 0 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11152,6 +11152,19 @@ and TcNormalizedBinding declKind (cenv: cenv) env tpenv overallTy safeThisValOpt
// If binding a ctor then set the ugly counter that permits us to write ctor expressions on the r.h.s.
let isCtor = (match memberFlagsOpt with Some memberFlags -> memberFlags.MemberKind = SynMemberKind.Constructor | _ -> false)

// For bindings with a type annotation, the parser wraps the RHS in SynExpr.Typed.
// Unwrap it and unify the annotation with the binding type separately so that
// type errors on the RHS report the actual expression type, not the annotation type.
let rhsExpr =
match rtyOpt, rhsExpr with
| Some (SynBindingReturnInfo(typeName = retInfoTy; range = mRetTy)), SynExpr.Typed(innerExpr, _, _) when spatsL.IsEmpty ->
let retTy, _ = TcTypeAndRecover cenv NewTyparsOK CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes envinner tpenv retInfoTy
try UnifyTypes cenv envinner pat.Range retTy overallExprTy
with RecoverableException exn -> errorRecovery exn mRetTy
innerExpr
| _ ->
Comment on lines +11160 to +11165
Copy link
Member

Choose a reason for hiding this comment

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

Consider

let a: T = b

If I understand it correctly, during the analysis the compiler rewrites it to

let a = (b: T)

T may be a partially annotated type, e.g. _ list in the source, and then it can be inferred to something like int list after the type checking of the b expression. Can we propagate this inferred type here instead of type checking T from scratch? Would there be downsides?

Copy link
Contributor Author

@travv0 travv0 Feb 13, 2026

Choose a reason for hiding this comment

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

Sorry if I'm misunderstanding what you're saying here, but the problem this addresses is that when it's rewritten to let a = (b: T), if a is a pattern and b doesn't typecheck, it reports the annotated type of a as the type of b instead of the actual type of b (in the specific scenario where the type annotation doesn't fit the pattern).

rhsExpr

// Now check the right of the binding.
//
// At each module binding, dive into the expression to check for syntax errors and suppress them if they show.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,17 @@ let main args =
(Error 1, Line 8, Col 25, Line 8, Col 37, "The tuples have differing lengths of 3 and 2")
]

[<Fact>]
let ``Binding with type annotation and tuple pattern reports correct type``() =
FSharp """
let a, b: int = ()
"""
|> typecheck
|> shouldFail
|> withDiagnostics [
(Error 1, Line 2, Col 5, Line 2, Col 9,
"This expression was expected to have type\n 'int' \nbut here has type\n ''a * 'b' ")
(Error 1, Line 2, Col 17, Line 2, Col 19,
"This expression was expected to have type\n ''a * 'b' \nbut here has type\n 'unit' ")
]

Loading