Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #21309 from jackh726/failed_obligations
Add an lsp extension to get failed obligations for a given function
24 files changed, 344 insertions, 16 deletions
diff --git a/Cargo.lock b/Cargo.lock index 7ada91d2b6..92a6c3e976 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -788,6 +788,7 @@ dependencies = [ "itertools 0.14.0", "ra-ap-rustc_type_ir", "rustc-hash 2.1.1", + "serde_json", "smallvec", "span", "stdx", @@ -901,6 +902,7 @@ dependencies = [ "rustc_apfloat", "salsa", "salsa-macros", + "serde", "smallvec", "span", "stdx", diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index c60ecef58e..e54a6190b4 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -18,6 +18,7 @@ itertools.workspace = true arrayvec.workspace = true smallvec.workspace = true ena = "0.14.3" +serde.workspace = true either.workspace = true oorandom = "11.1.5" tracing = { workspace = true, features = ["attributes"] } diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 4f739dc8ee..d527a4ae29 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -75,7 +75,7 @@ use crate::{ AliasTy, Const, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, Region, StoredGenericArgs, StoredTy, StoredTys, Ty, TyKind, Tys, abi::Safety, - infer::{InferCtxt, traits::ObligationCause}, + infer::{InferCtxt, ObligationInspector, traits::ObligationCause}, }, traits::FnTrait, utils::TargetFeatureIsSafeInTarget, @@ -94,11 +94,23 @@ pub(crate) use closure::analysis::{CaptureKind, CapturedItem, CapturedItemWithou /// The entry point of type inference. fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> InferenceResult { + infer_query_with_inspect(db, def, None) +} + +pub fn infer_query_with_inspect<'db>( + db: &'db dyn HirDatabase, + def: DefWithBodyId, + inspect: Option<ObligationInspector<'db>>, +) -> InferenceResult { let _p = tracing::info_span!("infer_query").entered(); let resolver = def.resolver(db); let body = db.body(def); let mut ctx = InferenceContext::new(db, def, &body, resolver); + if let Some(inspect) = inspect { + ctx.table.infer_ctxt.attach_obligation_inspector(inspect); + } + match def { DefWithBodyId::FunctionId(f) => { ctx.collect_fn(f); diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 6081196bac..1674771413 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -88,7 +88,7 @@ pub use infer::{ InferenceTyDiagnosticSource, OverloadedDeref, PointerCast, cast::CastError, closure::analysis::{CaptureKind, CapturedItem}, - could_coerce, could_unify, could_unify_deeply, + could_coerce, could_unify, could_unify_deeply, infer_query_with_inspect, }; pub use lower::{ GenericPredicates, ImplTraits, LifetimeElisionKind, TyDefId, TyLoweringContext, ValueTyDefId, diff --git a/crates/hir-ty/src/next_solver.rs b/crates/hir-ty/src/next_solver.rs index 298af7d124..605e31404c 100644 --- a/crates/hir-ty/src/next_solver.rs +++ b/crates/hir-ty/src/next_solver.rs @@ -9,6 +9,7 @@ mod binder; mod consts; mod def_id; pub mod fold; +pub mod format_proof_tree; pub mod fulfill; mod generic_arg; pub mod generics; diff --git a/crates/hir-ty/src/next_solver/format_proof_tree.rs b/crates/hir-ty/src/next_solver/format_proof_tree.rs new file mode 100644 index 0000000000..fa09cda234 --- /dev/null +++ b/crates/hir-ty/src/next_solver/format_proof_tree.rs @@ -0,0 +1,93 @@ +use rustc_type_ir::{solve::GoalSource, solve::inspect::GoalEvaluation}; +use serde::{Deserialize, Serialize}; + +use crate::next_solver::infer::InferCtxt; +use crate::next_solver::inspect::{InspectCandidate, InspectGoal}; +use crate::next_solver::{DbInterner, Span}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProofTreeData { + pub goal: String, + pub result: String, + pub depth: usize, + pub candidates: Vec<CandidateData>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CandidateData { + pub kind: String, + pub result: String, + pub impl_header: Option<String>, + pub nested_goals: Vec<ProofTreeData>, +} + +pub fn dump_proof_tree_structured<'db>( + proof_tree: GoalEvaluation<DbInterner<'db>>, + _span: Span, + infcx: &InferCtxt<'db>, +) -> ProofTreeData { + let goal_eval = InspectGoal::new(infcx, 0, proof_tree, None, GoalSource::Misc); + let mut serializer = ProofTreeSerializer::new(infcx); + serializer.serialize_goal(&goal_eval) +} + +struct ProofTreeSerializer<'a, 'db> { + infcx: &'a InferCtxt<'db>, +} + +impl<'a, 'db> ProofTreeSerializer<'a, 'db> { + fn new(infcx: &'a InferCtxt<'db>) -> Self { + Self { infcx } + } + + fn serialize_goal(&mut self, goal: &InspectGoal<'_, 'db>) -> ProofTreeData { + let candidates = goal.candidates(); + let candidates_data: Vec<CandidateData> = + candidates.iter().map(|c| self.serialize_candidate(c)).collect(); + + ProofTreeData { + goal: format!("{:?}", goal.goal()), + result: format!("{:?}", goal.result()), + depth: goal.depth(), + candidates: candidates_data, + } + } + + fn serialize_candidate(&mut self, candidate: &InspectCandidate<'_, 'db>) -> CandidateData { + let kind = candidate.kind(); + let impl_header = self.get_impl_header(candidate); + + let mut nested = Vec::new(); + self.infcx.probe(|_| { + for nested_goal in candidate.instantiate_nested_goals() { + nested.push(self.serialize_goal(&nested_goal)); + } + }); + + CandidateData { + kind: format!("{:?}", kind), + result: format!("{:?}", candidate.result()), + impl_header, + nested_goals: nested, + } + } + + fn get_impl_header(&self, candidate: &InspectCandidate<'_, 'db>) -> Option<String> { + use rustc_type_ir::solve::inspect::ProbeKind; + match candidate.kind() { + ProbeKind::TraitCandidate { source, .. } => { + use rustc_type_ir::solve::CandidateSource; + match source { + CandidateSource::Impl(impl_def_id) => { + use hir_def::{Lookup, src::HasSource}; + let db = self.infcx.interner.db; + let impl_src = impl_def_id.0.lookup(db).source(db); + Some(impl_src.value.to_string()) + } + _ => None, + } + } + _ => None, + } + } +} diff --git a/crates/hir-ty/src/next_solver/fulfill.rs b/crates/hir-ty/src/next_solver/fulfill.rs index 4a19e30c64..0fe0732972 100644 --- a/crates/hir-ty/src/next_solver/fulfill.rs +++ b/crates/hir-ty/src/next_solver/fulfill.rs @@ -187,6 +187,9 @@ impl<'db> FulfillmentCtxt<'db> { } let result = delegate.evaluate_root_goal(goal, Span::dummy(), stalled_on); + infcx.inspect_evaluated_obligation(&obligation, &result, || { + Some(delegate.evaluate_root_goal_for_proof_tree(goal, Span::dummy()).1) + }); let GoalEvaluation { goal: _, certainty, has_changed, stalled_on } = match result { Ok(result) => result, Err(NoSolution) => { diff --git a/crates/hir-ty/src/next_solver/infer/at.rs b/crates/hir-ty/src/next_solver/infer/at.rs index 6ba1aae2d4..dc0b584084 100644 --- a/crates/hir-ty/src/next_solver/infer/at.rs +++ b/crates/hir-ty/src/next_solver/infer/at.rs @@ -68,6 +68,7 @@ impl<'db> InferCtxt<'db> { inner: self.inner.clone(), tainted_by_errors: self.tainted_by_errors.clone(), universe: self.universe.clone(), + obligation_inspector: self.obligation_inspector.clone(), } } @@ -84,6 +85,7 @@ impl<'db> InferCtxt<'db> { inner: self.inner.clone(), tainted_by_errors: self.tainted_by_errors.clone(), universe: self.universe.clone(), + obligation_inspector: self.obligation_inspector.clone(), } } } diff --git a/crates/hir-ty/src/next_solver/infer/mod.rs b/crates/hir-ty/src/next_solver/infer/mod.rs index 14b8a61088..2926dc30de 100644 --- a/crates/hir-ty/src/next_solver/infer/mod.rs +++ b/crates/hir-ty/src/next_solver/infer/mod.rs @@ -10,8 +10,9 @@ use ena::unify as ut; use hir_def::GenericParamId; use opaque_types::{OpaqueHiddenType, OpaqueTypeStorage}; use region_constraints::{RegionConstraintCollector, RegionConstraintStorage}; -use rustc_next_trait_solver::solve::SolverDelegateEvalExt; +use rustc_next_trait_solver::solve::{GoalEvaluation, SolverDelegateEvalExt}; use rustc_pattern_analysis::Captures; +use rustc_type_ir::solve::{NoSolution, inspect}; use rustc_type_ir::{ ClosureKind, ConstVid, FloatVarValue, FloatVid, GenericArgKind, InferConst, InferTy, IntVarValue, IntVid, OutlivesPredicate, RegionVid, TermKind, TyVid, TypeFoldable, TypeFolder, @@ -27,6 +28,7 @@ use traits::{ObligationCause, PredicateObligations}; use type_variable::TypeVariableOrigin; use unify_key::{ConstVariableOrigin, ConstVariableValue, ConstVidKey}; +pub use crate::next_solver::infer::traits::ObligationInspector; use crate::next_solver::{ ArgOutlivesPredicate, BoundConst, BoundRegion, BoundTy, BoundVarKind, Goal, Predicate, SolverContext, @@ -250,6 +252,8 @@ pub struct InferCtxt<'db> { /// when we enter into a higher-ranked (`for<..>`) type or trait /// bound. universe: Cell<UniverseIndex>, + + obligation_inspector: Cell<Option<ObligationInspector<'db>>>, } /// See the `error_reporting` module for more details. @@ -375,6 +379,7 @@ impl<'db> InferCtxtBuilder<'db> { inner: RefCell::new(InferCtxtInner::new()), tainted_by_errors: Cell::new(None), universe: Cell::new(UniverseIndex::ROOT), + obligation_inspector: Cell::new(None), } } } @@ -1223,6 +1228,30 @@ impl<'db> InferCtxt<'db> { fn sub_unify_ty_vids_raw(&self, a: rustc_type_ir::TyVid, b: rustc_type_ir::TyVid) { self.inner.borrow_mut().type_variables().sub_unify(a, b); } + + /// Attach a callback to be invoked on each root obligation evaluated in the new trait solver. + pub fn attach_obligation_inspector(&self, inspector: ObligationInspector<'db>) { + debug_assert!( + self.obligation_inspector.get().is_none(), + "shouldn't override a set obligation inspector" + ); + self.obligation_inspector.set(Some(inspector)); + } + + pub fn inspect_evaluated_obligation( + &self, + obligation: &PredicateObligation<'db>, + result: &Result<GoalEvaluation<DbInterner<'db>>, NoSolution>, + get_proof_tree: impl FnOnce() -> Option<inspect::GoalEvaluation<DbInterner<'db>>>, + ) { + if let Some(inspector) = self.obligation_inspector.get() { + let result = match result { + Ok(GoalEvaluation { certainty, .. }) => Ok(*certainty), + Err(_) => Err(NoSolution), + }; + (inspector)(self, obligation, result, get_proof_tree()); + } + } } /// Helper for [InferCtxt::ty_or_const_infer_var_changed] (see comment on that), currently diff --git a/crates/hir-ty/src/next_solver/infer/traits.rs b/crates/hir-ty/src/next_solver/infer/traits.rs index 3409de17a1..14df42dc2a 100644 --- a/crates/hir-ty/src/next_solver/infer/traits.rs +++ b/crates/hir-ty/src/next_solver/infer/traits.rs @@ -9,8 +9,11 @@ use std::{ use hir_def::TraitId; use macros::{TypeFoldable, TypeVisitable}; -use rustc_type_ir::Upcast; use rustc_type_ir::elaborate::Elaboratable; +use rustc_type_ir::{ + Upcast, + solve::{Certainty, NoSolution, inspect}, +}; use tracing::debug; use crate::next_solver::{ @@ -79,6 +82,15 @@ pub struct Obligation<'db, T> { pub recursion_depth: usize, } +/// A callback that can be provided to `inspect_typeck`. Invoked on evaluation +/// of root obligations. +pub type ObligationInspector<'db> = fn( + &InferCtxt<'db>, + &PredicateObligation<'db>, + Result<Certainty, NoSolution>, + Option<inspect::GoalEvaluation<DbInterner<'db>>>, +); + /// For [`Obligation`], a sub-obligation is combined with the current obligation's /// param-env and cause code. impl<'db> Elaboratable<DbInterner<'db>> for PredicateObligation<'db> { diff --git a/crates/hir-ty/src/next_solver/inspect.rs b/crates/hir-ty/src/next_solver/inspect.rs index d66aa9f277..5286977549 100644 --- a/crates/hir-ty/src/next_solver/inspect.rs +++ b/crates/hir-ty/src/next_solver/inspect.rs @@ -74,7 +74,7 @@ impl<'a, 'db> std::fmt::Debug for InspectCandidate<'a, 'db> { /// treat `NormalizesTo` goals as if they apply the expected /// type at the end of each candidate. #[derive(Debug, Copy, Clone)] -struct NormalizesToTermHack<'db> { +pub(crate) struct NormalizesToTermHack<'db> { term: Term<'db>, unconstrained_term: Term<'db>, } @@ -311,10 +311,7 @@ impl<'a, 'db> InspectCandidate<'a, 'db> { /// Visit all nested goals of this candidate, rolling back /// all inference constraints. #[expect(dead_code, reason = "used in rustc")] - pub(crate) fn visit_nested_in_probe<V: ProofTreeVisitor<'db>>( - &self, - visitor: &mut V, - ) -> V::Result { + fn visit_nested_in_probe<V: ProofTreeVisitor<'db>>(&self, visitor: &mut V) -> V::Result { self.goal.infcx.probe(|_| self.visit_nested_no_probe(visitor)) } } @@ -430,7 +427,7 @@ impl<'a, 'db> InspectGoal<'a, 'db> { candidates.pop().filter(|_| candidates.is_empty()) } - fn new( + pub(crate) fn new( infcx: &'a InferCtxt<'db>, depth: usize, root: inspect::GoalEvaluation<DbInterner<'db>>, diff --git a/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs index ad8de293d5..fb598fe5ac 100644 --- a/crates/hir-ty/src/traits.rs +++ b/crates/hir-ty/src/traits.rs @@ -23,7 +23,10 @@ use crate::{ next_solver::{ Canonical, DbInterner, GenericArgs, Goal, ParamEnv, Predicate, SolverContext, Span, StoredClauses, Ty, TyKind, - infer::{DbInternerInferExt, InferCtxt, traits::ObligationCause}, + infer::{ + DbInternerInferExt, InferCtxt, + traits::{Obligation, ObligationCause}, + }, obligation_ctxt::ObligationCtxt, }, }; @@ -107,6 +110,16 @@ pub fn next_trait_solve_canonical_in_ctxt<'db>( let res = context.evaluate_root_goal(goal, Span::dummy(), None); + let obligation = Obligation { + cause: ObligationCause::dummy(), + param_env: goal.param_env, + recursion_depth: 0, + predicate: goal.predicate, + }; + infer_ctxt.inspect_evaluated_obligation(&obligation, &res, || { + Some(context.evaluate_root_goal_for_proof_tree(goal, Span::dummy()).1) + }); + let res = res.map(|r| (r.has_changed, r.certainty)); tracing::debug!("solve_nextsolver({:?}) => {:?}", goal, res); @@ -130,6 +143,16 @@ pub fn next_trait_solve_in_ctxt<'db, 'a>( let res = context.evaluate_root_goal(goal, Span::dummy(), None); + let obligation = Obligation { + cause: ObligationCause::dummy(), + param_env: goal.param_env, + recursion_depth: 0, + predicate: goal.predicate, + }; + infer_ctxt.inspect_evaluated_obligation(&obligation, &res, || { + Some(context.evaluate_root_goal_for_proof_tree(goal, Span::dummy()).1) + }); + let res = res.map(|r| (r.has_changed, r.certainty)); tracing::debug!("solve_nextsolver({:?}) => {:?}", goal, res); diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml index 24b2bd9150..d20ee1546f 100644 --- a/crates/hir/Cargo.toml +++ b/crates/hir/Cargo.toml @@ -17,6 +17,7 @@ rustc-hash.workspace = true either.workspace = true arrayvec.workspace = true itertools.workspace = true +serde_json.workspace = true smallvec.workspace = true tracing = { workspace = true, features = ["attributes"] } triomphe.workspace = true diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index fd08528e86..fcb97ab34e 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -32,11 +32,16 @@ use hir_expand::{ use hir_ty::{ InferenceResult, diagnostics::{unsafe_operations, unsafe_operations_for_body}, - next_solver::DbInterner, + infer_query_with_inspect, + next_solver::{ + DbInterner, Span, + format_proof_tree::{ProofTreeData, dump_proof_tree_structured}, + }, }; use intern::{Interned, Symbol, sym}; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_type_ir::inherent::Span as _; use smallvec::{SmallVec, smallvec}; use span::{FileId, SyntaxContext}; use stdx::{TupleExt, always}; @@ -2310,6 +2315,38 @@ impl<'db> SemanticsImpl<'db> { Some(locals) } + + pub fn get_failed_obligations(&self, token: SyntaxToken) -> Option<String> { + let node = token.parent()?; + let node = self.find_file(&node); + + let container = self.with_ctx(|ctx| ctx.find_container(node))?; + + match container { + ChildContainer::DefWithBodyId(def) => { + thread_local! { + static RESULT: RefCell<Vec<ProofTreeData>> = const { RefCell::new(Vec::new()) }; + } + infer_query_with_inspect( + self.db, + def, + Some(|infer_ctxt, _obligation, result, proof_tree| { + if result.is_err() + && let Some(tree) = proof_tree + { + let data = dump_proof_tree_structured(tree, Span::dummy(), infer_ctxt); + RESULT.with(|ctx| ctx.borrow_mut().push(data)); + } + }), + ); + let data: Vec<ProofTreeData> = + RESULT.with(|data| data.borrow_mut().drain(..).collect()); + let data = serde_json::to_string_pretty(&data).unwrap_or_else(|_| "[]".to_owned()); + Some(data) + } + _ => None, + } + } } // FIXME This can't be the best way to do this diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 0066ceed21..5e4d930393 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -73,7 +73,7 @@ use ide_db::{ }; use ide_db::{MiniCore, ra_fixture::RaFixtureAnalysis}; use macros::UpmapFromRaFixture; -use syntax::{SourceFile, ast}; +use syntax::{AstNode, SourceFile, ast}; use triomphe::Arc; use view_memory_layout::{RecursiveMemoryLayout, view_memory_layout}; @@ -905,6 +905,18 @@ impl Analysis { self.with_db(|db| view_memory_layout(db, position)) } + pub fn get_failed_obligations(&self, offset: TextSize, file_id: FileId) -> Cancellable<String> { + self.with_db(|db| { + let sema = Semantics::new(db); + let source_file = sema.parse_guess_edition(file_id); + + let Some(token) = source_file.syntax().token_at_offset(offset).next() else { + return String::new(); + }; + sema.get_failed_obligations(token).unwrap_or_default() + }) + } + pub fn editioned_file_id_to_vfs(&self, file_id: hir::EditionedFileId) -> FileId { file_id.file_id(&self.db) } diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index d15b519d69..4d97505768 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -42,8 +42,8 @@ use crate::{ lsp::{ LspError, completion_item_hash, ext::{ - InternalTestingFetchConfigOption, InternalTestingFetchConfigParams, - InternalTestingFetchConfigResponse, + GetFailedObligationsParams, InternalTestingFetchConfigOption, + InternalTestingFetchConfigParams, InternalTestingFetchConfigResponse, }, from_proto, to_proto, utils::{all_edits_are_disjoint, invalid_params_error}, @@ -2575,6 +2575,18 @@ pub(crate) fn internal_testing_fetch_config( })) } +pub(crate) fn get_failed_obligations( + snap: GlobalStateSnapshot, + params: GetFailedObligationsParams, +) -> anyhow::Result<String> { + let _p = tracing::info_span!("get_failed_obligations").entered(); + let file_id = try_default!(from_proto::file_id(&snap, ¶ms.text_document.uri)?); + let line_index = snap.file_line_index(file_id)?; + let offset = from_proto::offset(&line_index, params.position)?; + + Ok(snap.analysis.get_failed_obligations(offset, file_id)?) +} + /// Searches for the directory of a Rust crate given this crate's root file path. /// /// # Arguments diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs index b132323bec..e6493eefef 100644 --- a/crates/rust-analyzer/src/lsp/ext.rs +++ b/crates/rust-analyzer/src/lsp/ext.rs @@ -864,3 +864,18 @@ pub struct CompletionImport { pub struct ClientCommandOptions { pub commands: Vec<String>, } + +pub enum GetFailedObligations {} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetFailedObligationsParams { + pub text_document: TextDocumentIdentifier, + pub position: Position, +} + +impl Request for GetFailedObligations { + type Params = GetFailedObligationsParams; + type Result = String; + const METHOD: &'static str = "rust-analyzer/getFailedObligations"; +} diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 1a1c0182f8..6e08b7bb88 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -1328,6 +1328,7 @@ impl GlobalState { .on::<NO_RETRY, lsp_ext::MoveItem>(handlers::handle_move_item) // .on::<NO_RETRY, lsp_ext::InternalTestingFetchConfig>(handlers::internal_testing_fetch_config) + .on::<RETRY, lsp_ext::GetFailedObligations>(handlers::get_failed_obligations) .finish(); } diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs index 48433342d5..eb1b8c5dd0 100644 --- a/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/crates/rust-analyzer/tests/slow-tests/main.rs @@ -1488,3 +1488,42 @@ version = "0.0.0" server.request::<WorkspaceSymbolRequest>(Default::default(), json!([])); } + +#[test] +fn test_get_failed_obligations() { + use expect_test::expect; + if skip_slow_tests() { + return; + } + + let server = Project::with_fixture( + r#" +//- /Cargo.toml +[package] +name = "foo" +version = "0.0.0" + +//- /src/lib.rs +trait Trait {} +fn requires_trait<T: Trait>(x: T) {} + +fn test() { + requires_trait(0usize); +} +"#, + ) + .server() + .wait_until_workspace_is_loaded(); + + let res = server.send_request::<rust_analyzer::lsp::ext::GetFailedObligations>( + rust_analyzer::lsp::ext::GetFailedObligationsParams { + text_document: server.doc_id("src/lib.rs"), + position: Position::new(4, 19), + }, + ); + + let res: serde_json::Value = serde_json::from_str(res.as_str().unwrap()).unwrap(); + let arr = res.as_array().unwrap(); + assert_eq!(arr.len(), 2); + expect![[r#"{"goal":"Goal { param_env: ParamEnv { clauses: [] }, predicate: Binder { value: TraitPredicate(usize: Trait, polarity:Positive), bound_vars: [] } }","result":"Err(NoSolution)","depth":0,"candidates":[]}"#]].assert_eq(&arr[0].to_string()); +} diff --git a/docs/book/src/contributing/lsp-extensions.md b/docs/book/src/contributing/lsp-extensions.md index 5922f0b551..5d21c37806 100644 --- a/docs/book/src/contributing/lsp-extensions.md +++ b/docs/book/src/contributing/lsp-extensions.md @@ -1,5 +1,5 @@ <!--- -lsp/ext.rs hash: 78e87a78de8f288e +lsp/ext.rs hash: 235f56089da3dbb5 If you need to change the above hash to make the test pass, please check if you need to adjust this doc as well and ping this issue: @@ -731,6 +731,17 @@ For debugging or when working on rust-analyzer itself. Returns a textual representation of the MIR of the function containing the cursor. For debugging or when working on rust-analyzer itself. +## Get Failed Obligations + +**Method:** `rust-analyzer/getFailedObligations` + +**Request:** `TextDocumentPositionParams` + +**Response:** `string` + +Returns information about failed trait obligations at the given position. +For debugging or when working on rust-analyzer itself. + ## Interpret Function **Method:** `rust-analyzer/interpretFunction` diff --git a/editors/code/package.json b/editors/code/package.json index 97db1322dd..d0410c70da 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -339,6 +339,11 @@ "command": "rust-analyzer.openWalkthrough", "title": "Open Walkthrough", "category": "rust-analyzer" + }, + { + "command": "rust-analyzer.getFailedObligations", + "title": "Get Failed Obligations", + "category": "rust-analyzer (debug command)" } ], "keybindings": [ @@ -3700,6 +3705,10 @@ { "command": "rust-analyzer.syntaxTreeShowWhitespace", "when": "false" + }, + { + "command": "rust-analyzer.getFailedObligations", + "when": "inRustProject" } ], "editor/context": [ diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 16fc586d5d..c1b6f31030 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -599,6 +599,18 @@ export function viewMir(ctx: CtxInit): Cmd { return viewHirOrMir(ctx, "mir"); } +export function getFailedObligations(ctx: CtxInit): Cmd { + const uri = `rust-analyzer-failed-obligations://getFailedObligations/failedObligations.rs`; + const scheme = `rust-analyzer-failed-obligations`; + return viewFileUsingTextDocumentContentProvider( + ctx, + ra.getFailedObligations, + uri, + scheme, + true, + ); +} + // Opens the virtual file that will show the MIR of the function containing the cursor position // // The contents of the file come from the `TextDocumentContentProvider` diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 643f61b25d..9712bd4b7b 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -67,6 +67,9 @@ export const interpretFunction = new lc.RequestType<lc.TextDocumentPositionParam export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>( "rust-analyzer/viewItemTree", ); +export const getFailedObligations = new lc.RequestType<lc.TextDocumentPositionParams, string, void>( + "rust-analyzer/getFailedObligations", +); export type DiscoverTestParams = { testId?: string | undefined }; export type RunTestParams = { diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 190f5866d0..7d91286552 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -215,6 +215,7 @@ function createCommands(): Record<string, CommandFactory> { syntaxTreeShowWhitespace: { enabled: commands.syntaxTreeShowWhitespace, }, + getFailedObligations: { enabled: commands.getFailedObligations }, }; } |