diff options
Diffstat (limited to 'crates/ra_hir/src/path.rs')
-rw-r--r-- | crates/ra_hir/src/path.rs | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/crates/ra_hir/src/path.rs b/crates/ra_hir/src/path.rs new file mode 100644 index 000000000..8279daf4b --- /dev/null +++ b/crates/ra_hir/src/path.rs | |||
@@ -0,0 +1,148 @@ | |||
1 | use ra_syntax::{SmolStr, ast, AstNode, TextRange}; | ||
2 | |||
3 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
4 | pub(crate) struct Path { | ||
5 | pub(crate) kind: PathKind, | ||
6 | pub(crate) segments: Vec<SmolStr>, | ||
7 | } | ||
8 | |||
9 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
10 | pub(crate) enum PathKind { | ||
11 | Plain, | ||
12 | Self_, | ||
13 | Super, | ||
14 | Crate, | ||
15 | } | ||
16 | |||
17 | impl Path { | ||
18 | /// Calls `cb` with all paths, represented by this use item. | ||
19 | pub(crate) fn expand_use_item(item: ast::UseItem, mut cb: impl FnMut(Path, Option<TextRange>)) { | ||
20 | if let Some(tree) = item.use_tree() { | ||
21 | expand_use_tree(None, tree, &mut cb); | ||
22 | } | ||
23 | } | ||
24 | |||
25 | /// Converts an `ast::Path` to `Path`. Works with use trees. | ||
26 | pub(crate) fn from_ast(mut path: ast::Path) -> Option<Path> { | ||
27 | let mut kind = PathKind::Plain; | ||
28 | let mut segments = Vec::new(); | ||
29 | loop { | ||
30 | let segment = path.segment()?; | ||
31 | match segment.kind()? { | ||
32 | ast::PathSegmentKind::Name(name) => segments.push(name.text()), | ||
33 | ast::PathSegmentKind::CrateKw => { | ||
34 | kind = PathKind::Crate; | ||
35 | break; | ||
36 | } | ||
37 | ast::PathSegmentKind::SelfKw => { | ||
38 | kind = PathKind::Self_; | ||
39 | break; | ||
40 | } | ||
41 | ast::PathSegmentKind::SuperKw => { | ||
42 | kind = PathKind::Super; | ||
43 | break; | ||
44 | } | ||
45 | } | ||
46 | path = match qualifier(path) { | ||
47 | Some(it) => it, | ||
48 | None => break, | ||
49 | }; | ||
50 | } | ||
51 | segments.reverse(); | ||
52 | return Some(Path { kind, segments }); | ||
53 | |||
54 | fn qualifier(path: ast::Path) -> Option<ast::Path> { | ||
55 | if let Some(q) = path.qualifier() { | ||
56 | return Some(q); | ||
57 | } | ||
58 | // TODO: this bottom up traversal is not too precise. | ||
59 | // Should we handle do a top-down analysiss, recording results? | ||
60 | let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?; | ||
61 | let use_tree = use_tree_list.parent_use_tree(); | ||
62 | use_tree.path() | ||
63 | } | ||
64 | } | ||
65 | |||
66 | /// `true` is this path is a single identifier, like `foo` | ||
67 | pub(crate) fn is_ident(&self) -> bool { | ||
68 | self.kind == PathKind::Plain && self.segments.len() == 1 | ||
69 | } | ||
70 | } | ||
71 | |||
72 | fn expand_use_tree( | ||
73 | prefix: Option<Path>, | ||
74 | tree: ast::UseTree, | ||
75 | cb: &mut impl FnMut(Path, Option<TextRange>), | ||
76 | ) { | ||
77 | if let Some(use_tree_list) = tree.use_tree_list() { | ||
78 | let prefix = match tree.path() { | ||
79 | None => prefix, | ||
80 | Some(path) => match convert_path(prefix, path) { | ||
81 | Some(it) => Some(it), | ||
82 | None => return, // TODO: report errors somewhere | ||
83 | }, | ||
84 | }; | ||
85 | for tree in use_tree_list.use_trees() { | ||
86 | expand_use_tree(prefix.clone(), tree, cb); | ||
87 | } | ||
88 | } else { | ||
89 | if let Some(ast_path) = tree.path() { | ||
90 | if let Some(path) = convert_path(prefix, ast_path) { | ||
91 | let range = if tree.has_star() { | ||
92 | None | ||
93 | } else { | ||
94 | let range = ast_path.segment().unwrap().syntax().range(); | ||
95 | Some(range) | ||
96 | }; | ||
97 | cb(path, range) | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | |||
103 | fn convert_path(prefix: Option<Path>, path: ast::Path) -> Option<Path> { | ||
104 | let prefix = if let Some(qual) = path.qualifier() { | ||
105 | Some(convert_path(prefix, qual)?) | ||
106 | } else { | ||
107 | None | ||
108 | }; | ||
109 | let segment = path.segment()?; | ||
110 | let res = match segment.kind()? { | ||
111 | ast::PathSegmentKind::Name(name) => { | ||
112 | let mut res = prefix.unwrap_or_else(|| Path { | ||
113 | kind: PathKind::Plain, | ||
114 | segments: Vec::with_capacity(1), | ||
115 | }); | ||
116 | res.segments.push(name.text()); | ||
117 | res | ||
118 | } | ||
119 | ast::PathSegmentKind::CrateKw => { | ||
120 | if prefix.is_some() { | ||
121 | return None; | ||
122 | } | ||
123 | Path { | ||
124 | kind: PathKind::Crate, | ||
125 | segments: Vec::new(), | ||
126 | } | ||
127 | } | ||
128 | ast::PathSegmentKind::SelfKw => { | ||
129 | if prefix.is_some() { | ||
130 | return None; | ||
131 | } | ||
132 | Path { | ||
133 | kind: PathKind::Self_, | ||
134 | segments: Vec::new(), | ||
135 | } | ||
136 | } | ||
137 | ast::PathSegmentKind::SuperKw => { | ||
138 | if prefix.is_some() { | ||
139 | return None; | ||
140 | } | ||
141 | Path { | ||
142 | kind: PathKind::Super, | ||
143 | segments: Vec::new(), | ||
144 | } | ||
145 | } | ||
146 | }; | ||
147 | Some(res) | ||
148 | } | ||