Skip to main content

ide/
predicate_eval.rs

1//! Evaluates type predicates at a given position in the source code.
2
3use 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}