Skip to content

Commit 95e8766

Browse files
bors[bot]djrenren
andauthored
Merge #4178
4178: Validate the location of `crate` in paths r=matklad a=djrenren **This solution does not fully handle `use` statements. See below** This pull requests implements simple validation of usages of the `crate` keyword in `Path`s. Specifically it validates that: - If a `PathSegment` is starts with the `crate` keyword, it is also the first segment of the `Path` - All other usages of `crate` in `Path`s are considered errors. This aligns with `rustc`'s rules. Unlike rustc this implementation does not issue a special error message in the case of `::crate` but it does catch the error. Furthermore, this change does not cover all error cases. Specifically the following is not caught: ```rust use foo::{crate} ``` This is because this check is context sensitive. From an AST perspective, `crate` is the root of the `Path`. Only by inspecting the full `UseItem` do we see that it is not in fact the root. This problem becomes worse because `UseTree`s are allowed to be arbitrarily nested: ```rust use {crate, {{crate, foo::{crate}}} ``` So this is a hard problem to solve without essentially a breadth-first search. In a traditional compiler, I'd say this error is most easily found during the AST -> HIR conversion pass but within rust-analyzer I'm not sure where it belongs. Under the implementation in this PR, such errors are ignored so we're *more correct* just not *entirely correct*. Co-authored-by: John Renner <john@jrenner.net>
2 parents c2425fd + 0af727d commit 95e8766

File tree

5 files changed

+121
-1
lines changed

5 files changed

+121
-1
lines changed

crates/ra_syntax/src/ast/generated/nodes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,7 @@ pub struct PathSegment {
12491249
}
12501250
impl PathSegment {
12511251
pub fn coloncolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![::]) }
1252+
pub fn crate_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![crate]) }
12521253
pub fn l_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![<]) }
12531254
pub fn name_ref(&self) -> Option<NameRef> { support::child(&self.syntax) }
12541255
pub fn type_arg_list(&self) -> Option<TypeArgList> { support::child(&self.syntax) }

