-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
>=1.3.0 breaks type inference of unwrapped types #88
Comments
Ugh this feels like a potential compiler bug. cc @cartermp @baronfel. This seems to only happen when using the A less verbose work around is to add a type annotation to let f () = asyncResult {
let! (v : X) = async { return X() }
return v.A
} Additionally this problem is not apparent when using the |
Preview opts you into applicative support, are you thinking there's an interaction there with typechecking? |
If I expand the example to: let f () = asyncResult {
let! v = async { return X() }
let! x = async { return X() }
let! y = async { return X() }
return v.A, x.A, y.A
} This no longer works because it's not in applicative mode. So there's an issue with type inference with regards to |
This looks like a compiler regression given that it doesn't repro unless you turn on preview, which actives applicatives and the associating typechecking. |
That's not how I read it, @cartermp. In #88 (comment) @TheAngryByrd said that turning on preview fixed the initial report, which suggest to me that the rewrite to BindReturn that happened works as expected. The first example is a pretty classic BindReturn scenario. The example in #88 (comment) isn't something that's easily transformable to BindReturn, etc, so it surfaces the original issue even in an environment with |
Oh, derp. I misread |
here's the code I'm tinkering around with to show the difference between how classes are resolved vs how records are resolved: [<Sealed>]
type X() =
member _.A = 1
type Y = { A : int }
let f () = asyncResult {
let! a = async { return X() }
let! b = async { return X() }
let! c = async { return X() }
return a.A, b.A, c.A
}
let g () = asyncResult {
let! a = async { return { A = 1 } }
let! b = async { return { A = 1 } }
let! c = async { return { A = 1 } }
return a.A, b.A, c.A
} each of the
which to me looks like we're definitely mis-computing the type of the |
more findings: @TheAngryByrd and I added some logging to the CE typecheck pass and went through an exercise of manually translating the logged Final Delay call emitted by the compiler
expanded equivalent asyncResult.Delay(fun () ->
asyncResult.Bind(asyncResult.Source((async { return X() })), (fun a ->
asyncResult.Bind(asyncResult.Source((async { return X() })), (fun b ->
asyncResult.Bind(asyncResult.Source((async { return X() })), (fun c ->
asyncResult.Return((a.A, b.A, c.A))
))
))
))
) As best as we can tell, the emitted lambdas are correct in terms of shape. However, when we test this by manually entering this expansion into a library file with the open FsToolkit.ErrorHandling
type X() =
member _.A = 1
let thing =
asyncResult.Delay(fun () ->
asyncResult.Bind(asyncResult.Source((async { return X() })), (fun a ->
asyncResult.Bind(asyncResult.Source((async { return X() })), (fun b ->
asyncResult.Bind(asyncResult.Source((async { return X() })), (fun c ->
asyncResult.Return((a.A, b.A, c.A)) // here you get ambigiuous lookup errors on each usage of `.A`
))
))
))
) we did notice that if the lambdas were explicitly typed (as is what would happen if the So perhaps there's some more general type-inference problem at play here @cartermp? |
This seems to be because of the presence of the This seems like a huge use case for the 'restraint' concept that @TheAngryByrd likes to talk about: member _.Bind(a: Async<'t>, binder: 't -> Async<Result<'t, 'err>>) when 't is not Result<blah, blah> = ... |
I am also seeing similar behaviour with Postgres Type provider https://github.com/demetrixbio/FSharp.Data.Npgsql with latest version of FsToolkit but this works fine with version 1.2.2. |
Well the current options are:
|
I vote for 2 option. |
I too vote 2. As I mentioned, we use this type inference every single time we make a database query, and we cannot add a type annotation because the type is generated by the type provider and doesn't have a usable name. On the other side, the issue #86 (that the For reference, take this piece of v1.2.6 code:
Not sure how this could be done in v1.4.0 without forsaking the use of |
Yeah this was the same for one of my work projects, but there was no real good reason for us to be doing it. Ok I'm going to revert #87 and yank the release of 1.4.1 for now. |
@piaste so typically if I needed the Result directly for some reason, I would just use an We also might be missing other function helpers but the most basic to just handle either Ok or Error cases, FsToolkit.ErrorHandling/src/FsToolkit.ErrorHandling/AsyncResult.fs Lines 23 to 24 in aa9f8ad
tends to be able to handle all cases I've come across. |
@TheAngryByrd the issue happened when we had a long For now, I've solved it somewhat clumsily by running the right side of the unwrapping throgh |
Thanks again for fixing #86. After updating to 1.4.1, however, I still get errors from code which compiled with 1.2.6.
Here's a minimal repro:
Under 1.2.6, this compiles. Under 1.4.1, the compiler can't figure out the type of
v'
and errors out with a "lookup of indeterminate object type" onv'.A
Note that if you replace
X()
with a record, the code may compile because F# will infer types from members of records, but not from members of classes and anonymous records.Possible workaround: unwrap
v
inside a regularasync
CE instead:I'll still need to roll back to 1.2.6 though because we have this pattern every single time we make a database query:
The text was updated successfully, but these errors were encountered: