diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-09-16 20:38:27 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-09-16 20:41:44 +0200 |
| commit | 25613cfaf3a20f737ac347f7d7144a5dcaa709a5 (patch) | |
| tree | 150578e74ece5172133b990e9b900b8fedfecee7 | |
| parent | b7430f6da01550d8388d6a3bcccf0ef0aca9130c (diff) | |
Fix missing capturing of assignments
The previous commit was a bit overambitious. The left-hand side of assignments should actually be fully captured: Argument lists in `at` calls can contain captured variables. And if the assigned variable itself is captured, then the function is faulty anyway. (And we ensure the correct error message by capturing it.)
Fixes #2169
| -rw-r--r-- | crates/typst-syntax/src/ast.rs | 12 | ||||
| -rw-r--r-- | crates/typst/src/eval/func.rs | 11 | ||||
| -rw-r--r-- | tests/typ/compiler/closure.typ | 18 |
3 files changed, 19 insertions, 22 deletions
diff --git a/crates/typst-syntax/src/ast.rs b/crates/typst-syntax/src/ast.rs index 0df3d6d3..cb1d05b2 100644 --- a/crates/typst-syntax/src/ast.rs +++ b/crates/typst-syntax/src/ast.rs @@ -1440,18 +1440,6 @@ impl BinOp { }) } - /// Whether this is an assignment operator. - pub fn is_assignment(self) -> bool { - matches!( - self, - Self::Assign - | Self::AddAssign - | Self::SubAssign - | Self::MulAssign - | Self::DivAssign - ) - } - /// The precedence of this operator. pub fn precedence(self) -> usize { match self { diff --git a/crates/typst/src/eval/func.rs b/crates/typst/src/eval/func.rs index b7951cef..3e4ea370 100644 --- a/crates/typst/src/eval/func.rs +++ b/crates/typst/src/eval/func.rs @@ -688,16 +688,6 @@ impl<'a> CapturesVisitor<'a> { } } - // Don't capture left-hand side of an assignment. - Some(ast::Expr::Binary(binary)) if binary.op().is_assignment() => { - self.visit(binary.rhs().to_untyped()); - } - - // Don't capture the left-hand side of a destructuring assignment. - Some(ast::Expr::DestructAssign(assignment)) => { - self.visit(assignment.value().to_untyped()); - } - // A for loop contains one or two bindings in its pattern. These are // active after the iterable is evaluated but before the body is // evaluated. @@ -828,5 +818,6 @@ mod tests { test("#(body = 1)", &[]); test("#(body += y)", &["y"]); test("#{ (body, a) = (y, 1) }", &["y"]); + test("#(x.at(y) = 5)", &["x", "y"]) } } diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ index 92d01446..85e9dbe2 100644 --- a/tests/typ/compiler/closure.typ +++ b/tests/typ/compiler/closure.typ @@ -132,6 +132,24 @@ } --- +// Mutable method with capture in argument. +#let x = "b" +#let f() = { + let a = (b: 5) + a.at(x) = 10 + a +} +#f() + +--- +#let x = () +#let f() = { + // Error: 3-4 variables from outside the function are read-only and cannot be modified + x.at(1) = 2 +} +#f() + +--- // Named arguments. #{ let greet(name, birthday: false) = { |
