Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/predicate_eval.rs')
-rw-r--r--crates/ide/src/predicate_eval.rs165
1 files changed, 165 insertions, 0 deletions
diff --git a/crates/ide/src/predicate_eval.rs b/crates/ide/src/predicate_eval.rs
new file mode 100644
index 0000000000..c6ec4ca18e
--- /dev/null
+++ b/crates/ide/src/predicate_eval.rs
@@ -0,0 +1,165 @@
+//! Evaluates type predicates at a given position in the source code.
+
+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,
+ );
+ }
+}