Repository: rust-analyzer/rust-analyzer (rust)

Issue:
Bugs parsing casts in range expressions
This code causes red underlining in VS Code, even though rustc accepts it:

```rust
&buf[cur.position() as usize..]
```

(`Syntax Error: expected expression`, underlining only from the closing bracket.)

Pull request description:
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.

Patch:
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index dd43271f4c..962d5a8c15 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -157,7 +157,7 @@ impl Module {
     }
 
     /// Finds a child module with the specified name.
-    pub fn child(self, db: &impl DefDatabase, name: &Name) -> Option<Module> {
+    pub fn child(self, db: &impl HirDatabase, name: &Name) -> Option<Module> {
         let def_map = db.crate_def_map(self.id.krate);
         let child_id = def_map[self.id.module_id].children.get(name)?;
         Some(self.with_module_id(*child_id))
diff --git a/crates/ra_hir/src/from_source.rs b/crates/ra_hir/src/from_source.rs
index 7e5523c54a..9793af8587 100644
--- a/crates/ra_hir/src/from_source.rs
+++ b/crates/ra_hir/src/from_source.rs
@@ -1,6 +1,6 @@
 //! FIXME: write short doc here
 
-use hir_def::{ModuleId, StructId, StructOrUnionId, UnionId};
+use hir_def::{StructId, StructOrUnionId, UnionId};
 use hir_expand::name::AsName;
 use ra_syntax::{
     ast::{self, AstNode, NameOwner},
@@ -10,9 +10,9 @@ use ra_syntax::{
 use crate::{
     db::{AstDatabase, DefDatabase, HirDatabase},
     ids::{AstItemDef, LocationCtx},
-    Const, DefWithBody, Enum, EnumVariant, FieldSource, Function, HasBody, HasSource, ImplBlock,
-    Local, Module, ModuleSource, Source, Static, Struct, StructField, Trait, TypeAlias, Union,
-    VariantDef,
+    AstId, Const, Crate, DefWithBody, Enum, EnumVariant, FieldSource, Function, HasBody, HasSource,
+    ImplBlock, Local, Module, ModuleSource, Source, Static, Struct, StructField, Trait, TypeAlias,
+    Union, VariantDef,
 };
 
 pub trait FromSource: Sized {
@@ -152,48 +152,44 @@ impl Local {
 }
 
 impl Module {
-    pub fn from_declaration(db: &impl DefDatabase, src: Source<ast::Module>) -> Option<Self> {
-        let parent_declaration = src.ast.syntax().ancestors().skip(1).find_map(ast::Module::cast);
-
-        let parent_module = match parent_declaration {
-            Some(parent_declaration) => {
-                let src_parent = Source { file_id: src.file_id, ast: parent_declaration };
-                Module::from_declaration(db, src_parent)
-            }
-            _ => {
-                let src_parent = Source {
-                    file_id: src.file_id,
-                    ast: ModuleSource::new(db, Some(src.file_id.original_file(db)), None),
-                };
-                Module::from_definition(db, src_parent)
-            }
-        }?;
-
+    pub fn from_declaration(db: &impl HirDatabase, src: Source<ast::Module>) -> Option<Self> {
+        let src_parent = Source {
+            file_id: src.file_id,
+            ast: ModuleSource::new(db, Some(src.file_id.original_file(db)), None),
+        };
+        let parent_module = Module::from_definition(db, src_parent)?;
         let child_name = src.ast.name()?;
         parent_module.child(db, &child_name.as_name())
     }
 
-    pub fn from_definition(db: &impl DefDatabase, src: Source<ModuleSource>) -> Option<Self> {
-        match src.ast {
+    pub fn from_definition(
+        db: &(impl DefDatabase + AstDatabase),
+        src: Source<ModuleSource>,
+    ) -> Option<Self> {
+        let decl_id = match src.ast {
             ModuleSource::Module(ref module) => {
                 assert!(!module.has_semi());
-                return Module::from_declaration(
-                    db,
-                    Source { file_id: src.file_id, ast: module.clone() },
-                );
+                let ast_id_map = db.ast_id_map(src.file_id);
+                let item_id = AstId::new(src.file_id, ast_id_map.ast_id(module));
+                Some(item_id)
             }
-            ModuleSource::SourceFile(_) => (),
+            ModuleSource::SourceFile(_) => None,
         };
 
-        let original_file = src.file_id.original_file(db);
+        db.relevant_crates(src.file_id.original_file(db)).iter().find_map(|&crate_id| {
+            let def_map = db.crate_def_map(crate_id);
+
+            let (module_id, _module_data) =
+                def_map.modules.iter().find(|(_module_id, module_data)| {
+                    if decl_id.is_some() {
+                        module_data.declaration == decl_id
+                    } else {
+                        module_data.definition.map(|it| it.into()) == Some(src.file_id)
+                    }
+                })?;
 
-        let (krate, module_id) =
-            db.relevant_crates(original_file).iter().find_map(|&crate_id| {
-                let crate_def_map = db.crate_def_map(crate_id);
-                let local_module_id = crate_def_map.modules_for_file(original_file).next()?;
-                Some((crate_id, local_module_id))
-            })?;
-        Some(Module { id: ModuleId { krate, module_id } })
+            Some(Module::new(Crate { crate_id }, module_id))
+        })
     }
 }
 
diff --git a/crates/ra_hir_def/src/nameres.rs b/crates/ra_hir_def/src/nameres.rs
index 5fc5921500..d3ecabb9be 100644
--- a/crates/ra_hir_def/src/nameres.rs
+++ b/crates/ra_hir_def/src/nameres.rs
@@ -87,7 +87,7 @@ pub struct CrateDefMap {
     prelude: Option<ModuleId>,
     extern_prelude: FxHashMap<Name, ModuleDefId>,
     root: CrateModuleId,
-    modules: Arena<CrateModuleId, ModuleData>,
+    pub modules: Arena<CrateModuleId, ModuleData>,
 
     /// Some macros are not well-behavior, which leads to infinite loop
     /// e.g. macro_rules! foo { ($ty:ty) => { foo!($ty); } }
@@ -258,17 +258,6 @@ impl CrateDefMap {
         let res = self.resolve_path_fp_with_macro(db, ResolveMode::Other, original_module, path);
         (res.resolved_def, res.segment_index)
     }
-
-    pub fn modules(&self) -> impl Iterator<Item = CrateModuleId> + '_ {
-        self.modules.iter().map(|(id, _data)| id)
-    }
-
-    pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator<Item = CrateModuleId> + '_ {
-        self.modules
-            .iter()
-            .filter(move |(_id, data)| data.definition == Some(file_id))
-            .map(|(id, _data)| id)
-    }
 }
 
 mod diagnostics {
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<CompletedMarker>,
         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<CompletedMarker, CompletedMarker> {
         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<RangeOp> {
+        self.op_details().map(|t| t.2)
+    }
+
+    pub fn op_token(&self) -> Option<SyntaxToken> {
+        self.op_details().map(|t| t.1)
+    }
+
+    pub fn start(&self) -> Option<ast::Expr> {
+        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<ast::Expr> {
+        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<ast::Expr> {
         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<SyntaxError> {
                 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<SyntaxError>) {
             .push(SyntaxError::new(SyntaxErrorKind::VisibilityNotAllowed, vis.syntax.text_range()))
     }
 }
+
+fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec<SyntaxError>) {
+    if expr.op_kind() == Some(ast::RangeOp::Inclusive) && expr.end().is_none() {
+        errors.push(SyntaxError::new(
+            SyntaxErrorKind::InclusiveRangeMissingEnd,
+            expr.syntax().text_range(),
+        ));
+    }
+}


Test patch:
diff --git a/crates/ra_hir/src/test_db.rs b/crates/ra_hir/src/test_db.rs
index 1caa2e8759..5237b303af 100644
--- a/crates/ra_hir/src/test_db.rs
+++ b/crates/ra_hir/src/test_db.rs
@@ -81,7 +81,7 @@ impl TestDB {
         let crate_graph = self.crate_graph();
         for krate in crate_graph.iter().next() {
             let crate_def_map = self.crate_def_map(krate);
-            for module_id in crate_def_map.modules() {
+            for (module_id, _) in crate_def_map.modules.iter() {
                 let module_id = ModuleId { krate, module_id };
                 let module = crate::Module::from(module_id);
                 module.diagnostics(
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"