1use hir::{PredicateEvaluationResult, Semantics};
4use ide_db::{FilePosition, RootDatabase};
5use syntax::{AstNode, SourceFile, ast};
6
7pub(crate) fn evaluate_predicate(
8 db: &RootDatabase,
9 text: String,
10 position: FilePosition,
11) -> PredicateEvaluationResult {
12 let sema = Semantics::new(db);
13 let source_file = sema.parse_guess_edition(position.file_id);
14 let edition = sema.attach_first_edition(position.file_id).edition(db);
15
16 let Some(where_clause) = parse_where_clause(&text, edition) else {
17 return PredicateEvaluationResult::invalid("expected a single where-clause predicate");
18 };
19
20 let node = source_file
21 .syntax()
22 .token_at_offset(position.offset)
23 .next()
24 .and_then(|token| token.parent())
25 .unwrap_or_else(|| source_file.syntax().clone());
26 sema.evaluate_where_clause_at(&node, position.offset, where_clause)
27}
28
29fn parse_where_clause(text: &str, edition: span::Edition) -> Option<ast::WhereClause> {
30 let text = text.trim().trim_end_matches(',').trim_end();
31 let wrapped = format!("fn __ra_evaluate_predicate() where {text}, {{}}");
32 let parse = SourceFile::parse(&wrapped, edition);
33 if !parse.errors().is_empty() {
34 return None;
35 }
36
37 let where_clause = parse.tree().syntax().descendants().find_map(ast::WhereClause::cast)?;
38 if where_clause.predicates().count() == 1 { Some(where_clause) } else { None }
39}
40
41#[cfg(test)]
42mod tests {
43 use hir::PredicateEvaluationStatus;
44
45 use crate::fixture;
46
47 fn check(ra_fixture: &str, predicate: &str, status: PredicateEvaluationStatus) {
48 let (analysis, position) = fixture::position(ra_fixture);
49 let result = analysis.evaluate_predicate(predicate.to_owned(), position).unwrap();
50 assert_eq!(result.status, status, "{}", result.message);
51 }
52
53 #[test]
54 fn evaluates_concrete_trait_predicate() {
55 check(
56 r#"
57trait Trait {}
58struct S;
59impl Trait for S {}
60fn f() { $0 }
61"#,
62 "S: Trait",
63 PredicateEvaluationStatus::Holds,
64 );
65 }
66
67 #[test]
68 fn evaluates_generic_bound_from_environment() {
69 check(
70 r#"
71trait Trait {}
72fn f<T: Trait>() { $0 }
73"#,
74 "T: Trait",
75 PredicateEvaluationStatus::Holds,
76 );
77 }
78
79 #[test]
80 fn reports_missing_generic_bound_as_not_proven() {
81 check(
82 r#"
83trait Trait {}
84fn f<T>() { $0 }
85"#,
86 "T: Trait",
87 PredicateEvaluationStatus::NotProven,
88 );
89 }
90
91 #[test]
92 fn evaluates_associated_type_binding() {
93 check(
94 r#"
95trait Iterator { type Item; }
96fn f<I: Iterator<Item = u32>>() { $0 }
97"#,
98 "I: Iterator<Item = u32>",
99 PredicateEvaluationStatus::Holds,
100 );
101 }
102
103 #[test]
104 fn reports_unresolved_type_as_invalid() {
105 check(
106 r#"
107trait Trait {}
108fn f() { $0 }
109"#,
110 "Type: Trait",
111 PredicateEvaluationStatus::Invalid,
112 );
113 }
114
115 #[test]
116 fn reports_unresolved_trait_as_invalid() {
117 check(
118 r#"
119struct Type;
120fn f() { $0 }
121"#,
122 "Type: Trait",
123 PredicateEvaluationStatus::Invalid,
124 );
125 }
126
127 #[test]
128 fn evaluates_lifetime_predicate() {
129 check(
130 r#"
131fn f<'a, 'b>()
132where
133 'a: 'b,
134{
135 $0
136}
137"#,
138 "'a: 'b",
139 PredicateEvaluationStatus::Holds,
140 );
141 }
142
143 #[test]
144 fn evaluates_type_outlives_predicate() {
145 check(
146 r#"
147fn f<T: 'static>() { $0 }
148"#,
149 "T: 'static",
150 PredicateEvaluationStatus::Holds,
151 );
152 }
153
154 #[test]
155 fn rejects_invalid_predicate() {
156 check(
157 r#"
158trait Trait {}
159fn f() { $0 }
160"#,
161 "u32 Trait",
162 PredicateEvaluationStatus::Invalid,
163 );
164 }
165}