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.rs | 163 |
1 files changed, 163 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..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, + ); + } +} |