FuelLabs/sway · error

missing closing parenthesis

Error message

missing closing parenthesis

What it means

parse_pkg_dep_line handles optional parenthesized dependency names: if a line starts with '(', everything up to the first ')' is taken as the name. This error is the guard for a line that opens a parenthesis but never closes it (e.g. '(mydep git+https://...'). Note it is effectively defensive-only: str::split(')') always yields at least one item, so in practice the branch is near-unreachable and a ')' -less line instead mis-parses downstream (surfacing later as an invalid-source or salt error).

Source

Thrown at forc-pkg/src/lock.rs:315

}

type ParsedPkgLine<'a> = (Option<&'a str>, &'a str, Option<fuel_tx::Salt>);
// Parse the given `PkgDepLine` into its dependency name and unique string segments.
//
// I.e. given "(<dep_name>) <name> <source> (<salt>)", returns ("<dep_name>", "<name> <source>", "<salt>").
//
// Note that <source> may not appear in the case it is not required for disambiguation.
fn parse_pkg_dep_line(pkg_dep_line: &str) -> anyhow::Result<ParsedPkgLine> {
    let s = pkg_dep_line.trim();
    let (dep_name, s) = match s.starts_with('(') {
        false => (None, s),
        true => {
            // If we have the open bracket, grab everything until the closing bracket.
            let s = &s["(".len()..];
            let mut iter = s.split(')');
            let dep_name = iter
                .next()
                .ok_or_else(|| anyhow!("missing closing parenthesis"))?;
            // The rest is the unique package string and possibly the salt.
            let s = &s[dep_name.len() + ")".len()..];
            (Some(dep_name), s)
        }
    };

    // Check for salt.
    let mut iter = s.split('(');
    let pkg_str = iter
        .next()
        .ok_or_else(|| anyhow!("missing pkg string"))?
        .trim();
    let salt_str = iter.next().map(|s| s.trim()).map(|s| &s[..s.len() - 1]);
    let salt = match salt_str {
        Some(salt_str) => Some(
            fuel_tx::Salt::from_str(salt_str)
                .map_err(|e| anyhow!("invalid salt in lock file: {e}"))?,
        ),

View on GitHub (pinned to 47e5e902fa)

Solutions

  1. Fix the line to '(name source)' or the plain 'name source' form, or delete Forc.lock and regenerate with forc build.
  2. Prefer regenerating the lock over hand-editing dependency lines.

Example fix

# before (Forc.lock)
dependencies = [ "(auth git+https://github.com/x/auth#abc" ]

# after
dependencies = [ "(auth git+https://github.com/x/auth#abc123...)" ]
Defensive patterns

Strategy: validation

Validate before calling

// Rust, validate a dependency line's bracket balance before parsing:
fn balanced_parens(line: &str) -> bool {
    let mut depth = 0i32;
    for c in line.chars() { if c=='(' {depth+=1} else if c==')' {depth-=1} }
    depth == 0 && !line.trim_start().starts_with('(') || line.contains(')')
}

Try / catch

// Anyhow Result: on parse failure of a lock line, regenerate the lock:
if let Err(e) = parse_pkg_dep_line(line) { /* drop Forc.lock and re-resolve */ }

Prevention

When it happens

Trigger: A hand-edited or corrupted Forc.lock dependency line beginning with '(' and lacking the matching ')' - the intended, if rarely reachable, target of this guard.

Common situations: Hand-written lock lines imitating the '(name source) salt' contract-dependency form; truncation mid-line; scripts injecting malformed entries.

Related errors


AI-assisted analysis of FuelLabs/sway@47e5e902fa (2026-08-16). Data as JSON: /api/errors/e336680cc1053884. Report an issue: GitHub.