Skip to content

Support multiple assignments #978

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

Merged
merged 3 commits into from
Aug 21, 2022
Merged

Conversation

Smit-create
Copy link
Collaborator

@Smit-create Smit-create commented Aug 16, 2022

Fixes #928

@certik
Copy link
Contributor

certik commented Aug 17, 2022

Thanks. Can you add a test for this as well please?

@certik
Copy link
Contributor

certik commented Aug 19, 2022

Let me try to understand. This:

    a = b = c = 10

creates this ASR:

               (= 
                  (Var 2 c) (IntegerConstant 10 
                  (Integer 4 [])) 
                  (= 
                     (Var 2 b) (IntegerConstant 10 
                     (Integer 4 [])) 
                     (= 
                        (Var 2 a) (IntegerConstant 10 
                        (Integer 4 [])) ())))] () 

So this is doing c=10 and overloading it recursively, so we have b=10 and it is overloaded with a=10. I think that will not work with the way overloaded works: overloaded allows to specify a different (user defined) operation, typically a subroutine call. We should have checked this in verify, but our verify is not very strong yet.

This in turn is the reason for this change:

    void visit_Assignment(const ASR::Assignment_t &x) {
        if( x.m_overloaded ) {
            this->visit_stmt(*x.m_overloaded);
-           return ;
        }

But it will break LFortran, as in there we must do the overloaded operation instead of the original one. So I think the current approach will not work.

So how should this be implemented?

Well, what is a = b = c = 10?

Isn't that exactly equivalent to:

a = 10
b = 10
c = 10

? Or is there some semantic difference?

If it is equivalent, then the AST->ASR should encounter the AST node(s) for a = b = c = 10, and it should generate three ASR statement nodes (append them to the body) of the type a = 10, no overload.

There is a slight issue of appending more than one statement, but I think we have some mechanism for it, and if not, we need to create it.

@Smit-create
Copy link
Collaborator Author

Smit-create commented Aug 19, 2022

Maybe we can follow the same logic but let's leave the overload for now, and implement a new argument in the Assignment node something like multiple_assignment

 stmt
     = Allocate(alloc_arg* args, expr? stat, expr? errmsg, expr? source)
     | Assign(int label, identifier variable)
-    | Assignment(expr target, expr value, stmt? overloaded)
+    | Assignment(expr target, expr value, stmt? overloaded, stmt? multiple_assignment)
     | Associate(expr target, expr value)
     | Cycle()

or,
One approach might be to insert all the individual assignment nodes and then visit them.

@certik
Copy link
Contributor

certik commented Aug 19, 2022

It seems quite complicated, because every backend then has to support it etc. We are trying to keep the ASR as minimal as possible, but without losing any semantic information. It's not black and white, but this feature seems quite rarely used, so using the workaround with multiple Assignments seems like a better way, than modifying ASR. We should only extend ASR when we need to represent the semantic operation, or it simplifies the backends. In this case, it seems we do not necessarily need to preserve this operation, and it makes backends more complicated. So it seems it is not worth doing that way.

@czgdp1807
Copy link
Collaborator

czgdp1807 commented Aug 19, 2022

I think converting a = b = c = 10 into,

c = 10
b = c
a = b

should be doable. We should do this because it keeps the ASR simple. And semantics of a = b = c = 10 and the above three assignments are no different for LPython (as we do deepcopy by default). If we support shallow copy in future then also, c = 10 will work as it is and b = c and a = b will eventually make a, b point to c (which is the expected behaviour for shallow copy I think).

Appending multiple statements to body can be implemented in multiple ways. You can track the current body by making it a state of the visitor then modify it directly when you create multiple assignment statements. Not so safe approach. The other way is to make Vec<ASR::stmt_t*> stmts and then use it at places where body.push_back is called. Anyways should be doable.

@Smit-create
Copy link
Collaborator Author

This is ready for review @czgdp1807 @certik

overloaded);
tmp_vec.push_back(tmp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than assigning to both tmp and tmp_vec, why not use the convention that if it is just one, it will be in tmp, and if tmp == nullptr, then it will be in tmp_vec?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is how I am thinking about it:

  • tmp is not null: we have exactly one statement, the most common case
  • tmp is null: the tmp_vec is used to communicate what is returned: tmp_vec.size() == 0 (nothing), size() == 2 or more (two or more statements).

We could in principle only use tmp_vec even for 1 statement (and remove tmp), but I think the tmp convention is used for expressions as well, and it seems like the above idea is better.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I agree.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case we should update other code that currently returns tmp=null to mean "none" to ensure it sets tmp_vec.size() == 0.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Otherwise incorrect set of statements mind end up getting into ASR. Well we should always clear the tmp_vec after pushing all the elements from it to the body. That way we will know that its always empty once used.

Copy link
Contributor

@certik certik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. @czgdp1807 let me know if this looks good to you. See also my question above regarding the convention of tmp and tmp_vec.

@@ -8,4 +8,27 @@ def main0():
print(-i1 ^ -i2)
assert -i1 ^ -i2 == 6


def test_multiple_assign_1():
a: i32; b:i32; c:i32
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try with variables of different types as well. Something like,

d: f64; e: f32; f: c64; g: i32
c = d = e = g + 1.0

Also try with lists, tuples (of the same element types) variables everywhere in the multiple assignment statement.

Comment on lines 2855 to 2856
tmp = make_DictInsert_t(al, x.base.base.loc, se, key, tmp_value);
tmp_vec.push_back(tmp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this?

Suggested change
tmp = make_DictInsert_t(al, x.base.base.loc, se, key, tmp_value);
tmp_vec.push_back(tmp);
tmp = nullptr;
tmp_vec.push_back(make_DictInsert_t(al, x.base.base.loc, se, key, tmp_value));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, or is this just one? In that case, just put it into tmp, and leave tmp_vec be.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we can have many of them. We have a continue statement below this.

Copy link
Contributor

@certik certik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good to be merged, after adding the documentation comment (see my comment above) and polishing the git history.

@Smit-create Smit-create enabled auto-merge August 21, 2022 09:40
@Smit-create Smit-create merged commit bac0986 into lcompilers:main Aug 21, 2022
@certik
Copy link
Contributor

certik commented Aug 21, 2022

Awesome, thanks for implementing this @Smit-create !

@Smit-create Smit-create deleted the i-928 branch August 21, 2022 13:31
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

Successfully merging this pull request may close these issues.

Assignment to multiple targets not supported
3 participants