basilisp-lang / basilisp

A Clojure-compatible(-ish) Lisp dialect targeting Python 3.9+
https://basilisp.readthedocs.io
Eclipse Public License 1.0
290 stars 8 forks source link

Issue with using `await` in a `case` form within an async function #1130

Closed ikappaki closed 10 hours ago

ikappaki commented 5 days ago

Hi,

there appears to be an issue with using await in a case form with an defasync function: error occurred during macroexpansion: await forms may not appear in non-async context

To reproduce,

It otherwise works fine, for example within a when form

basilisp.user=> (defasync issue [abc] (when true (await abc)))
#'basilisp.user/issue

Thanks

chrisrink10 commented 2 days ago

Seems to be caused by #700

The problem with Python's async/await (and any language that has the same style async) is that such functions are "infectious". We're wrapping an await form in a non-async function which is syntactically invalid in Python. Even if you did wrap the expression safely in an async function, we'd also have to await it on the selection.

I'm not really sure if this is generically fixable within the case macro itself. There is nothing exposed in the macro environment to let you know if you're with an async context. I'm thinking this probably would need to be solved with a specialty macro for asynchronous case-like dispatch.

chrisrink10 commented 1 day ago

I can't think of any other way to do a constant-time dispatch for case without evaluating expressions except by wrapping in a function (as in #700). Given that, I'd like to request to close this ticket.

ikappaki commented 16 hours ago

Please go ahead and close it, as using condp = value ... as workaround is sufficient.

Out of curiosity, I noticed the analyzer can detect the code is with an async function (is_async_ctx). Would it then be possible having cond mark the wrapped function as potential async targets and have the compiler automatically convert them to async, emitting an await if they're within an async function? But I suppose you'd need to analyze the wrapped function first to determine if it should be awaited, which won't do. I think I've answered my own question 😅

Thanks

chrisrink10 commented 10 hours ago

Out of curiosity, I noticed the analyzer can detect the code is with an async function (is_async_ctx). Would it then be possible having cond mark the wrapped function as potential async targets and have the compiler automatically convert them to async, emitting an await if they're within an async function? But I suppose you'd need to analyze the wrapped function first to determine if it should be awaited, which won't do. I think I've answered my own question 😅

My first reaction is that I don't think I'd want the compiler to completely rewrite expressions like this even if it is potentially possible. Just seems like it wouldn't be what users expect to happen and could (?) have negative consequences that we haven't considered.

I'm not really a huge fan out Python's async/await implementation because it's too hard to use when mixed in with non-async code.