rust-lang/rust-analyzer · warning

cargo runnable should deserialize

Error message

cargo runnable should deserialize

What it means

The companion assertion of the cargo-runnable round-trip test: it deserializes the expected JSON back into Runnable and expects success. A panic here means the JSON fixture no longer matches the Runnable/RunnableArgs serde schema — serde_json::from_value returned a deserialization error (missing field, wrong shape, or the kind tag not selecting the Cargo variant).

Solutions

  1. Compare the fixture JSON against the current serde derive on Runnable and fix mismatched field names/shape.
  2. Ensure the enum uses a discriminant (the `kind` tag) so Cargo and Shell variants deserialize distinctly.
  3. Run with RUST_BACKTRACE=1 and print the serde error to see the exact missing/mismatched field.
  4. Update the expected JSON (via UPDATE_EXPECT=1 or manually) only after verifying the new schema is intended and the client extension matches.

Example fix

// before: untagged enum
#[derive(Serialize, Deserialize)]
enum RunnableArgs { Cargo(CargoRunnable), Shell(ShellRunnable) }
// after: internally tagged so variants are distinguishable
#[derive(Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum RunnableArgs { Cargo(CargoRunnable), Shell(ShellRunnable) }
Defensive patterns

Strategy: type-guard

Validate before calling

// validate fixture matches current schema before from_value
let value = serde_json::to_value(&runnable).expect("fixture must serialize");
assert!(value.get("kind").is_some(), "runnable payload must carry a kind tag");

Type guard

fn is_cargo_runnable(v: &serde_json::Value) -> bool {
    v.get("kind").and_then(|k| k.as_str()) == Some("cargo")
        && v.get("cargoArgs").map(|a| a.is_array()).unwrap_or(false)
}

Prevention

When it happens

Trigger: Changing the serde representation of RunnableArgs::Cargo (field names, tagging strategy like untagged vs internally tagged, removing/renaming cargoArgs or executableArgs) so the fixture JSON in the test fails to deserialize.

Common situations: Contributors refactoring the rust-analyzer runnable LSP extension types, accidentally switching to a tag-less enum where a shell payload parses as cargo, or renaming fields without updating the test's expected JSON.

Related errors


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/0bb212fa69e755b6. Report an issue: GitHub.

Appendix: source

Thrown at crates/rust-analyzer/src/lsp/ext.rs:1009

        };
        let expected = json!({
            "label": "cargo test -p my-crate",
            "kind": "cargo",
            "args": {
                "environment": {"RUSTC_TOOLCHAIN": "/toolchain"},
                "cwd": "/project",
                "overrideCargo": null,
                "workspaceRoot": "/project",
                "cargoArgs": ["test", "--package", "my-crate", "--lib"],
                "executableArgs": ["my_test", "--exact"],
            }
        });

        let serialized = serde_json::to_value(&runnable).expect("serialized runnable");
        assert_eq!(serialized, expected);

        let deserialized: Runnable =
            serde_json::from_value(expected).expect("cargo runnable should deserialize");
        let RunnableArgs::Cargo(cargo) = &deserialized.args else {
            panic!("expected Cargo variant, got {:?}", deserialized.args);
        };
        assert_eq!(cargo.cargo_args, vec!["test", "--package", "my-crate", "--lib"]);
        assert_eq!(cargo.executable_args, vec!["my_test", "--exact"]);
    }

    #[test]
    fn shell_runnable_round_trips() {
        let runnable = Runnable {
            label: "nextest test_one".to_owned(),
            location: None,
            args: RunnableArgs::Shell(ShellRunnableArgs {
                environment: [("RUSTC_TOOLCHAIN".to_owned(), "/toolchain".to_owned())]
                    .into_iter()
                    .collect(),
                cwd: "/project".into(),
                program: "cargo".into(),

View on GitHub (pinned to e8f7e90aa3)