Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | crates/hir-def/src/expr_store/lower.rs | 2 | ||||
| -rw-r--r-- | crates/hir-ty/src/traits.rs | 75 | ||||
| -rw-r--r-- | crates/hir/src/lib.rs | 32 | ||||
| -rw-r--r-- | crates/hir/src/semantics.rs | 14 | ||||
| -rw-r--r-- | crates/hir/src/source_analyzer.rs | 55 | ||||
| -rw-r--r-- | crates/ide/src/lib.rs | 11 | ||||
| -rw-r--r-- | crates/ide/src/predicate_eval.rs | 163 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/handlers/request.rs | 22 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/lsp/ext.rs | 33 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 1 | ||||
| -rw-r--r-- | crates/rust-analyzer/tests/slow-tests/main.rs | 39 | ||||
| -rw-r--r-- | editors/code/package.json | 9 | ||||
| -rw-r--r-- | editors/code/src/commands.ts | 96 | ||||
| -rw-r--r-- | editors/code/src/lsp_ext.ts | 15 | ||||
| -rw-r--r-- | editors/code/src/main.ts | 1 |
15 files changed, 558 insertions, 10 deletions
diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs index 5685c21608..3094e08a53 100644 --- a/crates/hir-def/src/expr_store/lower.rs +++ b/crates/hir-def/src/expr_store/lower.rs @@ -204,7 +204,7 @@ pub(crate) fn lower_type_ref( (store, source_map, type_ref) } -pub(crate) fn lower_generic_params( +pub fn lower_generic_params( db: &dyn DefDatabase, module: ModuleId, def: GenericDefId, diff --git a/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs index f6b5adfb6f..a8e42b9a91 100644 --- a/crates/hir-ty/src/traits.rs +++ b/crates/hir-ty/src/traits.rs @@ -1,12 +1,15 @@ //! Trait solving using next trait solver. -use std::hash::Hash; +use std::{cell::OnceCell, hash::Hash}; use base_db::Crate; use hir_def::{ - AdtId, AssocItemId, HasModule, ImplId, Lookup, TraitId, + AdtId, AssocItemId, ExpressionStoreOwnerId, GenericDefId, HasModule, ImplId, Lookup, TraitId, + expr_store::ExpressionStore, + hir::generics::WherePredicate, lang_item::LangItems, nameres::DefMap, + resolver::Resolver, signatures::{ ConstFlags, ConstSignature, EnumFlags, EnumSignature, FnFlags, FunctionSignature, StructFlags, StructSignature, TraitFlags, TraitSignature, TypeAliasFlags, @@ -16,13 +19,14 @@ use hir_def::{ use hir_expand::name::Name; use intern::sym; use rustc_type_ir::{ - TypingMode, + TypeVisitableExt, TypingMode, inherent::{BoundExistentialPredicates, IntoKind}, }; use crate::{ - Span, + LifetimeElisionKind, Span, TyLoweringContext, db::HirDatabase, + generics::Generics, next_solver::{ DbInterner, GenericArgs, ParamEnv, StoredClauses, Ty, TyKind, infer::{ @@ -153,6 +157,69 @@ pub fn implements_trait_unique_with_infcx<'db>( infcx.predicate_must_hold_modulo_regions(&obligation) } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WherePredicateEvaluation { + Holds, + NotProven, + HasErrors, + NoObligations, +} + +/// This should not be used in `hir-ty`, only in `hir`. +/// This is exposed to allow the IDE to evaluate arbitrary predicates. +pub fn where_predicate_must_hold<'db>( + db: &'db dyn HirDatabase, + resolver: &Resolver<'db>, + store: &ExpressionStore, + def: ExpressionStoreOwnerId, + generic_def: GenericDefId, + env: ParamEnvAndCrate<'db>, + predicate: &WherePredicate, +) -> WherePredicateEvaluation { + let interner = DbInterner::new_with(db, env.krate); + let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis); + let generics = OnceCell::<Generics<'db>>::new(); + let mut ctx = TyLoweringContext::new( + db, + resolver, + store, + def, + generic_def, + &generics, + LifetimeElisionKind::Infer, + ); + let clauses = + ctx.lower_where_predicate(predicate, false).map(|(clause, _)| clause).collect::<Vec<_>>(); + + if !ctx.diagnostics.is_empty() + || clauses.iter().any(|clause| clause.as_predicate().references_error()) + { + return WherePredicateEvaluation::HasErrors; + } + + if clauses.is_empty() { + return if ctx.unsized_types.is_empty() { + WherePredicateEvaluation::HasErrors + } else { + WherePredicateEvaluation::NoObligations + }; + } + + for clause in clauses { + let obligation = Obligation::new( + interner, + ObligationCause::dummy(), + env.param_env, + clause.as_predicate(), + ); + if !infcx.predicate_must_hold_modulo_regions(&obligation) { + return WherePredicateEvaluation::NotProven; + } + } + + WherePredicateEvaluation::Holds +} + pub fn is_inherent_impl_coherent(db: &dyn HirDatabase, def_map: &DefMap, impl_id: ImplId) -> bool { let self_ty = db.impl_self_ty(impl_id).instantiate_identity().skip_norm_wip(); let self_ty = self_ty.kind(); diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 519edaea02..f676a700cc 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -117,6 +117,38 @@ use triomphe::Arc; use crate::db::{DefDatabase, HirDatabase}; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PredicateEvaluationStatus { + Holds, + NotProven, + Invalid, + Unsupported, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PredicateEvaluationResult { + pub status: PredicateEvaluationStatus, + pub message: String, +} + +impl PredicateEvaluationResult { + pub fn holds(message: impl Into<String>) -> Self { + Self { status: PredicateEvaluationStatus::Holds, message: message.into() } + } + + pub fn not_proven(message: impl Into<String>) -> Self { + Self { status: PredicateEvaluationStatus::NotProven, message: message.into() } + } + + pub fn invalid(message: impl Into<String>) -> Self { + Self { status: PredicateEvaluationStatus::Invalid, message: message.into() } + } + + pub fn unsupported(message: impl Into<String>) -> Self { + Self { status: PredicateEvaluationStatus::Unsupported, message: message.into() } + } +} + pub use crate::{ attrs::{AttrsWithOwner, HasAttrs, resolve_doc_path_on}, diagnostics::*, diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index f633bb063f..0277eb6219 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -2564,6 +2564,20 @@ impl<'db> SemanticsImpl<'db> { Some(locals) } + pub fn evaluate_where_clause_at( + &self, + node: &SyntaxNode, + offset: TextSize, + where_clause: ast::WhereClause, + ) -> crate::PredicateEvaluationResult { + let Some(analyzer) = self.analyze_with_offset_no_infer(node, offset) else { + return crate::PredicateEvaluationResult::unsupported( + "predicate evaluation is only supported in files that belong to a crate", + ); + }; + analyzer.evaluate_where_clause(self.db, where_clause) + } + pub fn get_failed_obligations(&self, token: SyntaxToken) -> Option<String> { let node = token.parent()?; let node = self.find_file(&node); diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 6d265c460e..687f130af4 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -13,10 +13,10 @@ use std::{ use either::Either; use hir_def::{ AdtId, AssocItemId, CallableDefId, ConstId, DefWithBodyId, ExpressionStoreOwnerId, FieldId, - FunctionId, GenericDefId, LocalFieldId, ModuleDefId, StructId, VariantId, + FunctionId, GenericDefId, HasModule, LocalFieldId, ModuleDefId, StructId, VariantId, expr_store::{ Body, BodySourceMap, ExpressionStore, ExpressionStoreSourceMap, HygieneId, - lower::ExprCollector, + lower::{ExprCollector, lower_generic_params}, path::Path, scope::{ExprScopes, ScopeId}, }, @@ -44,7 +44,7 @@ use hir_ty::{ AliasTy, DbInterner, DefaultAny, EarlyBinder, ErrorGuaranteed, GenericArgs, ParamEnv, Region, Ty, TyKind, TypingMode, infer::DbInternerInferExt, }, - traits::structurally_normalize_ty, + traits::{WherePredicateEvaluation, structurally_normalize_ty, where_predicate_must_hold}, }; use intern::sym; use itertools::Itertools; @@ -63,7 +63,8 @@ use syntax::{ use crate::{ Adt, AnyFunctionId, AssocItem, BindingMode, BuiltinAttr, BuiltinType, Callable, Const, DeriveHelper, EnumVariant, Field, Function, GenericSubstitution, Local, Macro, ModuleDef, - SemanticsImpl, Static, Struct, ToolModule, Trait, TupleField, Type, TypeAlias, TypeOwnerId, + PredicateEvaluationResult, SemanticsImpl, Static, Struct, ToolModule, Trait, TupleField, Type, + TypeAlias, TypeOwnerId, db::HirDatabase, semantics::{PathResolution, PathResolutionPerNs}, }; @@ -364,6 +365,52 @@ impl<'db> SourceAnalyzer<'db> { )) } + pub(crate) fn evaluate_where_clause( + &self, + db: &'db dyn HirDatabase, + where_clause: ast::WhereClause, + ) -> PredicateEvaluationResult { + let Some(owner) = self.owner() else { + // FIXME + return PredicateEvaluationResult::unsupported( + "predicate evaluation is only supported inside an item", + ); + }; + let generic_def = owner.generic_def(db); + let module = generic_def.module(db); + let (store, params, _) = + lower_generic_params(db, module, generic_def, self.file_id, None, Some(where_clause)); + let predicates = params.where_predicates(); + if predicates.is_empty() { + return PredicateEvaluationResult::holds("predicate does not impose any obligations"); + } + + let env = self.trait_environment(db); + for predicate in predicates { + match where_predicate_must_hold( + db, + &self.resolver, + &store, + owner, + generic_def, + env, + predicate, + ) { + WherePredicateEvaluation::Holds | WherePredicateEvaluation::NoObligations => {} + WherePredicateEvaluation::HasErrors => { + return PredicateEvaluationResult::invalid( + "predicate contains unresolved names or invalid type syntax", + ); + } + WherePredicateEvaluation::NotProven => { + return PredicateEvaluationResult::not_proven("predicate is not known to hold"); + } + } + } + + PredicateEvaluationResult::holds("predicate holds") + } + pub(crate) fn expr_id(&self, expr: ast::Expr) -> Option<ExprOrPatId> { let src = InFile { file_id: self.file_id, value: expr }; self.store_sm()?.node_expr(src.as_ref()) diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index e131e7bdd1..88cb570c6b 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -41,6 +41,7 @@ mod matching_brace; mod moniker; mod move_item; mod parent_module; +mod predicate_eval; mod references; mod rename; mod runnables; @@ -122,7 +123,7 @@ pub use crate::{ }, test_explorer::{TestItem, TestItemKind}, }; -pub use hir::Semantics; +pub use hir::{PredicateEvaluationResult, PredicateEvaluationStatus, Semantics}; pub use ide_assists::{ Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve, }; @@ -391,6 +392,14 @@ impl Analysis { self.with_db(|db| view_hir::view_hir(db, position)) } + pub fn evaluate_predicate( + &self, + text: String, + position: FilePosition, + ) -> Cancellable<PredicateEvaluationResult> { + self.with_db(|db| predicate_eval::evaluate_predicate(db, text, position)) + } + pub fn view_mir(&self, position: FilePosition) -> Cancellable<String> { self.with_db(|db| view_mir::view_mir(db, position)) } diff --git a/crates/ide/src/predicate_eval.rs b/crates/ide/src/predicate_eval.rs new file mode 100644 index 0000000000..8ae340bd95 --- /dev/null +++ b/crates/ide/src/predicate_eval.rs @@ -0,0 +1,163 @@ +use hir::{PredicateEvaluationResult, Semantics}; +use ide_db::{FilePosition, RootDatabase}; +use syntax::{AstNode, SourceFile, ast}; + +pub(crate) fn evaluate_predicate( + db: &RootDatabase, + text: String, + position: FilePosition, +) -> PredicateEvaluationResult { + let sema = Semantics::new(db); + let source_file = sema.parse_guess_edition(position.file_id); + let edition = sema.attach_first_edition(position.file_id).edition(db); + + let Some(where_clause) = parse_where_clause(&text, edition) else { + return PredicateEvaluationResult::invalid("expected a single where-clause predicate"); + }; + + let node = source_file + .syntax() + .token_at_offset(position.offset) + .next() + .and_then(|token| token.parent()) + .unwrap_or_else(|| source_file.syntax().clone()); + sema.evaluate_where_clause_at(&node, position.offset, where_clause) +} + +fn parse_where_clause(text: &str, edition: span::Edition) -> Option<ast::WhereClause> { + let text = text.trim().trim_end_matches(',').trim_end(); + let wrapped = format!("fn __ra_evaluate_predicate() where {text}, {{}}"); + let parse = SourceFile::parse(&wrapped, edition); + if !parse.errors().is_empty() { + return None; + } + + let where_clause = parse.tree().syntax().descendants().find_map(ast::WhereClause::cast)?; + if where_clause.predicates().count() == 1 { Some(where_clause) } else { None } +} + +#[cfg(test)] +mod tests { + use hir::PredicateEvaluationStatus; + + use crate::fixture; + + fn check(ra_fixture: &str, predicate: &str, status: PredicateEvaluationStatus) { + let (analysis, position) = fixture::position(ra_fixture); + let result = analysis.evaluate_predicate(predicate.to_owned(), position).unwrap(); + assert_eq!(result.status, status, "{}", result.message); + } + + #[test] + fn evaluates_concrete_trait_predicate() { + check( + r#" +trait Trait {} +struct S; +impl Trait for S {} +fn f() { $0 } +"#, + "S: Trait", + PredicateEvaluationStatus::Holds, + ); + } + + #[test] + fn evaluates_generic_bound_from_environment() { + check( + r#" +trait Trait {} +fn f<T: Trait>() { $0 } +"#, + "T: Trait", + PredicateEvaluationStatus::Holds, + ); + } + + #[test] + fn reports_missing_generic_bound_as_not_proven() { + check( + r#" +trait Trait {} +fn f<T>() { $0 } +"#, + "T: Trait", + PredicateEvaluationStatus::NotProven, + ); + } + + #[test] + fn evaluates_associated_type_binding() { + check( + r#" +trait Iterator { type Item; } +fn f<I: Iterator<Item = u32>>() { $0 } +"#, + "I: Iterator<Item = u32>", + PredicateEvaluationStatus::Holds, + ); + } + + #[test] + fn reports_unresolved_type_as_invalid() { + check( + r#" +trait Trait {} +fn f() { $0 } +"#, + "Type: Trait", + PredicateEvaluationStatus::Invalid, + ); + } + + #[test] + fn reports_unresolved_trait_as_invalid() { + check( + r#" +struct Type; +fn f() { $0 } +"#, + "Type: Trait", + PredicateEvaluationStatus::Invalid, + ); + } + + #[test] + fn evaluates_lifetime_predicate() { + check( + r#" +fn f<'a, 'b>() +where + 'a: 'b, +{ + $0 +} +"#, + "'a: 'b", + PredicateEvaluationStatus::Holds, + ); + } + + #[test] + fn evaluates_type_outlives_predicate() { + check( + r#" +fn f<T: 'static>() { $0 } +"#, + "T: 'static", + PredicateEvaluationStatus::Holds, + ); + } + + #[test] + fn rejects_invalid_predicate() { + check( + r#" +trait Trait {} +fn f() { $0 } +"#, + "u32 Trait", + PredicateEvaluationStatus::Invalid, + ); + } +} diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 5bc0f5f0a7..cf85db39f3 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -2598,6 +2598,28 @@ pub(crate) fn internal_testing_fetch_config( })) } +pub(crate) fn handle_evaluate_predicate( + snap: GlobalStateSnapshot, + params: lsp_ext::EvaluatePredicateParams, +) -> anyhow::Result<lsp_ext::EvaluatePredicateResult> { + let _p = tracing::info_span!("handle_evaluate_predicate").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)?; + + let result = snap.analysis.evaluate_predicate(params.text, FilePosition { file_id, offset })?; + let status = match result.status { + ide::PredicateEvaluationStatus::Holds => lsp_ext::PredicateEvaluationStatus::Holds, + ide::PredicateEvaluationStatus::NotProven => lsp_ext::PredicateEvaluationStatus::NotProven, + ide::PredicateEvaluationStatus::Invalid => lsp_ext::PredicateEvaluationStatus::Invalid, + ide::PredicateEvaluationStatus::Unsupported => { + lsp_ext::PredicateEvaluationStatus::Unsupported + } + }; + + Ok(lsp_ext::EvaluatePredicateResult { status, message: result.message }) +} + pub(crate) fn get_failed_obligations( snap: GlobalStateSnapshot, params: GetFailedObligationsParams, diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs index 754d6e65fe..444715891f 100644 --- a/crates/rust-analyzer/src/lsp/ext.rs +++ b/crates/rust-analyzer/src/lsp/ext.rs @@ -859,6 +859,39 @@ pub struct ClientCommandOptions { pub commands: Vec<String>, } +pub enum EvaluatePredicate {} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct EvaluatePredicateParams { + pub text: String, + pub text_document: TextDocumentIdentifier, + pub position: Position, +} + +#[derive(Deserialize, Serialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct EvaluatePredicateResult { + pub status: PredicateEvaluationStatus, + pub message: String, +} + +#[derive(Deserialize, Serialize, Debug, Default, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum PredicateEvaluationStatus { + Holds, + #[default] + NotProven, + Invalid, + Unsupported, +} + +impl Request for EvaluatePredicate { + type Params = EvaluatePredicateParams; + type Result = EvaluatePredicateResult; + const METHOD: &'static str = "rust-analyzer/evaluatePredicate"; +} + pub enum GetFailedObligations {} #[derive(Deserialize, Serialize, Debug)] diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 92d5323e96..edf3da5e6c 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -1379,6 +1379,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::EvaluatePredicate>(handlers::handle_evaluate_predicate) .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 a863263078..b91bde8428 100644 --- a/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/crates/rust-analyzer/tests/slow-tests/main.rs @@ -1543,6 +1543,45 @@ version = "0.0.0" } #[test] +fn test_evaluate_predicate() { + 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 {} +struct S; +impl Trait for S {} + +fn test<T: Trait>() { + let _ = 0;$0 +} +"#, + ) + .server() + .wait_until_workspace_is_loaded(); + + let res = server.send_request::<rust_analyzer::lsp::ext::EvaluatePredicate>( + rust_analyzer::lsp::ext::EvaluatePredicateParams { + text: "T: Trait".to_owned(), + text_document: server.doc_id("src/lib.rs"), + position: Position::new(5, 14), + }, + ); + + let res: rust_analyzer::lsp::ext::EvaluatePredicateResult = + serde_json::from_value(res).unwrap(); + assert_eq!(res.status, rust_analyzer::lsp::ext::PredicateEvaluationStatus::Holds); +} + +#[test] fn test_get_failed_obligations() { use expect_test::expect; if skip_slow_tests() { diff --git a/editors/code/package.json b/editors/code/package.json index 8df606d4c4..53c5a295ce 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -347,6 +347,11 @@ "command": "rust-analyzer.getFailedObligations", "title": "Get Failed Obligations", "category": "rust-analyzer (debug command)" + }, + { + "command": "rust-analyzer.evaluatePredicate", + "title": "Evaluate Predicate", + "category": "rust-analyzer" } ], "keybindings": [ @@ -3864,6 +3869,10 @@ { "command": "rust-analyzer.getFailedObligations", "when": "inRustProject" + }, + { + "command": "rust-analyzer.evaluatePredicate", + "when": "inRustProject" } ], "editor/context": [ diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 302f51dee4..9c8b7071b3 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -494,6 +494,102 @@ export function ssr(ctx: CtxInit): Cmd { }; } +const EVALUATE_PREDICATE_LAST_INPUT_KEY = "evaluatePredicate.lastInput"; + +export function evaluatePredicate(ctx: CtxInit): Cmd { + return async () => { + const editor = ctx.activeRustEditor; + if (!editor) { + await vscode.window.showWarningMessage( + "rust-analyzer: evaluate predicate requires an active Rust editor", + ); + return; + } + + const client = ctx.client; + const textDocument = client.code2ProtocolConverter.asTextDocumentIdentifier( + editor.document, + ); + const position = client.code2ProtocolConverter.asPosition(editor.selection.active); + + const input = vscode.window.createInputBox(); + input.value = ctx.extCtx.workspaceState.get<string>( + EVALUATE_PREDICATE_LAST_INPUT_KEY, + "Vec<u32>: Clone", + ); + input.prompt = "Enter a Rust where-clause predicate"; + input.placeholder = "Vec<u32>: Clone"; + + let requestId = 0; + let hidden = false; + const updatePredicateResult = async (text: string) => { + const currentRequestId = ++requestId; + await ctx.extCtx.workspaceState.update(EVALUATE_PREDICATE_LAST_INPUT_KEY, text); + if (text.trim() === "") { + input.validationMessage = undefined; + return; + } + + try { + const result = await client.sendRequest(ra.evaluatePredicate, { + text, + textDocument, + position, + }); + if (!hidden && currentRequestId === requestId) { + input.validationMessage = predicateEvaluationValidationMessage(result); + } + } catch (error) { + if (!hidden && currentRequestId === requestId) { + input.validationMessage = { + message: String(error), + severity: vscode.InputBoxValidationSeverity.Error, + }; + } + } + }; + + await new Promise<void>((resolve) => { + input.onDidChangeValue((text) => void updatePredicateResult(text)); + input.onDidAccept(() => input.hide()); + input.onDidHide(() => { + hidden = true; + input.dispose(); + resolve(); + }); + input.show(); + void updatePredicateResult(input.value); + }); + }; +} + +function predicateEvaluationValidationMessage( + result: ra.EvaluatePredicateResult, +): vscode.InputBoxValidationMessage { + switch (result.status) { + case "holds": + return { + message: result.message, + severity: vscode.InputBoxValidationSeverity.Info, + }; + case "notProven": + return { + message: result.message, + severity: vscode.InputBoxValidationSeverity.Warning, + }; + case "invalid": + return { + message: result.message, + severity: vscode.InputBoxValidationSeverity.Error, + }; + case "unsupported": + return { + message: result.message, + severity: vscode.InputBoxValidationSeverity.Warning, + }; + } +} + export function serverVersion(ctx: CtxInit): Cmd { return async () => { if (!ctx.serverPath) { diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index cf190ea3ce..80cb04de99 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -67,6 +67,21 @@ export const interpretFunction = new lc.RequestType<lc.TextDocumentPositionParam export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>( "rust-analyzer/viewItemTree", ); +export type EvaluatePredicateParams = { + text: string; + textDocument: lc.TextDocumentIdentifier; + position: lc.Position; +}; +export type PredicateEvaluationStatus = "holds" | "notProven" | "invalid" | "unsupported"; +export type EvaluatePredicateResult = { + status: PredicateEvaluationStatus; + message: string; +}; +export const evaluatePredicate = new lc.RequestType< + EvaluatePredicateParams, + EvaluatePredicateResult, + void +>("rust-analyzer/evaluatePredicate"); export const getFailedObligations = new lc.RequestType<lc.TextDocumentPositionParams, string, void>( "rust-analyzer/getFailedObligations", ); diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 7d91286552..6567bcd2f1 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -187,6 +187,7 @@ function createCommands(): Record<string, CommandFactory> { clearFlycheck: { enabled: commands.clearFlycheck }, runFlycheck: { enabled: commands.runFlycheck }, ssr: { enabled: commands.ssr }, + evaluatePredicate: { enabled: commands.evaluatePredicate }, serverVersion: { enabled: commands.serverVersion }, viewMemoryLayout: { enabled: commands.viewMemoryLayout }, toggleCheckOnSave: { enabled: commands.toggleCheckOnSave }, |