@@ -67,7 +67,7 @@ function is_effect_free(ex)
6767 k = kind (ex)
6868 # TODO : metas
6969 is_literal (k) || is_identifier_like (ex) || k == K " Symbol" ||
70- k == K " inert" || k == K " top" || k == K " core"
70+ k == K " inert" || k == K " top" || k == K " core" || k == K " Value "
7171 # flisp also includes `a.b` with simple `a`, but this seems like a bug
7272 # because this calls the user-defined getproperty?
7373end
9090# Destructuring
9191
9292# Convert things like `(x,y,z) = (a,b,c)` to assignments, eliminating the
93- # tuple. Includes support for slurping/splatting.
93+ # tuple. Includes support for slurping/splatting. This function assumes that
94+ # `_tuple_sides_match` returns true, so the following have already been
95+ # checked:
96+ # * There's max one `...` on the left hand side
97+ # * There's max one `...` on the right hand side, in the last place, or
98+ # matched with an lhs... in the last place. (required so that
99+ # pairwise-matching terms from the right is valid)
100+ # * Neither side has any key=val terms or parameter blocks
94101#
95- # If lhss and rhss are the list of terms on each side, this function assumes
96- # the following have been checked:
97- # * There's only one `...` on the left hand side
98- # * Neither side has any key=val terms
99- # * _tuple_sides_match returns true
102+ # Tuple elimination must act /as if/ the right hand side tuple was first
103+ # constructed followed by destructuring. In particular, any side effects due to
104+ # evaluating the individual terms in the right hand side tuple must happen in
105+ # order.
100106function tuple_to_assignments (ctx, ex)
101107 lhs = ex[1 ]
102108 rhs = ex[2 ]
109+
110+ # Tuple elimination aims to turn assignments between tuples into lists of assignments.
111+ #
112+ # However, there's a complex interplay of side effects due to the
113+ # individual assignments and these can be surprisingly complicated to
114+ # model. For example `(x[i], y) = (f(), g)` can contain the following
115+ # surprises:
116+ # * `tmp = f()` calls `f` which might throw, or modify the bindings for
117+ # `x` or `y`.
118+ # * `x[i] = tmp` is lowered to `setindex!` which might throw or modify the
119+ # bindings for `x` or `y`.
120+ # * `g` might throw an `UndefVarError`
121+ #
122+ # Thus for correctness we introduce temporaries for all right hand sides
123+ # with observable side effects and ensure they're evaluated in order.
124+ n_lhs = numchildren (lhs)
125+ n_rhs = numchildren (rhs)
103126 stmts = SyntaxList (ctx)
104- end_stmts = SyntaxList (ctx)
105- elements = SyntaxList (ctx)
106- assigned = SyntaxList (ctx)
127+ rhs_tmps = SyntaxList (ctx)
128+ for i in 1 : n_rhs
129+ rh = rhs[i]
130+ r = if kind (rh) == K " ..."
131+ rh[1 ]
132+ else
133+ rh
134+ end
135+ k = kind (r)
136+ if is_literal (k) || k == K " Symbol" || k == K " inert" || k == K " top" ||
137+ k == K " core" || k == K " Value"
138+ # Effect-free and nothrow right hand sides do not need a temporary
139+ # (we require nothrow because the order of rhs terms is observable
140+ # due to sequencing, thus identifiers are not allowed)
141+ else
142+ # Example rhs which need a temporary
143+ # * `f()` - arbitrary side effects to any binding
144+ # * `z` - might throw UndefVarError
145+ tmp = emit_assign_tmp (stmts, ctx, r)
146+ rh = kind (rh) == K " ..." ? @ast (ctx, rh, [K " ..." tmp]) : tmp
147+ end
148+ push! (rhs_tmps, rh)
149+ end
107150
108151 il = 0
109152 ir = 0
110- while il < numchildren (lhs)
153+ while il < n_lhs
111154 il += 1
112155 ir += 1
113156 lh = lhs[il]
114157 if kind (lh) == K " ..."
115- TODO (lhs, " ... in tuple lhs" )
116- n_lhs = numchildren (lhs)
117- n_rhs = numchildren (rhs)
118- if il == n_lhs
119- # Simple case: exactly one `...` at end of lhs. Examples:
120- # (x, ys...) = (a,b,c)
121- # (ys...) = ()
122- rhs_tmp = emit_assign_tmp (stmts, ctx,
123- @ast (ctx, rhs, [K " tuple" rhs[ir: end ]. .. ]),
124- " rhs_tmp"
125- )
126- push! (stmts, @ast ctx ex [K " =" lh[1 ] rhs_tmp])
127- push! (elements, @ast ctx rhs_tmp [K " ..." rhs_tmp])
128- break
129- else
130- # Exactly one lhs `...` occurs in the middle somewhere, with a
131- # general rhs which has one `...` term or at least as many
132- # non-`...` terms.
133- # Examples:
134- # (x, ys..., z) = (a, b, c, d)
135- # (x, ys..., z) = (a, bs...)
136- # (xs..., y) = (a, bs...)
137- # in this case we pairwise-match arguments from the end
138- # backward, with rhs splats falling back to the general case.
139- jl = n_lhs + 1
140- jr = n_rhs + 1
141- while jl > il && jr > ir
142- if kind (lhs[jl- 1 ]) == K " ..." || kind (rhs[jr- 1 ]) == K " ..."
143- break
144- end
145- jl -= 1
146- jr -= 1
158+ # Exactly one lhs `...` occurs in the middle somewhere, with a
159+ # general rhs which has at least as many non-`...` terms or one
160+ # `...` term at the end.
161+ # Examples:
162+ # (x, ys..., z) = (a, b, c, d)
163+ # (x, ys..., z) = (a, bs...)
164+ # (xs..., y) = (a, bs...)
165+ # (xs...) = (a, b, c)
166+ # in this case we can pairwise-match arguments from the end
167+ # backward and emit a general tuple assignment for the middle.
168+ jl = n_lhs
169+ jr = n_rhs
170+ while jl > il && jr > ir
171+ if kind (lhs[jl]) == K " ..." || kind (rhs_tmps[jr]) == K " ..."
172+ break
147173 end
148- rhs[jr]
174+ jl -= 1
175+ jr -= 1
149176 end
150- continue
151- end
152- rh = rhs[ir] # In other cases `rhs[ir]` must exist
153- if kind (rh) == K " ..."
154- @assert ir == numchildren (rhs) # _tuple_sides_match ensures this
155- rh_tmp = emit_assign_tmp (stmts, ctx, rh[1 ])
156- push! (end_stmts, @ast ctx ex [K " =" [K " tuple" lhs[il: end ]. .. ] rh_tmp])
157- push! (elements, @ast ctx rh [K " ..." rh_tmp])
158- break
177+ middle = emit_assign_tmp (stmts, ctx,
178+ @ast (ctx, rhs, [K " tuple" rhs_tmps[ir: jr]. .. ]),
179+ " rhs_tmp"
180+ )
181+ if il == jl
182+ # (x, ys...) = (a,b,c)
183+ # (x, ys...) = (a,bs...)
184+ # (ys...) = ()
185+ push! (stmts, @ast ctx ex [K " =" lh[1 ] middle])
186+ else
187+ # (x, ys..., z) = (a, b, c, d)
188+ # (x, ys..., z) = (a, bs...)
189+ # (xs..., y) = (a, bs...)
190+ push! (stmts, @ast ctx ex [K " =" [K " tuple" lhs[il: jl]. .. ] middle])
191+ end
192+ # Continue with the remainder of the list of non-splat terms
193+ il = jl
194+ ir = jr
159195 else
160- if is_identifier_like (lh) && is_effect_free (rh) &&
161- ! any (contains_identifier (rhs[j], lh) for j in ir+ 1 : lastindex (rhs))
162- ! any (contains_identifier (a, rh) for a in assigned)
163- # Overwrite `lh` directly if that won't cause conflicts with
164- # other symbols
165- push! (stmts, @ast ctx ex [K " =" lh rh])
166- push! (assigned, lh)
167- push! (elements, rh)
196+ rh = rhs_tmps[ir]
197+ if kind (rh) == K " ..."
198+ push! (stmts, @ast ctx ex [K " =" [K " tuple" lhs[il: end ]. .. ] rh[1 ]])
199+ break
168200 else
169- # In other cases we need a temporary and we'll overwrite `lh` at the end.
170- tmp = ssavar (ctx, rh)
171- push! (stmts, @ast ctx ex [K " =" tmp rh])
172- # `push!(assigned, lh)` is not required when we assign `lh` later.
173- push! (end_stmts, @ast ctx ex [K " =" lh tmp])
174- push! (elements, tmp)
201+ push! (stmts, @ast ctx ex [K " =" lh rh])
175202 end
176203 end
177204 end
178205
179206 @ast ctx ex [K " block"
180207 stmts...
181- end_stmts...
182- [K " removable" [K " tuple" elements... ]]
208+ [K " removable" [K " tuple" rhs_tmps... ]]
183209 ]
184210end
185211
@@ -354,12 +380,7 @@ function expand_property_destruct(ctx, ex)
354380 @assert kind (params) == K " parameters"
355381 rhs = ex[2 ]
356382 stmts = SyntaxList (ctx)
357- rhs1 = if is_ssa (ctx, rhs) || (is_identifier_like (rhs) &&
358- ! any (is_same_identifier_like (l, rhs) for l in children (params)))
359- rhs
360- else
361- emit_assign_tmp (stmts, ctx, expand_forms_2 (ctx, rhs))
362- end
383+ rhs1 = emit_assign_tmp (stmts, ctx, expand_forms_2 (ctx, rhs))
363384 for prop in children (params)
364385 propname = kind (prop) == K " Identifier" ? prop :
365386 kind (prop) == K " ::" && kind (prop[1 ]) == K " Identifier" ? prop[1 ] :
0 commit comments