Repository: typst/typst
File: crates/typst-syntax/src/parser.rs (Rust)

Code before:
                    array_last = false;
                }
            }
            _ => p.unexpected(),
        }
    }

    // Check if we need to wrap the preceding arguments in an array.
    if array_last && maybe_array_start != p.marker() && has_arrays && positional {
        p.wrap(maybe_array_start, SyntaxKind::Array);
    }
}

/// Parses a single argument in a math argument list.
///
/// Returns whether the argument parsed was positional or not.
fn math_arg<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>) -> bool {
    let m = p.marker();

    // Parses a spread argument: `..args`.
    if p.eat_if(SyntaxKind::Dots) {
        math_expr(p);
        p.wrap(m, SyntaxKind::Spread);
        return false;
    }

    let mut positional = false;

    // Parses a named argument: `thickness: #12pt`.
    if p.at(SyntaxKind::Ident) {
        let text = p.current_text();
        p.eat();
        p.convert_and_eat(SyntaxKind::Colon);
        if !seen.insert(text) {
            p[m].convert_to_error(eco_format!("duplicate argument: {text}"));
        }
    } else {
        positional = true;
    }

    // Parses a normal position argument.
    let mut count = 0;
    let arg = p.marker();
    while !p.at_set(syntax_set!(Comma, Semicolon, RightParen)) {
        if p.at_set(set::MATH_EXPR) {
            math_expr(p);
            count += 1;
        } else {
            p.unexpected();
        }
    }

Review comment (suggestion):
There should be some tests for having more than one expression after the dots: `$ func(..x + y - z) $`.

Code after:
            SyntaxKind::End | SyntaxKind::Dollar | SyntaxKind::RightParen => {}
            _ => p.expected("comma or semicolon"),
        }
    }

    // Check if we need to wrap the preceding arguments in an array.
    if maybe_array_start != p.marker() && has_arrays && positional {
        p.wrap(maybe_array_start, SyntaxKind::Array);
    }

    p.expect_closing_delimiter(m, SyntaxKind::RightParen);
    p.wrap(m, SyntaxKind::Args);
}

/// Parses a single argument in a math argument list.
///
/// Returns whether the parsed argument was positional or not.
fn math_arg<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>) -> bool {
    let m = p.marker();
    let start = p.current_start();

    if p.at(SyntaxKind::Dot) {
        // Parses a spread argument: `..args`.
        if let Some(spread) = p.lexer.maybe_math_spread_arg(start) {
            p.token.node = spread;
            p.eat();
            math_expr(p);
            p.wrap(m, SyntaxKind::Spread);
            return true;
        }
    }

    let mut positional = true;
    if p.at_set(syntax_set!(Text, MathIdent, Underscore)) {
        // Parses a named argument: `thickness: #12pt`.
        if let Some(named) = p.lexer.maybe_math_named_arg(start) {
            p.token.node = named;
            let text = p.current_text();
            p.eat();
            p.convert_and_eat(SyntaxKind::Colon);
            if !seen.insert(text) {
                p[m].convert_to_error(eco_format!("duplicate argument: {text}"));
            }
            positional = false;
        }
    }

    // Parses a normal positional argument.
    let arg = p.marker();
    let count = math_exprs(p, syntax_set!(End, Dollar, Comma, Semicolon, RightParen));
    if count == 0 {