Destructuring improvements (#3463)

This commit is contained in:
Laurenz 2024-02-21 09:38:47 +01:00 committed by GitHub
parent b2e509d472
commit be49935753
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 1132 additions and 928 deletions

View File

@ -1285,7 +1285,7 @@ impl<'a> CompletionContext<'a> {
let mut sibling = Some(node.clone());
while let Some(node) = &sibling {
if let Some(v) = node.cast::<ast::LetBinding>() {
for ident in v.kind().idents() {
for ident in v.kind().bindings() {
defined.insert(ident.get().clone());
}
}
@ -1323,7 +1323,7 @@ impl<'a> CompletionContext<'a> {
if let Some(v) = parent.cast::<ast::ForLoop>() {
if node.prev_sibling_kind() != Some(SyntaxKind::In) {
let pattern = v.pattern();
for ident in pattern.idents() {
for ident in pattern.bindings() {
defined.insert(ident.get().clone());
}
}

View File

@ -1157,9 +1157,18 @@ node! {
impl<'a> Parenthesized<'a> {
/// The wrapped expression.
///
/// Should only be accessed if this is contained in an `Expr`.
pub fn expr(self) -> Expr<'a> {
self.0.cast_first_match().unwrap_or_default()
}
/// The wrapped pattern.
///
/// Should only be accessed if this is contained in a `Pattern`.
pub fn pattern(self) -> Pattern<'a> {
self.0.cast_first_match().unwrap_or_default()
}
}
node! {
@ -1180,13 +1189,13 @@ pub enum ArrayItem<'a> {
/// A bare expression: `12`.
Pos(Expr<'a>),
/// A spread expression: `..things`.
Spread(Expr<'a>),
Spread(Spread<'a>),
}
impl<'a> AstNode<'a> for ArrayItem<'a> {
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
match node.kind() {
SyntaxKind::Spread => node.cast_first_match().map(Self::Spread),
SyntaxKind::Spread => node.cast().map(Self::Spread),
_ => node.cast().map(Self::Pos),
}
}
@ -1219,7 +1228,7 @@ pub enum DictItem<'a> {
/// A keyed pair: `"spacy key": true`.
Keyed(Keyed<'a>),
/// A spread expression: `..things`.
Spread(Expr<'a>),
Spread(Spread<'a>),
}
impl<'a> AstNode<'a> for DictItem<'a> {
@ -1227,7 +1236,7 @@ impl<'a> AstNode<'a> for DictItem<'a> {
match node.kind() {
SyntaxKind::Named => node.cast().map(Self::Named),
SyntaxKind::Keyed => node.cast().map(Self::Keyed),
SyntaxKind::Spread => node.cast_first_match().map(Self::Spread),
SyntaxKind::Spread => node.cast().map(Self::Spread),
_ => Option::None,
}
}
@ -1253,13 +1262,19 @@ impl<'a> Named<'a> {
}
/// The right-hand side of the pair: `3pt`.
///
/// This should only be accessed if this `Named` is contained in a
/// `DictItem`, `Arg`, or `Param`.
pub fn expr(self) -> Expr<'a> {
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<'a>> {
self.0.cast_last_match()
/// The right-hand side of the pair as a pattern.
///
/// This should only be accessed if this `Named` is contained in a
/// `Destructuring`.
pub fn pattern(self) -> Pattern<'a> {
self.0.cast_last_match().unwrap_or_default()
}
}
@ -1275,11 +1290,45 @@ impl<'a> Keyed<'a> {
}
/// The right-hand side of the pair: `true`.
///
/// This should only be accessed if this `Keyed` is contained in a
/// `DictItem`.
pub fn expr(self) -> Expr<'a> {
self.0.cast_last_match().unwrap_or_default()
}
}
node! {
/// A spread: `..x` or `..x.at(0)`.
Spread
}
impl<'a> Spread<'a> {
/// The spreaded expression.
///
/// This should only be accessed if this `Spread` is contained in an
/// `ArrayItem`, `DictItem`, or `Arg`.
pub fn expr(self) -> Expr<'a> {
self.0.cast_first_match().unwrap_or_default()
}
/// The sink identifier, if present.
///
/// This should only be accessed if this `Spread` is contained in a
/// `Param` or binding `DestructuringItem`.
pub fn sink_ident(self) -> Option<Ident<'a>> {
self.0.cast_first_match()
}
/// The sink expressions, if present.
///
/// This should only be accessed if this `Spread` is contained in a
/// `DestructuringItem`.
pub fn sink_expr(self) -> Option<Expr<'a>> {
self.0.cast_first_match()
}
}
node! {
/// A unary operation: `-x`.
Unary
@ -1591,14 +1640,14 @@ pub enum Arg<'a> {
/// A named argument: `draw: false`.
Named(Named<'a>),
/// A spread argument: `..things`.
Spread(Expr<'a>),
Spread(Spread<'a>),
}
impl<'a> AstNode<'a> for Arg<'a> {
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
match node.kind() {
SyntaxKind::Named => node.cast().map(Self::Named),
SyntaxKind::Spread => node.cast_first_match().map(Self::Spread),
SyntaxKind::Spread => node.cast().map(Self::Spread),
_ => node.cast().map(Self::Pos),
}
}
@ -1648,28 +1697,6 @@ impl<'a> Params<'a> {
}
}
node! {
/// A spread: `..x` or `..x.at(0)`.
Spread
}
impl<'a> Spread<'a> {
/// Try to get an identifier.
pub fn name(self) -> Option<Ident<'a>> {
self.0.cast_first_match()
}
/// Try to get an expression.
pub fn expr(self) -> Option<Expr<'a>> {
self.0.cast_first_match()
}
}
node! {
/// An underscore: `_`
Underscore
}
/// A parameter to a closure.
#[derive(Debug, Copy, Clone, Hash)]
pub enum Param<'a> {
@ -1677,15 +1704,15 @@ pub enum Param<'a> {
Pos(Pattern<'a>),
/// A named parameter with a default value: `draw: false`.
Named(Named<'a>),
/// An argument sink: `..args`.
Sink(Spread<'a>),
/// An argument sink: `..args` or `..`.
Spread(Spread<'a>),
}
impl<'a> AstNode<'a> for Param<'a> {
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
match node.kind() {
SyntaxKind::Named => node.cast().map(Self::Named),
SyntaxKind::Spread => node.cast().map(Self::Sink),
SyntaxKind::Spread => node.cast().map(Self::Spread),
_ => node.cast().map(Self::Pos),
}
}
@ -1694,62 +1721,7 @@ impl<'a> AstNode<'a> for Param<'a> {
match self {
Self::Pos(v) => v.to_untyped(),
Self::Named(v) => v.to_untyped(),
Self::Sink(v) => v.to_untyped(),
}
}
}
node! {
/// A destructuring pattern: `x` or `(x, _, ..y)`.
Destructuring
}
impl<'a> Destructuring<'a> {
/// The bindings of the destructuring.
pub fn bindings(self) -> impl DoubleEndedIterator<Item = DestructuringKind<'a>> {
self.0.children().filter_map(SyntaxNode::cast)
}
/// Returns a list of all identifiers in the pattern.
pub fn idents(self) -> impl DoubleEndedIterator<Item = Ident<'a>> {
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, Copy, Clone, Hash)]
pub enum DestructuringKind<'a> {
/// An expression: `x`.
Normal(Expr<'a>),
/// An argument sink: `..y`.
Sink(Spread<'a>),
/// Named arguments: `x: 1`.
Named(Named<'a>),
/// A placeholder: `_`.
Placeholder(Underscore<'a>),
}
impl<'a> AstNode<'a> for DestructuringKind<'a> {
fn from_untyped(node: &'a 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 to_untyped(self) -> &'a SyntaxNode {
match self {
Self::Normal(v) => v.to_untyped(),
Self::Named(v) => v.to_untyped(),
Self::Sink(v) => v.to_untyped(),
Self::Placeholder(v) => v.to_untyped(),
Self::Spread(v) => v.to_untyped(),
}
}
}
@ -1761,6 +1733,8 @@ pub enum Pattern<'a> {
Normal(Expr<'a>),
/// A placeholder: `_`.
Placeholder(Underscore<'a>),
/// A parenthesized pattern.
Parenthesized(Parenthesized<'a>),
/// A destructuring pattern: `(x, _, ..y)`.
Destructuring(Destructuring<'a>),
}
@ -1768,8 +1742,9 @@ pub enum Pattern<'a> {
impl<'a> AstNode<'a> for Pattern<'a> {
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
match node.kind() {
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
SyntaxKind::Parenthesized => node.cast().map(Self::Parenthesized),
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
_ => node.cast().map(Self::Normal),
}
}
@ -1777,18 +1752,20 @@ impl<'a> AstNode<'a> for Pattern<'a> {
fn to_untyped(self) -> &'a SyntaxNode {
match self {
Self::Normal(v) => v.to_untyped(),
Self::Destructuring(v) => v.to_untyped(),
Self::Placeholder(v) => v.to_untyped(),
Self::Parenthesized(v) => v.to_untyped(),
Self::Destructuring(v) => v.to_untyped(),
}
}
}
impl<'a> Pattern<'a> {
/// Returns a list of all identifiers in the pattern.
pub fn idents(self) -> Vec<Ident<'a>> {
/// Returns a list of all new bindings introduced by the pattern.
pub fn bindings(self) -> Vec<Ident<'a>> {
match self {
Pattern::Normal(Expr::Ident(ident)) => vec![ident],
Pattern::Destructuring(destruct) => destruct.idents().collect(),
Self::Normal(Expr::Ident(ident)) => vec![ident],
Self::Parenthesized(v) => v.pattern().bindings(),
Self::Destructuring(v) => v.bindings(),
_ => vec![],
}
}
@ -1800,6 +1777,65 @@ impl Default for Pattern<'_> {
}
}
node! {
/// An underscore: `_`
Underscore
}
node! {
/// A destructuring pattern: `x` or `(x, _, ..y)`.
Destructuring
}
impl<'a> Destructuring<'a> {
/// The items of the destructuring.
pub fn items(self) -> impl DoubleEndedIterator<Item = DestructuringItem<'a>> {
self.0.children().filter_map(SyntaxNode::cast)
}
/// Returns a list of all new bindings introduced by the destructuring.
pub fn bindings(self) -> Vec<Ident<'a>> {
self.items()
.flat_map(|binding| match binding {
DestructuringItem::Pattern(pattern) => pattern.bindings(),
DestructuringItem::Named(named) => named.pattern().bindings(),
DestructuringItem::Spread(spread) => {
spread.sink_ident().into_iter().collect()
}
})
.collect()
}
}
/// The kind of an element in a destructuring pattern.
#[derive(Debug, Copy, Clone, Hash)]
pub enum DestructuringItem<'a> {
/// A sub-pattern: `x`.
Pattern(Pattern<'a>),
/// A renamed destructuring: `x: y`.
Named(Named<'a>),
/// A destructuring sink: `..y` or `..`.
Spread(Spread<'a>),
}
impl<'a> AstNode<'a> for DestructuringItem<'a> {
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
match node.kind() {
SyntaxKind::Named => node.cast().map(Self::Named),
SyntaxKind::Spread => node.cast().map(Self::Spread),
_ => node.cast().map(Self::Pattern),
}
}
fn to_untyped(self) -> &'a SyntaxNode {
match self {
Self::Pattern(v) => v.to_untyped(),
Self::Named(v) => v.to_untyped(),
Self::Spread(v) => v.to_untyped(),
}
}
}
node! {
/// A let binding: `let x = 1`.
LetBinding
@ -1815,13 +1851,11 @@ pub enum LetBindingKind<'a> {
}
impl<'a> LetBindingKind<'a> {
/// Returns a list of all identifiers in the pattern.
pub fn idents(self) -> Vec<Ident<'a>> {
/// Returns a list of all new bindings introduced by the let binding.
pub fn bindings(self) -> Vec<Ident<'a>> {
match self {
LetBindingKind::Normal(pattern) => pattern.idents(),
LetBindingKind::Closure(ident) => {
vec![ident]
}
LetBindingKind::Normal(pattern) => pattern.bindings(),
LetBindingKind::Closure(ident) => vec![ident],
}
}
}
@ -1840,7 +1874,7 @@ impl<'a> LetBinding<'a> {
/// The expression the binding is initialized with.
pub fn init(self) -> Option<Expr<'a>> {
match self.kind() {
LetBindingKind::Normal(Pattern::Normal(_)) => {
LetBindingKind::Normal(Pattern::Normal(_) | Pattern::Parenthesized(_)) => {
self.0.children().filter_map(SyntaxNode::cast).nth(1)
}
LetBindingKind::Normal(_) => self.0.cast_first_match(),

View File

@ -136,7 +136,7 @@ pub enum SyntaxKind {
StarEq,
/// The divide-assign operator: `/=`.
SlashEq,
/// The spread operator: `..`.
/// Indicates a spread or sink: `..`.
Dots,
/// An arrow between a closure's parameters and body: `=>`.
Arrow,

View File

@ -3,7 +3,7 @@ use std::ops::{Deref, Range};
use std::rc::Rc;
use std::sync::Arc;
use ecow::{eco_vec, EcoString, EcoVec};
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use crate::ast::AstNode;
use crate::{FileId, Span, SyntaxKind};
@ -177,14 +177,9 @@ impl SyntaxNode {
}
impl SyntaxNode {
/// Mark this node as erroneous.
pub(super) fn make_erroneous(&mut self) {
if let Repr::Inner(inner) = &mut self.0 {
Arc::make_mut(inner).erroneous = true;
}
}
/// Convert the child to another kind.
///
/// Don't use this for converting to an error!
#[track_caller]
pub(super) fn convert_to_kind(&mut self, kind: SyntaxKind) {
debug_assert!(!kind.is_error());
@ -195,10 +190,30 @@ impl SyntaxNode {
}
}
/// Convert the child to an error.
/// Convert the child to an error, if it isn't already one.
pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) {
let text = std::mem::take(self).into_text();
*self = SyntaxNode::error(message, text);
if !self.kind().is_error() {
let text = std::mem::take(self).into_text();
*self = SyntaxNode::error(message, text);
}
}
/// Convert the child to an error stating that the given thing was
/// expected, but the current kind was found.
pub(super) fn expected(&mut self, expected: &str) {
let kind = self.kind();
self.convert_to_error(eco_format!("expected {expected}, found {}", kind.name()));
if kind.is_keyword() && matches!(expected, "identifier" | "pattern") {
self.hint(eco_format!(
"keyword `{text}` is not allowed as an identifier; try `{text}_` instead",
text = self.text(),
));
}
}
/// Convert the child to an error stating it was unexpected.
pub(super) fn unexpected(&mut self) {
self.convert_to_error(eco_format!("unexpected {}", self.kind().name()));
}
/// Assign spans to each node.

File diff suppressed because it is too large Load Diff

View File

@ -10,17 +10,16 @@ pub struct SyntaxSet(u128);
impl SyntaxSet {
/// Create a new set from a slice of kinds.
pub const fn new(slice: &[SyntaxKind]) -> Self {
let mut bits = 0;
let mut i = 0;
while i < slice.len() {
bits |= bit(slice[i]);
i += 1;
}
Self(bits)
pub const fn new() -> Self {
Self(0)
}
/// Insert a syntax kind into the set.
pub const fn add(self, kind: SyntaxKind) -> Self {
Self(self.0 | bit(kind))
}
/// Combine two syntax sets.
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
@ -36,56 +35,53 @@ const fn bit(kind: SyntaxKind) -> u128 {
}
/// Syntax kinds that can start a statement.
pub const STMT: SyntaxSet = SyntaxSet::new(&[
SyntaxKind::Let,
SyntaxKind::Set,
SyntaxKind::Show,
SyntaxKind::Import,
SyntaxKind::Include,
SyntaxKind::Return,
]);
pub const STMT: SyntaxSet = SyntaxSet::new()
.add(SyntaxKind::Let)
.add(SyntaxKind::Set)
.add(SyntaxKind::Show)
.add(SyntaxKind::Import)
.add(SyntaxKind::Include)
.add(SyntaxKind::Return);
/// Syntax kinds that can start a markup expression.
pub const MARKUP_EXPR: SyntaxSet = SyntaxSet::new(&[
SyntaxKind::Space,
SyntaxKind::Parbreak,
SyntaxKind::LineComment,
SyntaxKind::BlockComment,
SyntaxKind::Text,
SyntaxKind::Linebreak,
SyntaxKind::Escape,
SyntaxKind::Shorthand,
SyntaxKind::SmartQuote,
SyntaxKind::Raw,
SyntaxKind::Link,
SyntaxKind::Label,
SyntaxKind::Hash,
SyntaxKind::Star,
SyntaxKind::Underscore,
SyntaxKind::HeadingMarker,
SyntaxKind::ListMarker,
SyntaxKind::EnumMarker,
SyntaxKind::TermMarker,
SyntaxKind::RefMarker,
SyntaxKind::Dollar,
SyntaxKind::LeftBracket,
SyntaxKind::RightBracket,
SyntaxKind::Colon,
]);
pub const MARKUP_EXPR: SyntaxSet = SyntaxSet::new()
.add(SyntaxKind::Space)
.add(SyntaxKind::Parbreak)
.add(SyntaxKind::LineComment)
.add(SyntaxKind::BlockComment)
.add(SyntaxKind::Text)
.add(SyntaxKind::Linebreak)
.add(SyntaxKind::Escape)
.add(SyntaxKind::Shorthand)
.add(SyntaxKind::SmartQuote)
.add(SyntaxKind::Raw)
.add(SyntaxKind::Link)
.add(SyntaxKind::Label)
.add(SyntaxKind::Hash)
.add(SyntaxKind::Star)
.add(SyntaxKind::Underscore)
.add(SyntaxKind::HeadingMarker)
.add(SyntaxKind::ListMarker)
.add(SyntaxKind::EnumMarker)
.add(SyntaxKind::TermMarker)
.add(SyntaxKind::RefMarker)
.add(SyntaxKind::Dollar)
.add(SyntaxKind::LeftBracket)
.add(SyntaxKind::RightBracket)
.add(SyntaxKind::Colon);
/// Syntax kinds that can start a math expression.
pub const MATH_EXPR: SyntaxSet = SyntaxSet::new(&[
SyntaxKind::Hash,
SyntaxKind::MathIdent,
SyntaxKind::Text,
SyntaxKind::Shorthand,
SyntaxKind::Linebreak,
SyntaxKind::MathAlignPoint,
SyntaxKind::Escape,
SyntaxKind::Str,
SyntaxKind::Root,
SyntaxKind::Prime,
]);
pub const MATH_EXPR: SyntaxSet = SyntaxSet::new()
.add(SyntaxKind::Hash)
.add(SyntaxKind::MathIdent)
.add(SyntaxKind::Text)
.add(SyntaxKind::Shorthand)
.add(SyntaxKind::Linebreak)
.add(SyntaxKind::MathAlignPoint)
.add(SyntaxKind::Escape)
.add(SyntaxKind::Str)
.add(SyntaxKind::Root)
.add(SyntaxKind::Prime);
/// Syntax kinds that can start a code expression.
pub const CODE_EXPR: SyntaxSet = CODE_PRIMARY.union(UNARY_OP);
@ -94,63 +90,81 @@ pub const CODE_EXPR: SyntaxSet = CODE_PRIMARY.union(UNARY_OP);
pub const ATOMIC_CODE_EXPR: SyntaxSet = ATOMIC_CODE_PRIMARY;
/// Syntax kinds that can start a code primary.
pub const CODE_PRIMARY: SyntaxSet =
ATOMIC_CODE_PRIMARY.union(SyntaxSet::new(&[SyntaxKind::Underscore]));
pub const CODE_PRIMARY: SyntaxSet = ATOMIC_CODE_PRIMARY.add(SyntaxKind::Underscore);
/// Syntax kinds that can start an atomic code primary.
pub const ATOMIC_CODE_PRIMARY: SyntaxSet = SyntaxSet::new(&[
SyntaxKind::Ident,
SyntaxKind::LeftBrace,
SyntaxKind::LeftBracket,
SyntaxKind::LeftParen,
SyntaxKind::Dollar,
SyntaxKind::Let,
SyntaxKind::Set,
SyntaxKind::Show,
SyntaxKind::If,
SyntaxKind::While,
SyntaxKind::For,
SyntaxKind::Import,
SyntaxKind::Include,
SyntaxKind::Break,
SyntaxKind::Continue,
SyntaxKind::Return,
SyntaxKind::None,
SyntaxKind::Auto,
SyntaxKind::Int,
SyntaxKind::Float,
SyntaxKind::Bool,
SyntaxKind::Numeric,
SyntaxKind::Str,
SyntaxKind::Label,
SyntaxKind::Raw,
]);
pub const ATOMIC_CODE_PRIMARY: SyntaxSet = SyntaxSet::new()
.add(SyntaxKind::Ident)
.add(SyntaxKind::LeftBrace)
.add(SyntaxKind::LeftBracket)
.add(SyntaxKind::LeftParen)
.add(SyntaxKind::Dollar)
.add(SyntaxKind::Let)
.add(SyntaxKind::Set)
.add(SyntaxKind::Show)
.add(SyntaxKind::If)
.add(SyntaxKind::While)
.add(SyntaxKind::For)
.add(SyntaxKind::Import)
.add(SyntaxKind::Include)
.add(SyntaxKind::Break)
.add(SyntaxKind::Continue)
.add(SyntaxKind::Return)
.add(SyntaxKind::None)
.add(SyntaxKind::Auto)
.add(SyntaxKind::Int)
.add(SyntaxKind::Float)
.add(SyntaxKind::Bool)
.add(SyntaxKind::Numeric)
.add(SyntaxKind::Str)
.add(SyntaxKind::Label)
.add(SyntaxKind::Raw);
/// Syntax kinds that are unary operators.
pub const UNARY_OP: SyntaxSet =
SyntaxSet::new(&[SyntaxKind::Plus, SyntaxKind::Minus, SyntaxKind::Not]);
pub const UNARY_OP: SyntaxSet = SyntaxSet::new()
.add(SyntaxKind::Plus)
.add(SyntaxKind::Minus)
.add(SyntaxKind::Not);
/// Syntax kinds that are binary operators.
pub const BINARY_OP: SyntaxSet = SyntaxSet::new(&[
SyntaxKind::Plus,
SyntaxKind::Minus,
SyntaxKind::Star,
SyntaxKind::Slash,
SyntaxKind::And,
SyntaxKind::Or,
SyntaxKind::EqEq,
SyntaxKind::ExclEq,
SyntaxKind::Lt,
SyntaxKind::LtEq,
SyntaxKind::Gt,
SyntaxKind::GtEq,
SyntaxKind::Eq,
SyntaxKind::In,
SyntaxKind::PlusEq,
SyntaxKind::HyphEq,
SyntaxKind::StarEq,
SyntaxKind::SlashEq,
]);
pub const BINARY_OP: SyntaxSet = SyntaxSet::new()
.add(SyntaxKind::Plus)
.add(SyntaxKind::Minus)
.add(SyntaxKind::Star)
.add(SyntaxKind::Slash)
.add(SyntaxKind::And)
.add(SyntaxKind::Or)
.add(SyntaxKind::EqEq)
.add(SyntaxKind::ExclEq)
.add(SyntaxKind::Lt)
.add(SyntaxKind::LtEq)
.add(SyntaxKind::Gt)
.add(SyntaxKind::GtEq)
.add(SyntaxKind::Eq)
.add(SyntaxKind::In)
.add(SyntaxKind::PlusEq)
.add(SyntaxKind::HyphEq)
.add(SyntaxKind::StarEq)
.add(SyntaxKind::SlashEq);
/// Syntax kinds that can start an argument in a function call.
pub const ARRAY_OR_DICT_ITEM: SyntaxSet = CODE_EXPR.add(SyntaxKind::Dots);
/// Syntax kinds that can start an argument in a function call.
pub const ARG: SyntaxSet = CODE_EXPR.add(SyntaxKind::Dots);
/// Syntax kinds that can start a parameter in a parameter list.
pub const PARAM: SyntaxSet = PATTERN.add(SyntaxKind::Dots);
/// Syntax kinds that can start a destructuring item.
pub const DESTRUCTURING_ITEM: SyntaxSet = PATTERN.add(SyntaxKind::Dots);
/// Syntax kinds that can start a pattern.
pub const PATTERN: SyntaxSet =
PATTERN_LEAF.add(SyntaxKind::LeftParen).add(SyntaxKind::Underscore);
/// Syntax kinds that can start a pattern leaf.
pub const PATTERN_LEAF: SyntaxSet = ATOMIC_CODE_EXPR;
#[cfg(test)]
mod tests {
@ -163,7 +177,7 @@ mod tests {
#[test]
fn test_set() {
let set = SyntaxSet::new(&[SyntaxKind::And, SyntaxKind::Or]);
let set = SyntaxSet::new().add(SyntaxKind::And).add(SyntaxKind::Or);
assert!(set.contains(SyntaxKind::And));
assert!(set.contains(SyntaxKind::Or));
assert!(!set.contains(SyntaxKind::Not));

View File

@ -31,7 +31,7 @@ impl Eval for ast::DestructAssignment<'_> {
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
let value = self.value().eval(vm)?;
destructure_impl(vm, self.pattern(), value, |vm, expr, value| {
destructure_impl(vm, self.pattern(), value, &mut |vm, expr, value| {
let location = expr.access(vm)?;
*location = value;
Ok(())
@ -46,33 +46,34 @@ pub(crate) fn destructure(
pattern: ast::Pattern,
value: Value,
) -> SourceResult<()> {
destructure_impl(vm, pattern, value, |vm, expr, value| match expr {
destructure_impl(vm, pattern, value, &mut |vm, expr, value| match expr {
ast::Expr::Ident(ident) => {
vm.define(ident, value);
Ok(())
}
_ => bail!(expr.span(), "nested patterns are currently not supported"),
_ => bail!(expr.span(), "cannot assign to this expression"),
})
}
/// Destruct the given value into the pattern and apply the function to each binding.
fn destructure_impl<T>(
fn destructure_impl<F>(
vm: &mut Vm,
pattern: ast::Pattern,
value: Value,
f: T,
f: &mut F,
) -> SourceResult<()>
where
T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
{
match pattern {
ast::Pattern::Normal(expr) => {
f(vm, expr, value)?;
}
ast::Pattern::Normal(expr) => f(vm, expr, value)?,
ast::Pattern::Placeholder(_) => {}
ast::Pattern::Parenthesized(parenthesized) => {
destructure_impl(vm, parenthesized.pattern(), value, f)?
}
ast::Pattern::Destructuring(destruct) => match value {
Value::Array(value) => destructure_array(vm, pattern, value, f, destruct)?,
Value::Dict(value) => destructure_dict(vm, value, f, destruct)?,
Value::Array(value) => destructure_array(vm, destruct, value, f)?,
Value::Dict(value) => destructure_dict(vm, destruct, value, f)?,
_ => bail!(pattern.span(), "cannot destructure {}", value.ty()),
},
}
@ -81,51 +82,44 @@ where
fn destructure_array<F>(
vm: &mut Vm,
pattern: ast::Pattern,
value: Array,
f: F,
destruct: ast::Destructuring,
value: Array,
f: &mut F,
) -> SourceResult<()>
where
F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
{
let mut i = 0;
let len = value.as_slice().len();
for p in destruct.bindings() {
let mut i = 0;
for p in destruct.items() {
match p {
ast::DestructuringKind::Normal(expr) => {
ast::DestructuringItem::Pattern(pattern) => {
let Ok(v) = value.at(i as i64, None) else {
bail!(expr.span(), "not enough elements to destructure");
bail!(pattern.span(), "not enough elements to destructure");
};
f(vm, expr, v)?;
destructure_impl(vm, pattern, v, f)?;
i += 1;
}
ast::DestructuringKind::Sink(spread) => {
let sink_size = (1 + len).checked_sub(destruct.bindings().count());
ast::DestructuringItem::Spread(spread) => {
let sink_size = (1 + len).checked_sub(destruct.items().count());
let sink = sink_size.and_then(|s| value.as_slice().get(i..i + s));
if let (Some(sink_size), Some(sink)) = (sink_size, sink) {
if let Some(expr) = spread.expr() {
f(vm, expr, Value::Array(sink.into()))?;
}
i += sink_size;
} else {
bail!(pattern.span(), "not enough elements to destructure")
let (Some(sink_size), Some(sink)) = (sink_size, sink) else {
bail!(spread.span(), "not enough elements to destructure");
};
if let Some(expr) = spread.sink_expr() {
f(vm, expr, Value::Array(sink.into()))?;
}
i += sink_size;
}
ast::DestructuringKind::Named(named) => {
bail!(named.span(), "cannot destructure named elements from an array")
}
ast::DestructuringKind::Placeholder(underscore) => {
if i < len {
i += 1
} else {
bail!(underscore.span(), "not enough elements to destructure")
}
ast::DestructuringItem::Named(named) => {
bail!(named.span(), "cannot destructure named pattern from an array")
}
}
}
if i < len {
bail!(pattern.span(), "too many elements to destructure");
bail!(destruct.span(), "too many elements to destructure");
}
Ok(())
@ -133,32 +127,35 @@ where
fn destructure_dict<F>(
vm: &mut Vm,
dict: Dict,
f: F,
destruct: ast::Destructuring,
dict: Dict,
f: &mut F,
) -> SourceResult<()>
where
F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
{
let mut sink = None;
let mut used = HashSet::new();
for p in destruct.bindings() {
for p in destruct.items() {
match p {
ast::DestructuringKind::Normal(ast::Expr::Ident(ident)) => {
// Shorthand for a direct identifier.
ast::DestructuringItem::Pattern(ast::Pattern::Normal(ast::Expr::Ident(
ident,
))) => {
let v = dict.get(&ident).at(ident.span())?;
f(vm, ast::Expr::Ident(ident), v.clone())?;
used.insert(ident.as_str());
used.insert(ident.get().clone());
}
ast::DestructuringKind::Sink(spread) => sink = spread.expr(),
ast::DestructuringKind::Named(named) => {
ast::DestructuringItem::Named(named) => {
let name = named.name();
let v = dict.get(&name).at(name.span())?;
f(vm, named.expr(), v.clone())?;
used.insert(name.as_str());
destructure_impl(vm, named.pattern(), v.clone(), f)?;
used.insert(name.get().clone());
}
ast::DestructuringKind::Placeholder(_) => {}
ast::DestructuringKind::Normal(expr) => {
bail!(expr.span(), "expected key, found expression");
ast::DestructuringItem::Spread(spread) => sink = spread.sink_expr(),
ast::DestructuringItem::Pattern(expr) => {
bail!(expr.span(), "cannot destructure unnamed pattern from dictionary");
}
}
}

View File

@ -192,13 +192,14 @@ impl Eval for ast::Args<'_> {
});
}
ast::Arg::Named(named) => {
let expr = named.expr();
items.push(Arg {
span,
name: Some(named.name().get().clone().into()),
value: Spanned::new(named.expr().eval(vm)?, named.expr().span()),
value: Spanned::new(expr.eval(vm)?, expr.span()),
});
}
ast::Arg::Spread(expr) => match expr.eval(vm)? {
ast::Arg::Spread(spread) => match spread.expr().eval(vm)? {
Value::None => {}
Value::Array(array) => {
items.extend(array.into_iter().map(|value| Arg {
@ -215,7 +216,7 @@ impl Eval for ast::Args<'_> {
}));
}
Value::Args(args) => items.extend(args.items),
v => bail!(expr.span(), "cannot spread {}", v.ty()),
v => bail!(spread.span(), "cannot spread {}", v.ty()),
},
}
}
@ -311,7 +312,6 @@ pub(crate) fn call_closure(
ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
vm.define(ident, args.expect::<Value>(&ident)?)
}
ast::Pattern::Normal(_) => unreachable!(),
pattern => {
crate::eval::destructure(
&mut vm,
@ -320,8 +320,8 @@ pub(crate) fn call_closure(
)?;
}
},
ast::Param::Sink(ident) => {
sink = Some(ident.name());
ast::Param::Spread(spread) => {
sink = Some(spread.sink_ident());
if let Some(sink_size) = sink_size {
sink_pos_values = Some(args.consume(sink_size)?);
}
@ -336,10 +336,10 @@ pub(crate) fn call_closure(
}
}
if let Some(sink_name) = sink {
if let Some(sink) = sink {
// Remaining args are captured regardless of whether the sink is named.
let mut remaining_args = args.take();
if let Some(sink_name) = sink_name {
if let Some(sink_name) = sink {
if let Some(sink_pos_values) = sink_pos_values {
remaining_args.items.extend(sink_pos_values);
}
@ -436,13 +436,15 @@ impl<'a> CapturesVisitor<'a> {
for param in expr.params().children() {
match param {
ast::Param::Pos(pattern) => {
for ident in pattern.idents() {
for ident in pattern.bindings() {
self.bind(ident);
}
}
ast::Param::Named(named) => self.bind(named.name()),
ast::Param::Sink(spread) => {
self.bind(spread.name().unwrap_or_default())
ast::Param::Spread(spread) => {
if let Some(ident) = spread.sink_ident() {
self.bind(ident);
}
}
}
}
@ -458,7 +460,7 @@ impl<'a> CapturesVisitor<'a> {
self.visit(init.to_untyped());
}
for ident in expr.kind().idents() {
for ident in expr.kind().bindings() {
self.bind(ident);
}
}
@ -471,7 +473,7 @@ impl<'a> CapturesVisitor<'a> {
self.internal.enter();
let pattern = expr.pattern();
for ident in pattern.idents() {
for ident in pattern.bindings() {
self.bind(ident);
}

View File

@ -210,10 +210,10 @@ impl Eval for ast::Array<'_> {
for item in items {
match item {
ast::ArrayItem::Pos(expr) => vec.push(expr.eval(vm)?),
ast::ArrayItem::Spread(expr) => match expr.eval(vm)? {
ast::ArrayItem::Spread(spread) => match spread.expr().eval(vm)? {
Value::None => {}
Value::Array(array) => vec.extend(array.into_iter()),
v => bail!(expr.span(), "cannot spread {} into array", v.ty()),
v => bail!(spread.span(), "cannot spread {} into array", v.ty()),
},
}
}
@ -227,7 +227,6 @@ impl Eval for ast::Dict<'_> {
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
let mut map = indexmap::IndexMap::new();
let mut invalid_keys = eco_vec![];
for item in self.items() {
@ -245,10 +244,10 @@ impl Eval for ast::Dict<'_> {
});
map.insert(key, keyed.expr().eval(vm)?);
}
ast::DictItem::Spread(expr) => match expr.eval(vm)? {
ast::DictItem::Spread(spread) => match spread.expr().eval(vm)? {
Value::None => {}
Value::Dict(dict) => map.extend(dict.into_iter()),
v => bail!(expr.span(), "cannot spread {} into dictionary", v.ty()),
v => bail!(spread.span(), "cannot spread {} into dictionary", v.ty()),
},
}
}

View File

@ -884,7 +884,7 @@ fn print_annotation(
let start_col = 1 + source.byte_to_column(range.start).unwrap();
let end_line = 1 + line + source.byte_to_line(range.end).unwrap();
let end_col = 1 + source.byte_to_column(range.end).unwrap();
write!(output, "{start_line}:{start_col}-{end_line}:{end_col}: ").unwrap();
write!(output, "{start_line}:{start_col}-{end_line}:{end_col} ").unwrap();
}
writeln!(output, "{text}").unwrap();
}

View File

@ -0,0 +1,86 @@
// Ref: false
// Test bugs related to destructuring and parenthesized parsing.
---
// https://github.com/typst/typst/issues/1338
#let foo = "foo"
#let bar = "bar"
// Error: 8-9 expected expression, found underscore
// Error: 16-17 expected expression, found underscore
#(foo: _, bar: _)
---
// https://github.com/typst/typst/issues/1342
// Error: 5-8 expected named or keyed pair, found identifier
// Error: 10-13 expected named or keyed pair, found identifier
#(: foo, bar)
---
// https://github.com/typst/typst/issues/1351
// Error: 17-22 expected pattern, found string
#let foo((test: "bar")) = {}
---
// https://github.com/typst/typst/issues/3014
// Error: 8-17 expected expression, found named pair
#(box, fill: red)
---
// https://github.com/typst/typst/issues/3144
#let f(a: 10) = a(1) + 1
#test(f(a: _ => 5), 6)
---
// Error: 18-20 missing argument: pattern parameter
#let f(a: 10) = a() + 1
#f(a: _ => 5)
---
// This wasn't allowed.
#let ((x)) = 1
#test(x, 1)
---
// This also wasn't allowed.
#let ((a, b)) = (1, 2)
#test(a, 1)
#test(b, 2)
---
// This was unintentionally allowed ...
// Error: 9 expected equals sign
#let (a)
---
// ... where this wasn't.
// Error: 12 expected equals sign
#let (a, b)
---
// This wasn't allowed before the bug fix ...
#let f(..) = {}
#f(arg: 1)
---
// ... but this was.
#let f(..x) = {}
#f(arg: 1)
---
// Here, `best` was accessed as a variable, where it shouldn't have.
#{
(best: _) = (best: "brr")
}
---
// Same here.
#{
let array = (1, 2, 3, 4)
(test: array.at(1), best: _) = (test: "baz", best: "brr")
test(array, (1, "baz", 3, 4))
}
---
// Here, `a` is not duplicate, where it was previously identified as one.
#let f((a: b), (c,), a) = (a, b, c)
#test(f((a: 1), (2,), 3), (3, 1, 2))

View File

@ -0,0 +1,33 @@
// Ensure that parser backtracking doesn't lead to exponential time consumption.
// If this regresses, the test suite will not terminate, which is a bit
// unfortunate compared to a good error, but at least we know something is up.
//
// Ref: false
---
#{
let s = "(x: 1) => x"
let pat = "(x: {}) => 1 + x()"
for _ in range(50) {
s = pat.replace("{}", s)
}
test(eval(s)(), 51)
}
---
#{
let s = "(x) = 1"
let pat = "(x: {_}) = 1"
for _ in range(100) {
s = pat.replace("_", s)
}
// Error: 8-9 cannot destructure integer
eval(s)
}
---
// Test whitespace after memoized part.
#( (x: () => 1 ) => 1 )
// -------
// This is memoized and we want to ensure that whitespace after this
// is handled correctly.

View File

@ -126,8 +126,7 @@
// Should output `3`.
#{
// Error: 6 expected identifier
// Error: 10 expected block
// Error: 7-10 expected pattern, found string
for "v"
// Error: 8 expected keyword `in`

View File

@ -75,8 +75,7 @@
#f[1](2)
---
// Error: 7 expected expression
// Error: 8 expected expression
// Error: 7-8 unexpected colon
#func(:)
// Error: 10-12 unexpected end of block comment

View File

@ -171,25 +171,26 @@
#let f((k: a, b), c: 3, (d,)) = (a, b, c, d)
#test(f((k: 1, b: 2), (4,)), (1, 2, 3, 4))
// Error: 22-23 duplicate parameter: a
#let f((a: b), (c,), a) = none
// Error: 8-14 expected identifier, found array
// Error: 8-14 expected identifier, found destructuring pattern
#let f((a, b): 0) = none
// Error: 10-19 expected identifier, found destructuring pattern
// Error: 10-19 expected pattern, found array
#let f(..(a, b: c)) = none
// Error: 10-16 expected identifier, found array
// Error: 10-16 expected pattern, found array
#let f(..(a, b)) = none
// Error: 10-19 expected identifier, found destructuring pattern
#let f(..(a, b: c)) = none
---
// Error: 11-12 duplicate parameter: x
#let f(x, x) = none
---
// Error: 21 expected comma
// Error: 22-23 expected pattern, found integer
// Error: 24-25 unexpected plus
// Error: 26-27 expected pattern, found integer
#let f = (x: () => 1 2 + 3) => 4
---
// Error: 14-15 duplicate parameter: a
// Error: 23-24 duplicate parameter: b
@ -201,17 +202,18 @@
#let f(a, ..a) = none
---
// Error: 7-17 expected identifier, named pair or argument sink, found keyed pair
// Error: 7-14 expected pattern, found string
#((a, "named": b) => none)
---
// Error: 10-15 expected identifier, found string
// Error: 10-15 expected pattern, found string
#let foo("key": b) = key
---
// Error: 10-14 expected identifier, found `none`
// Error: 10-14 expected pattern, found `none`
// Hint: 10-14 keyword `none` is not allowed as an identifier; try `none_` instead
#let foo(none: b) = key
---
// Error: 11 expected comma
// Error: 10-11 expected identifier, found underscore
#let foo(_: 3) = none

View File

@ -110,7 +110,6 @@
// Identified as dictionary due to initial colon.
// The boolean key is allowed for now since it will only cause an error at the evaluation stage.
// Error: 4-5 expected named or keyed pair, found integer
// Error: 5 expected comma
// Error: 17 expected expression
#(:1 b:"", true:)
@ -152,7 +151,7 @@
---
// Error: 7-10 expected identifier, found group
// Error: 12-14 expected identifier, found integer
// Error: 12-14 expected pattern, found integer
#let ((a): 10) = "world"
---

View File

@ -2,13 +2,13 @@
// Ref: false
---
// Error: 6-8 expected identifier, found keyword `as`
// Error: 6-8 expected pattern, found keyword `as`
// Hint: 6-8 keyword `as` is not allowed as an identifier; try `as_` instead
#let as = 1 + 2
---
#{
// Error: 7-9 expected identifier, found keyword `as`
// Error: 7-9 expected pattern, found keyword `as`
// Hint: 7-9 keyword `as` is not allowed as an identifier; try `as_` instead
let as = 10
}

View File

@ -92,19 +92,24 @@
---
// Destructuring without parentheses.
// Error: 7 expected keyword `in`
// Hint: 7 did you mean to use a destructuring pattern?
// Error: 7-8 unexpected comma
// Hint: 7-8 destructuring patterns must be wrapped in parentheses
#for k, v in (a: 4, b: 5) {
dont-care
}
// Error: 5 expected identifier
// Error: 7-8 unexpected comma
// Hint: 7-8 destructuring patterns must be wrapped in parentheses
#for k, in () {}
---
// Error: 5 expected pattern
#for
// Error: 5 expected identifier
// Error: 5 expected pattern
#for//
// Error: 6 expected identifier
// Error: 6 expected pattern
#{for}
// Error: 7 expected keyword `in`
@ -116,15 +121,15 @@
// Error: 15 expected block
#for v in iter
// Error: 5 expected identifier
// Error: 5 expected pattern
#for
v in iter {}
// Error: 6 expected identifier
// Error: 10 expected block
// Error: 7-10 expected pattern, found string
// Error: 16 expected block
A#for "v" thing
// Error: 5 expected identifier
// Error: 6-9 expected pattern, found string
#for "v" in iter {}
// Error: 7 expected keyword `in`

View File

@ -125,22 +125,22 @@ Three
#test(a, 1)
#test(b, 4)
// Error: 10-11 at most one binding per identifier is allowed
// Error: 10-11 duplicate binding: a
#let (a, a) = (1, 2)
// Error: 12-15 at most one destructuring sink is allowed
// Error: 12-15 only one destructuring sink is allowed
#let (..a, ..a) = (1, 2)
// Error: 12-13 at most one binding per identifier is allowed
// Error: 12-13 duplicate binding: a
#let (a, ..a) = (1, 2)
// Error: 13-14 at most one binding per identifier is allowed
// Error: 13-14 duplicate binding: a
#let (a: a, a) = (a: 1, b: 2)
// Error: 13-20 expected identifier, found function call
// Error: 13-20 expected pattern, found function call
#let (a, b: b.at(0)) = (a: 1, b: 2)
// Error: 7-14 expected identifier or destructuring sink, found function call
// Error: 7-14 expected pattern, found function call
#let (a.at(0),) = (1,)
---
@ -148,7 +148,7 @@ Three
#let (a, b, c) = (1, 2)
---
// Error: 6-20 not enough elements to destructure
// Error: 7-10 not enough elements to destructure
#let (..a, b, c, d) = (1, 2)
---
@ -193,6 +193,24 @@ Three
#let (a, ..) = (a: 1, b: 2)
#test(a, 1)
---
// Ref: false
// Nested destructuring.
#let ((a, b), (key: c)) = ((1, 2), (key: 3))
#test((a, b, c), (1, 2, 3))
---
// Keyed destructuring is not currently supported.
// Error: 7-18 expected pattern, found string
#let ("spacy key": val) = ("spacy key": 123)
#val
---
// Keyed destructuring is not currently supported.
#let x = "spacy key"
// Error: 7-10 expected identifier, found group
#let ((x): v) = ("spacy key": 123)
---
// Trailing placeholders.
// Error: 10-11 not enough elements to destructure
@ -200,8 +218,8 @@ Three
#test(a, 1)
---
// Error: 10-13 expected identifier, found string
// Error: 18-19 expected identifier, found integer
// Error: 10-13 expected pattern, found string
// Error: 18-19 expected pattern, found integer
#let (a: "a", b: 2) = (a: 1, b: 2)
---
@ -213,18 +231,17 @@ Three
#let (a, b: b) = (a: 1)
---
// Error: 7-11 cannot destructure named elements from an array
// Error: 7-11 cannot destructure named pattern from an array
#let (a: a, b) = (1, 2, 3)
---
// Error: 5 expected identifier
// Error: 5 expected pattern
#let
// Error: 6 expected identifier
// Error: 6 expected pattern
#{let}
// Error: 5 expected identifier
// Error: 5 expected semicolon or line break
// Error: 6-9 expected pattern, found string
#let "v"
// Error: 7 expected semicolon or line break
@ -233,8 +250,7 @@ Three
// Error: 9 expected expression
#let v =
// Error: 5 expected identifier
// Error: 5 expected semicolon or line break
// Error: 6-9 expected pattern, found string
#let "v" = 1
// Terminated because expression ends.
@ -246,7 +262,7 @@ Three
// Error: 11-12 unclosed delimiter
#let v5 = (1, 2 + ; Five
// Error: 9-13 expected identifier, found boolean
// Error: 9-13 expected pattern, found boolean
#let (..true) = false
---
@ -257,7 +273,7 @@ Three
// Error: 2-3 unexpected underscore
#_
// Error: 8-9 unexpected underscore
// Error: 8-9 expected expression, found underscore
#lorem(_)
// Error: 3-4 expected expression, found underscore
@ -275,9 +291,11 @@ Three
// Error: 15 expected expression
#let func(x) =
---
// Error: 12 expected equals sign
#let (func)(x)
---
// Error: 12 expected equals sign
// Error: 15-15 expected semicolon or line break

View File

@ -273,6 +273,38 @@
#test(a, ((2, 3, 4), 2))
#test(b, 1)
---
// Test comma placement in destructuring assignment.
#let array = (1, 2, 3)
#((key: array.at(1)) = (key: "hi"))
#test(array, (1, "hi", 3))
#let array = (1, 2, 3)
#((array.at(1)) = ("hi"))
#test(array, (1, "hi", 3))
#let array = (1, 2, 3)
#((array.at(1),) = ("hi",))
#test(array, (1, "hi", 3))
#let array = (1, 2, 3)
#((array.at(1)) = ("hi",))
#test(array, (1, ("hi",), 3))
---
// Test nested destructuring assignment.
#let a
#let b
#let c
#(((a, b), (key: c)) = ((1, 2), (key: 3)))
#test((a, b, c), (1, 2, 3))
---
#let array = (1, 2, 3)
// Error: 3-17 cannot destructure string
#((array.at(1),) = ("hi"))
#test(array, (1, ("hi",), 3))
---
// Error: 3-6 cannot mutate a constant: box
#(box = 1)

View File

@ -61,11 +61,11 @@
#test(f(1, 2, 3), 3)
---
// Error: 13-19 cannot spread string
// Error: 11-19 cannot spread string
#calc.min(.."nope")
---
// Error: 10-14 expected identifier, found boolean
// Error: 10-14 expected pattern, found boolean
#let f(..true) = none
---
@ -90,11 +90,11 @@
}
---
// Error: 11-17 cannot spread dictionary into array
// Error: 9-17 cannot spread dictionary into array
#(1, 2, ..(a: 1))
---
// Error: 5-11 cannot spread array into dictionary
// Error: 3-11 cannot spread array into dictionary
#(..(1, 2), a: 1)
---

View File

@ -86,7 +86,7 @@
#eval("RR_1^NN", mode: "math", scope: (RR: math.NN, NN: math.RR))
---
// Error: 7-12 expected identifier
// Error: 7-12 expected pattern
#eval("let")
---