crates/ra_syntax/src/validation.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> {
9696
ast::RecordField(it) => validate_numeric_name(it.name_ref(), &mut errors),
9797
ast::Visibility(it) => validate_visibility(it, &mut errors),
9898
ast::RangeExpr(it) => validate_range_expr(it, &mut errors),
99+
ast::PathSegment(it) => validate_crate_keyword_in_path_segment(it, &mut errors),
99100
_ => (),
100101
}
101102
}
@@ -222,3 +223,41 @@ fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec<SyntaxError>) {
222223
));
223224
}
224225
}
226+
227+
fn validate_crate_keyword_in_path_segment(
228+
segment: ast::PathSegment,
229+
errors: &mut Vec<SyntaxError>,
230+
) {
231+
const ERR_MSG: &str = "The `crate` keyword is only allowed as the first segment of a path";
232+
233+
let crate_token = match segment.crate_token() {
234+
None => return,
235+
Some(it) => it,
236+
};
237+
238+
// Disallow both ::crate and foo::crate
239+
let path = segment.parent_path();
240+
if segment.coloncolon_token().is_some() || path.qualifier().is_some() {
241+
errors.push(SyntaxError::new(ERR_MSG, crate_token.text_range()));
242+
return;
243+
}
244+
245+
// We now know that the path variable describes a complete path.
246+
// For expressions and types, validation is complete, but we still have
247+
// to handle UseItems like this:
248+
// use foo:{crate};
249+
// so we crawl upwards looking for any preceding paths on `UseTree`s
250+
for node in path.syntax().ancestors().skip(1) {
251+
match_ast! {
252+
match node {
253+
ast::UseTree(it) => if let Some(tree_path) = it.path() {
254+
if tree_path != path {
255+
errors.push(SyntaxError::new(ERR_MSG, crate_token.text_range()));
256+
}
257+
},
258+
ast::UseTreeList(_it) => continue,
259+
_ => return,
260+
}
261+
};
262+
}
263+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
SOURCE_FILE@0..83
2+
USE_ITEM@0..12
3+
USE_KW@0..3 "use"
4+
WHITESPACE@3..4 " "
5+
USE_TREE@4..11
6+
PATH@4..11
7+
PATH_SEGMENT@4..11
8+
COLON2@4..6 "::"
9+
CRATE_KW@6..11 "crate"
10+
SEMICOLON@11..12 ";"
11+
WHITESPACE@12..13 "\n"
12+
USE_ITEM@13..39
13+
USE_KW@13..16 "use"
14+
WHITESPACE@16..17 " "
15+
USE_TREE@17..38
16+
USE_TREE_LIST@17..38
17+
L_CURLY@17..18 "{"
18+
USE_TREE@18..23
19+
PATH@18..23
20+
PATH_SEGMENT@18..23
21+
CRATE_KW@18..23 "crate"
22+
COMMA@23..24 ","
23+
WHITESPACE@24..25 " "
24+
USE_TREE@25..37
25+
PATH@25..28
26+
PATH_SEGMENT@25..28
27+
NAME_REF@25..28
28+
IDENT@25..28 "foo"
29+
COLON2@28..30 "::"
30+
USE_TREE_LIST@30..37
31+
L_CURLY@30..31 "{"
32+
USE_TREE@31..36
33+
PATH@31..36
34+
PATH_SEGMENT@31..36
35+
CRATE_KW@31..36 "crate"
36+
R_CURLY@36..37 "}"
37+
R_CURLY@37..38 "}"
38+
SEMICOLON@38..39 ";"
39+
WHITESPACE@39..40 "\n"
40+
USE_ITEM@40..57
41+
USE_KW@40..43 "use"
42+
WHITESPACE@43..44 " "
43+
USE_TREE@44..56
44+
PATH@44..56
45+
PATH@44..49
46+
PATH_SEGMENT@44..49
47+
NAME_REF@44..49
48+
IDENT@44..49 "hello"
49+
COLON2@49..51 "::"
50+
PATH_SEGMENT@51..56
51+
CRATE_KW@51..56 "crate"
52+
SEMICOLON@56..57 ";"
53+
WHITESPACE@57..58 "\n"
54+
USE_ITEM@58..82
55+
USE_KW@58..61 "use"
56+
WHITESPACE@61..62 " "
57+
USE_TREE@62..81
58+
PATH@62..81
59+
PATH@62..74
60+
PATH@62..67
61+
PATH_SEGMENT@62..67
62+
NAME_REF@62..67
63+
IDENT@62..67 "hello"
64+
COLON2@67..69 "::"
65+
PATH_SEGMENT@69..74
66+
CRATE_KW@69..74 "crate"
67+
COLON2@74..76 "::"
68+
PATH_SEGMENT@76..81
69+
NAME_REF@76..81
70+
IDENT@76..81 "there"
71+
SEMICOLON@81..82 ";"
72+
WHITESPACE@82..83 "\n"
73+
error 6..11: The `crate` keyword is only allowed as the first segment of a path
74+
error 31..36: The `crate` keyword is only allowed as the first segment of a path
75+
error 51..56: The `crate` keyword is only allowed as the first segment of a path
76+
error 69..74: The `crate` keyword is only allowed as the first segment of a path
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
use ::crate;
2+
use {crate, foo::{crate}};
3+
use hello::crate;
4+
use hello::crate::there;

xtask/src/ast_src.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
595595
qualifier: Path,
596596
}
597597
struct PathSegment {
598-
T![::], T![<], NameRef, TypeArgList, ParamList, RetType, PathType, T![>]
598+
T![::], T![crate], T![<], NameRef, TypeArgList, ParamList, RetType, PathType, T![>]
599599
}
600600
struct TypeArgList {
601601
T![::],

0 commit comments

Comments
 (0)