summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarmare314 <49279081+Marmare314@users.noreply.github.com>2023-04-25 11:22:12 +0200
committerGitHub <noreply@github.com>2023-04-25 11:22:12 +0200
commitd5d98b67a83944d72a5c0f8e8e2f43aeee667122 (patch)
tree11580441926d1c24c6243868232b44b844663c4b
parentefad1e71fa699e0d2413d3a6a3ce5a4163e38112 (diff)
Destructuring assign (#703)
-rw-r--r--src/eval/func.rs4
-rw-r--r--src/eval/mod.rs223
-rw-r--r--src/ide/highlight.rs1
-rw-r--r--src/syntax/ast.rs165
-rw-r--r--src/syntax/kind.rs3
-rw-r--r--src/syntax/parser.rs45
-rw-r--r--tests/typ/compiler/let.typ10
-rw-r--r--tests/typ/compiler/ops-invalid.typ4
-rw-r--r--tests/typ/compiler/ops.typ42
9 files changed, 336 insertions, 161 deletions
diff --git a/src/eval/func.rs b/src/eval/func.rs
index 489527ef..e3b6a99c 100644
--- a/src/eval/func.rs
+++ b/src/eval/func.rs
@@ -445,7 +445,9 @@ impl<'a> CapturesVisitor<'a> {
match param {
ast::Param::Pos(ident) => self.bind(ident),
ast::Param::Named(named) => self.bind(named.name()),
- ast::Param::Sink(Some(ident)) => self.bind(ident),
+ ast::Param::Sink(spread) => {
+ self.bind(spread.name().unwrap_or_default())
+ }
_ => {}
}
}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 850a3d32..d8f49d66 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -456,6 +456,7 @@ impl Eval for ast::Expr {
Self::Unary(v) => v.eval(vm),
Self::Binary(v) => v.eval(vm),
Self::Let(v) => v.eval(vm),
+ Self::DestructAssign(v) => v.eval(vm),
Self::Set(_) => bail!(forbidden("set")),
Self::Show(_) => bail!(forbidden("show")),
Self::Conditional(v) => v.eval(vm),
@@ -1212,8 +1213,8 @@ impl Eval for ast::Closure {
ast::Param::Named(named) => {
params.push(Param::Named(named.name(), named.expr().eval(vm)?));
}
- ast::Param::Sink(name) => params.push(Param::Sink(name)),
- ast::Param::Placeholder => params.push(Param::Placeholder),
+ ast::Param::Sink(spread) => params.push(Param::Sink(spread.name())),
+ ast::Param::Placeholder(_) => params.push(Param::Placeholder),
}
}
@@ -1231,98 +1232,142 @@ impl Eval for ast::Closure {
}
impl ast::Pattern {
- // Destruct the given value into the pattern.
- #[tracing::instrument(skip_all)]
- pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> {
- match self {
- ast::Pattern::Ident(ident) => {
- vm.define(ident.clone(), value);
- Ok(Value::None)
- }
- ast::Pattern::Placeholder => Ok(Value::None),
- ast::Pattern::Destructuring(destruct) => {
- match value {
- Value::Array(value) => {
- let mut i = 0;
- for p in destruct.bindings() {
- match p {
- ast::DestructuringKind::Ident(ident) => {
- let Ok(v) = value.at(i) else {
- bail!(ident.span(), "not enough elements to destructure");
- };
- vm.define(ident.clone(), v.clone());
- i += 1;
- }
- ast::DestructuringKind::Sink(ident) => {
- (1 + value.len() as usize).checked_sub(destruct.bindings().count()).and_then(|sink_size| {
- let Ok(sink) = value.slice(i, Some(i + sink_size as i64)) else {
- return None;
- };
- if let Some(ident) = ident {
- vm.define(ident, sink);
- }
- i += sink_size as i64;
- Some(())
- }).ok_or("not enough elements to destructure").at(self.span())?;
- }
- ast::DestructuringKind::Named(key, _) => {
- bail!(
- key.span(),
- "cannot destructure named elements from an array"
- )
- }
- ast::DestructuringKind::Placeholder => i += 1,
- }
- }
- if i < value.len() {
- bail!(self.span(), "too many elements to destructure");
+ fn destruct_array<T>(
+ &self,
+ vm: &mut Vm,
+ value: Array,
+ f: T,
+ destruct: &ast::Destructuring,
+ ) -> SourceResult<Value>
+ where
+ T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>,
+ {
+ let mut i = 0;
+ for p in destruct.bindings() {
+ match p {
+ ast::DestructuringKind::Normal(expr) => {
+ let Ok(v) = value.at(i) else {
+ bail!(expr.span(), "not enough elements to destructure");
+ };
+ f(vm, expr, v.clone())?;
+ i += 1;
+ }
+ ast::DestructuringKind::Sink(spread) => {
+ let sink_size = (1 + value.len() as usize)
+ .checked_sub(destruct.bindings().count());
+ let sink =
+ sink_size.and_then(|s| value.slice(i, Some(i + s as i64)).ok());
+
+ if let (Some(sink_size), Some(sink)) = (sink_size, sink) {
+ if let Some(expr) = spread.expr() {
+ f(vm, expr, Value::Array(sink.clone()))?;
}
+ i += sink_size as i64;
+ } else {
+ bail!(self.span(), "not enough elements to destructure")
}
- Value::Dict(value) => {
- let mut sink = None;
- let mut used = HashSet::new();
- for p in destruct.bindings() {
- match p {
- ast::DestructuringKind::Ident(ident) => {
- let Ok(v) = value.at(&ident) else {
+ }
+ ast::DestructuringKind::Named(named) => {
+ bail!(named.span(), "cannot destructure named elements from an array")
+ }
+ ast::DestructuringKind::Placeholder(_) => i += 1,
+ }
+ }
+ if i < value.len() {
+ bail!(self.span(), "too many elements to destructure");
+ }
+
+ Ok(Value::None)
+ }
+
+ fn destruct_dict<T>(
+ &self,
+ vm: &mut Vm,
+ value: Dict,
+ f: T,
+ destruct: &ast::Destructuring,
+ ) -> SourceResult<Value>
+ where
+ T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>,
+ {
+ let mut sink = None;
+ let mut used = HashSet::new();
+ for p in destruct.bindings() {
+ match p {
+ ast::DestructuringKind::Normal(ast::Expr::Ident(ident)) => {
+ let Ok(v) = value.at(&ident) else {
bail!(ident.span(), "destructuring key not found in dictionary");
};
- vm.define(ident.clone(), v.clone());
- used.insert(ident.clone().take());
- }
- ast::DestructuringKind::Sink(ident) => {
- sink = ident.clone()
- }
- ast::DestructuringKind::Named(key, ident) => {
- let Ok(v) = value.at(&key) else {
- bail!(ident.span(), "destructuring key not found in dictionary");
+ f(vm, ast::Expr::Ident(ident.clone()), v.clone())?;
+ used.insert(ident.take());
+ }
+ ast::DestructuringKind::Sink(spread) => sink = spread.expr(),
+ ast::DestructuringKind::Named(named) => {
+ let Ok(v) = value.at(named.name().as_str()) else {
+ bail!(named.name().span(), "destructuring key not found in dictionary");
};
- vm.define(ident.clone(), v.clone());
- used.insert(key.clone().take());
- }
- ast::DestructuringKind::Placeholder => {}
- }
- }
+ f(vm, named.expr(), v.clone())?;
+ used.insert(named.name().take());
+ }
+ ast::DestructuringKind::Placeholder(_) => {}
+ ast::DestructuringKind::Normal(expr) => {
+ bail!(expr.span(), "expected key, found expression");
+ }
+ }
+ }
- if let Some(ident) = sink {
- let mut sink = Dict::new();
- for (key, value) in value {
- if !used.contains(key.as_str()) {
- sink.insert(key, value);
- }
- }
- vm.define(ident, Value::Dict(sink));
- }
- }
- _ => {
- bail!(self.span(), "cannot destructure {}", value.type_name());
- }
+ if let Some(expr) = sink {
+ let mut sink = Dict::new();
+ for (key, value) in value {
+ if !used.contains(key.as_str()) {
+ sink.insert(key, value);
}
+ }
+ f(vm, expr, Value::Dict(sink))?;
+ }
+
+ Ok(Value::None)
+ }
+ /// Destruct the given value into the pattern and apply the function to each binding.
+ #[tracing::instrument(skip_all)]
+ fn apply<T>(&self, vm: &mut Vm, value: Value, f: T) -> SourceResult<Value>
+ where
+ T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>,
+ {
+ match self {
+ ast::Pattern::Normal(expr) => {
+ f(vm, expr.clone(), value)?;
Ok(Value::None)
}
+ ast::Pattern::Placeholder(_) => Ok(Value::None),
+ ast::Pattern::Destructuring(destruct) => match value {
+ Value::Array(value) => self.destruct_array(vm, value, f, destruct),
+ Value::Dict(value) => self.destruct_dict(vm, value, f, destruct),
+ _ => bail!(self.span(), "cannot destructure {}", value.type_name()),
+ },
}
}
+
+ /// Destruct the value into the pattern by binding.
+ pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> {
+ self.apply(vm, value, |vm, expr, value| match expr {
+ ast::Expr::Ident(ident) => {
+ vm.define(ident, value);
+ Ok(Value::None)
+ }
+ _ => unreachable!(),
+ })
+ }
+
+ /// Destruct the value into the pattern by assignment.
+ pub fn assign(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> {
+ self.apply(vm, value, |vm, expr, value| {
+ let location = expr.access(vm)?;
+ *location = value;
+ Ok(Value::None)
+ })
+ }
}
impl Eval for ast::LetBinding {
@@ -1345,6 +1390,16 @@ impl Eval for ast::LetBinding {
}
}
+impl Eval for ast::DestructAssignment {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let value = self.value().eval(vm)?;
+ self.pattern().assign(vm, value)?;
+ Ok(Value::None)
+ }
+}
+
impl Eval for ast::SetRule {
type Output = Styles;
@@ -1514,7 +1569,7 @@ impl Eval for ast::ForLoop {
let pattern = self.pattern();
match (&pattern, iter.clone()) {
- (ast::Pattern::Ident(_), Value::Str(string)) => {
+ (ast::Pattern::Normal(_), Value::Str(string)) => {
// Iterate over graphemes of string.
iter!(for pattern in string.as_str().graphemes(true));
}
@@ -1526,7 +1581,7 @@ impl Eval for ast::ForLoop {
// Iterate over values of array.
iter!(for pattern in array);
}
- (ast::Pattern::Ident(_), _) => {
+ (ast::Pattern::Normal(_), _) => {
bail!(self.iter().span(), "cannot loop over {}", iter.type_name());
}
(_, _) => {
diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs
index 259d34c3..b7b063a6 100644
--- a/src/ide/highlight.rs
+++ b/src/ide/highlight.rs
@@ -246,6 +246,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Tag> {
SyntaxKind::LoopContinue => None,
SyntaxKind::FuncReturn => None,
SyntaxKind::Destructuring => None,
+ SyntaxKind::DestructAssignment => None,
SyntaxKind::LineComment => Some(Tag::Comment),
SyntaxKind::BlockComment => Some(Tag::Comment),
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index f22508e8..1fa6c89f 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -164,6 +164,8 @@ pub enum Expr {
Closure(Closure),
/// A let binding: `let x = 1`.
Let(LetBinding),
+ //// A destructuring assignment: `(x, y) = (1, 2)`.
+ DestructAssign(DestructAssignment),
/// A set rule: `set text(...)`.
Set(SetRule),
/// A show rule: `show heading: it => emph(it.body)`.
@@ -240,6 +242,7 @@ impl AstNode for Expr {
SyntaxKind::FuncCall => node.cast().map(Self::FuncCall),
SyntaxKind::Closure => node.cast().map(Self::Closure),
SyntaxKind::LetBinding => node.cast().map(Self::Let),
+ SyntaxKind::DestructAssignment => node.cast().map(Self::DestructAssign),
SyntaxKind::SetRule => node.cast().map(Self::Set),
SyntaxKind::ShowRule => node.cast().map(Self::Show),
SyntaxKind::Conditional => node.cast().map(Self::Conditional),
@@ -299,6 +302,7 @@ impl AstNode for Expr {
Self::FuncCall(v) => v.as_untyped(),
Self::Closure(v) => v.as_untyped(),
Self::Let(v) => v.as_untyped(),
+ Self::DestructAssign(v) => v.as_untyped(),
Self::Set(v) => v.as_untyped(),
Self::Show(v) => v.as_untyped(),
Self::Conditional(v) => v.as_untyped(),
@@ -1179,6 +1183,11 @@ impl Named {
pub fn expr(&self) -> Expr {
self.0.cast_last_match().unwrap_or_default()
}
+
+ /// The right-hand side of the pair as an identifier.
+ pub fn expr_ident(&self) -> Option<Ident> {
+ self.0.cast_last_match()
+ }
}
node! {
@@ -1559,6 +1568,28 @@ impl Params {
}
}
+node! {
+ /// A spread: `..x` or `..x.at(0)`.
+ Spread
+}
+
+impl Spread {
+ /// Try to get an identifier.
+ pub fn name(&self) -> Option<Ident> {
+ self.0.cast_first_match()
+ }
+
+ /// Try to get an expression.
+ pub fn expr(&self) -> Option<Expr> {
+ self.0.cast_first_match()
+ }
+}
+
+node! {
+ /// An underscore: `_`
+ Underscore
+}
+
/// A parameter to a closure.
#[derive(Debug, Clone, Hash)]
pub enum Param {
@@ -1567,9 +1598,9 @@ pub enum Param {
/// A named parameter with a default value: `draw: false`.
Named(Named),
/// An argument sink: `..args`.
- Sink(Option<Ident>),
+ Sink(Spread),
/// A placeholder: `_`.
- Placeholder,
+ Placeholder(Underscore),
}
impl AstNode for Param {
@@ -1577,8 +1608,8 @@ impl AstNode for Param {
match node.kind() {
SyntaxKind::Ident => node.cast().map(Self::Pos),
SyntaxKind::Named => node.cast().map(Self::Named),
- SyntaxKind::Spread => Some(Self::Sink(node.cast_first_match())),
- SyntaxKind::Underscore => Some(Self::Placeholder),
+ SyntaxKind::Spread => node.cast().map(Self::Sink),
+ SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
_ => Option::None,
}
}
@@ -1587,8 +1618,8 @@ impl AstNode for Param {
match self {
Self::Pos(v) => v.as_untyped(),
Self::Named(v) => v.as_untyped(),
- Self::Sink(_) => self.as_untyped(),
- Self::Placeholder => self.as_untyped(),
+ Self::Sink(v) => v.as_untyped(),
+ Self::Placeholder(v) => v.as_untyped(),
}
}
}
@@ -1598,56 +1629,63 @@ node! {
Destructuring
}
+impl Destructuring {
+ /// The bindings of the destructuring.
+ pub fn bindings(&self) -> impl Iterator<Item = DestructuringKind> + '_ {
+ self.0.children().filter_map(SyntaxNode::cast)
+ }
+
+ // Returns a list of all identifiers in the pattern.
+ pub fn idents(&self) -> impl Iterator<Item = Ident> + '_ {
+ self.bindings().filter_map(|binding| match binding {
+ DestructuringKind::Normal(Expr::Ident(ident)) => Some(ident),
+ DestructuringKind::Sink(spread) => spread.name(),
+ DestructuringKind::Named(named) => named.expr_ident(),
+ _ => Option::None,
+ })
+ }
+}
+
/// The kind of an element in a destructuring pattern.
#[derive(Debug, Clone, Hash)]
pub enum DestructuringKind {
- /// An identifier: `x`.
- Ident(Ident),
+ /// An expression: `x`.
+ Normal(Expr),
/// An argument sink: `..y`.
- Sink(Option<Ident>),
+ Sink(Spread),
/// Named arguments: `x: 1`.
- Named(Ident, Ident),
+ Named(Named),
/// A placeholder: `_`.
- Placeholder,
+ Placeholder(Underscore),
}
-impl Destructuring {
- /// The bindings of the destructuring.
- pub fn bindings(&self) -> impl Iterator<Item = DestructuringKind> + '_ {
- self.0.children().filter_map(|child| match child.kind() {
- SyntaxKind::Ident => {
- Some(DestructuringKind::Ident(child.cast().unwrap_or_default()))
- }
- SyntaxKind::Spread => Some(DestructuringKind::Sink(child.cast_first_match())),
- SyntaxKind::Named => {
- let mut filtered = child.children().filter_map(SyntaxNode::cast);
- let key = filtered.next().unwrap_or_default();
- let ident = filtered.next().unwrap_or_default();
- Some(DestructuringKind::Named(key, ident))
- }
- SyntaxKind::Underscore => Some(DestructuringKind::Placeholder),
- _ => Option::None,
- })
+impl AstNode for DestructuringKind {
+ fn from_untyped(node: &SyntaxNode) -> Option<Self> {
+ match node.kind() {
+ SyntaxKind::Named => node.cast().map(Self::Named),
+ SyntaxKind::Spread => node.cast().map(Self::Sink),
+ SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
+ _ => node.cast().map(Self::Normal),
+ }
}
- // Returns a list of all identifiers in the pattern.
- pub fn idents(&self) -> impl Iterator<Item = Ident> + '_ {
- self.bindings().filter_map(|binding| match binding {
- DestructuringKind::Ident(ident) => Some(ident),
- DestructuringKind::Sink(ident) => ident,
- DestructuringKind::Named(_, ident) => Some(ident),
- DestructuringKind::Placeholder => Option::None,
- })
+ fn as_untyped(&self) -> &SyntaxNode {
+ match self {
+ Self::Normal(v) => v.as_untyped(),
+ Self::Named(v) => v.as_untyped(),
+ Self::Sink(v) => v.as_untyped(),
+ Self::Placeholder(v) => v.as_untyped(),
+ }
}
}
/// The kind of a pattern.
#[derive(Debug, Clone, Hash)]
pub enum Pattern {
- /// A single identifier: `x`.
- Ident(Ident),
+ /// A single expression: `x`.
+ Normal(Expr),
/// A placeholder: `_`.
- Placeholder,
+ Placeholder(Underscore),
/// A destructuring pattern: `(x, _, ..y)`.
Destructuring(Destructuring),
}
@@ -1655,18 +1693,17 @@ pub enum Pattern {
impl AstNode for Pattern {
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() {
- SyntaxKind::Ident => node.cast().map(Self::Ident),
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
- SyntaxKind::Underscore => Some(Self::Placeholder),
- _ => Option::None,
+ SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
+ _ => node.cast().map(Self::Normal),
}
}
fn as_untyped(&self) -> &SyntaxNode {
match self {
- Self::Ident(v) => v.as_untyped(),
+ Self::Normal(v) => v.as_untyped(),
Self::Destructuring(v) => v.as_untyped(),
- Self::Placeholder => self.as_untyped(),
+ Self::Placeholder(v) => v.as_untyped(),
}
}
}
@@ -1675,16 +1712,16 @@ impl Pattern {
// Returns a list of all identifiers in the pattern.
pub fn idents(&self) -> Vec<Ident> {
match self {
- Pattern::Ident(ident) => vec![ident.clone()],
+ Pattern::Normal(Expr::Ident(ident)) => vec![ident.clone()],
Pattern::Destructuring(destruct) => destruct.idents().collect(),
- Pattern::Placeholder => vec![],
+ _ => vec![],
}
}
}
impl Default for Pattern {
fn default() -> Self {
- Self::Ident(Ident::default())
+ Self::Normal(Expr::default())
}
}
@@ -1716,23 +1753,18 @@ impl LetBindingKind {
impl LetBinding {
/// The kind of the let binding.
pub fn kind(&self) -> LetBindingKind {
- if let Some(pattern) = self.0.cast_first_match::<Pattern>() {
- LetBindingKind::Normal(pattern)
- } else {
- LetBindingKind::Closure(
- self.0
- .cast_first_match::<Closure>()
- .unwrap_or_default()
- .name()
- .unwrap_or_default(),
- )
+ match self.0.cast_first_match::<Pattern>() {
+ Some(Pattern::Normal(Expr::Closure(closure))) => {
+ LetBindingKind::Closure(closure.name().unwrap_or_default())
+ }
+ pattern => LetBindingKind::Normal(pattern.unwrap_or_default()),
}
}
/// The expression the binding is initialized with.
pub fn init(&self) -> Option<Expr> {
match self.kind() {
- LetBindingKind::Normal(Pattern::Ident(_)) => {
+ LetBindingKind::Normal(Pattern::Normal(_)) => {
self.0.children().filter_map(SyntaxNode::cast).nth(1)
}
LetBindingKind::Normal(_) => self.0.cast_first_match(),
@@ -1742,6 +1774,23 @@ impl LetBinding {
}
node! {
+ /// An assignment expression `(x, y) = (1, 2)`.
+ DestructAssignment
+}
+
+impl DestructAssignment {
+ /// The pattern of the assignment.
+ pub fn pattern(&self) -> Pattern {
+ self.0.cast_first_match::<Pattern>().unwrap_or_default()
+ }
+
+ /// The expression that is assigned.
+ pub fn value(&self) -> Expr {
+ self.0.cast_last_match().unwrap_or_default()
+ }
+}
+
+node! {
/// A set rule: `set text(...)`.
SetRule
}
diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs
index d35901b0..0717e16c 100644
--- a/src/syntax/kind.rs
+++ b/src/syntax/kind.rs
@@ -244,6 +244,8 @@ pub enum SyntaxKind {
FuncReturn,
/// A destructuring pattern: `(x, _, ..y)`.
Destructuring,
+ /// A destructuring assignment expression: `(x, y) = (1, 2)`.
+ DestructAssignment,
/// A line comment: `// ...`.
LineComment,
@@ -430,6 +432,7 @@ impl SyntaxKind {
Self::LoopContinue => "`continue` expression",
Self::FuncReturn => "`return` expression",
Self::Destructuring => "destructuring pattern",
+ Self::DestructAssignment => "destructuring assignment expression",
Self::LineComment => "line comment",
Self::BlockComment => "block comment",
Self::Error => "syntax error",
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
index 389cf026..0bbf77e2 100644
--- a/src/syntax/parser.rs
+++ b/src/syntax/parser.rs
@@ -725,7 +725,16 @@ fn with_paren(p: &mut Parser) {
p.assert(SyntaxKind::Arrow);
code_expr(p);
kind = SyntaxKind::Closure;
+ } else if p.at(SyntaxKind::Eq) && kind != SyntaxKind::Parenthesized {
+ // TODO: add warning if p.at(SyntaxKind::Eq) && kind == SyntaxKind::Parenthesized
+
+ validate_destruct_pattern(p, m, false);
+ p.wrap(m, SyntaxKind::Destructuring);
+ p.assert(SyntaxKind::Eq);
+ code_expr(p);
+ kind = SyntaxKind::DestructAssignment;
}
+
match kind {
SyntaxKind::Array => validate_array(p, m),
SyntaxKind::Dict => validate_dict(p, m),
@@ -866,7 +875,7 @@ fn pattern(p: &mut Parser) -> PatternKind {
let m = p.marker();
if p.at(SyntaxKind::LeftParen) {
let kind = collection(p, false);
- validate_destruct_pattern(p, m);
+ validate_destruct_pattern(p, m, true);
if kind == SyntaxKind::Parenthesized {
PatternKind::Ident
@@ -1184,7 +1193,7 @@ fn validate_args(p: &mut Parser, m: Marker) {
}
}
-fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
+fn validate_destruct_pattern(p: &mut Parser, m: Marker, forbid_expressions: bool) {
let mut used_spread = false;
let mut used = HashSet::new();
for child in p.post_process(m) {
@@ -1206,7 +1215,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
if within.kind() == SyntaxKind::Dots {
continue;
- } else if within.kind() != SyntaxKind::Ident {
+ } else if forbid_expressions && within.kind() != SyntaxKind::Ident {
within.convert_to_error(eco_format!(
"expected identifier, found {}",
within.kind().name(),
@@ -1231,15 +1240,17 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
child.make_erroneous();
}
- let Some(within) = child.children_mut().last_mut() else { return };
- if within.kind() != SyntaxKind::Ident
- && within.kind() != SyntaxKind::Underscore
- {
- within.convert_to_error(eco_format!(
- "expected identifier, found {}",
- within.kind().name(),
- ));
- child.make_erroneous();
+ if forbid_expressions {
+ let Some(within) = child.children_mut().last_mut() else { return };
+ if within.kind() != SyntaxKind::Ident
+ && within.kind() != SyntaxKind::Underscore
+ {
+ within.convert_to_error(eco_format!(
+ "expected identifier, found {}",
+ within.kind().name(),
+ ));
+ child.make_erroneous();
+ }
}
}
SyntaxKind::LeftParen
@@ -1247,10 +1258,12 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
| SyntaxKind::Comma
| SyntaxKind::Underscore => {}
kind => {
- child.convert_to_error(eco_format!(
- "expected identifier or destructuring sink, found {}",
- kind.name()
- ));
+ if forbid_expressions {
+ child.convert_to_error(eco_format!(
+ "expected identifier or destructuring sink, found {}",
+ kind.name()
+ ));
+ }
}
}
}
diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ
index 160d2147..7081bcf0 100644
--- a/tests/typ/compiler/let.typ
+++ b/tests/typ/compiler/let.typ
@@ -121,6 +121,12 @@ Three
// Error: 13-14 at most one binding per identifier is allowed
#let (a: a, a) = (a: 1, b: 2)
+// Error: 13-20 expected identifier, found function call
+#let (a, b: b.at(0)) = (a: 1, b: 2)
+
+// Error: 7-14 expected identifier or destructuring sink, found function call
+#let (a.at(0),) = (1,)
+
---
// Error: 13-14 not enough elements to destructure
#let (a, b, c) = (1, 2)
@@ -181,11 +187,11 @@ Three
#let (a, b) = (a: 1)
---
-// Error: 13-14 destructuring key not found in dictionary
+// Error: 10-11 destructuring key not found in dictionary
#let (a, b: b) = (a: 1)
---
-// Error: 7-8 cannot destructure named elements from an array
+// Error: 7-11 cannot destructure named elements from an array
#let (a: a, b) = (1, 2, 3)
---
diff --git a/tests/typ/compiler/ops-invalid.typ b/tests/typ/compiler/ops-invalid.typ
index 3ab62929..2af3b767 100644
--- a/tests/typ/compiler/ops-invalid.typ
+++ b/tests/typ/compiler/ops-invalid.typ
@@ -101,6 +101,10 @@
#((x) = "")
---
+// Error: 4-5 unknown variable: x
+#((x,) = (1,))
+
+---
// Error: 3-8 cannot mutate a temporary value
#(1 + 2 += 3)
diff --git a/tests/typ/compiler/ops.typ b/tests/typ/compiler/ops.typ
index a6c64cbd..2bb06e4d 100644
--- a/tests/typ/compiler/ops.typ
+++ b/tests/typ/compiler/ops.typ
@@ -201,6 +201,48 @@
#(x += "thing") #test(x, "something")
---
+// Test destructuring assignments.
+
+#let a = none
+#let b = none
+#let c = none
+#((a,) = (1,))
+#test(a, 1)
+
+#((_, a, b, _) = (1, 2, 3, 4))
+#test(a, 2)
+#test(b, 3)
+
+#((a, b, ..c) = (1, 2, 3, 4, 5, 6))
+#test(a, 1)
+#test(b, 2)
+#test(c, (3, 4, 5, 6))
+
+#((a: a, b, x: c) = (a: 1, b: 2, x: 3))
+#test(a, 1)
+#test(b, 2)
+#test(c, 3)
+
+#let a = (1, 2)
+#((a: a.at(0), b) = (a: 3, b: 4))
+#test(a, (3, 2))
+#test(b, 4)
+
+#let a = (1, 2)
+#((a.at(0), b) = (3, 4))
+#test(a, (3, 2))
+#test(b, 4)
+
+#((a, ..b) = (1, 2, 3, 4))
+#test(a, 1)
+#test(b, (2, 3, 4))
+
+#let a = (1, 2)
+#((b, ..a.at(0)) = (1, 2, 3, 4))
+#test(a, ((2, 3, 4), 2))
+#test(b, 1)
+
+---
// Error: 3-6 cannot mutate a constant: box
#(box = 1)