aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ssr/src/resolving.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ssr/src/resolving.rs')
-rw-r--r--crates/ra_ssr/src/resolving.rs173
1 files changed, 173 insertions, 0 deletions
diff --git a/crates/ra_ssr/src/resolving.rs b/crates/ra_ssr/src/resolving.rs
new file mode 100644
index 000000000..75f556785
--- /dev/null
+++ b/crates/ra_ssr/src/resolving.rs
@@ -0,0 +1,173 @@
1//! This module is responsible for resolving paths within rules.
2
3use crate::errors::error;
4use crate::{parsing, SsrError};
5use parsing::Placeholder;
6use ra_syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
7use rustc_hash::{FxHashMap, FxHashSet};
8use test_utils::mark;
9
10pub(crate) struct ResolvedRule {
11 pub(crate) pattern: ResolvedPattern,
12 pub(crate) template: Option<ResolvedPattern>,
13 pub(crate) index: usize,
14}
15
16pub(crate) struct ResolvedPattern {
17 pub(crate) placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
18 pub(crate) node: SyntaxNode,
19 // Paths in `node` that we've resolved.
20 pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>,
21 pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, hir::Function>,
22}
23
24pub(crate) struct ResolvedPath {
25 pub(crate) resolution: hir::PathResolution,
26 /// The depth of the ast::Path that was resolved within the pattern.
27 pub(crate) depth: u32,
28}
29
30impl ResolvedRule {
31 pub(crate) fn new(
32 rule: parsing::ParsedRule,
33 scope: &hir::SemanticsScope,
34 hygiene: &hir::Hygiene,
35 index: usize,
36 ) -> Result<ResolvedRule, SsrError> {
37 let resolver =
38 Resolver { scope, hygiene, placeholders_by_stand_in: rule.placeholders_by_stand_in };
39 let resolved_template = if let Some(template) = rule.template {
40 Some(resolver.resolve_pattern_tree(template)?)
41 } else {
42 None
43 };
44 Ok(ResolvedRule {
45 pattern: resolver.resolve_pattern_tree(rule.pattern)?,
46 template: resolved_template,
47 index,
48 })
49 }
50
51 pub(crate) fn get_placeholder(&self, token: &SyntaxToken) -> Option<&Placeholder> {
52 if token.kind() != SyntaxKind::IDENT {
53 return None;
54 }
55 self.pattern.placeholders_by_stand_in.get(token.text())
56 }
57}
58
59struct Resolver<'a, 'db> {
60 scope: &'a hir::SemanticsScope<'db>,
61 hygiene: &'a hir::Hygiene,
62 placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
63}
64
65impl Resolver<'_, '_> {
66 fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> {
67 let mut resolved_paths = FxHashMap::default();
68 self.resolve(pattern.clone(), 0, &mut resolved_paths)?;
69 let ufcs_function_calls = resolved_paths
70 .iter()
71 .filter_map(|(path_node, resolved)| {
72 if let Some(grandparent) = path_node.parent().and_then(|parent| parent.parent()) {
73 if grandparent.kind() == SyntaxKind::CALL_EXPR {
74 if let hir::PathResolution::AssocItem(hir::AssocItem::Function(function)) =
75 &resolved.resolution
76 {
77 return Some((grandparent, *function));
78 }
79 }
80 }
81 None
82 })
83 .collect();
84 Ok(ResolvedPattern {
85 node: pattern,
86 resolved_paths,
87 placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
88 ufcs_function_calls,
89 })
90 }
91
92 fn resolve(
93 &self,
94 node: SyntaxNode,
95 depth: u32,
96 resolved_paths: &mut FxHashMap<SyntaxNode, ResolvedPath>,
97 ) -> Result<(), SsrError> {
98 use ra_syntax::ast::AstNode;
99 if let Some(path) = ast::Path::cast(node.clone()) {
100 // Check if this is an appropriate place in the path to resolve. If the path is
101 // something like `a::B::<i32>::c` then we want to resolve `a::B`. If the path contains
102 // a placeholder. e.g. `a::$b::c` then we want to resolve `a`.
103 if !path_contains_type_arguments(path.qualifier())
104 && !self.path_contains_placeholder(&path)
105 {
106 let resolution = self
107 .resolve_path(&path)
108 .ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?;
109 resolved_paths.insert(node, ResolvedPath { resolution, depth });
110 return Ok(());
111 }
112 }
113 for node in node.children() {
114 self.resolve(node, depth + 1, resolved_paths)?;
115 }
116 Ok(())
117 }
118
119 /// Returns whether `path` contains a placeholder, but ignores any placeholders within type
120 /// arguments.
121 fn path_contains_placeholder(&self, path: &ast::Path) -> bool {
122 if let Some(segment) = path.segment() {
123 if let Some(name_ref) = segment.name_ref() {
124 if self.placeholders_by_stand_in.contains_key(name_ref.text()) {
125 return true;
126 }
127 }
128 }
129 if let Some(qualifier) = path.qualifier() {
130 return self.path_contains_placeholder(&qualifier);
131 }
132 false
133 }
134
135 fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> {
136 let hir_path = hir::Path::from_src(path.clone(), self.hygiene)?;
137 // First try resolving the whole path. This will work for things like
138 // `std::collections::HashMap`, but will fail for things like
139 // `std::collections::HashMap::new`.
140 if let Some(resolution) = self.scope.resolve_hir_path(&hir_path) {
141 return Some(resolution);
142 }
143 // Resolution failed, try resolving the qualifier (e.g. `std::collections::HashMap` and if
144 // that succeeds, then iterate through the candidates on the resolved type with the provided
145 // name.
146 let resolved_qualifier = self.scope.resolve_hir_path_qualifier(&hir_path.qualifier()?)?;
147 if let hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) = resolved_qualifier {
148 adt.ty(self.scope.db).iterate_path_candidates(
149 self.scope.db,
150 self.scope.module()?.krate(),
151 &FxHashSet::default(),
152 Some(hir_path.segments().last()?.name),
153 |_ty, assoc_item| Some(hir::PathResolution::AssocItem(assoc_item)),
154 )
155 } else {
156 None
157 }
158 }
159}
160
161/// Returns whether `path` or any of its qualifiers contains type arguments.
162fn path_contains_type_arguments(path: Option<ast::Path>) -> bool {
163 if let Some(path) = path {
164 if let Some(segment) = path.segment() {
165 if segment.type_arg_list().is_some() {
166 mark::hit!(type_arguments_within_path);
167 return true;
168 }
169 }
170 return path_contains_type_arguments(path.qualifier());
171 }
172 false
173}