-
-
Notifications
You must be signed in to change notification settings - Fork 409
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
"with blocks" with no "else" clause are desugared incorrectly #3759
Comments
I understand it might be a bit hard to fix the above given how the desugaring takes place. One solution would be to modify the class Foo
new ref create() => None
fun apply(): String => ""
fun dispose() => None
actor Main
new create(env: Env) =>
let s: String =
// A new scope is created here
let f = Foo
// TK_TRY_NO_CHECK is used to avoid "try expression never results in an error"
try f.apply() else f.apply() then f.dispose() end |
I think this is duplicate of: #1571 |
One clean way to fix this here is to desugar into try-then (without an else, unless one was provided). This avoids having to add another unique concept to the compiler. Secondarily, the try-then block typechecking should be fixed so that it does not result in a union with None if the try block cannot error (i.e. if an else block is not necessary). It's not clear how easy this would be in the compiler |
@jemc Thanks for the duplicate check, I tried searching for an open issue but didn't find that one. Do you want to close either one as a dup? |
I don't mind closing the older one. Done. Just note for anyone reading along that the other ticket has a reference to a sync call in 2017 where this was discussed. In case anyone wants to go read that comment and/or listen to the recording from that sync call. |
I just encountered this one. Boy is it annoying. Would be nice to see this one fixed. |
So
This doesnt work because everything else in the compiler expects there to be try/else/then and if there was no else for it be a None which replaced a TK_NONE in sugar_try. actor Main
new create(env: Env) =>
let s: String =
try
"foo"
then
""
end said issue also causes the above code to not compile. It appears from my quick attempt to change |
I have a simple fix for class Foo
new ref create() => None
fun apply(): String => ""
fun dispose() => None
actor Main
new create(env: Env) =>
let s: String = with f = Foo do
f.apply()
else
""
end Ill be opening a PR for that and then looking at making |
I don't see a good, not hack way of fixing with as sugar over try. I discussed with Joe and we came up with a new construct that isn't sugar that I am still trying to figure out if there is an in-between on this that keeps the existing while/else (which is kind of a hack to support the try sugar) because it wouldn't need to go through an RFC, however, the amount of mental energy I have put into that over the last 3 weeks is probably more than if I had added the new constructs and made |
so I'm thinking something along the lines of removing the sugaring of
I had discussed with @jemc a more general mechanism, but I'd rather start with a specific mechanism for now and go from there, it could be generalized later. Currently the parser has: DEF(with);
PRINT_INLINE();
TOKEN(NULL, TK_WITH);
ANNOTATE(annotations);
RULE("with expression", withexpr);
SKIP(NULL, TK_DO);
RULE("with body", rawseq);
IF(TK_ELSE, RULE("else clause", annotatedrawseq));
TERMINATE("with expression", TK_END);
DONE(); And we'd have: DEF(with);
PRINT_INLINE();
TOKEN(NULL, TK_WITH);
ANNOTATE(annotations);
RULE("with expression", withexpr);
SKIP(NULL, TK_DO);
RULE("with body", rawseq);
TERMINATE("with expression", TK_END);
DONE(); Things we might need to modify (for some definition of modify):
To figure out: currently, in sugar.c how do we add this "not user settable clause"? note to self: i don't think we care about "jump away" as we do the same thing no matter what. |
-- learning underway, this might not be possible -- Ok, so as I learn more. I can't keep TK_WITH past the sugar phase. I'll need to desugar to something else. That something else is TK_DISPOSING_BLOCK The desugared will look like: SEQ
(= (let (id $0) x) (seq (reference (id Disposable))))
TK_DISPOSING_BLOCK
SEQ
(= (let (id a) x) (reference (id $0)))
BODY HERE
SEQ
(= (let (id a) x) (reference (id $0)))
(call (. (reference (id a)) (id dispose)) x x x) |
Ok, I'm rolling now. Look out @ergl, before too long you'll have usable |
If anyone is curious, the work is on the branch |
My list of things to do/look at now that I'm rolling:
|
@SeanTAllen I'm excited, looking forward to having this done! |
Ugh, I ran into a problem with This is "workaround-able", but still... requires a bit of a think to get right and requires a bit of restructuring of existing logic. |
|
The `with` keyword has been little used in most Pony programs. `with` was implemented as "sugar" over the `try/else/then` construct. Over the course of time, this became problematic as changes were made to make `try` more friendly. However, these `try` changes negatively impacted `with`. Prior to this change, `with` had become [barely usable](#3759) . We've reimplemented `with` to address the usability problems that built up over time. `with` is no longer sugar over `try` and as such, shouldn't develop any unrelated problems going forward. However, the reimplemented `with` is a breaking change. Because `with` was sugar over `try`, the full expression was `with/else`. Any error that occurred within a `with` block would be handled by provided `else`. The existence of `with/else` rather than pure `with` was not a principled choice. The `else` only existed because it was needed to satisfy error-handling in the `try` based implementation. Our new implementation of `with` does not have the optional `else` clause. All error handling is in the hands of the programmer like it would be with any other Pony construct. Previously, you would have had: ```pony with obj = SomeObjectThatNeedsDisposing() do // use obj else // only run if an error has occurred end ``` Now, you would do: ```pony try with obj = SomeObjectThatNeedsDisposing() do // use obj end else // only run if an error has occurred end ``` Or perhaps: ```pony with obj = SomeObjectThatNeedsDisposing() do try // use obj else // only run if an error has occurred end end ``` The new `with` block guarantees that `dispose` will be called on all `with` condition variables before jumping away whether due to an `error` or a control flow structure such as `return`. This first version of the "new `with`" maintains one weakness that the previous implementation suffered from; you can't use an `iso` variable as a `with` condition. The following code will not compile: ```pony use @pony_exitcode[None](code: I32) class Disposable var _exit_code: I32 new iso create() => _exit_code = 0 fun ref set_exit(code: I32) => _exit_code = code fun dispose() => @pony_exitcode(_exit_code) actor Main new create(env: Env) => with d = Disposable do d.set_exit(10) end ``` A future improvement to the `with` implementation will allow the usage of `iso` variables as `with` conditions. Internally now, we have a new abstract token `TK_DISPOSING_BLOCK` that is used to implement `with`. The code for it is still very similar to `try` except, it doesn't have an `else` clause. Instead, it has a body and a dispose clause that correspond to the body and then clauses in `try`. Fixes #3759
@ergl enjoy! |
The tutorial mentions that the return type of a
with
block is "the value of the last expression in the block, or of the last expression in the else block if there is one and an error occurred."However, the above is incorrect in cases where the body of a with block doesn't raise an error. In that case, the compiler will generate a
try
block that returnsNone
in theelse
clause, causing the return type to always be a union type.This makes
with
blocks impossible to use without error checking in cases where one is only interested in the auto-disposing behaviour. Consider the following code:The above fails to compile with "try expression never results in an error", informing us that we don't need an
else
clause (although the message is confusing, since the user never used a try expression in the code).If we change the above to remove the
else
clause:Now we're hit with:
The
None
type above comes from the desugaredtry
block, as we can see from the AST:The text was updated successfully, but these errors were encountered: