From 989cebc99c02acd427f01724c4fa77d81691e886 Mon Sep 17 00:00:00 2001 From: Geoffry Song Date: Fri, 15 Nov 2019 00:08:43 -0800 Subject: Fix parsing of "postfix" range expressions. Right now they are handled in `postfix_dot_expr`, but that doesn't allow it to correctly handle precedence. Integrate it more tightly with the Pratt parser instead. Also includes a drive-by fix for parsing `match .. {}`. Fixes #2242. --- .../parser/err/0038_endless_inclusive_range.rs | 3 ++ .../parser/err/0038_endless_inclusive_range.txt | 24 ++++++++++ .../ra_syntax/test_data/parser/ok/0060_as_range.rs | 4 ++ .../test_data/parser/ok/0060_as_range.txt | 56 ++++++++++++++++++++++ .../test_data/parser/ok/0061_match_full_range.rs | 4 ++ .../test_data/parser/ok/0061_match_full_range.txt | 27 +++++++++++ 6 files changed, 118 insertions(+) create mode 100644 crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.rs create mode 100644 crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt create mode 100644 crates/ra_syntax/test_data/parser/ok/0060_as_range.rs create mode 100644 crates/ra_syntax/test_data/parser/ok/0060_as_range.txt create mode 100644 crates/ra_syntax/test_data/parser/ok/0061_match_full_range.rs create mode 100644 crates/ra_syntax/test_data/parser/ok/0061_match_full_range.txt (limited to 'crates/ra_syntax') diff --git a/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.rs b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.rs new file mode 100644 index 000000000..ecd25afaf --- /dev/null +++ b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.rs @@ -0,0 +1,3 @@ +fn main() { + 0..=; +} diff --git a/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt new file mode 100644 index 000000000..3efe98164 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt @@ -0,0 +1,24 @@ +SOURCE_FILE@[0; 24) + FN_DEF@[0; 23) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 7) + IDENT@[3; 7) "main" + PARAM_LIST@[7; 9) + L_PAREN@[7; 8) "(" + R_PAREN@[8; 9) ")" + WHITESPACE@[9; 10) " " + BLOCK_EXPR@[10; 23) + BLOCK@[10; 23) + L_CURLY@[10; 11) "{" + WHITESPACE@[11; 16) "\n " + EXPR_STMT@[16; 21) + RANGE_EXPR@[16; 20) + LITERAL@[16; 17) + INT_NUMBER@[16; 17) "0" + DOTDOTEQ@[17; 20) "..=" + SEMI@[20; 21) ";" + WHITESPACE@[21; 22) "\n" + R_CURLY@[22; 23) "}" + WHITESPACE@[23; 24) "\n" +error 20: expected expression to end inclusive range diff --git a/crates/ra_syntax/test_data/parser/ok/0060_as_range.rs b/crates/ra_syntax/test_data/parser/ok/0060_as_range.rs new file mode 100644 index 000000000..f063ffadb --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0060_as_range.rs @@ -0,0 +1,4 @@ +fn main() { + 0 as usize ..; + 1 + 2 as usize ..; +} diff --git a/crates/ra_syntax/test_data/parser/ok/0060_as_range.txt b/crates/ra_syntax/test_data/parser/ok/0060_as_range.txt new file mode 100644 index 000000000..ad0c4a3fe --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0060_as_range.txt @@ -0,0 +1,56 @@ +SOURCE_FILE@[0; 56) + FN_DEF@[0; 55) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 7) + IDENT@[3; 7) "main" + PARAM_LIST@[7; 9) + L_PAREN@[7; 8) "(" + R_PAREN@[8; 9) ")" + WHITESPACE@[9; 10) " " + BLOCK_EXPR@[10; 55) + BLOCK@[10; 55) + L_CURLY@[10; 11) "{" + WHITESPACE@[11; 16) "\n " + EXPR_STMT@[16; 30) + RANGE_EXPR@[16; 29) + CAST_EXPR@[16; 26) + LITERAL@[16; 17) + INT_NUMBER@[16; 17) "0" + WHITESPACE@[17; 18) " " + AS_KW@[18; 20) "as" + WHITESPACE@[20; 21) " " + PATH_TYPE@[21; 26) + PATH@[21; 26) + PATH_SEGMENT@[21; 26) + NAME_REF@[21; 26) + IDENT@[21; 26) "usize" + WHITESPACE@[26; 27) " " + DOTDOT@[27; 29) ".." + SEMI@[29; 30) ";" + WHITESPACE@[30; 35) "\n " + EXPR_STMT@[35; 53) + RANGE_EXPR@[35; 52) + BIN_EXPR@[35; 49) + LITERAL@[35; 36) + INT_NUMBER@[35; 36) "1" + WHITESPACE@[36; 37) " " + PLUS@[37; 38) "+" + WHITESPACE@[38; 39) " " + CAST_EXPR@[39; 49) + LITERAL@[39; 40) + INT_NUMBER@[39; 40) "2" + WHITESPACE@[40; 41) " " + AS_KW@[41; 43) "as" + WHITESPACE@[43; 44) " " + PATH_TYPE@[44; 49) + PATH@[44; 49) + PATH_SEGMENT@[44; 49) + NAME_REF@[44; 49) + IDENT@[44; 49) "usize" + WHITESPACE@[49; 50) " " + DOTDOT@[50; 52) ".." + SEMI@[52; 53) ";" + WHITESPACE@[53; 54) "\n" + R_CURLY@[54; 55) "}" + WHITESPACE@[55; 56) "\n" diff --git a/crates/ra_syntax/test_data/parser/ok/0061_match_full_range.rs b/crates/ra_syntax/test_data/parser/ok/0061_match_full_range.rs new file mode 100644 index 000000000..2c4ed11e1 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0061_match_full_range.rs @@ -0,0 +1,4 @@ +fn main() { + match .. { + } +} diff --git a/crates/ra_syntax/test_data/parser/ok/0061_match_full_range.txt b/crates/ra_syntax/test_data/parser/ok/0061_match_full_range.txt new file mode 100644 index 000000000..bdfac9b76 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0061_match_full_range.txt @@ -0,0 +1,27 @@ +SOURCE_FILE@[0; 35) + FN_DEF@[0; 34) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 7) + IDENT@[3; 7) "main" + PARAM_LIST@[7; 9) + L_PAREN@[7; 8) "(" + R_PAREN@[8; 9) ")" + WHITESPACE@[9; 10) " " + BLOCK_EXPR@[10; 34) + BLOCK@[10; 34) + L_CURLY@[10; 11) "{" + WHITESPACE@[11; 16) "\n " + MATCH_EXPR@[16; 32) + MATCH_KW@[16; 21) "match" + WHITESPACE@[21; 22) " " + RANGE_EXPR@[22; 24) + DOTDOT@[22; 24) ".." + WHITESPACE@[24; 25) " " + MATCH_ARM_LIST@[25; 32) + L_CURLY@[25; 26) "{" + WHITESPACE@[26; 31) "\n " + R_CURLY@[31; 32) "}" + WHITESPACE@[32; 33) "\n" + R_CURLY@[33; 34) "}" + WHITESPACE@[34; 35) "\n" -- cgit v1.2.3 From a68aefdc463af054e7e98293c06b751c135911d5 Mon Sep 17 00:00:00 2001 From: Geoffry Song Date: Fri, 15 Nov 2019 01:04:37 -0800 Subject: Move inclusive range check to validation --- crates/ra_syntax/src/syntax_error.rs | 4 ++++ crates/ra_syntax/src/validation.rs | 14 ++++++++++++++ .../parser/err/0038_endless_inclusive_range.rs | 1 + .../parser/err/0038_endless_inclusive_range.txt | 22 ++++++++++++++-------- 4 files changed, 33 insertions(+), 8 deletions(-) (limited to 'crates/ra_syntax') diff --git a/crates/ra_syntax/src/syntax_error.rs b/crates/ra_syntax/src/syntax_error.rs index 1f60a7aab..6c171df8d 100644 --- a/crates/ra_syntax/src/syntax_error.rs +++ b/crates/ra_syntax/src/syntax_error.rs @@ -83,6 +83,7 @@ pub enum SyntaxErrorKind { InvalidMatchInnerAttr, InvalidTupleIndexFormat, VisibilityNotAllowed, + InclusiveRangeMissingEnd, } impl fmt::Display for SyntaxErrorKind { @@ -103,6 +104,9 @@ impl fmt::Display for SyntaxErrorKind { VisibilityNotAllowed => { write!(f, "unnecessary visibility qualifier") } + InclusiveRangeMissingEnd => { + write!(f, "An inclusive range must have an end expression") + } } } } diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs index 2d596763e..e01333e23 100644 --- a/crates/ra_syntax/src/validation.rs +++ b/crates/ra_syntax/src/validation.rs @@ -103,6 +103,7 @@ pub(crate) fn validate(root: &SyntaxNode) -> Vec { ast::FieldExpr(it) => { validate_numeric_name(it.name_ref(), &mut errors) }, ast::RecordField(it) => { validate_numeric_name(it.name_ref(), &mut errors) }, ast::Visibility(it) => { validate_visibility(it, &mut errors) }, + ast::RangeExpr(it) => { validate_range_expr(it, &mut errors) }, _ => (), } } @@ -227,3 +228,16 @@ fn validate_visibility(vis: ast::Visibility, errors: &mut Vec) { .push(SyntaxError::new(SyntaxErrorKind::VisibilityNotAllowed, vis.syntax.text_range())) } } + +fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec) { + let last_child = match expr.syntax().last_child_or_token() { + Some(it) => it, + None => return, + }; + if last_child.kind() == T![..=] { + errors.push(SyntaxError::new( + SyntaxErrorKind::InclusiveRangeMissingEnd, + last_child.text_range(), + )); + } +} diff --git a/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.rs b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.rs index ecd25afaf..0b4ed7a2b 100644 --- a/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.rs +++ b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.rs @@ -1,3 +1,4 @@ fn main() { 0..=; + ..=; } diff --git a/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt index 3efe98164..749d53609 100644 --- a/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt +++ b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt @@ -1,5 +1,5 @@ -SOURCE_FILE@[0; 24) - FN_DEF@[0; 23) +SOURCE_FILE@[0; 33) + FN_DEF@[0; 32) FN_KW@[0; 2) "fn" WHITESPACE@[2; 3) " " NAME@[3; 7) @@ -8,8 +8,8 @@ SOURCE_FILE@[0; 24) L_PAREN@[7; 8) "(" R_PAREN@[8; 9) ")" WHITESPACE@[9; 10) " " - BLOCK_EXPR@[10; 23) - BLOCK@[10; 23) + BLOCK_EXPR@[10; 32) + BLOCK@[10; 32) L_CURLY@[10; 11) "{" WHITESPACE@[11; 16) "\n " EXPR_STMT@[16; 21) @@ -18,7 +18,13 @@ SOURCE_FILE@[0; 24) INT_NUMBER@[16; 17) "0" DOTDOTEQ@[17; 20) "..=" SEMI@[20; 21) ";" - WHITESPACE@[21; 22) "\n" - R_CURLY@[22; 23) "}" - WHITESPACE@[23; 24) "\n" -error 20: expected expression to end inclusive range + WHITESPACE@[21; 26) "\n " + EXPR_STMT@[26; 30) + RANGE_EXPR@[26; 29) + DOTDOTEQ@[26; 29) "..=" + SEMI@[29; 30) ";" + WHITESPACE@[30; 31) "\n" + R_CURLY@[31; 32) "}" + WHITESPACE@[32; 33) "\n" +error [17; 20): An inclusive range must have an end expression +error [26; 29): An inclusive range must have an end expression -- cgit v1.2.3 From 5645c153e0379874d1f44ab149c3ec9257812692 Mon Sep 17 00:00:00 2001 From: Geoffry Song Date: Fri, 15 Nov 2019 12:05:29 -0800 Subject: Attempt to implement typed accessors --- crates/ra_syntax/src/ast.rs | 2 +- crates/ra_syntax/src/ast/expr_extensions.rs | 46 ++++++++++++++++++++++ crates/ra_syntax/src/validation.rs | 8 +--- .../parser/err/0038_endless_inclusive_range.txt | 2 +- 4 files changed, 50 insertions(+), 8 deletions(-) (limited to 'crates/ra_syntax') diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 1ec9881b9..277532a8c 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -16,7 +16,7 @@ use crate::{ }; pub use self::{ - expr_extensions::{ArrayExprKind, BinOp, ElseBranch, LiteralKind, PrefixOp}, + expr_extensions::{ArrayExprKind, BinOp, ElseBranch, LiteralKind, PrefixOp, RangeOp}, extensions::{FieldKind, PathSegmentKind, SelfParamKind, StructKind, TypeBoundKind}, generated::*, tokens::*, diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs index 25dbd0bed..7c53aa934 100644 --- a/crates/ra_syntax/src/ast/expr_extensions.rs +++ b/crates/ra_syntax/src/ast/expr_extensions.rs @@ -189,6 +189,52 @@ impl ast::BinExpr { } } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum RangeOp { + /// `..` + Exclusive, + /// `..=` + Inclusive, +} + +impl ast::RangeExpr { + fn op_details(&self) -> Option<(usize, SyntaxToken, RangeOp)> { + self.syntax().children_with_tokens().enumerate().find_map(|(ix, child)| { + let token = child.into_token()?; + let bin_op = match token.kind() { + T![..] => RangeOp::Exclusive, + T![..=] => RangeOp::Inclusive, + _ => return None, + }; + Some((ix, token, bin_op)) + }) + } + + pub fn op_kind(&self) -> Option { + self.op_details().map(|t| t.2) + } + + pub fn op_token(&self) -> Option { + self.op_details().map(|t| t.1) + } + + pub fn start(&self) -> Option { + let op_ix = self.op_details()?.0; + self.syntax() + .children_with_tokens() + .take(op_ix) + .find_map(|it| ast::Expr::cast(it.into_node()?)) + } + + pub fn end(&self) -> Option { + let op_ix = self.op_details()?.0; + self.syntax() + .children_with_tokens() + .skip(op_ix + 1) + .find_map(|it| ast::Expr::cast(it.into_node()?)) + } +} + impl ast::IndexExpr { pub fn base(&self) -> Option { children(self).nth(0) diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs index e01333e23..222ac15f8 100644 --- a/crates/ra_syntax/src/validation.rs +++ b/crates/ra_syntax/src/validation.rs @@ -230,14 +230,10 @@ fn validate_visibility(vis: ast::Visibility, errors: &mut Vec) { } fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec) { - let last_child = match expr.syntax().last_child_or_token() { - Some(it) => it, - None => return, - }; - if last_child.kind() == T![..=] { + if expr.op_kind() == Some(ast::RangeOp::Inclusive) && expr.end().is_none() { errors.push(SyntaxError::new( SyntaxErrorKind::InclusiveRangeMissingEnd, - last_child.text_range(), + expr.syntax().text_range(), )); } } diff --git a/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt index 749d53609..3810b9680 100644 --- a/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt +++ b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt @@ -26,5 +26,5 @@ SOURCE_FILE@[0; 33) WHITESPACE@[30; 31) "\n" R_CURLY@[31; 32) "}" WHITESPACE@[32; 33) "\n" -error [17; 20): An inclusive range must have an end expression +error [16; 20): An inclusive range must have an end expression error [26; 29): An inclusive range must have an end expression -- cgit v1.2.3 From d898ecb8f2c19eb041bcb27c7ce9edd9d891f2c2 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 16 Nov 2019 00:56:51 +0300 Subject: Force passing Source when creating a SourceAnalyzer --- crates/ra_syntax/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'crates/ra_syntax') diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index 5dcb6a95a..9931fec84 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs @@ -176,9 +176,11 @@ impl SourceFile { /// ``` #[macro_export] macro_rules! match_ast { - (match $node:ident { + (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; + + (match ($node:expr) { $( ast::$ast:ident($it:ident) => $res:block, )* - _ => $catch_all:expr, + _ => $catch_all:expr $(,)? }) => {{ $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )* { $catch_all } -- cgit v1.2.3 From 5b54a93fe71137606674ff11dc57bc6e7eaa37f9 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 16 Nov 2019 22:50:41 +0300 Subject: Add ast for plain and raw string literals --- crates/ra_syntax/src/ast/tokens.rs | 95 ++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 10 deletions(-) (limited to 'crates/ra_syntax') diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs index 87cca325d..ed8661faf 100644 --- a/crates/ra_syntax/src/ast/tokens.rs +++ b/crates/ra_syntax/src/ast/tokens.rs @@ -2,8 +2,8 @@ use crate::{ ast::AstToken, - SyntaxKind::{COMMENT, WHITESPACE}, - SyntaxToken, + SyntaxKind::{COMMENT, RAW_STRING, STRING, WHITESPACE}, + SyntaxToken, TextRange, TextUnit, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -11,10 +11,9 @@ pub struct Comment(SyntaxToken); impl AstToken for Comment { fn cast(token: SyntaxToken) -> Option { - if token.kind() == COMMENT { - Some(Comment(token)) - } else { - None + match token.kind() { + COMMENT => Some(Comment(token)), + _ => None, } } fn syntax(&self) -> &SyntaxToken { @@ -94,10 +93,9 @@ pub struct Whitespace(SyntaxToken); impl AstToken for Whitespace { fn cast(token: SyntaxToken) -> Option { - if token.kind() == WHITESPACE { - Some(Whitespace(token)) - } else { - None + match token.kind() { + WHITESPACE => Some(Whitespace(token)), + _ => None, } } fn syntax(&self) -> &SyntaxToken { @@ -111,3 +109,80 @@ impl Whitespace { text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n')) } } + +pub struct String(SyntaxToken); + +impl AstToken for String { + fn cast(token: SyntaxToken) -> Option { + match token.kind() { + STRING => Some(String(token)), + _ => None, + } + } + fn syntax(&self) -> &SyntaxToken { + &self.0 + } +} + +impl String { + pub fn value(&self) -> Option { + let text = self.text().as_str(); + let usual_string_range = find_usual_string_range(text)?; + let start_of_inside = usual_string_range.start().to_usize() + 1; + let end_of_inside = usual_string_range.end().to_usize(); + let inside_str = &text[start_of_inside..end_of_inside]; + + let mut buf = std::string::String::with_capacity(inside_str.len()); + let mut has_error = false; + rustc_lexer::unescape::unescape_str(inside_str, &mut |_, unescaped_char| { + match unescaped_char { + Ok(c) => buf.push(c), + Err(_) => has_error = true, + } + }); + + if has_error { + return None; + } + Some(buf) + } +} + +pub struct RawString(SyntaxToken); + +impl AstToken for RawString { + fn cast(token: SyntaxToken) -> Option { + match token.kind() { + RAW_STRING => Some(RawString(token)), + _ => None, + } + } + fn syntax(&self) -> &SyntaxToken { + &self.0 + } +} + +impl RawString { + pub fn value(&self) -> Option { + let text = self.text().as_str(); + let usual_string_range = find_usual_string_range(text)?; + let start_of_inside = usual_string_range.start().to_usize() + 1; + let end_of_inside = usual_string_range.end().to_usize(); + let inside_str = &text[start_of_inside..end_of_inside]; + Some(inside_str.to_string()) + } +} + +fn find_usual_string_range(s: &str) -> Option { + let left_quote = s.find('"')?; + let right_quote = s.rfind('"')?; + if left_quote == right_quote { + // `s` only contains one quote + None + } else { + Some(TextRange::from_to( + TextUnit::from(left_quote as u32), + TextUnit::from(right_quote as u32), + )) + } +} -- cgit v1.2.3 From 7e2f4b30db8a0d734b3a1fc9f6ad77b2adc9aa2a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 17 Nov 2019 18:35:05 +0300 Subject: Disable doctests --- crates/ra_syntax/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) (limited to 'crates/ra_syntax') diff --git a/crates/ra_syntax/Cargo.toml b/crates/ra_syntax/Cargo.toml index 9c0e856e8..45a18a73f 100644 --- a/crates/ra_syntax/Cargo.toml +++ b/crates/ra_syntax/Cargo.toml @@ -7,6 +7,9 @@ license = "MIT OR Apache-2.0" description = "Comment and whitespace preserving parser for the Rust langauge" repository = "https://github.com/rust-analyzer/rust-analyzer" +[lib] +doctest = false + [dependencies] itertools = "0.8.0" rowan = "0.6.1" -- cgit v1.2.3