diff --git a/crates/ra_parser/src/grammar/expressions.rs b/crates/ra_parser/src/grammar/expressions.rs index 45f2e3de47..81d4f75f99 100644 --- a/crates/ra_parser/src/grammar/expressions.rs +++ b/crates/ra_parser/src/grammar/expressions.rs @@ -290,6 +290,22 @@ fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> (Option, let m = lhs.precede(p); p.bump(op); + if is_range { + // test postfix_range + // fn foo() { + // let x = 1..; + // match 1.. { _ => () }; + // match a.b()..S { _ => () }; + // } + let has_trailing_expression = + p.at_ts(EXPR_FIRST) && !(r.forbid_structs && p.at(T!['{'])); + if !has_trailing_expression { + // no RHS + lhs = m.complete(p, RANGE_EXPR); + break; + } + } + expr_bp(p, r, op_bp + 1); lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR }); } @@ -330,7 +346,7 @@ fn lhs(p: &mut Parser, r: Restrictions) -> Option<(CompletedMarker, BlockLike)> if p.at(op) { m = p.start(); p.bump(op); - if p.at_ts(EXPR_FIRST) { + if p.at_ts(EXPR_FIRST) && !(r.forbid_structs && p.at(T!['{'])) { expr_bp(p, r, 2); } return Some((m.complete(p, RANGE_EXPR), BlockLike::NotBlock)); @@ -344,13 +360,7 @@ fn lhs(p: &mut Parser, r: Restrictions) -> Option<(CompletedMarker, BlockLike)> // } // let (lhs, blocklike) = atom::atom_expr(p, r)?; - return Some(postfix_expr( - p, - lhs, - blocklike, - !(r.prefer_stmt && blocklike.is_block()), - r.forbid_structs, - )); + return Some(postfix_expr(p, lhs, blocklike, !(r.prefer_stmt && blocklike.is_block()))); } }; // parse the interior of the unary expression @@ -366,7 +376,6 @@ fn postfix_expr( // `while true {break}; ();` mut block_like: BlockLike, mut allow_calls: bool, - forbid_structs: bool, ) -> (CompletedMarker, BlockLike) { loop { lhs = match p.current() { @@ -380,7 +389,7 @@ fn postfix_expr( // } T!['('] if allow_calls => call_expr(p, lhs), T!['['] if allow_calls => index_expr(p, lhs), - T![.] => match postfix_dot_expr(p, lhs, forbid_structs) { + T![.] => match postfix_dot_expr(p, lhs) { Ok(it) => it, Err(it) => { lhs = it; @@ -398,7 +407,6 @@ fn postfix_expr( fn postfix_dot_expr( p: &mut Parser, lhs: CompletedMarker, - forbid_structs: bool, ) -> Result { assert!(p.at(T![.])); if p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::])) { @@ -418,25 +426,8 @@ fn postfix_expr( return Ok(m.complete(p, AWAIT_EXPR)); } - // test postfix_range - // fn foo() { - // let x = 1..; - // match 1.. { _ => () }; - // match a.b()..S { _ => () }; - // } - for &(op, la) in &[(T![..=], 3), (T![..], 2)] { - if p.at(op) { - let next_token = p.nth(la); - let has_trailing_expression = - !(forbid_structs && next_token == T!['{']) && EXPR_FIRST.contains(next_token); - return if has_trailing_expression { - Err(lhs) - } else { - let m = lhs.precede(p); - p.bump(op); - Ok(m.complete(p, RANGE_EXPR)) - }; - } + if p.at(T![..=]) || p.at(T![..]) { + return Err(lhs); } Ok(field_expr(p, lhs)) diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 1ec9881b98..277532a8cb 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 25dbd0bed6..7c53aa9344 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/syntax_error.rs b/crates/ra_syntax/src/syntax_error.rs index 1f60a7aabd..6c171df8d5 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 2d596763ee..222ac15f87 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,12 @@ 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) { + if expr.op_kind() == Some(ast::RangeOp::Inclusive) && expr.end().is_none() { + errors.push(SyntaxError::new( + SyntaxErrorKind::InclusiveRangeMissingEnd, + expr.syntax().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 new file mode 100644 index 0000000000..0b4ed7a2bc --- /dev/null +++ b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.rs @@ -0,0 +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 new file mode 100644 index 0000000000..3810b9680a --- /dev/null +++ b/crates/ra_syntax/test_data/parser/err/0038_endless_inclusive_range.txt @@ -0,0 +1,30 @@ +SOURCE_FILE@[0; 33) + FN_DEF@[0; 32) + 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; 32) + BLOCK@[10; 32) + 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; 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 [16; 20): An inclusive range must have an end expression +error [26; 29): An inclusive range must have an end expression 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 0000000000..f063ffadb3 --- /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 0000000000..ad0c4a3fef --- /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 0000000000..2c4ed11e1e --- /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 0000000000..bdfac9b764 --- /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"