Destructuring assign (#703)

This commit is contained in:
Marmare314 2023-04-25 11:22:12 +02:00 committed by GitHub
parent efad1e71fa
commit d5d98b67a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 342 additions and 167 deletions

View File

@ -445,7 +445,9 @@ impl<'a> CapturesVisitor<'a> {
match param { match param {
ast::Param::Pos(ident) => self.bind(ident), ast::Param::Pos(ident) => self.bind(ident),
ast::Param::Named(named) => self.bind(named.name()), 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())
}
_ => {} _ => {}
} }
} }

View File

@ -456,6 +456,7 @@ impl Eval for ast::Expr {
Self::Unary(v) => v.eval(vm), Self::Unary(v) => v.eval(vm),
Self::Binary(v) => v.eval(vm), Self::Binary(v) => v.eval(vm),
Self::Let(v) => v.eval(vm), Self::Let(v) => v.eval(vm),
Self::DestructAssign(v) => v.eval(vm),
Self::Set(_) => bail!(forbidden("set")), Self::Set(_) => bail!(forbidden("set")),
Self::Show(_) => bail!(forbidden("show")), Self::Show(_) => bail!(forbidden("show")),
Self::Conditional(v) => v.eval(vm), Self::Conditional(v) => v.eval(vm),
@ -1212,8 +1213,8 @@ impl Eval for ast::Closure {
ast::Param::Named(named) => { ast::Param::Named(named) => {
params.push(Param::Named(named.name(), named.expr().eval(vm)?)); params.push(Param::Named(named.name(), named.expr().eval(vm)?));
} }
ast::Param::Sink(name) => params.push(Param::Sink(name)), ast::Param::Sink(spread) => params.push(Param::Sink(spread.name())),
ast::Param::Placeholder => params.push(Param::Placeholder), ast::Param::Placeholder(_) => params.push(Param::Placeholder),
} }
} }
@ -1231,97 +1232,141 @@ impl Eval for ast::Closure {
} }
impl ast::Pattern { impl ast::Pattern {
// Destruct the given value into the pattern. fn destruct_array<T>(
#[tracing::instrument(skip_all)] &self,
pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { vm: &mut Vm,
match self { value: Array,
ast::Pattern::Ident(ident) => { f: T,
vm.define(ident.clone(), value); destruct: &ast::Destructuring,
Ok(Value::None) ) -> SourceResult<Value>
} where
ast::Pattern::Placeholder => Ok(Value::None), T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>,
ast::Pattern::Destructuring(destruct) => { {
match value { let mut i = 0;
Value::Array(value) => { for p in destruct.bindings() {
let mut i = 0; match p {
for p in destruct.bindings() { ast::DestructuringKind::Normal(expr) => {
match p { let Ok(v) = value.at(i) else {
ast::DestructuringKind::Ident(ident) => { bail!(expr.span(), "not enough elements to destructure");
let Ok(v) = value.at(i) else { };
bail!(ident.span(), "not enough elements to destructure"); f(vm, expr, v.clone())?;
}; i += 1;
vm.define(ident.clone(), v.clone()); }
i += 1; ast::DestructuringKind::Sink(spread) => {
} let sink_size = (1 + value.len() as usize)
ast::DestructuringKind::Sink(ident) => { .checked_sub(destruct.bindings().count());
(1 + value.len() as usize).checked_sub(destruct.bindings().count()).and_then(|sink_size| { let sink =
let Ok(sink) = value.slice(i, Some(i + sink_size as i64)) else { sink_size.and_then(|s| value.slice(i, Some(i + s as i64)).ok());
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");
}
}
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 {
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");
};
vm.define(ident.clone(), v.clone());
used.insert(key.clone().take());
}
ast::DestructuringKind::Placeholder => {}
}
}
if let Some(ident) = sink { if let (Some(sink_size), Some(sink)) = (sink_size, sink) {
let mut sink = Dict::new(); if let Some(expr) = spread.expr() {
for (key, value) in value { f(vm, expr, Value::Array(sink.clone()))?;
if !used.contains(key.as_str()) {
sink.insert(key, value);
}
}
vm.define(ident, Value::Dict(sink));
} }
} i += sink_size as i64;
_ => { } else {
bail!(self.span(), "cannot destructure {}", value.type_name()); bail!(self.span(), "not enough elements to destructure")
} }
} }
ast::DestructuringKind::Named(named) => {
Ok(Value::None) 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");
};
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");
};
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(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)
})
} }
} }
@ -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 { impl Eval for ast::SetRule {
type Output = Styles; type Output = Styles;
@ -1514,7 +1569,7 @@ impl Eval for ast::ForLoop {
let pattern = self.pattern(); let pattern = self.pattern();
match (&pattern, iter.clone()) { match (&pattern, iter.clone()) {
(ast::Pattern::Ident(_), Value::Str(string)) => { (ast::Pattern::Normal(_), Value::Str(string)) => {
// Iterate over graphemes of string. // Iterate over graphemes of string.
iter!(for pattern in string.as_str().graphemes(true)); iter!(for pattern in string.as_str().graphemes(true));
} }
@ -1526,7 +1581,7 @@ impl Eval for ast::ForLoop {
// Iterate over values of array. // Iterate over values of array.
iter!(for pattern in array); iter!(for pattern in array);
} }
(ast::Pattern::Ident(_), _) => { (ast::Pattern::Normal(_), _) => {
bail!(self.iter().span(), "cannot loop over {}", iter.type_name()); bail!(self.iter().span(), "cannot loop over {}", iter.type_name());
} }
(_, _) => { (_, _) => {

View File

@ -246,6 +246,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Tag> {
SyntaxKind::LoopContinue => None, SyntaxKind::LoopContinue => None,
SyntaxKind::FuncReturn => None, SyntaxKind::FuncReturn => None,
SyntaxKind::Destructuring => None, SyntaxKind::Destructuring => None,
SyntaxKind::DestructAssignment => None,
SyntaxKind::LineComment => Some(Tag::Comment), SyntaxKind::LineComment => Some(Tag::Comment),
SyntaxKind::BlockComment => Some(Tag::Comment), SyntaxKind::BlockComment => Some(Tag::Comment),

View File

@ -164,6 +164,8 @@ pub enum Expr {
Closure(Closure), Closure(Closure),
/// A let binding: `let x = 1`. /// A let binding: `let x = 1`.
Let(LetBinding), Let(LetBinding),
//// A destructuring assignment: `(x, y) = (1, 2)`.
DestructAssign(DestructAssignment),
/// A set rule: `set text(...)`. /// A set rule: `set text(...)`.
Set(SetRule), Set(SetRule),
/// A show rule: `show heading: it => emph(it.body)`. /// 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::FuncCall => node.cast().map(Self::FuncCall),
SyntaxKind::Closure => node.cast().map(Self::Closure), SyntaxKind::Closure => node.cast().map(Self::Closure),
SyntaxKind::LetBinding => node.cast().map(Self::Let), SyntaxKind::LetBinding => node.cast().map(Self::Let),
SyntaxKind::DestructAssignment => node.cast().map(Self::DestructAssign),
SyntaxKind::SetRule => node.cast().map(Self::Set), SyntaxKind::SetRule => node.cast().map(Self::Set),
SyntaxKind::ShowRule => node.cast().map(Self::Show), SyntaxKind::ShowRule => node.cast().map(Self::Show),
SyntaxKind::Conditional => node.cast().map(Self::Conditional), SyntaxKind::Conditional => node.cast().map(Self::Conditional),
@ -299,6 +302,7 @@ impl AstNode for Expr {
Self::FuncCall(v) => v.as_untyped(), Self::FuncCall(v) => v.as_untyped(),
Self::Closure(v) => v.as_untyped(), Self::Closure(v) => v.as_untyped(),
Self::Let(v) => v.as_untyped(), Self::Let(v) => v.as_untyped(),
Self::DestructAssign(v) => v.as_untyped(),
Self::Set(v) => v.as_untyped(), Self::Set(v) => v.as_untyped(),
Self::Show(v) => v.as_untyped(), Self::Show(v) => v.as_untyped(),
Self::Conditional(v) => v.as_untyped(), Self::Conditional(v) => v.as_untyped(),
@ -1179,6 +1183,11 @@ impl Named {
pub fn expr(&self) -> Expr { pub fn expr(&self) -> Expr {
self.0.cast_last_match().unwrap_or_default() 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! { 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. /// A parameter to a closure.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub enum Param { pub enum Param {
@ -1567,9 +1598,9 @@ pub enum Param {
/// A named parameter with a default value: `draw: false`. /// A named parameter with a default value: `draw: false`.
Named(Named), Named(Named),
/// An argument sink: `..args`. /// An argument sink: `..args`.
Sink(Option<Ident>), Sink(Spread),
/// A placeholder: `_`. /// A placeholder: `_`.
Placeholder, Placeholder(Underscore),
} }
impl AstNode for Param { impl AstNode for Param {
@ -1577,8 +1608,8 @@ impl AstNode for Param {
match node.kind() { match node.kind() {
SyntaxKind::Ident => node.cast().map(Self::Pos), SyntaxKind::Ident => node.cast().map(Self::Pos),
SyntaxKind::Named => node.cast().map(Self::Named), SyntaxKind::Named => node.cast().map(Self::Named),
SyntaxKind::Spread => Some(Self::Sink(node.cast_first_match())), SyntaxKind::Spread => node.cast().map(Self::Sink),
SyntaxKind::Underscore => Some(Self::Placeholder), SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
_ => Option::None, _ => Option::None,
} }
} }
@ -1587,8 +1618,8 @@ impl AstNode for Param {
match self { match self {
Self::Pos(v) => v.as_untyped(), Self::Pos(v) => v.as_untyped(),
Self::Named(v) => v.as_untyped(), Self::Named(v) => v.as_untyped(),
Self::Sink(_) => self.as_untyped(), Self::Sink(v) => v.as_untyped(),
Self::Placeholder => self.as_untyped(), Self::Placeholder(v) => v.as_untyped(),
} }
} }
} }
@ -1598,56 +1629,63 @@ node! {
Destructuring Destructuring
} }
/// The kind of an element in a destructuring pattern.
#[derive(Debug, Clone, Hash)]
pub enum DestructuringKind {
/// An identifier: `x`.
Ident(Ident),
/// An argument sink: `..y`.
Sink(Option<Ident>),
/// Named arguments: `x: 1`.
Named(Ident, Ident),
/// A placeholder: `_`.
Placeholder,
}
impl Destructuring { impl Destructuring {
/// The bindings of the destructuring. /// The bindings of the destructuring.
pub fn bindings(&self) -> impl Iterator<Item = DestructuringKind> + '_ { pub fn bindings(&self) -> impl Iterator<Item = DestructuringKind> + '_ {
self.0.children().filter_map(|child| match child.kind() { self.0.children().filter_map(SyntaxNode::cast)
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,
})
} }
// Returns a list of all identifiers in the pattern. // Returns a list of all identifiers in the pattern.
pub fn idents(&self) -> impl Iterator<Item = Ident> + '_ { pub fn idents(&self) -> impl Iterator<Item = Ident> + '_ {
self.bindings().filter_map(|binding| match binding { self.bindings().filter_map(|binding| match binding {
DestructuringKind::Ident(ident) => Some(ident), DestructuringKind::Normal(Expr::Ident(ident)) => Some(ident),
DestructuringKind::Sink(ident) => ident, DestructuringKind::Sink(spread) => spread.name(),
DestructuringKind::Named(_, ident) => Some(ident), DestructuringKind::Named(named) => named.expr_ident(),
DestructuringKind::Placeholder => Option::None, _ => Option::None,
}) })
} }
} }
/// The kind of an element in a destructuring pattern.
#[derive(Debug, Clone, Hash)]
pub enum DestructuringKind {
/// An expression: `x`.
Normal(Expr),
/// An argument sink: `..y`.
Sink(Spread),
/// Named arguments: `x: 1`.
Named(Named),
/// A placeholder: `_`.
Placeholder(Underscore),
}
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),
}
}
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. /// The kind of a pattern.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub enum Pattern { pub enum Pattern {
/// A single identifier: `x`. /// A single expression: `x`.
Ident(Ident), Normal(Expr),
/// A placeholder: `_`. /// A placeholder: `_`.
Placeholder, Placeholder(Underscore),
/// A destructuring pattern: `(x, _, ..y)`. /// A destructuring pattern: `(x, _, ..y)`.
Destructuring(Destructuring), Destructuring(Destructuring),
} }
@ -1655,18 +1693,17 @@ pub enum Pattern {
impl AstNode for Pattern { impl AstNode for Pattern {
fn from_untyped(node: &SyntaxNode) -> Option<Self> { fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() { match node.kind() {
SyntaxKind::Ident => node.cast().map(Self::Ident),
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring), SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
SyntaxKind::Underscore => Some(Self::Placeholder), SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
_ => Option::None, _ => node.cast().map(Self::Normal),
} }
} }
fn as_untyped(&self) -> &SyntaxNode { fn as_untyped(&self) -> &SyntaxNode {
match self { match self {
Self::Ident(v) => v.as_untyped(), Self::Normal(v) => v.as_untyped(),
Self::Destructuring(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. // Returns a list of all identifiers in the pattern.
pub fn idents(&self) -> Vec<Ident> { pub fn idents(&self) -> Vec<Ident> {
match self { match self {
Pattern::Ident(ident) => vec![ident.clone()], Pattern::Normal(Expr::Ident(ident)) => vec![ident.clone()],
Pattern::Destructuring(destruct) => destruct.idents().collect(), Pattern::Destructuring(destruct) => destruct.idents().collect(),
Pattern::Placeholder => vec![], _ => vec![],
} }
} }
} }
impl Default for Pattern { impl Default for Pattern {
fn default() -> Self { fn default() -> Self {
Self::Ident(Ident::default()) Self::Normal(Expr::default())
} }
} }
@ -1716,23 +1753,18 @@ impl LetBindingKind {
impl LetBinding { impl LetBinding {
/// The kind of the let binding. /// The kind of the let binding.
pub fn kind(&self) -> LetBindingKind { pub fn kind(&self) -> LetBindingKind {
if let Some(pattern) = self.0.cast_first_match::<Pattern>() { match self.0.cast_first_match::<Pattern>() {
LetBindingKind::Normal(pattern) Some(Pattern::Normal(Expr::Closure(closure))) => {
} else { LetBindingKind::Closure(closure.name().unwrap_or_default())
LetBindingKind::Closure( }
self.0 pattern => LetBindingKind::Normal(pattern.unwrap_or_default()),
.cast_first_match::<Closure>()
.unwrap_or_default()
.name()
.unwrap_or_default(),
)
} }
} }
/// The expression the binding is initialized with. /// The expression the binding is initialized with.
pub fn init(&self) -> Option<Expr> { pub fn init(&self) -> Option<Expr> {
match self.kind() { match self.kind() {
LetBindingKind::Normal(Pattern::Ident(_)) => { LetBindingKind::Normal(Pattern::Normal(_)) => {
self.0.children().filter_map(SyntaxNode::cast).nth(1) self.0.children().filter_map(SyntaxNode::cast).nth(1)
} }
LetBindingKind::Normal(_) => self.0.cast_first_match(), LetBindingKind::Normal(_) => self.0.cast_first_match(),
@ -1741,6 +1773,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! { node! {
/// A set rule: `set text(...)`. /// A set rule: `set text(...)`.
SetRule SetRule

View File

@ -244,6 +244,8 @@ pub enum SyntaxKind {
FuncReturn, FuncReturn,
/// A destructuring pattern: `(x, _, ..y)`. /// A destructuring pattern: `(x, _, ..y)`.
Destructuring, Destructuring,
/// A destructuring assignment expression: `(x, y) = (1, 2)`.
DestructAssignment,
/// A line comment: `// ...`. /// A line comment: `// ...`.
LineComment, LineComment,
@ -430,6 +432,7 @@ impl SyntaxKind {
Self::LoopContinue => "`continue` expression", Self::LoopContinue => "`continue` expression",
Self::FuncReturn => "`return` expression", Self::FuncReturn => "`return` expression",
Self::Destructuring => "destructuring pattern", Self::Destructuring => "destructuring pattern",
Self::DestructAssignment => "destructuring assignment expression",
Self::LineComment => "line comment", Self::LineComment => "line comment",
Self::BlockComment => "block comment", Self::BlockComment => "block comment",
Self::Error => "syntax error", Self::Error => "syntax error",

View File

@ -725,7 +725,16 @@ fn with_paren(p: &mut Parser) {
p.assert(SyntaxKind::Arrow); p.assert(SyntaxKind::Arrow);
code_expr(p); code_expr(p);
kind = SyntaxKind::Closure; 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 { match kind {
SyntaxKind::Array => validate_array(p, m), SyntaxKind::Array => validate_array(p, m),
SyntaxKind::Dict => validate_dict(p, m), SyntaxKind::Dict => validate_dict(p, m),
@ -866,7 +875,7 @@ fn pattern(p: &mut Parser) -> PatternKind {
let m = p.marker(); let m = p.marker();
if p.at(SyntaxKind::LeftParen) { if p.at(SyntaxKind::LeftParen) {
let kind = collection(p, false); let kind = collection(p, false);
validate_destruct_pattern(p, m); validate_destruct_pattern(p, m, true);
if kind == SyntaxKind::Parenthesized { if kind == SyntaxKind::Parenthesized {
PatternKind::Ident 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_spread = false;
let mut used = HashSet::new(); let mut used = HashSet::new();
for child in p.post_process(m) { 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 { if within.kind() == SyntaxKind::Dots {
continue; continue;
} else if within.kind() != SyntaxKind::Ident { } else if forbid_expressions && within.kind() != SyntaxKind::Ident {
within.convert_to_error(eco_format!( within.convert_to_error(eco_format!(
"expected identifier, found {}", "expected identifier, found {}",
within.kind().name(), within.kind().name(),
@ -1231,15 +1240,17 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
child.make_erroneous(); child.make_erroneous();
} }
let Some(within) = child.children_mut().last_mut() else { return }; if forbid_expressions {
if within.kind() != SyntaxKind::Ident let Some(within) = child.children_mut().last_mut() else { return };
&& within.kind() != SyntaxKind::Underscore if within.kind() != SyntaxKind::Ident
{ && within.kind() != SyntaxKind::Underscore
within.convert_to_error(eco_format!( {
"expected identifier, found {}", within.convert_to_error(eco_format!(
within.kind().name(), "expected identifier, found {}",
)); within.kind().name(),
child.make_erroneous(); ));
child.make_erroneous();
}
} }
} }
SyntaxKind::LeftParen SyntaxKind::LeftParen
@ -1247,10 +1258,12 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
| SyntaxKind::Comma | SyntaxKind::Comma
| SyntaxKind::Underscore => {} | SyntaxKind::Underscore => {}
kind => { kind => {
child.convert_to_error(eco_format!( if forbid_expressions {
"expected identifier or destructuring sink, found {}", child.convert_to_error(eco_format!(
kind.name() "expected identifier or destructuring sink, found {}",
)); kind.name()
));
}
} }
} }
} }

View File

@ -121,6 +121,12 @@ Three
// Error: 13-14 at most one binding per identifier is allowed // Error: 13-14 at most one binding per identifier is allowed
#let (a: a, a) = (a: 1, b: 2) #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 // Error: 13-14 not enough elements to destructure
#let (a, b, c) = (1, 2) #let (a, b, c) = (1, 2)
@ -181,11 +187,11 @@ Three
#let (a, b) = (a: 1) #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) #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) #let (a: a, b) = (1, 2, 3)
--- ---

View File

@ -100,6 +100,10 @@
// Error: 4-5 unknown variable: x // Error: 4-5 unknown variable: x
#((x) = "") #((x) = "")
---
// Error: 4-5 unknown variable: x
#((x,) = (1,))
--- ---
// Error: 3-8 cannot mutate a temporary value // Error: 3-8 cannot mutate a temporary value
#(1 + 2 += 3) #(1 + 2 += 3)

View File

@ -200,6 +200,48 @@
#(x = "some") #test(x, "some") #(x = "some") #test(x, "some")
#(x += "thing") #test(x, "something") #(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 // Error: 3-6 cannot mutate a constant: box
#(box = 1) #(box = 1)