summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst-syntax/src/ast.rs14
-rw-r--r--crates/typst/src/eval/func.rs30
2 files changed, 42 insertions, 2 deletions
diff --git a/crates/typst-syntax/src/ast.rs b/crates/typst-syntax/src/ast.rs
index e1741403..0df3d6d3 100644
--- a/crates/typst-syntax/src/ast.rs
+++ b/crates/typst-syntax/src/ast.rs
@@ -42,7 +42,7 @@ macro_rules! node {
impl<'a> AstNode<'a> for $name<'a> {
#[inline]
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
- if matches!(node.kind(), SyntaxKind::$name) {
+ if node.kind() == SyntaxKind::$name {
Some(Self(node))
} else {
Option::None
@@ -1440,6 +1440,18 @@ 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 fcde26d7..b7951cef 100644
--- a/crates/typst/src/eval/func.rs
+++ b/crates/typst/src/eval/func.rs
@@ -638,6 +638,11 @@ impl<'a> CapturesVisitor<'a> {
self.internal.exit();
}
+ // Don't capture the field of a field access.
+ Some(ast::Expr::FieldAccess(access)) => {
+ self.visit(access.target().to_untyped());
+ }
+
// A closure contains parameter bindings, which are bound before the
// body is evaluated. Care must be taken so that the default values
// of named parameters cannot access previous parameter bindings.
@@ -683,6 +688,16 @@ 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.
@@ -710,8 +725,14 @@ impl<'a> CapturesVisitor<'a> {
}
}
- // Everything else is traversed from left to right.
_ => {
+ // Never capture the name part of a named pair.
+ if let Some(named) = node.cast::<ast::Named>() {
+ self.visit(named.expr().to_untyped());
+ return;
+ }
+
+ // Everything else is traversed from left to right.
for child in node.children() {
self.visit(child);
}
@@ -800,5 +821,12 @@ mod tests {
// Blocks.
test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
test("#[#let x = 1]#x", &["x"]);
+
+ // Field access.
+ test("#foo(body: 1)", &[]);
+ test("#(body: 1)", &[]);
+ test("#(body = 1)", &[]);
+ test("#(body += y)", &["y"]);
+ test("#{ (body, a) = (y, 1) }", &["y"]);
}
}