Skip to content
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

Fable unable to resolve function parameter passed into another function that returns Async<unit> #1900

Closed
Zaid-Ajaj opened this issue Sep 11, 2019 · 4 comments

Comments

@Zaid-Ajaj
Copy link
Member

Description

Here I have got a really weird issue in an Elmish application used in SAFE stack, I broke it down to the following repro:

type Model = { Message: string }

type Msg =
    | DoSomething of int
    | DidSomething
    | Error of exn

module Server =
    let api = {|
        // int -> Async<unit>
        doSomething = fun (n: int) -> async {
            do! Async.Sleep 1000
            printf "%s" (n.ToString())
            return ()
        }
    |}

let update (msg : Msg) (model : Model) : Model * Cmd<Msg> =
    match msg with
    | DoSomething x ->
        { Message = "Doing something..." },
        Cmd.OfAsync.either Server.api.doSomething 3000 (fun _ -> DidSomething) Error
    | DidSomething ->
        { Message = "Success!"}, Cmd.none
    | Error e->
        { Message = sprintf "Error: %O" e}, Cmd.none

When the message DoSomething is dispatched from the user interface, you would expect to see "3000" printed in the console but instead the value null is passed to Server.api.doSomething, this is determined in compile time, if I use fable-splitter to compile the code, I get the following:

export function update(msg, model) {
  var clo1$$1;

  switch (msg.tag) {
    case 1:
      {
        return [new Model("Success!"), Cmd$0024$0024$0024none()];
      }

    case 2:
      {
        return [new Model((clo1$$1 = toText(printf("Error: %O")), clo1$$1(msg.fields[0]))), Cmd$0024$0024$0024none()];
      }

    default:
      {
        // READ THIS LINE TO SEE "null" statically compiled in the place of the argument
        return [new Model("Doing something..."), Cmd$0024002EOfAsyncWith$0024$0024$0024either(Cmd$0024002EOfAsync$0024$0024$0024start, Server$$$api.doSomething, null, function ofSuccess() {
          return new Msg(1, "DidSomething");
        }, function ofError(arg0) {
          return new Msg(2, "Error", arg0);
        })];
      }
  }
}

You can see the error that occurs in the UI:

async-bug

Now make the simple change by changing the return type of Server.api.doSomething to anything else such as Async<string> :

module Server =
    let api = {|
        doSomething = fun (n: int) -> async {
            do! Async.Sleep 1000
            printf "%s" (n.ToString())
            return "whatever"
        }
    |}

Now the application works as expected and does not throw:

async-bug-fix

It must be something that has to do with Async<unit> as the return type, hence the name of the issue

Repro code

See this repo

This was originally a bug in Fable.Remoting/#135 but it seems to be a Fable specific bug

Related information

Latest of everything

  • fable-compiler version: 2.3.24

  • fable-loader: 2.1.8

  • Fable splitter: 2.1.11

  • Operating system: Windows 10

@alfonsogarciacaro
Copy link
Member

Hmm, I tried your code in the REPL and it's working 😕 Can you please delete node_modules just in case and install again to make sure you are using latest fable-compiler version?

@Zaid-Ajaj
Copy link
Member Author

I did a fresh build and run from the repo and another from the local one after removing node_module, .fable, obj, bin etc. and the issue remains. Actually I have tried the code on the REPL and it works but not when I run the same code using the local fable-compiler, could you give it a try locally?

git clone https://github.com/Zaid-Ajaj/AsyncUnitBug.git
cd AsyncUnitBug
fake build -t run

@alfonsogarciacaro
Copy link
Member

Wow, it does fail indeed. First something something works in the REPL but not with fable-compiler ;)

@Zaid-Ajaj
Copy link
Member Author

I know right?! at first I was guessing it has something to do with the fact the project has multiple files and the fact that we are using nested modules (i.e. the Server module) which is something that the REPL doesn't deal with but it still fails if you use a local value in the Client module

let doSomething = fun (n: int) -> async {
    do! Async.Sleep 1000
    printf "%s" (n.ToString())
    return ()
}

let update (msg : Msg) (model : Model) : Model * Cmd<Msg> =
    match msg with
    | DoSomething x ->
        { Message = "Doing something..." },
        Cmd.OfAsync.either doSomething 3000 (fun _ -> DidSomething) Error
    | DidSomething ->
        { Message = "Success!"}, Cmd.none
    | Error e->
        { Message = sprintf "Error: %O" e}, Cmd.none

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants