-
-
Notifications
You must be signed in to change notification settings - Fork 416
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
Close over free variables in lambdas and object literals #1648
Conversation
I didn't create an RFC for this, as I think it is a "principle of least surprise" bug. |
THIS MAKES ME SO GOD DAMN HAPPY! |
src/libponyc/expr/lambda.c
Outdated
} | ||
|
||
// TODO: add as a field, add as a constructor parameter, init field from | ||
// parameter in constructor, add at the call site |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this TODO need to be handled before merging? Should we create an issue for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, stray TODO
, already handled.
@@ -292,6 +292,11 @@ ast_result_t pass_expr(ast_t** astp, pass_opt_t* options) | |||
case TK_ADDRESS: r = expr_addressof(options, ast); break; | |||
case TK_DIGESTOF: r = expr_digestof(options, ast); break; | |||
|
|||
case TK_OBJECT: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for my own edificaton, what's this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's moving object literals from the sugar pass (where they used to be, as pure sugar for an anonymous type and a constructor) to the expression pass, where they live now, because they need to know about the existence and type of variables within lexical scope.
@sylvanc should the 6 commits be squashed to one logical commit before merging? |
Yes, squash before merge, definitely. I'm afraid those commits are a bit "stream of work". |
Looks good to me if CI passes and the squashing happens. But @jemc is far more qualified than I in all matters compiler. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!
src/libponyc/ast/astbuild.h
Outdated
@@ -69,7 +69,7 @@ | |||
#define TREE_CLEAR_PASS(tree) \ | |||
{ \ | |||
if(ast_parent(tree) != NULL) tree = ast_dup(tree); \ | |||
ast_resetpass(tree); \ | |||
ast_resetpass(tree. 0); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a typo here - should this period be a comma?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops! Yup, typo. I guess that macro is never used :)
" object ref be foo() => 4 end"; | ||
|
||
TEST_ERROR(short_form); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot of tests removed here that were covering some important aspects of object literals.
I understand that the object processing was moved out of the sugar
pass and into the expr
pass, so that these tests don't really belong in this file anymore, and that TEST_EQUIV
no longer makes sense. However, adapting these tests for the expr
pass and figuring out how to cover the same aspects would be ideal, so we can prevent regressions as we move forward.
Also, any other tests that can be made to reflect possible pitfalls of the capture process would be great. I saw that this PR adds a test to builtin_test
, but it doesn't seem like enough to really cover this major feature, especially with all these tests having been removed.
If you don't have the time to devote to adding more tests to this PR, I'd be happy to help chip in and make sure we have some more tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One aspect coming to mind - it would be great to confirm the right behaviour and/or error message in some of the problem cases, like closing over a non-sendable local in an object iso
, for example.
@sylvanc Does anything in the tutorial need to be updated to reflect this new feature? |
I agree that additional tests would be good, probably in For the tutorial: https://tutorial.ponylang.org/expressions/object-literals.html That page could use a bit of editing to show closing over free variables. I don't think it needs a big explanation or anything, just showing how we can capture from the lexical scope anywhere, not just in field initialisers. |
I added a few test cases. |
Added a tutorial PR as well: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding some more test cases!
If I think of any more to add, I can do it asynchronously in another PR.
Looks good to me - feel free to merge when CI passes.
Previously, using a variable from the surrounding lexical scope in an object literal or a lambda produced a compiler error. This was surprising to the programmer.
This change allows both lambdas and object literals to use variables in the surrounding lexical scope, as the programmer would expect. In both cases, the closure is expressed under the hood as adding a field that is initialised by passing an alias of the variable to the constructor. For example:
Here, when
f
rewrites to an object literal, it becomes: