{"record":{"id":"e336680cc1053884","repo":"FuelLabs/sway","slug":"missing-closing-parenthesis","errorCode":null,"errorMessage":"missing closing parenthesis","messagePattern":"missing closing parenthesis","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"forc-pkg/src/lock.rs","lineNumber":315,"sourceCode":"}\n\ntype ParsedPkgLine<'a> = (Option<&'a str>, &'a str, Option<fuel_tx::Salt>);\n// Parse the given `PkgDepLine` into its dependency name and unique string segments.\n//\n// I.e. given \"(<dep_name>) <name> <source> (<salt>)\", returns (\"<dep_name>\", \"<name> <source>\", \"<salt>\").\n//\n// Note that <source> may not appear in the case it is not required for disambiguation.\nfn parse_pkg_dep_line(pkg_dep_line: &str) -> anyhow::Result<ParsedPkgLine> {\n    let s = pkg_dep_line.trim();\n    let (dep_name, s) = match s.starts_with('(') {\n        false => (None, s),\n        true => {\n            // If we have the open bracket, grab everything until the closing bracket.\n            let s = &s[\"(\".len()..];\n            let mut iter = s.split(')');\n            let dep_name = iter\n                .next()\n                .ok_or_else(|| anyhow!(\"missing closing parenthesis\"))?;\n            // The rest is the unique package string and possibly the salt.\n            let s = &s[dep_name.len() + \")\".len()..];\n            (Some(dep_name), s)\n        }\n    };\n\n    // Check for salt.\n    let mut iter = s.split('(');\n    let pkg_str = iter\n        .next()\n        .ok_or_else(|| anyhow!(\"missing pkg string\"))?\n        .trim();\n    let salt_str = iter.next().map(|s| s.trim()).map(|s| &s[..s.len() - 1]);\n    let salt = match salt_str {\n        Some(salt_str) => Some(\n            fuel_tx::Salt::from_str(salt_str)\n                .map_err(|e| anyhow!(\"invalid salt in lock file: {e}\"))?,\n        ),","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/FuelLabs/sway/blob/47e5e902faa42baf652dd6a0c88cd23390c1a614/forc-pkg/src/lock.rs#L297-L333","documentation":"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).","triggerScenarios":"A hand-edited or corrupted Forc.lock dependency line beginning with '(' and lacking the matching ')' - the intended, if rarely reachable, target of this guard.","commonSituations":"Hand-written lock lines imitating the '(name source) salt' contract-dependency form; truncation mid-line; scripts injecting malformed entries.","solutions":["Fix the line to '(name source)' or the plain 'name source' form, or delete Forc.lock and regenerate with forc build.","Prefer regenerating the lock over hand-editing dependency lines."],"exampleFix":"# before (Forc.lock)\ndependencies = [ \"(auth git+https://github.com/x/auth#abc\" ]\n\n# after\ndependencies = [ \"(auth git+https://github.com/x/auth#abc123...)\" ]","handlingStrategy":"validation","validationCode":"// Rust, validate a dependency line's bracket balance before parsing:\nfn balanced_parens(line: &str) -> bool {\n    let mut depth = 0i32;\n    for c in line.chars() { if c=='(' {depth+=1} else if c==')' {depth-=1} }\n    depth == 0 && !line.trim_start().starts_with('(') || line.contains(')')\n}","typeGuard":null,"tryCatchPattern":"// Anyhow Result: on parse failure of a lock line, regenerate the lock:\nif let Err(e) = parse_pkg_dep_line(line) { /* drop Forc.lock and re-resolve */ }","preventionTips":["Keep dependency lines in the canonical 'name source' or '(name source) salt' shapes.","Avoid hand-authoring contract-dependency lines in Forc.lock.","Validate bracket balance when scripts generate lock content."],"tags":["forc","forc-pkg","lockfile","parsing","defensive-code"],"backgroundTag":null,"analyzedSha":"47e5e902faa42baf652dd6a0c88cd23390c1a614","analyzedAt":"2026-08-16T07:57:45.555Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}