]> git.lizzy.rs Git - rust.git/blob - crates/syntax/src/validation.rs
Update completions test output
[rust.git] / crates / syntax / src / validation.rs
1 //! FIXME: write short doc here
2
3 mod block;
4
5 use crate::{
6     algo,
7     ast::{self, VisibilityOwner},
8     match_ast, AstNode, SyntaxError,
9     SyntaxKind::{CONST, FN, INT_NUMBER, TYPE_ALIAS},
10     SyntaxNode, SyntaxToken, TextSize, T,
11 };
12 use rowan::Direction;
13 use rustc_lexer::unescape::{
14     self, unescape_byte, unescape_byte_literal, unescape_char, unescape_literal, Mode,
15 };
16 use std::convert::TryFrom;
17
18 fn rustc_unescape_error_to_string(err: unescape::EscapeError) -> &'static str {
19     use unescape::EscapeError as EE;
20
21     #[rustfmt::skip]
22     let err_message = match err {
23         EE::ZeroChars => {
24             "Literal must not be empty"
25         }
26         EE::MoreThanOneChar => {
27             "Literal must be one character long"
28         }
29         EE::LoneSlash => {
30             "Character must be escaped: `\\`"
31         }
32         EE::InvalidEscape => {
33             "Invalid escape"
34         }
35         EE::BareCarriageReturn | EE::BareCarriageReturnInRawString => {
36             "Character must be escaped: `\r`"
37         }
38         EE::EscapeOnlyChar => {
39             "Escape character `\\` must be escaped itself"
40         }
41         EE::TooShortHexEscape => {
42             "ASCII hex escape code must have exactly two digits"
43         }
44         EE::InvalidCharInHexEscape => {
45             "ASCII hex escape code must contain only hex characters"
46         }
47         EE::OutOfRangeHexEscape => {
48             "ASCII hex escape code must be at most 0x7F"
49         }
50         EE::NoBraceInUnicodeEscape => {
51             "Missing `{` to begin the unicode escape"
52         }
53         EE::InvalidCharInUnicodeEscape => {
54             "Unicode escape must contain only hex characters and underscores"
55         }
56         EE::EmptyUnicodeEscape => {
57             "Unicode escape must not be empty"
58         }
59         EE::UnclosedUnicodeEscape => {
60             "Missing `}` to terminate the unicode escape"
61         }
62         EE::LeadingUnderscoreUnicodeEscape => {
63             "Unicode escape code must not begin with an underscore"
64         }
65         EE::OverlongUnicodeEscape => {
66             "Unicode escape code must have at most 6 digits"
67         }
68         EE::LoneSurrogateUnicodeEscape => {
69             "Unicode escape code must not be a surrogate"
70         }
71         EE::OutOfRangeUnicodeEscape => {
72             "Unicode escape code must be at most 0x10FFFF"
73         }
74         EE::UnicodeEscapeInByte => {
75             "Byte literals must not contain unicode escapes"
76         }
77         EE::NonAsciiCharInByte | EE::NonAsciiCharInByteString => {
78             "Byte literals must not contain non-ASCII characters"
79         }
80     };
81
82     err_message
83 }
84
85 pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> {
86     // FIXME:
87     // * Add unescape validation of raw string literals and raw byte string literals
88     // * Add validation of doc comments are being attached to nodes
89
90     let mut errors = Vec::new();
91     for node in root.descendants() {
92         match_ast! {
93             match node {
94                 ast::Literal(it) => validate_literal(it, &mut errors),
95                 ast::BlockExpr(it) => block::validate_block_expr(it, &mut errors),
96                 ast::FieldExpr(it) => validate_numeric_name(it.name_ref(), &mut errors),
97                 ast::RecordExprField(it) => validate_numeric_name(it.name_ref(), &mut errors),
98                 ast::Visibility(it) => validate_visibility(it, &mut errors),
99                 ast::RangeExpr(it) => validate_range_expr(it, &mut errors),
100                 ast::PathSegment(it) => validate_path_keywords(it, &mut errors),
101                 ast::RefType(it) => validate_trait_object_ref_ty(it, &mut errors),
102                 ast::PtrType(it) => validate_trait_object_ptr_ty(it, &mut errors),
103                 ast::FnPtrType(it) => validate_trait_object_fn_ptr_ret_ty(it, &mut errors),
104                 ast::MacroRules(it) => validate_macro_rules(it, &mut errors),
105                 _ => (),
106             }
107         }
108     }
109     errors
110 }
111
112 fn validate_literal(literal: ast::Literal, acc: &mut Vec<SyntaxError>) {
113     // FIXME: move this function to outer scope (https://github.com/rust-analyzer/rust-analyzer/pull/2834#discussion_r366196658)
114     fn unquote(text: &str, prefix_len: usize, end_delimiter: char) -> Option<&str> {
115         text.rfind(end_delimiter).and_then(|end| text.get(prefix_len..end))
116     }
117
118     let token = literal.token();
119     let text = token.text().as_str();
120
121     // FIXME: lift this lambda refactor to `fn` (https://github.com/rust-analyzer/rust-analyzer/pull/2834#discussion_r366199205)
122     let mut push_err = |prefix_len, (off, err): (usize, unescape::EscapeError)| {
123         let off = token.text_range().start() + TextSize::try_from(off + prefix_len).unwrap();
124         acc.push(SyntaxError::new_at_offset(rustc_unescape_error_to_string(err), off));
125     };
126
127     match literal.kind() {
128         ast::LiteralKind::String(s) => {
129             if !s.is_raw() {
130                 if let Some(without_quotes) = unquote(text, 1, '"') {
131                     unescape_literal(without_quotes, Mode::Str, &mut |range, char| {
132                         if let Err(err) = char {
133                             push_err(1, (range.start, err));
134                         }
135                     })
136                 }
137             }
138         }
139         ast::LiteralKind::ByteString(s) => {
140             if !s.is_raw() {
141                 if let Some(without_quotes) = unquote(text, 2, '"') {
142                     unescape_byte_literal(without_quotes, Mode::ByteStr, &mut |range, char| {
143                         if let Err(err) = char {
144                             push_err(2, (range.start, err));
145                         }
146                     })
147                 }
148             }
149         }
150         ast::LiteralKind::Char => {
151             if let Some(Err(e)) = unquote(text, 1, '\'').map(unescape_char) {
152                 push_err(1, e);
153             }
154         }
155         ast::LiteralKind::Byte => {
156             if let Some(Err(e)) = unquote(text, 2, '\'').map(unescape_byte) {
157                 push_err(2, e);
158             }
159         }
160         ast::LiteralKind::IntNumber(_)
161         | ast::LiteralKind::FloatNumber(_)
162         | ast::LiteralKind::Bool(_) => {}
163     }
164 }
165
166 pub(crate) fn validate_block_structure(root: &SyntaxNode) {
167     let mut stack = Vec::new();
168     for node in root.descendants() {
169         match node.kind() {
170             T!['{'] => stack.push(node),
171             T!['}'] => {
172                 if let Some(pair) = stack.pop() {
173                     assert_eq!(
174                         node.parent(),
175                         pair.parent(),
176                         "\nunpaired curlys:\n{}\n{:#?}\n",
177                         root.text(),
178                         root,
179                     );
180                     assert!(
181                         node.next_sibling().is_none() && pair.prev_sibling().is_none(),
182                         "\nfloating curlys at {:?}\nfile:\n{}\nerror:\n{}\n",
183                         node,
184                         root.text(),
185                         node.text(),
186                     );
187                 }
188             }
189             _ => (),
190         }
191     }
192 }
193
194 fn validate_numeric_name(name_ref: Option<ast::NameRef>, errors: &mut Vec<SyntaxError>) {
195     if let Some(int_token) = int_token(name_ref) {
196         if int_token.text().chars().any(|c| !c.is_digit(10)) {
197             errors.push(SyntaxError::new(
198                 "Tuple (struct) field access is only allowed through \
199                 decimal integers with no underscores or suffix",
200                 int_token.text_range(),
201             ));
202         }
203     }
204
205     fn int_token(name_ref: Option<ast::NameRef>) -> Option<SyntaxToken> {
206         name_ref?.syntax().first_child_or_token()?.into_token().filter(|it| it.kind() == INT_NUMBER)
207     }
208 }
209
210 fn validate_visibility(vis: ast::Visibility, errors: &mut Vec<SyntaxError>) {
211     let parent = match vis.syntax().parent() {
212         Some(it) => it,
213         None => return,
214     };
215     match parent.kind() {
216         FN | CONST | TYPE_ALIAS => (),
217         _ => return,
218     }
219
220     let impl_def = match parent.parent().and_then(|it| it.parent()).and_then(ast::Impl::cast) {
221         Some(it) => it,
222         None => return,
223     };
224     if impl_def.trait_().is_some() {
225         errors.push(SyntaxError::new("Unnecessary visibility qualifier", vis.syntax.text_range()));
226     }
227 }
228
229 fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec<SyntaxError>) {
230     if expr.op_kind() == Some(ast::RangeOp::Inclusive) && expr.end().is_none() {
231         errors.push(SyntaxError::new(
232             "An inclusive range must have an end expression",
233             expr.syntax().text_range(),
234         ));
235     }
236 }
237
238 fn validate_path_keywords(segment: ast::PathSegment, errors: &mut Vec<SyntaxError>) {
239     use ast::PathSegmentKind;
240
241     let path = segment.parent_path();
242     let is_path_start = segment.coloncolon_token().is_none() && path.qualifier().is_none();
243
244     if let Some(token) = segment.self_token() {
245         if !is_path_start {
246             errors.push(SyntaxError::new(
247                 "The `self` keyword is only allowed as the first segment of a path",
248                 token.text_range(),
249             ));
250         }
251     } else if let Some(token) = segment.crate_token() {
252         if !is_path_start || use_prefix(path).is_some() {
253             errors.push(SyntaxError::new(
254                 "The `crate` keyword is only allowed as the first segment of a path",
255                 token.text_range(),
256             ));
257         }
258     } else if let Some(token) = segment.super_token() {
259         if segment.coloncolon_token().is_some() || !all_supers(&path) {
260             errors.push(SyntaxError::new(
261                 "The `super` keyword may only be preceded by other `super`s",
262                 token.text_range(),
263             ));
264             return;
265         }
266
267         let mut curr_path = path;
268         while let Some(prefix) = use_prefix(curr_path) {
269             if !all_supers(&prefix) {
270                 errors.push(SyntaxError::new(
271                     "The `super` keyword may only be preceded by other `super`s",
272                     token.text_range(),
273                 ));
274                 return;
275             }
276             curr_path = prefix;
277         }
278     }
279
280     fn use_prefix(mut path: ast::Path) -> Option<ast::Path> {
281         for node in path.syntax().ancestors().skip(1) {
282             match_ast! {
283                 match node {
284                     ast::UseTree(it) => if let Some(tree_path) = it.path() {
285                         // Even a top-level path exists within a `UseTree` so we must explicitly
286                         // allow our path but disallow anything else
287                         if tree_path != path {
288                             return Some(tree_path);
289                         }
290                     },
291                     ast::UseTreeList(_it) => continue,
292                     ast::Path(parent) => path = parent,
293                     _ => return None,
294                 }
295             };
296         }
297         return None;
298     }
299
300     fn all_supers(path: &ast::Path) -> bool {
301         let segment = match path.segment() {
302             Some(it) => it,
303             None => return false,
304         };
305
306         if segment.kind() != Some(PathSegmentKind::SuperKw) {
307             return false;
308         }
309
310         if let Some(ref subpath) = path.qualifier() {
311             return all_supers(subpath);
312         }
313
314         return true;
315     }
316 }
317
318 fn validate_trait_object_ref_ty(ty: ast::RefType, errors: &mut Vec<SyntaxError>) {
319     if let Some(ast::Type::DynTraitType(ty)) = ty.ty() {
320         if let Some(err) = validate_trait_object_ty(ty) {
321             errors.push(err);
322         }
323     }
324 }
325
326 fn validate_trait_object_ptr_ty(ty: ast::PtrType, errors: &mut Vec<SyntaxError>) {
327     if let Some(ast::Type::DynTraitType(ty)) = ty.ty() {
328         if let Some(err) = validate_trait_object_ty(ty) {
329             errors.push(err);
330         }
331     }
332 }
333
334 fn validate_trait_object_fn_ptr_ret_ty(ty: ast::FnPtrType, errors: &mut Vec<SyntaxError>) {
335     if let Some(ast::Type::DynTraitType(ty)) = ty.ret_type().and_then(|ty| ty.ty()) {
336         if let Some(err) = validate_trait_object_ty(ty) {
337             errors.push(err);
338         }
339     }
340 }
341
342 fn validate_trait_object_ty(ty: ast::DynTraitType) -> Option<SyntaxError> {
343     let tbl = ty.type_bound_list()?;
344
345     if tbl.bounds().count() > 1 {
346         let dyn_token = ty.dyn_token()?;
347         let potential_parenthesis =
348             algo::skip_trivia_token(dyn_token.prev_token()?, Direction::Prev)?;
349         let kind = potential_parenthesis.kind();
350         if !matches!(kind, T!['('] | T![<] | T![=]) {
351             return Some(SyntaxError::new("ambiguous `+` in a type", ty.syntax().text_range()));
352         }
353     }
354     None
355 }
356
357 fn validate_macro_rules(mac: ast::MacroRules, errors: &mut Vec<SyntaxError>) {
358     if let Some(vis) = mac.visibility() {
359         errors.push(SyntaxError::new(
360             "visibilities are not allowed on `macro_rules!` items",
361             vis.syntax().text_range(),
362         ));
363     }
364 }