Skip to content

Commit 21588e1

Browse files
bors[bot]djrenren
andauthored
Merge #4246
4246: Validate uses of self and super r=matklad a=djrenren This change follows on the validation of the `crate` keyword in paths. It verifies the following things: `super`: - May only be preceded by other `super` segments - If in a `UseItem` then all semantically preceding paths also consist only of `super` `self` - May only be the start of a path Just a note, a couple times while working on this I found myself really wanting a Visitor of some sort so that I could traverse descendants while skipping sub-trees that are unimportant. Iterators don't really work for this, so as you can see I reached for recursion. Considering paths are generally small a fancy debounced visitor probably isn't important but figured I'd say something in case we had something like this lying around and I wasn't using it. Co-authored-by: John Renner <john@jrenner.net>
2 parents 302777f + 3bb4604 commit 21588e1

9 files changed

+178
-75
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,8 @@ pub struct PathSegment {
12411241
impl PathSegment {
12421242
pub fn coloncolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![::]) }
12431243
pub fn crate_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![crate]) }
1244+
pub fn self_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![self]) }
1245+
pub fn super_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![super]) }
12441246
pub fn l_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![<]) }
12451247
pub fn name_ref(&self) -> Option<NameRef> { support::child(&self.syntax) }
12461248
pub fn type_arg_list(&self) -> Option<TypeArgList> { support::child(&self.syntax) }

crates/ra_syntax/src/validation.rs

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +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),
99+
ast::PathSegment(it) => validate_path_keywords(it, &mut errors),
100100
_ => (),
101101
}
102102
}
@@ -224,59 +224,82 @@ fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec<SyntaxError>) {
224224
}
225225
}
226226

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";
227+
fn validate_path_keywords(segment: ast::PathSegment, errors: &mut Vec<SyntaxError>) {
228+
use ast::PathSegmentKind;
232229

233-
let crate_token = match segment.crate_token() {
234-
None => return,
235-
Some(it) => it,
236-
};
230+
let path = segment.parent_path();
231+
let is_path_start = segment.coloncolon_token().is_none() && path.qualifier().is_none();
232+
233+
if let Some(token) = segment.self_token() {
234+
if !is_path_start {
235+
errors.push(SyntaxError::new(
236+
"The `self` keyword is only allowed as the first segment of a path",
237+
token.text_range(),
238+
));
239+
}
240+
} else if let Some(token) = segment.crate_token() {
241+
if !is_path_start || use_prefix(path).is_some() {
242+
errors.push(SyntaxError::new(
243+
"The `crate` keyword is only allowed as the first segment of a path",
244+
token.text_range(),
245+
));
246+
}
247+
} else if let Some(token) = segment.super_token() {
248+
if !all_supers(&path) {
249+
errors.push(SyntaxError::new(
250+
"The `super` keyword may only be preceded by other `super`s",
251+
token.text_range(),
252+
));
253+
return;
254+
}
237255

238-
// Disallow both ::crate and foo::crate
239-
let mut 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;
256+
let mut curr_path = path;
257+
while let Some(prefix) = use_prefix(curr_path) {
258+
if !all_supers(&prefix) {
259+
errors.push(SyntaxError::new(
260+
"The `super` keyword may only be preceded by other `super`s",
261+
token.text_range(),
262+
));
263+
return;
264+
}
265+
curr_path = prefix;
266+
}
243267
}
244268

245-
// For expressions and types, validation is complete, but we still have
246-
// to handle invalid UseItems like this:
247-
//
248-
// use foo:{crate::bar::baz};
249-
//
250-
// To handle this we must inspect the parent `UseItem`s and `UseTree`s
251-
// but right now we're looking deep inside the nested `Path` nodes because
252-
// `Path`s are left-associative:
253-
//
254-
// ((crate)::bar)::baz)
255-
// ^ current value of path
256-
//
257-
// So we need to climb to the top
258-
while let Some(parent) = path.parent_path() {
259-
path = parent;
269+
fn use_prefix(mut path: ast::Path) -> Option<ast::Path> {
270+
for node in path.syntax().ancestors().skip(1) {
271+
match_ast! {
272+
match node {
273+
ast::UseTree(it) => if let Some(tree_path) = it.path() {
274+
// Even a top-level path exists within a `UseTree` so we must explicitly
275+
// allow our path but disallow anything else
276+
if tree_path != path {
277+
return Some(tree_path);
278+
}
279+
},
280+
ast::UseTreeList(_it) => continue,
281+
ast::Path(parent) => path = parent,
282+
_ => return None,
283+
}
284+
};
285+
}
286+
return None;
260287
}
261288

262-
// Now that we've found the whole path we need to see if there's a prefix
263-
// somewhere in the UseTree hierarchy. This check is arbitrarily deep
264-
// because rust allows arbitrary nesting like so:
265-
//
266-
// use {foo::{{{{crate::bar::baz}}}}};
267-
for node in path.syntax().ancestors().skip(1) {
268-
match_ast! {
269-
match node {
270-
ast::UseTree(it) => if let Some(tree_path) = it.path() {
271-
// Even a top-level path exists within a `UseTree` so we must explicitly
272-
// allow our path but disallow anything else
273-
if tree_path != path {
274-
errors.push(SyntaxError::new(ERR_MSG, crate_token.text_range()));
275-
}
276-
},
277-
ast::UseTreeList(_it) => continue,
278-
_ => return,
279-
}
289+
fn all_supers(path: &ast::Path) -> bool {
290+
let segment = match path.segment() {
291+
Some(it) => it,
292+
None => return false,
280293
};
294+
295+
if segment.kind() != Some(PathSegmentKind::SuperKw) {
296+
return false;
297+
}
298+
299+
if let Some(ref subpath) = path.qualifier() {
300+
return all_supers(subpath);
301+
}
302+
303+
return true;
281304
}
282305
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
SOURCE_FILE@0..67
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+
SUPER_KW@6..11 "super"
10+
SEMICOLON@11..12 ";"
11+
WHITESPACE@12..13 "\n"
12+
USE_ITEM@13..26
13+
USE_KW@13..16 "use"
14+
WHITESPACE@16..17 " "
15+
USE_TREE@17..25
16+
PATH@17..25
17+
PATH@17..18
18+
PATH_SEGMENT@17..18
19+
NAME_REF@17..18
20+
IDENT@17..18 "a"
21+
COLON2@18..20 "::"
22+
PATH_SEGMENT@20..25
23+
SUPER_KW@20..25 "super"
24+
SEMICOLON@25..26 ";"
25+
WHITESPACE@26..27 "\n"
26+
USE_ITEM@27..47
27+
USE_KW@27..30 "use"
28+
WHITESPACE@30..31 " "
29+
USE_TREE@31..46
30+
PATH@31..46
31+
PATH@31..39
32+
PATH@31..36
33+
PATH_SEGMENT@31..36
34+
SUPER_KW@31..36 "super"
35+
COLON2@36..38 "::"
36+
PATH_SEGMENT@38..39
37+
NAME_REF@38..39
38+
IDENT@38..39 "a"
39+
COLON2@39..41 "::"
40+
PATH_SEGMENT@41..46
41+
SUPER_KW@41..46 "super"
42+
SEMICOLON@46..47 ";"
43+
WHITESPACE@47..48 "\n"
44+
USE_ITEM@48..66
45+
USE_KW@48..51 "use"
46+
WHITESPACE@51..52 " "
47+
USE_TREE@52..65
48+
PATH@52..53
49+
PATH_SEGMENT@52..53
50+
NAME_REF@52..53
51+
IDENT@52..53 "a"
52+
COLON2@53..55 "::"
53+
USE_TREE_LIST@55..65
54+
L_CURLY@55..56 "{"
55+
USE_TREE@56..64
56+
PATH@56..64
57+
PATH@56..61
58+
PATH_SEGMENT@56..61
59+
SUPER_KW@56..61 "super"
60+
COLON2@61..63 "::"
61+
PATH_SEGMENT@63..64
62+
NAME_REF@63..64
63+
IDENT@63..64 "b"
64+
R_CURLY@64..65 "}"
65+
SEMICOLON@65..66 ";"
66+
WHITESPACE@66..67 "\n"
67+
error 6..11: The `super` keyword may only be preceded by other `super`s
68+
error 20..25: The `super` keyword may only be preceded by other `super`s
69+
error 41..46: The `super` keyword may only be preceded by other `super`s
70+
error 56..61: The `super` keyword may only be preceded by other `super`s
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
use ::super;
2+
use a::super;
3+
use super::a::super;
4+
use a::{super::b};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
SOURCE_FILE@0..25
2+
USE_ITEM@0..11
3+
USE_KW@0..3 "use"
4+
WHITESPACE@3..4 " "
5+
USE_TREE@4..10
6+
PATH@4..10
7+
PATH_SEGMENT@4..10
8+
COLON2@4..6 "::"
9+
SELF_KW@6..10 "self"
10+
SEMICOLON@10..11 ";"
11+
WHITESPACE@11..12 "\n"
12+
USE_ITEM@12..24
13+
USE_KW@12..15 "use"
14+
WHITESPACE@15..16 " "
15+
USE_TREE@16..23
16+
PATH@16..23
17+
PATH@16..17
18+
PATH_SEGMENT@16..17
19+
NAME_REF@16..17
20+
IDENT@16..17 "a"
21+
COLON2@17..19 "::"
22+
PATH_SEGMENT@19..23
23+
SELF_KW@19..23 "self"
24+
SEMICOLON@23..24 ";"
25+
WHITESPACE@24..25 "\n"
26+
error 6..10: The `self` keyword is only allowed as the first segment of a path
27+
error 19..23: The `self` keyword is only allowed as the first segment of a path
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
use ::self;
2+
use a::self;
Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
SOURCE_FILE@0..65
1+
SOURCE_FILE@0..38
22
USE_ITEM@0..14
33
USE_KW@0..3 "use"
44
WHITESPACE@3..4 " "
@@ -31,27 +31,3 @@ SOURCE_FILE@0..65
3131
IDENT@33..36 "bar"
3232
SEMICOLON@36..37 ";"
3333
WHITESPACE@37..38 "\n"
34-
USE_ITEM@38..64
35-
USE_KW@38..41 "use"
36-
WHITESPACE@41..42 " "
37-
USE_TREE@42..63
38-
PATH@42..63
39-
PATH@42..58
40-
PATH@42..51
41-
PATH@42..48
42-
PATH_SEGMENT@42..48
43-
COLON2@42..44 "::"
44-
SELF_KW@44..48 "self"
45-
COLON2@48..50 "::"
46-
PATH_SEGMENT@50..51
47-
NAME_REF@50..51
48-
IDENT@50..51 "a"
49-
COLON2@51..53 "::"
50-
PATH_SEGMENT@53..58
51-
SUPER_KW@53..58 "super"
52-
COLON2@58..60 "::"
53-
PATH_SEGMENT@60..63
54-
NAME_REF@60..63
55-
IDENT@60..63 "bar"
56-
SEMICOLON@63..64 ";"
57-
WHITESPACE@64..65 "\n"
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
use self::foo;
22
use super::super::bar;
3-
use ::self::a::super::bar;

xtask/src/ast_src.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
593593
qualifier: Path,
594594
}
595595
struct PathSegment {
596-
T![::], T![crate], T![<], NameRef, TypeArgList, ParamList, RetType, PathType, T![>]
596+
T![::], T![crate], T![self], T![super], T![<], NameRef, TypeArgList, ParamList, RetType, PathType, T![>]
597597
}
598598
struct TypeArgList {
599599
T![::],

0 commit comments

Comments
 (0)