fix: Stale diagnostics with rust-project.json and rustc JSON

PR #18043 changed flycheck to be scoped to the relevant package. This
broke projects using check commands that invoke rustc directly,
because diagnostic JSON from rustc doesn't contain the package ID.

This was visible in the rust-analyzer logs when RA_LOG is set to
`rust_analyzer::flycheck=trace`.

Before:

    2026-02-02T07:03:48.020184937-08:00 TRACE diagnostic received flycheck_id=0 mismatched types package_id=None scope=Workspace
    ...
    2026-02-02T07:03:55.082046488-08:00 TRACE clearing diagnostics flycheck_id=0 scope=Workspace

After:

    2026-02-02T07:14:32.760707785-08:00 TRACE diagnostic received flycheck_id=0 mismatched types package_id=None scope=Package { package: BuildInfo { label: "fbcode//rust_devx/rust-guess-deps:rust-guess-deps" }, workspace_deps: Some({}) }
    ...
    2026-02-02T07:14:48.355981415-08:00 TRACE clearing diagnostics flycheck_id=0 scope=Package { package: BuildInfo { label: "fbcode//rust_devx/rust-guess-deps:rust-guess-deps" }, workspace_deps: Some({}) }

Previously r-a assumed that a diagnostic without a package ID applied
to the whole workspace. We would insert the diagnostic at the
workspace level, but then only clear diagnostics for the package.

As a result, red squiggles would get 'stuck'. Users who had fixed
compilation issues would still see the old red squiggles until they
introduced a new compilation error.

Instead, always apply diagnostics to the current package if flycheck
is scoped to a package and the diagnostic doesn't specify a
package. This makes CargoCheckEvent(None) and CargoCheckEvent(Some(_))
more consistent, as they now both match on scope.
This commit is contained in:
Wilfred Hughes 2026-02-02 16:09:59 +00:00
parent df2a3f8e04
commit 93dece9ae7

View file

@ -740,42 +740,70 @@ impl FlycheckActor {
flycheck_id = self.id,
message = diagnostic.message,
package_id = package_id.as_ref().map(|it| it.as_str()),
scope = ?self.scope,
"diagnostic received"
);
if self.diagnostics_received == DiagnosticsReceived::NotYet {
self.diagnostics_received = DiagnosticsReceived::AtLeastOne;
}
if let Some(package_id) = &package_id {
if self.diagnostics_cleared_for.insert(package_id.clone()) {
tracing::trace!(
flycheck_id = self.id,
package_id = package_id.as_str(),
"clearing diagnostics"
);
self.send(FlycheckMessage::ClearDiagnostics {
match &self.scope {
FlycheckScope::Workspace => {
if self.diagnostics_received == DiagnosticsReceived::NotYet {
self.send(FlycheckMessage::ClearDiagnostics {
id: self.id,
kind: ClearDiagnosticsKind::All(ClearScope::Workspace),
});
self.diagnostics_received =
DiagnosticsReceived::AtLeastOneAndClearedWorkspace;
}
if let Some(package_id) = package_id {
tracing::warn!(
"Ignoring package label {:?} and applying diagnostics to the whole workspace",
package_id
);
}
self.send(FlycheckMessage::AddDiagnostic {
id: self.id,
kind: ClearDiagnosticsKind::All(ClearScope::Package(
package_id.clone(),
)),
generation: self.generation,
package_id: None,
workspace_root: self.root.clone(),
diagnostic,
});
}
FlycheckScope::Package { package: flycheck_package, .. } => {
if self.diagnostics_received == DiagnosticsReceived::NotYet {
self.diagnostics_received = DiagnosticsReceived::AtLeastOne;
}
// If the package has been set in the diagnostic JSON, respect that. Otherwise, use the
// package that the current flycheck is scoped to. This is useful when a project is
// directly using rustc for its checks (e.g. custom check commands in rust-project.json).
let package_id = package_id.unwrap_or(flycheck_package.clone());
if self.diagnostics_cleared_for.insert(package_id.clone()) {
tracing::trace!(
flycheck_id = self.id,
package_id = package_id.as_str(),
"clearing diagnostics"
);
self.send(FlycheckMessage::ClearDiagnostics {
id: self.id,
kind: ClearDiagnosticsKind::All(ClearScope::Package(
package_id.clone(),
)),
});
}
self.send(FlycheckMessage::AddDiagnostic {
id: self.id,
generation: self.generation,
package_id: Some(package_id),
workspace_root: self.root.clone(),
diagnostic,
});
}
} else if self.diagnostics_received
!= DiagnosticsReceived::AtLeastOneAndClearedWorkspace
{
self.diagnostics_received =
DiagnosticsReceived::AtLeastOneAndClearedWorkspace;
self.send(FlycheckMessage::ClearDiagnostics {
id: self.id,
kind: ClearDiagnosticsKind::All(ClearScope::Workspace),
});
}
self.send(FlycheckMessage::AddDiagnostic {
id: self.id,
generation: self.generation,
package_id,
workspace_root: self.root.clone(),
diagnostic,
});
}
},
}