aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_cli/Cargo.toml1
-rw-r--r--crates/ra_cli/src/main.rs22
-rw-r--r--crates/ra_db/src/lib.rs2
-rw-r--r--crates/ra_db/src/loc2id.rs10
-rw-r--r--crates/ra_hir/src/expr.rs7
-rw-r--r--crates/ra_hir/src/ids.rs34
-rw-r--r--crates/ra_hir/src/name.rs5
-rw-r--r--crates/ra_hir/src/nameres/collector.rs2
-rw-r--r--crates/ra_hir/src/ty/lower.rs4
-rw-r--r--crates/ra_hir/src/ty/primitive.rs66
-rw-r--r--crates/ra_ide_api/src/extend_selection.rs377
-rw-r--r--crates/ra_ide_api/src/lib.rs17
-rw-r--r--crates/ra_ide_api_light/src/extend_selection.rs369
-rw-r--r--crates/ra_ide_api_light/src/lib.rs34
-rw-r--r--crates/ra_parser/src/event.rs4
-rw-r--r--crates/ra_parser/src/grammar/expressions.rs94
-rw-r--r--crates/ra_parser/src/grammar/expressions/atom.rs4
-rw-r--r--crates/ra_parser/src/parser.rs34
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs4
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt55
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs6
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt88
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs4
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt54
24 files changed, 810 insertions, 487 deletions
diff --git a/crates/ra_cli/Cargo.toml b/crates/ra_cli/Cargo.toml
index ff30bf0b3..4c666f556 100644
--- a/crates/ra_cli/Cargo.toml
+++ b/crates/ra_cli/Cargo.toml
@@ -13,6 +13,7 @@ flexi_logger = "0.11.0"
13indicatif = "0.11.0" 13indicatif = "0.11.0"
14 14
15ra_syntax = { path = "../ra_syntax" } 15ra_syntax = { path = "../ra_syntax" }
16ra_ide_api = { path = "../ra_ide_api" }
16ra_ide_api_light = { path = "../ra_ide_api_light" } 17ra_ide_api_light = { path = "../ra_ide_api_light" }
17tools = { path = "../tools" } 18tools = { path = "../tools" }
18ra_batch = { path = "../ra_batch" } 19ra_batch = { path = "../ra_batch" }
diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs
index 294f4b8af..5285f1f28 100644
--- a/crates/ra_cli/src/main.rs
+++ b/crates/ra_cli/src/main.rs
@@ -4,7 +4,8 @@ use std::{fs, io::Read, path::Path, time::Instant};
4 4
5use clap::{App, Arg, SubCommand}; 5use clap::{App, Arg, SubCommand};
6use join_to_string::join; 6use join_to_string::join;
7use ra_ide_api_light::{extend_selection, file_structure}; 7use ra_ide_api::{Analysis, FileRange};
8use ra_ide_api_light::file_structure;
8use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode}; 9use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode};
9use tools::collect_tests; 10use tools::collect_tests;
10use flexi_logger::Logger; 11use flexi_logger::Logger;
@@ -59,8 +60,8 @@ fn main() -> Result<()> {
59 ("extend-selection", Some(matches)) => { 60 ("extend-selection", Some(matches)) => {
60 let start: u32 = matches.value_of("start").unwrap().parse()?; 61 let start: u32 = matches.value_of("start").unwrap().parse()?;
61 let end: u32 = matches.value_of("end").unwrap().parse()?; 62 let end: u32 = matches.value_of("end").unwrap().parse()?;
62 let file = file()?; 63 let text = read_stdin()?;
63 let sels = selections(&file, start, end); 64 let sels = selections(text, start, end);
64 println!("{}", sels) 65 println!("{}", sels)
65 } 66 }
66 ("analysis-stats", Some(matches)) => { 67 ("analysis-stats", Some(matches)) => {
@@ -98,12 +99,17 @@ fn render_test(file: &Path, line: usize) -> Result<(String, String)> {
98 Ok((test.text, tree)) 99 Ok((test.text, tree))
99} 100}
100 101
101fn selections(file: &SourceFile, start: u32, end: u32) -> String { 102fn selections(text: String, start: u32, end: u32) -> String {
103 let (analysis, file_id) = Analysis::from_single_file(text);
102 let mut ranges = Vec::new(); 104 let mut ranges = Vec::new();
103 let mut cur = Some(TextRange::from_to((start - 1).into(), (end - 1).into())); 105 let mut range = TextRange::from_to((start - 1).into(), (end - 1).into());
104 while let Some(r) = cur { 106 loop {
105 ranges.push(r); 107 ranges.push(range);
106 cur = extend_selection(file.syntax(), r); 108 let next = analysis.extend_selection(FileRange { file_id, range }).unwrap();
109 if range == next {
110 break;
111 }
112 range = next;
107 } 113 }
108 let ranges = ranges 114 let ranges = ranges
109 .iter() 115 .iter()
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs
index e006c6d27..f3389c91f 100644
--- a/crates/ra_db/src/lib.rs
+++ b/crates/ra_db/src/lib.rs
@@ -16,7 +16,7 @@ pub use crate::{
16 input::{ 16 input::{
17 FileId, CrateId, SourceRoot, SourceRootId, CrateGraph, Dependency, Edition, 17 FileId, CrateId, SourceRoot, SourceRootId, CrateGraph, Dependency, Edition,
18 }, 18 },
19 loc2id::LocationIntener, 19 loc2id::LocationInterner,
20}; 20};
21 21
22pub trait CheckCanceled: panic::RefUnwindSafe { 22pub trait CheckCanceled: panic::RefUnwindSafe {
diff --git a/crates/ra_db/src/loc2id.rs b/crates/ra_db/src/loc2id.rs
index d27fa7682..eae64a4eb 100644
--- a/crates/ra_db/src/loc2id.rs
+++ b/crates/ra_db/src/loc2id.rs
@@ -59,7 +59,7 @@ where
59} 59}
60 60
61#[derive(Debug)] 61#[derive(Debug)]
62pub struct LocationIntener<LOC, ID> 62pub struct LocationInterner<LOC, ID>
63where 63where
64 ID: ArenaId + Clone, 64 ID: ArenaId + Clone,
65 LOC: Clone + Eq + Hash, 65 LOC: Clone + Eq + Hash,
@@ -67,7 +67,7 @@ where
67 map: Mutex<Loc2IdMap<LOC, ID>>, 67 map: Mutex<Loc2IdMap<LOC, ID>>,
68} 68}
69 69
70impl<LOC, ID> panic::RefUnwindSafe for LocationIntener<LOC, ID> 70impl<LOC, ID> panic::RefUnwindSafe for LocationInterner<LOC, ID>
71where 71where
72 ID: ArenaId + Clone, 72 ID: ArenaId + Clone,
73 LOC: Clone + Eq + Hash, 73 LOC: Clone + Eq + Hash,
@@ -76,17 +76,17 @@ where
76{ 76{
77} 77}
78 78
79impl<LOC, ID> Default for LocationIntener<LOC, ID> 79impl<LOC, ID> Default for LocationInterner<LOC, ID>
80where 80where
81 ID: ArenaId + Clone, 81 ID: ArenaId + Clone,
82 LOC: Clone + Eq + Hash, 82 LOC: Clone + Eq + Hash,
83{ 83{
84 fn default() -> Self { 84 fn default() -> Self {
85 LocationIntener { map: Default::default() } 85 LocationInterner { map: Default::default() }
86 } 86 }
87} 87}
88 88
89impl<LOC, ID> LocationIntener<LOC, ID> 89impl<LOC, ID> LocationInterner<LOC, ID>
90where 90where
91 ID: ArenaId + Clone, 91 ID: ArenaId + Clone,
92 LOC: Clone + Eq + Hash, 92 LOC: Clone + Eq + Hash,
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs
index 6c7489e63..486314cc5 100644
--- a/crates/ra_hir/src/expr.rs
+++ b/crates/ra_hir/src/expr.rs
@@ -723,8 +723,7 @@ impl ExprCollector {
723 723
724 let lit = match child.flavor() { 724 let lit = match child.flavor() {
725 LiteralFlavor::IntNumber { suffix } => { 725 LiteralFlavor::IntNumber { suffix } => {
726 let known_name = 726 let known_name = suffix.and_then(|it| UncertainIntTy::from_suffix(&it));
727 suffix.map(Name::new).and_then(|name| UncertainIntTy::from_name(&name));
728 727
729 Literal::Int( 728 Literal::Int(
730 Default::default(), 729 Default::default(),
@@ -732,9 +731,7 @@ impl ExprCollector {
732 ) 731 )
733 } 732 }
734 LiteralFlavor::FloatNumber { suffix } => { 733 LiteralFlavor::FloatNumber { suffix } => {
735 let known_name = suffix 734 let known_name = suffix.and_then(|it| UncertainFloatTy::from_suffix(&it));
736 .map(Name::new)
737 .and_then(|name| UncertainFloatTy::from_name(&name));
738 735
739 Literal::Float( 736 Literal::Float(
740 Default::default(), 737 Default::default(),
diff --git a/crates/ra_hir/src/ids.rs b/crates/ra_hir/src/ids.rs
index 9596488d3..3d0a881c2 100644
--- a/crates/ra_hir/src/ids.rs
+++ b/crates/ra_hir/src/ids.rs
@@ -4,7 +4,7 @@ use std::{
4 sync::Arc, 4 sync::Arc,
5}; 5};
6 6
7use ra_db::{LocationIntener, FileId}; 7use ra_db::{LocationInterner, FileId};
8use ra_syntax::{TreeArc, SyntaxNode, SourceFile, AstNode, SyntaxNodePtr, ast}; 8use ra_syntax::{TreeArc, SyntaxNode, SourceFile, AstNode, SyntaxNodePtr, ast};
9use ra_arena::{Arena, RawId, ArenaId, impl_arena_id}; 9use ra_arena::{Arena, RawId, ArenaId, impl_arena_id};
10 10
@@ -15,14 +15,14 @@ use crate::{
15 15
16#[derive(Debug, Default)] 16#[derive(Debug, Default)]
17pub struct HirInterner { 17pub struct HirInterner {
18 macros: LocationIntener<MacroCallLoc, MacroCallId>, 18 macros: LocationInterner<MacroCallLoc, MacroCallId>,
19 fns: LocationIntener<ItemLoc<ast::FnDef>, FunctionId>, 19 fns: LocationInterner<ItemLoc<ast::FnDef>, FunctionId>,
20 structs: LocationIntener<ItemLoc<ast::StructDef>, StructId>, 20 structs: LocationInterner<ItemLoc<ast::StructDef>, StructId>,
21 enums: LocationIntener<ItemLoc<ast::EnumDef>, EnumId>, 21 enums: LocationInterner<ItemLoc<ast::EnumDef>, EnumId>,
22 consts: LocationIntener<ItemLoc<ast::ConstDef>, ConstId>, 22 consts: LocationInterner<ItemLoc<ast::ConstDef>, ConstId>,
23 statics: LocationIntener<ItemLoc<ast::StaticDef>, StaticId>, 23 statics: LocationInterner<ItemLoc<ast::StaticDef>, StaticId>,
24 traits: LocationIntener<ItemLoc<ast::TraitDef>, TraitId>, 24 traits: LocationInterner<ItemLoc<ast::TraitDef>, TraitId>,
25 types: LocationIntener<ItemLoc<ast::TypeAliasDef>, TypeId>, 25 types: LocationInterner<ItemLoc<ast::TypeAliasDef>, TypeId>,
26} 26}
27 27
28impl HirInterner { 28impl HirInterner {
@@ -204,7 +204,7 @@ impl<'a, DB: PersistentHirDatabase> LocationCtx<&'a DB> {
204} 204}
205 205
206pub(crate) trait AstItemDef<N: AstNode>: ArenaId + Clone { 206pub(crate) trait AstItemDef<N: AstNode>: ArenaId + Clone {
207 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<N>, Self>; 207 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<N>, Self>;
208 fn from_ast(ctx: LocationCtx<&impl PersistentHirDatabase>, ast: &N) -> Self { 208 fn from_ast(ctx: LocationCtx<&impl PersistentHirDatabase>, ast: &N) -> Self {
209 let items = ctx.db.file_items(ctx.file_id); 209 let items = ctx.db.file_items(ctx.file_id);
210 let item_id = items.id_of(ctx.file_id, ast.syntax()); 210 let item_id = items.id_of(ctx.file_id, ast.syntax());
@@ -238,7 +238,7 @@ pub(crate) trait AstItemDef<N: AstNode>: ArenaId + Clone {
238pub struct FunctionId(RawId); 238pub struct FunctionId(RawId);
239impl_arena_id!(FunctionId); 239impl_arena_id!(FunctionId);
240impl AstItemDef<ast::FnDef> for FunctionId { 240impl AstItemDef<ast::FnDef> for FunctionId {
241 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::FnDef>, Self> { 241 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::FnDef>, Self> {
242 &interner.fns 242 &interner.fns
243 } 243 }
244} 244}
@@ -247,7 +247,7 @@ impl AstItemDef<ast::FnDef> for FunctionId {
247pub struct StructId(RawId); 247pub struct StructId(RawId);
248impl_arena_id!(StructId); 248impl_arena_id!(StructId);
249impl AstItemDef<ast::StructDef> for StructId { 249impl AstItemDef<ast::StructDef> for StructId {
250 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::StructDef>, Self> { 250 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::StructDef>, Self> {
251 &interner.structs 251 &interner.structs
252 } 252 }
253} 253}
@@ -256,7 +256,7 @@ impl AstItemDef<ast::StructDef> for StructId {
256pub struct EnumId(RawId); 256pub struct EnumId(RawId);
257impl_arena_id!(EnumId); 257impl_arena_id!(EnumId);
258impl AstItemDef<ast::EnumDef> for EnumId { 258impl AstItemDef<ast::EnumDef> for EnumId {
259 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::EnumDef>, Self> { 259 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::EnumDef>, Self> {
260 &interner.enums 260 &interner.enums
261 } 261 }
262} 262}
@@ -265,7 +265,7 @@ impl AstItemDef<ast::EnumDef> for EnumId {
265pub struct ConstId(RawId); 265pub struct ConstId(RawId);
266impl_arena_id!(ConstId); 266impl_arena_id!(ConstId);
267impl AstItemDef<ast::ConstDef> for ConstId { 267impl AstItemDef<ast::ConstDef> for ConstId {
268 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::ConstDef>, Self> { 268 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::ConstDef>, Self> {
269 &interner.consts 269 &interner.consts
270 } 270 }
271} 271}
@@ -274,7 +274,7 @@ impl AstItemDef<ast::ConstDef> for ConstId {
274pub struct StaticId(RawId); 274pub struct StaticId(RawId);
275impl_arena_id!(StaticId); 275impl_arena_id!(StaticId);
276impl AstItemDef<ast::StaticDef> for StaticId { 276impl AstItemDef<ast::StaticDef> for StaticId {
277 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::StaticDef>, Self> { 277 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::StaticDef>, Self> {
278 &interner.statics 278 &interner.statics
279 } 279 }
280} 280}
@@ -283,7 +283,7 @@ impl AstItemDef<ast::StaticDef> for StaticId {
283pub struct TraitId(RawId); 283pub struct TraitId(RawId);
284impl_arena_id!(TraitId); 284impl_arena_id!(TraitId);
285impl AstItemDef<ast::TraitDef> for TraitId { 285impl AstItemDef<ast::TraitDef> for TraitId {
286 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::TraitDef>, Self> { 286 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::TraitDef>, Self> {
287 &interner.traits 287 &interner.traits
288 } 288 }
289} 289}
@@ -292,7 +292,7 @@ impl AstItemDef<ast::TraitDef> for TraitId {
292pub struct TypeId(RawId); 292pub struct TypeId(RawId);
293impl_arena_id!(TypeId); 293impl_arena_id!(TypeId);
294impl AstItemDef<ast::TypeAliasDef> for TypeId { 294impl AstItemDef<ast::TypeAliasDef> for TypeId {
295 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::TypeAliasDef>, Self> { 295 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::TypeAliasDef>, Self> {
296 &interner.types 296 &interner.types
297 } 297 }
298} 298}
diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir/src/name.rs
index 06bafa6f0..677d18efc 100644
--- a/crates/ra_hir/src/name.rs
+++ b/crates/ra_hir/src/name.rs
@@ -23,7 +23,10 @@ impl fmt::Debug for Name {
23} 23}
24 24
25impl Name { 25impl Name {
26 pub(crate) fn new(text: SmolStr) -> Name { 26 /// Note: this is private to make creating name from random string hard.
27 /// Hopefully, this should allow us to integrate hygiene cleaner in the
28 /// future, and to switch to interned representation of names.
29 fn new(text: SmolStr) -> Name {
27 Name { text } 30 Name { text }
28 } 31 }
29 32
diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs
index 12ed49a0a..3ea8d592c 100644
--- a/crates/ra_hir/src/nameres/collector.rs
+++ b/crates/ra_hir/src/nameres/collector.rs
@@ -124,7 +124,7 @@ where
124 } 124 }
125 125
126 fn resolve_import( 126 fn resolve_import(
127 &mut self, 127 &self,
128 module_id: CrateModuleId, 128 module_id: CrateModuleId,
129 import: &raw::ImportData, 129 import: &raw::ImportData,
130 ) -> (PerNs<ModuleDef>, ReachedFixedPoint) { 130 ) -> (PerNs<ModuleDef>, ReachedFixedPoint) {
diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs
index 278f592d3..389a2fc68 100644
--- a/crates/ra_hir/src/ty/lower.rs
+++ b/crates/ra_hir/src/ty/lower.rs
@@ -63,9 +63,9 @@ impl Ty {
63 pub(crate) fn from_hir_path(db: &impl HirDatabase, resolver: &Resolver, path: &Path) -> Self { 63 pub(crate) fn from_hir_path(db: &impl HirDatabase, resolver: &Resolver, path: &Path) -> Self {
64 if let Some(name) = path.as_ident() { 64 if let Some(name) = path.as_ident() {
65 // TODO handle primitive type names in resolver as well? 65 // TODO handle primitive type names in resolver as well?
66 if let Some(int_ty) = primitive::UncertainIntTy::from_name(name) { 66 if let Some(int_ty) = primitive::UncertainIntTy::from_type_name(name) {
67 return Ty::Int(int_ty); 67 return Ty::Int(int_ty);
68 } else if let Some(float_ty) = primitive::UncertainFloatTy::from_name(name) { 68 } else if let Some(float_ty) = primitive::UncertainFloatTy::from_type_name(name) {
69 return Ty::Float(float_ty); 69 return Ty::Float(float_ty);
70 } else if let Some(known) = name.as_known_name() { 70 } else if let Some(known) = name.as_known_name() {
71 match known { 71 match known {
diff --git a/crates/ra_hir/src/ty/primitive.rs b/crates/ra_hir/src/ty/primitive.rs
index 30aeac48e..421f7e980 100644
--- a/crates/ra_hir/src/ty/primitive.rs
+++ b/crates/ra_hir/src/ty/primitive.rs
@@ -10,10 +10,20 @@ pub enum UncertainIntTy {
10} 10}
11 11
12impl UncertainIntTy { 12impl UncertainIntTy {
13 pub fn from_name(name: &Name) -> Option<UncertainIntTy> { 13 pub(crate) fn from_type_name(name: &Name) -> Option<UncertainIntTy> {
14 if let Some(ty) = IntTy::from_name(name) { 14 if let Some(ty) = IntTy::from_type_name(name) {
15 Some(UncertainIntTy::Signed(ty)) 15 Some(UncertainIntTy::Signed(ty))
16 } else if let Some(ty) = UintTy::from_name(name) { 16 } else if let Some(ty) = UintTy::from_type_name(name) {
17 Some(UncertainIntTy::Unsigned(ty))
18 } else {
19 None
20 }
21 }
22
23 pub(crate) fn from_suffix(suffix: &str) -> Option<UncertainIntTy> {
24 if let Some(ty) = IntTy::from_suffix(suffix) {
25 Some(UncertainIntTy::Signed(ty))
26 } else if let Some(ty) = UintTy::from_suffix(suffix) {
17 Some(UncertainIntTy::Unsigned(ty)) 27 Some(UncertainIntTy::Unsigned(ty))
18 } else { 28 } else {
19 None 29 None
@@ -38,12 +48,12 @@ pub enum UncertainFloatTy {
38} 48}
39 49
40impl UncertainFloatTy { 50impl UncertainFloatTy {
41 pub fn from_name(name: &Name) -> Option<UncertainFloatTy> { 51 pub(crate) fn from_type_name(name: &Name) -> Option<UncertainFloatTy> {
42 if let Some(ty) = FloatTy::from_name(name) { 52 FloatTy::from_type_name(name).map(UncertainFloatTy::Known)
43 Some(UncertainFloatTy::Known(ty)) 53 }
44 } else { 54
45 None 55 pub(crate) fn from_suffix(suffix: &str) -> Option<UncertainFloatTy> {
46 } 56 FloatTy::from_suffix(suffix).map(UncertainFloatTy::Known)
47 } 57 }
48} 58}
49 59
@@ -87,7 +97,7 @@ impl fmt::Display for IntTy {
87} 97}
88 98
89impl IntTy { 99impl IntTy {
90 pub fn from_name(name: &Name) -> Option<IntTy> { 100 fn from_type_name(name: &Name) -> Option<IntTy> {
91 match name.as_known_name()? { 101 match name.as_known_name()? {
92 KnownName::Isize => Some(IntTy::Isize), 102 KnownName::Isize => Some(IntTy::Isize),
93 KnownName::I8 => Some(IntTy::I8), 103 KnownName::I8 => Some(IntTy::I8),
@@ -98,6 +108,18 @@ impl IntTy {
98 _ => None, 108 _ => None,
99 } 109 }
100 } 110 }
111
112 fn from_suffix(suffix: &str) -> Option<IntTy> {
113 match suffix {
114 "isize" => Some(IntTy::Isize),
115 "i8" => Some(IntTy::I8),
116 "i16" => Some(IntTy::I16),
117 "i32" => Some(IntTy::I32),
118 "i64" => Some(IntTy::I64),
119 "i128" => Some(IntTy::I128),
120 _ => None,
121 }
122 }
101} 123}
102 124
103#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)] 125#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
@@ -125,7 +147,7 @@ impl fmt::Display for UintTy {
125} 147}
126 148
127impl UintTy { 149impl UintTy {
128 pub fn from_name(name: &Name) -> Option<UintTy> { 150 fn from_type_name(name: &Name) -> Option<UintTy> {
129 match name.as_known_name()? { 151 match name.as_known_name()? {
130 KnownName::Usize => Some(UintTy::Usize), 152 KnownName::Usize => Some(UintTy::Usize),
131 KnownName::U8 => Some(UintTy::U8), 153 KnownName::U8 => Some(UintTy::U8),
@@ -136,6 +158,18 @@ impl UintTy {
136 _ => None, 158 _ => None,
137 } 159 }
138 } 160 }
161
162 fn from_suffix(suffix: &str) -> Option<UintTy> {
163 match suffix {
164 "usize" => Some(UintTy::Usize),
165 "u8" => Some(UintTy::U8),
166 "u16" => Some(UintTy::U16),
167 "u32" => Some(UintTy::U32),
168 "u64" => Some(UintTy::U64),
169 "u128" => Some(UintTy::U128),
170 _ => None,
171 }
172 }
139} 173}
140 174
141impl fmt::Debug for UintTy { 175impl fmt::Debug for UintTy {
@@ -170,11 +204,19 @@ impl FloatTy {
170 } 204 }
171 } 205 }
172 206
173 pub fn from_name(name: &Name) -> Option<FloatTy> { 207 fn from_type_name(name: &Name) -> Option<FloatTy> {
174 match name.as_known_name()? { 208 match name.as_known_name()? {
175 KnownName::F32 => Some(FloatTy::F32), 209 KnownName::F32 => Some(FloatTy::F32),
176 KnownName::F64 => Some(FloatTy::F64), 210 KnownName::F64 => Some(FloatTy::F64),
177 _ => None, 211 _ => None,
178 } 212 }
179 } 213 }
214
215 fn from_suffix(suffix: &str) -> Option<FloatTy> {
216 match suffix {
217 "f32" => Some(FloatTy::F32),
218 "f64" => Some(FloatTy::F64),
219 _ => None,
220 }
221 }
180} 222}
diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs
index d23290b74..63879a0b5 100644
--- a/crates/ra_ide_api/src/extend_selection.rs
+++ b/crates/ra_ide_api/src/extend_selection.rs
@@ -1,13 +1,378 @@
1use ra_db::SourceDatabase; 1use ra_db::SourceDatabase;
2use ra_syntax::AstNode; 2use ra_syntax::{
3 3 Direction, SyntaxNode, TextRange, TextUnit, AstNode,
4use crate::{ 4 algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
5 TextRange, FileRange, 5 SyntaxKind::*,
6 db::RootDatabase,
7}; 6};
8 7
8use crate::{FileRange, db::RootDatabase};
9
9// FIXME: restore macro support 10// FIXME: restore macro support
10pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 11pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
11 let source_file = db.parse(frange.file_id); 12 let source_file = db.parse(frange.file_id);
12 ra_ide_api_light::extend_selection(source_file.syntax(), frange.range).unwrap_or(frange.range) 13 try_extend_selection(source_file.syntax(), frange.range).unwrap_or(frange.range)
14}
15
16fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> {
17 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
18 let list_kinds = [
19 FIELD_PAT_LIST,
20 MATCH_ARM_LIST,
21 NAMED_FIELD_DEF_LIST,
22 POS_FIELD_DEF_LIST,
23 NAMED_FIELD_LIST,
24 ENUM_VARIANT_LIST,
25 USE_TREE_LIST,
26 TYPE_PARAM_LIST,
27 TYPE_ARG_LIST,
28 PARAM_LIST,
29 ARG_LIST,
30 ARRAY_EXPR,
31 ];
32
33 if range.is_empty() {
34 let offset = range.start();
35 let mut leaves = find_leaf_at_offset(root, offset);
36 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
37 return Some(extend_ws(root, leaves.next()?, offset));
38 }
39 let leaf_range = match leaves {
40 LeafAtOffset::None => return None,
41 LeafAtOffset::Single(l) => {
42 if string_kinds.contains(&l.kind()) {
43 extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range())
44 } else {
45 l.range()
46 }
47 }
48 LeafAtOffset::Between(l, r) => pick_best(l, r).range(),
49 };
50 return Some(leaf_range);
51 };
52 let node = find_covering_node(root, range);
53
54 // Using shallowest node with same range allows us to traverse siblings.
55 let node = node.ancestors().take_while(|n| n.range() == node.range()).last().unwrap();
56
57 if range == node.range() {
58 if string_kinds.contains(&node.kind()) {
59 if let Some(range) = extend_comments(node) {
60 return Some(range);
61 }
62 }
63
64 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
65 if let Some(range) = extend_list_item(node) {
66 return Some(range);
67 }
68 }
69 }
70
71 match node.ancestors().skip_while(|n| n.range() == range).next() {
72 None => None,
73 Some(parent) => Some(parent.range()),
74 }
75}
76
77fn extend_single_word_in_comment_or_string(
78 leaf: &SyntaxNode,
79 offset: TextUnit,
80) -> Option<TextRange> {
81 let text: &str = leaf.leaf_text()?;
82 let cursor_position: u32 = (offset - leaf.range().start()).into();
83
84 let (before, after) = text.split_at(cursor_position as usize);
85
86 fn non_word_char(c: char) -> bool {
87 !(c.is_alphanumeric() || c == '_')
88 }
89
90 let start_idx = before.rfind(non_word_char)? as u32;
91 let end_idx = after.find(non_word_char).unwrap_or(after.len()) as u32;
92
93 let from: TextUnit = (start_idx + 1).into();
94 let to: TextUnit = (cursor_position + end_idx).into();
95
96 let range = TextRange::from_to(from, to);
97 if range.is_empty() {
98 None
99 } else {
100 Some(range + leaf.range().start())
101 }
102}
103
104fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange {
105 let ws_text = ws.leaf_text().unwrap();
106 let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start();
107 let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start();
108 let ws_suffix = &ws_text.as_str()[suffix];
109 let ws_prefix = &ws_text.as_str()[prefix];
110 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
111 if let Some(node) = ws.next_sibling() {
112 let start = match ws_prefix.rfind('\n') {
113 Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32),
114 None => node.range().start(),
115 };
116 let end = if root.text().char_at(node.range().end()) == Some('\n') {
117 node.range().end() + TextUnit::of_char('\n')
118 } else {
119 node.range().end()
120 };
121 return TextRange::from_to(start, end);
122 }
123 }
124 ws.range()
125}
126
127fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode {
128 return if priority(r) > priority(l) { r } else { l };
129 fn priority(n: &SyntaxNode) -> usize {
130 match n.kind() {
131 WHITESPACE => 0,
132 IDENT | SELF_KW | SUPER_KW | CRATE_KW | LIFETIME => 2,
133 _ => 1,
134 }
135 }
136}
137
138/// Extend list item selection to include nearby comma and whitespace.
139fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
140 fn is_single_line_ws(node: &SyntaxNode) -> bool {
141 node.kind() == WHITESPACE && !node.leaf_text().unwrap().contains('\n')
142 }
143
144 fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<&SyntaxNode> {
145 node.siblings(dir)
146 .skip(1)
147 .skip_while(|node| is_single_line_ws(node))
148 .next()
149 .filter(|node| node.kind() == COMMA)
150 }
151
152 if let Some(comma_node) = nearby_comma(node, Direction::Prev) {
153 return Some(TextRange::from_to(comma_node.range().start(), node.range().end()));
154 }
155
156 if let Some(comma_node) = nearby_comma(node, Direction::Next) {
157 // Include any following whitespace when comma if after list item.
158 let final_node = comma_node
159 .siblings(Direction::Next)
160 .skip(1)
161 .next()
162 .filter(|node| is_single_line_ws(node))
163 .unwrap_or(comma_node);
164
165 return Some(TextRange::from_to(node.range().start(), final_node.range().end()));
166 }
167
168 return None;
169}
170
171fn extend_comments(node: &SyntaxNode) -> Option<TextRange> {
172 let prev = adj_comments(node, Direction::Prev);
173 let next = adj_comments(node, Direction::Next);
174 if prev != next {
175 Some(TextRange::from_to(prev.range().start(), next.range().end()))
176 } else {
177 None
178 }
179}
180
181fn adj_comments(node: &SyntaxNode, dir: Direction) -> &SyntaxNode {
182 let mut res = node;
183 for node in node.siblings(dir) {
184 match node.kind() {
185 COMMENT => res = node,
186 WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (),
187 _ => break,
188 }
189 }
190 res
191}
192
193#[cfg(test)]
194mod tests {
195 use ra_syntax::{SourceFile, AstNode};
196 use test_utils::extract_offset;
197
198 use super::*;
199
200 fn do_check(before: &str, afters: &[&str]) {
201 let (cursor, before) = extract_offset(before);
202 let file = SourceFile::parse(&before);
203 let mut range = TextRange::offset_len(cursor, 0.into());
204 for &after in afters {
205 range = try_extend_selection(file.syntax(), range).unwrap();
206 let actual = &before[range];
207 assert_eq!(after, actual);
208 }
209 }
210
211 #[test]
212 fn test_extend_selection_arith() {
213 do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
214 }
215
216 #[test]
217 fn test_extend_selection_list() {
218 do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
219 do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
220 do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,"]);
221 do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
222 do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", ", y: i32"]);
223 do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
224
225 do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
226 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
227 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", ", 33"]);
228
229 do_check(
230 r#"
231const FOO: [usize; 2] = [
232 22,
233 <|>33,
234]"#,
235 &["33", "33,"],
236 );
237
238 do_check(
239 r#"
240const FOO: [usize; 2] = [
241 22
242 , 33<|>,
243]"#,
244 &["33", ", 33"],
245 );
246 }
247
248 #[test]
249 fn test_extend_selection_start_of_the_line() {
250 do_check(
251 r#"
252impl S {
253<|> fn foo() {
254
255 }
256}"#,
257 &[" fn foo() {\n\n }\n"],
258 );
259 }
260
261 #[test]
262 fn test_extend_selection_doc_comments() {
263 do_check(
264 r#"
265struct A;
266
267/// bla
268/// bla
269struct B {
270 <|>
271}
272 "#,
273 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
274 )
275 }
276
277 #[test]
278 fn test_extend_selection_comments() {
279 do_check(
280 r#"
281fn bar(){}
282
283// fn foo() {
284// 1 + <|>1
285// }
286
287// fn foo(){}
288 "#,
289 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
290 );
291
292 do_check(
293 r#"
294// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
295// pub enum Direction {
296// <|> Next,
297// Prev
298// }
299"#,
300 &[
301 "// Next,",
302 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
303 ],
304 );
305
306 do_check(
307 r#"
308/*
309foo
310_bar1<|>*/
311 "#,
312 &["_bar1", "/*\nfoo\n_bar1*/"],
313 );
314
315 do_check(
316 r#"
317//!<|>foo_2 bar
318 "#,
319 &["foo_2", "//!foo_2 bar"],
320 );
321
322 do_check(
323 r#"
324/<|>/foo bar
325 "#,
326 &["//foo bar"],
327 );
328 }
329
330 #[test]
331 fn test_extend_selection_prefer_idents() {
332 do_check(
333 r#"
334fn main() { foo<|>+bar;}
335 "#,
336 &["foo", "foo+bar"],
337 );
338 do_check(
339 r#"
340fn main() { foo+<|>bar;}
341 "#,
342 &["bar", "foo+bar"],
343 );
344 }
345
346 #[test]
347 fn test_extend_selection_prefer_lifetimes() {
348 do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
349 do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
350 }
351
352 #[test]
353 fn test_extend_selection_select_first_word() {
354 do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
355 do_check(
356 r#"
357impl S {
358 fn foo() {
359 // hel<|>lo world
360 }
361}
362 "#,
363 &["hello", "// hello world"],
364 );
365 }
366
367 #[test]
368 fn test_extend_selection_string() {
369 do_check(
370 r#"
371fn bar(){}
372
373" fn f<|>oo() {"
374 "#,
375 &["foo", "\" fn foo() {\""],
376 );
377 }
13} 378}
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index b8a4adbce..81ca57035 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -211,6 +211,23 @@ pub struct Analysis {
211// API, the API should in theory be usable as a library, or via a different 211// API, the API should in theory be usable as a library, or via a different
212// protocol. 212// protocol.
213impl Analysis { 213impl Analysis {
214 // Creates an analysis instance for a single file, without any extenal
215 // dependencies, stdlib support or ability to apply changes. See
216 // `AnalysisHost` for creating a fully-featured analysis.
217 pub fn from_single_file(text: String) -> (Analysis, FileId) {
218 let mut host = AnalysisHost::default();
219 let source_root = SourceRootId(0);
220 let mut change = AnalysisChange::new();
221 change.add_root(source_root, true);
222 let mut crate_graph = CrateGraph::default();
223 let file_id = FileId(0);
224 crate_graph.add_crate_root(file_id, Edition::Edition2018);
225 change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text));
226 change.set_crate_graph(crate_graph);
227 host.apply_change(change);
228 (host.analysis(), file_id)
229 }
230
214 /// Debug info about the current state of the analysis 231 /// Debug info about the current state of the analysis
215 pub fn status(&self) -> String { 232 pub fn status(&self) -> String {
216 status::status(&*self.db) 233 status::status(&*self.db)
diff --git a/crates/ra_ide_api_light/src/extend_selection.rs b/crates/ra_ide_api_light/src/extend_selection.rs
deleted file mode 100644
index 28d62f290..000000000
--- a/crates/ra_ide_api_light/src/extend_selection.rs
+++ /dev/null
@@ -1,369 +0,0 @@
1use ra_syntax::{
2 Direction, SyntaxNode, TextRange, TextUnit,
3 algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
4 SyntaxKind::*,
5};
6
7pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> {
8 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
9 let list_kinds = [
10 FIELD_PAT_LIST,
11 MATCH_ARM_LIST,
12 NAMED_FIELD_DEF_LIST,
13 POS_FIELD_DEF_LIST,
14 NAMED_FIELD_LIST,
15 ENUM_VARIANT_LIST,
16 USE_TREE_LIST,
17 TYPE_PARAM_LIST,
18 TYPE_ARG_LIST,
19 PARAM_LIST,
20 ARG_LIST,
21 ARRAY_EXPR,
22 ];
23
24 if range.is_empty() {
25 let offset = range.start();
26 let mut leaves = find_leaf_at_offset(root, offset);
27 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
28 return Some(extend_ws(root, leaves.next()?, offset));
29 }
30 let leaf_range = match leaves {
31 LeafAtOffset::None => return None,
32 LeafAtOffset::Single(l) => {
33 if string_kinds.contains(&l.kind()) {
34 extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range())
35 } else {
36 l.range()
37 }
38 }
39 LeafAtOffset::Between(l, r) => pick_best(l, r).range(),
40 };
41 return Some(leaf_range);
42 };
43 let node = find_covering_node(root, range);
44
45 // Using shallowest node with same range allows us to traverse siblings.
46 let node = node.ancestors().take_while(|n| n.range() == node.range()).last().unwrap();
47
48 if range == node.range() {
49 if string_kinds.contains(&node.kind()) {
50 if let Some(range) = extend_comments(node) {
51 return Some(range);
52 }
53 }
54
55 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
56 if let Some(range) = extend_list_item(node) {
57 return Some(range);
58 }
59 }
60 }
61
62 match node.ancestors().skip_while(|n| n.range() == range).next() {
63 None => None,
64 Some(parent) => Some(parent.range()),
65 }
66}
67
68fn extend_single_word_in_comment_or_string(
69 leaf: &SyntaxNode,
70 offset: TextUnit,
71) -> Option<TextRange> {
72 let text: &str = leaf.leaf_text()?;
73 let cursor_position: u32 = (offset - leaf.range().start()).into();
74
75 let (before, after) = text.split_at(cursor_position as usize);
76
77 fn non_word_char(c: char) -> bool {
78 !(c.is_alphanumeric() || c == '_')
79 }
80
81 let start_idx = before.rfind(non_word_char)? as u32;
82 let end_idx = after.find(non_word_char).unwrap_or(after.len()) as u32;
83
84 let from: TextUnit = (start_idx + 1).into();
85 let to: TextUnit = (cursor_position + end_idx).into();
86
87 let range = TextRange::from_to(from, to);
88 if range.is_empty() {
89 None
90 } else {
91 Some(range + leaf.range().start())
92 }
93}
94
95fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange {
96 let ws_text = ws.leaf_text().unwrap();
97 let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start();
98 let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start();
99 let ws_suffix = &ws_text.as_str()[suffix];
100 let ws_prefix = &ws_text.as_str()[prefix];
101 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
102 if let Some(node) = ws.next_sibling() {
103 let start = match ws_prefix.rfind('\n') {
104 Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32),
105 None => node.range().start(),
106 };
107 let end = if root.text().char_at(node.range().end()) == Some('\n') {
108 node.range().end() + TextUnit::of_char('\n')
109 } else {
110 node.range().end()
111 };
112 return TextRange::from_to(start, end);
113 }
114 }
115 ws.range()
116}
117
118fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode {
119 return if priority(r) > priority(l) { r } else { l };
120 fn priority(n: &SyntaxNode) -> usize {
121 match n.kind() {
122 WHITESPACE => 0,
123 IDENT | SELF_KW | SUPER_KW | CRATE_KW | LIFETIME => 2,
124 _ => 1,
125 }
126 }
127}
128
129/// Extend list item selection to include nearby comma and whitespace.
130fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
131 fn is_single_line_ws(node: &SyntaxNode) -> bool {
132 node.kind() == WHITESPACE && !node.leaf_text().unwrap().contains('\n')
133 }
134
135 fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<&SyntaxNode> {
136 node.siblings(dir)
137 .skip(1)
138 .skip_while(|node| is_single_line_ws(node))
139 .next()
140 .filter(|node| node.kind() == COMMA)
141 }
142
143 if let Some(comma_node) = nearby_comma(node, Direction::Prev) {
144 return Some(TextRange::from_to(comma_node.range().start(), node.range().end()));
145 }
146
147 if let Some(comma_node) = nearby_comma(node, Direction::Next) {
148 // Include any following whitespace when comma if after list item.
149 let final_node = comma_node
150 .siblings(Direction::Next)
151 .skip(1)
152 .next()
153 .filter(|node| is_single_line_ws(node))
154 .unwrap_or(comma_node);
155
156 return Some(TextRange::from_to(node.range().start(), final_node.range().end()));
157 }
158
159 return None;
160}
161
162fn extend_comments(node: &SyntaxNode) -> Option<TextRange> {
163 let prev = adj_comments(node, Direction::Prev);
164 let next = adj_comments(node, Direction::Next);
165 if prev != next {
166 Some(TextRange::from_to(prev.range().start(), next.range().end()))
167 } else {
168 None
169 }
170}
171
172fn adj_comments(node: &SyntaxNode, dir: Direction) -> &SyntaxNode {
173 let mut res = node;
174 for node in node.siblings(dir) {
175 match node.kind() {
176 COMMENT => res = node,
177 WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (),
178 _ => break,
179 }
180 }
181 res
182}
183
184#[cfg(test)]
185mod tests {
186 use ra_syntax::{SourceFile, AstNode};
187 use test_utils::extract_offset;
188
189 use super::*;
190
191 fn do_check(before: &str, afters: &[&str]) {
192 let (cursor, before) = extract_offset(before);
193 let file = SourceFile::parse(&before);
194 let mut range = TextRange::offset_len(cursor, 0.into());
195 for &after in afters {
196 range = extend_selection(file.syntax(), range).unwrap();
197 let actual = &before[range];
198 assert_eq!(after, actual);
199 }
200 }
201
202 #[test]
203 fn test_extend_selection_arith() {
204 do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
205 }
206
207 #[test]
208 fn test_extend_selection_list() {
209 do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
210 do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
211 do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,"]);
212 do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
213 do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", ", y: i32"]);
214 do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
215
216 do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
217 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
218 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", ", 33"]);
219
220 do_check(
221 r#"
222const FOO: [usize; 2] = [
223 22,
224 <|>33,
225]"#,
226 &["33", "33,"],
227 );
228
229 do_check(
230 r#"
231const FOO: [usize; 2] = [
232 22
233 , 33<|>,
234]"#,
235 &["33", ", 33"],
236 );
237 }
238
239 #[test]
240 fn test_extend_selection_start_of_the_line() {
241 do_check(
242 r#"
243impl S {
244<|> fn foo() {
245
246 }
247}"#,
248 &[" fn foo() {\n\n }\n"],
249 );
250 }
251
252 #[test]
253 fn test_extend_selection_doc_comments() {
254 do_check(
255 r#"
256struct A;
257
258/// bla
259/// bla
260struct B {
261 <|>
262}
263 "#,
264 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
265 )
266 }
267
268 #[test]
269 fn test_extend_selection_comments() {
270 do_check(
271 r#"
272fn bar(){}
273
274// fn foo() {
275// 1 + <|>1
276// }
277
278// fn foo(){}
279 "#,
280 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
281 );
282
283 do_check(
284 r#"
285// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
286// pub enum Direction {
287// <|> Next,
288// Prev
289// }
290"#,
291 &[
292 "// Next,",
293 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
294 ],
295 );
296
297 do_check(
298 r#"
299/*
300foo
301_bar1<|>*/
302 "#,
303 &["_bar1", "/*\nfoo\n_bar1*/"],
304 );
305
306 do_check(
307 r#"
308//!<|>foo_2 bar
309 "#,
310 &["foo_2", "//!foo_2 bar"],
311 );
312
313 do_check(
314 r#"
315/<|>/foo bar
316 "#,
317 &["//foo bar"],
318 );
319 }
320
321 #[test]
322 fn test_extend_selection_prefer_idents() {
323 do_check(
324 r#"
325fn main() { foo<|>+bar;}
326 "#,
327 &["foo", "foo+bar"],
328 );
329 do_check(
330 r#"
331fn main() { foo+<|>bar;}
332 "#,
333 &["bar", "foo+bar"],
334 );
335 }
336
337 #[test]
338 fn test_extend_selection_prefer_lifetimes() {
339 do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
340 do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
341 }
342
343 #[test]
344 fn test_extend_selection_select_first_word() {
345 do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
346 do_check(
347 r#"
348impl S {
349 fn foo() {
350 // hel<|>lo world
351 }
352}
353 "#,
354 &["hello", "// hello world"],
355 );
356 }
357
358 #[test]
359 fn test_extend_selection_string() {
360 do_check(
361 r#"
362fn bar(){}
363
364" fn f<|>oo() {"
365 "#,
366 &["foo", "\" fn foo() {\""],
367 );
368 }
369}
diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs
index 43cdd6ea4..ca13eb018 100644
--- a/crates/ra_ide_api_light/src/lib.rs
+++ b/crates/ra_ide_api_light/src/lib.rs
@@ -3,7 +3,6 @@
3//! This usually means functions which take syntax tree as an input and produce 3//! This usually means functions which take syntax tree as an input and produce
4//! an edit or some auxiliary info. 4//! an edit or some auxiliary info.
5 5
6mod extend_selection;
7mod folding_ranges; 6mod folding_ranges;
8mod line_index; 7mod line_index;
9mod line_index_utils; 8mod line_index_utils;
@@ -14,15 +13,16 @@ mod join_lines;
14mod typing; 13mod typing;
15mod diagnostics; 14mod diagnostics;
16 15
17#[derive(Debug)] 16use rustc_hash::FxHashSet;
18pub struct LocalEdit { 17use ra_text_edit::TextEditBuilder;
19 pub label: String, 18use ra_syntax::{
20 pub edit: ra_text_edit::TextEdit, 19 SourceFile, SyntaxNode, TextRange, TextUnit, Direction,
21 pub cursor_position: Option<TextUnit>, 20 algo::find_leaf_at_offset,
22} 21 SyntaxKind::{self, *},
22 ast::{self, AstNode},
23};
23 24
24pub use self::{ 25pub use crate::{
25 extend_selection::extend_selection,
26 folding_ranges::{folding_ranges, Fold, FoldKind}, 26 folding_ranges::{folding_ranges, Fold, FoldKind},
27 line_index::{LineCol, LineIndex}, 27 line_index::{LineCol, LineIndex},
28 line_index_utils::translate_offset_with_edit, 28 line_index_utils::translate_offset_with_edit,
@@ -30,16 +30,14 @@ pub use self::{
30 diagnostics::diagnostics, 30 diagnostics::diagnostics,
31 join_lines::join_lines, 31 join_lines::join_lines,
32 typing::{on_enter, on_dot_typed, on_eq_typed}, 32 typing::{on_enter, on_dot_typed, on_eq_typed},
33
34}; 33};
35use ra_text_edit::TextEditBuilder; 34
36use ra_syntax::{ 35#[derive(Debug)]
37 SourceFile, SyntaxNode, TextRange, TextUnit, Direction, 36pub struct LocalEdit {
38 SyntaxKind::{self, *}, 37 pub label: String,
39 ast::{self, AstNode}, 38 pub edit: ra_text_edit::TextEdit,
40 algo::find_leaf_at_offset, 39 pub cursor_position: Option<TextUnit>,
41}; 40}
42use rustc_hash::FxHashSet;
43 41
44#[derive(Debug)] 42#[derive(Debug)]
45pub struct HighlightedRange { 43pub struct HighlightedRange {
diff --git a/crates/ra_parser/src/event.rs b/crates/ra_parser/src/event.rs
index 6361d5d86..c1773e8e0 100644
--- a/crates/ra_parser/src/event.rs
+++ b/crates/ra_parser/src/event.rs
@@ -105,7 +105,9 @@ pub(super) fn process(sink: &mut dyn TreeSink, mut events: Vec<Event>) {
105 // append `A`'s forward_parent `B` 105 // append `A`'s forward_parent `B`
106 fp = match mem::replace(&mut events[idx], Event::tombstone()) { 106 fp = match mem::replace(&mut events[idx], Event::tombstone()) {
107 Event::Start { kind, forward_parent } => { 107 Event::Start { kind, forward_parent } => {
108 forward_parents.push(kind); 108 if kind != TOMBSTONE {
109 forward_parents.push(kind);
110 }
109 forward_parent 111 forward_parent
110 } 112 }
111 _ => unreachable!(), 113 _ => unreachable!(),
diff --git a/crates/ra_parser/src/grammar/expressions.rs b/crates/ra_parser/src/grammar/expressions.rs
index 83812e938..73e1acd5a 100644
--- a/crates/ra_parser/src/grammar/expressions.rs
+++ b/crates/ra_parser/src/grammar/expressions.rs
@@ -8,10 +8,10 @@ const EXPR_FIRST: TokenSet = LHS_FIRST;
8 8
9pub(super) fn expr(p: &mut Parser) -> BlockLike { 9pub(super) fn expr(p: &mut Parser) -> BlockLike {
10 let r = Restrictions { forbid_structs: false, prefer_stmt: false }; 10 let r = Restrictions { forbid_structs: false, prefer_stmt: false };
11 expr_bp(p, r, 1) 11 expr_bp(p, r, 1).1
12} 12}
13 13
14pub(super) fn expr_stmt(p: &mut Parser) -> BlockLike { 14pub(super) fn expr_stmt(p: &mut Parser) -> (Option<CompletedMarker>, BlockLike) {
15 let r = Restrictions { forbid_structs: false, prefer_stmt: true }; 15 let r = Restrictions { forbid_structs: false, prefer_stmt: true };
16 expr_bp(p, r, 1) 16 expr_bp(p, r, 1)
17} 17}
@@ -38,6 +38,13 @@ pub(crate) fn block(p: &mut Parser) {
38 m.complete(p, BLOCK); 38 m.complete(p, BLOCK);
39} 39}
40 40
41fn is_expr_stmt_attr_allowed(kind: SyntaxKind) -> bool {
42 match kind {
43 BIN_EXPR | RANGE_EXPR | IF_EXPR => false,
44 _ => true,
45 }
46}
47
41pub(crate) fn expr_block_contents(p: &mut Parser) { 48pub(crate) fn expr_block_contents(p: &mut Parser) {
42 // This is checked by a validator 49 // This is checked by a validator
43 attributes::inner_attributes(p); 50 attributes::inner_attributes(p);
@@ -55,6 +62,13 @@ pub(crate) fn expr_block_contents(p: &mut Parser) {
55 // test block_items 62 // test block_items
56 // fn a() { fn b() {} } 63 // fn a() { fn b() {} }
57 let m = p.start(); 64 let m = p.start();
65 // test attr_on_expr_stmt
66 // fn foo() {
67 // #[A] foo();
68 // #[B] bar!{}
69 // #[C] #[D] {}
70 // #[D] return ();
71 // }
58 let has_attrs = p.at(POUND); 72 let has_attrs = p.at(POUND);
59 attributes::outer_attributes(p); 73 attributes::outer_attributes(p);
60 if p.at(LET_KW) { 74 if p.at(LET_KW) {
@@ -67,35 +81,51 @@ pub(crate) fn expr_block_contents(p: &mut Parser) {
67 Err(m) => m, 81 Err(m) => m,
68 }; 82 };
69 83
70 if has_attrs { 84 let (cm, blocklike) = expr_stmt(p);
71 m.abandon(p); 85 let kind = cm.as_ref().map(|cm| cm.kind()).unwrap_or(ERROR);
72 p.error("expected a let statement or an item after attributes in block"); 86
73 } else { 87 if has_attrs && !is_expr_stmt_attr_allowed(kind) {
74 let is_blocklike = expressions::expr_stmt(p) == BlockLike::Block; 88 // test_err attr_on_expr_not_allowed
75 if p.at(R_CURLY) { 89 // fn foo() {
90 // #[A] 1 + 2;
91 // #[B] if true {};
92 // }
93 p.error(format!("attributes are not allowed on {:?}", kind));
94 }
95
96 if p.at(R_CURLY) {
97 // test attr_on_last_expr_in_block
98 // fn foo() {
99 // { #[A] bar!()? }
100 // #[B] &()
101 // }
102 if let Some(cm) = cm {
103 cm.undo_completion(p).abandon(p);
104 m.complete(p, kind);
105 } else {
76 m.abandon(p); 106 m.abandon(p);
107 }
108 } else {
109 // test no_semi_after_block
110 // fn foo() {
111 // if true {}
112 // loop {}
113 // match () {}
114 // while true {}
115 // for _ in () {}
116 // {}
117 // {}
118 // macro_rules! test {
119 // () => {}
120 // }
121 // test!{}
122 // }
123 if blocklike.is_block() {
124 p.eat(SEMI);
77 } else { 125 } else {
78 // test no_semi_after_block 126 p.expect(SEMI);
79 // fn foo() {
80 // if true {}
81 // loop {}
82 // match () {}
83 // while true {}
84 // for _ in () {}
85 // {}
86 // {}
87 // macro_rules! test {
88 // () => {}
89 // }
90 // test!{}
91 // }
92 if is_blocklike {
93 p.eat(SEMI);
94 } else {
95 p.expect(SEMI);
96 }
97 m.complete(p, EXPR_STMT);
98 } 127 }
128 m.complete(p, EXPR_STMT);
99 } 129 }
100 } 130 }
101 131
@@ -176,7 +206,7 @@ fn current_op(p: &Parser) -> (u8, Op) {
176} 206}
177 207
178// Parses expression with binding power of at least bp. 208// Parses expression with binding power of at least bp.
179fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> BlockLike { 209fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> (Option<CompletedMarker>, BlockLike) {
180 let mut lhs = match lhs(p, r) { 210 let mut lhs = match lhs(p, r) {
181 Some((lhs, blocklike)) => { 211 Some((lhs, blocklike)) => {
182 // test stmt_bin_expr_ambiguity 212 // test stmt_bin_expr_ambiguity
@@ -185,11 +215,11 @@ fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> BlockLike {
185 // {1} &2; 215 // {1} &2;
186 // } 216 // }
187 if r.prefer_stmt && blocklike.is_block() { 217 if r.prefer_stmt && blocklike.is_block() {
188 return BlockLike::Block; 218 return (Some(lhs), BlockLike::Block);
189 } 219 }
190 lhs 220 lhs
191 } 221 }
192 None => return BlockLike::NotBlock, 222 None => return (None, BlockLike::NotBlock),
193 }; 223 };
194 224
195 loop { 225 loop {
@@ -208,7 +238,7 @@ fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> BlockLike {
208 expr_bp(p, r, op_bp + 1); 238 expr_bp(p, r, op_bp + 1);
209 lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR }); 239 lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR });
210 } 240 }
211 BlockLike::NotBlock 241 (Some(lhs), BlockLike::NotBlock)
212} 242}
213 243
214const LHS_FIRST: TokenSet = 244const LHS_FIRST: TokenSet =
diff --git a/crates/ra_parser/src/grammar/expressions/atom.rs b/crates/ra_parser/src/grammar/expressions/atom.rs
index d933288cd..a23977bfb 100644
--- a/crates/ra_parser/src/grammar/expressions/atom.rs
+++ b/crates/ra_parser/src/grammar/expressions/atom.rs
@@ -392,9 +392,9 @@ fn match_arm(p: &mut Parser) -> BlockLike {
392 match_guard(p); 392 match_guard(p);
393 } 393 }
394 p.expect(FAT_ARROW); 394 p.expect(FAT_ARROW);
395 let ret = expr_stmt(p); 395 let blocklike = expr_stmt(p).1;
396 m.complete(p, MATCH_ARM); 396 m.complete(p, MATCH_ARM);
397 ret 397 blocklike
398} 398}
399 399
400// test match_guard 400// test match_guard
diff --git a/crates/ra_parser/src/parser.rs b/crates/ra_parser/src/parser.rs
index a18458148..3c326452b 100644
--- a/crates/ra_parser/src/parser.rs
+++ b/crates/ra_parser/src/parser.rs
@@ -212,8 +212,9 @@ impl Marker {
212 } 212 }
213 _ => unreachable!(), 213 _ => unreachable!(),
214 } 214 }
215 let finish_pos = p.events.len() as u32;
215 p.push_event(Event::Finish); 216 p.push_event(Event::Finish);
216 CompletedMarker::new(self.pos, kind) 217 CompletedMarker::new(self.pos, finish_pos, kind)
217 } 218 }
218 219
219 /// Abandons the syntax tree node. All its children 220 /// Abandons the syntax tree node. All its children
@@ -230,11 +231,15 @@ impl Marker {
230 } 231 }
231} 232}
232 233
233pub(crate) struct CompletedMarker(u32, SyntaxKind); 234pub(crate) struct CompletedMarker {
235 start_pos: u32,
236 finish_pos: u32,
237 kind: SyntaxKind,
238}
234 239
235impl CompletedMarker { 240impl CompletedMarker {
236 fn new(pos: u32, kind: SyntaxKind) -> Self { 241 fn new(start_pos: u32, finish_pos: u32, kind: SyntaxKind) -> Self {
237 CompletedMarker(pos, kind) 242 CompletedMarker { start_pos, finish_pos, kind }
238 } 243 }
239 244
240 /// This method allows to create a new node which starts 245 /// This method allows to create a new node which starts
@@ -251,17 +256,32 @@ impl CompletedMarker {
251 /// distance to `NEWSTART` into forward_parent(=2 in this case); 256 /// distance to `NEWSTART` into forward_parent(=2 in this case);
252 pub(crate) fn precede(self, p: &mut Parser) -> Marker { 257 pub(crate) fn precede(self, p: &mut Parser) -> Marker {
253 let new_pos = p.start(); 258 let new_pos = p.start();
254 let idx = self.0 as usize; 259 let idx = self.start_pos as usize;
255 match p.events[idx] { 260 match p.events[idx] {
256 Event::Start { ref mut forward_parent, .. } => { 261 Event::Start { ref mut forward_parent, .. } => {
257 *forward_parent = Some(new_pos.pos - self.0); 262 *forward_parent = Some(new_pos.pos - self.start_pos);
258 } 263 }
259 _ => unreachable!(), 264 _ => unreachable!(),
260 } 265 }
261 new_pos 266 new_pos
262 } 267 }
263 268
269 /// Undo this completion and turns into a `Marker`
270 pub(crate) fn undo_completion(self, p: &mut Parser) -> Marker {
271 let start_idx = self.start_pos as usize;
272 let finish_idx = self.finish_pos as usize;
273 match p.events[start_idx] {
274 Event::Start { ref mut kind, forward_parent: None } => *kind = TOMBSTONE,
275 _ => unreachable!(),
276 }
277 match p.events[finish_idx] {
278 ref mut slot @ Event::Finish => *slot = Event::tombstone(),
279 _ => unreachable!(),
280 }
281 Marker::new(self.start_pos)
282 }
283
264 pub(crate) fn kind(&self) -> SyntaxKind { 284 pub(crate) fn kind(&self) -> SyntaxKind {
265 self.1 285 self.kind
266 } 286 }
267} 287}
diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs
new file mode 100644
index 000000000..d725a07ce
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs
@@ -0,0 +1,4 @@
1fn foo() {
2 #[A] 1 + 2;
3 #[B] if true {};
4}
diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt
new file mode 100644
index 000000000..fdea1ec1e
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt
@@ -0,0 +1,55 @@
1SOURCE_FILE@[0; 48)
2 FN_DEF@[0; 47)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 47)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 14)
14 EXPR_STMT@[14; 25)
15 ATTR@[14; 18)
16 POUND@[14; 15)
17 TOKEN_TREE@[15; 18)
18 L_BRACK@[15; 16)
19 IDENT@[16; 17) "A"
20 R_BRACK@[17; 18)
21 WHITESPACE@[18; 19)
22 BIN_EXPR@[19; 24)
23 LITERAL@[19; 20)
24 INT_NUMBER@[19; 20) "1"
25 WHITESPACE@[20; 21)
26 PLUS@[21; 22)
27 WHITESPACE@[22; 23)
28 LITERAL@[23; 24)
29 INT_NUMBER@[23; 24) "2"
30 err: `attributes are not allowed on BIN_EXPR`
31 SEMI@[24; 25)
32 WHITESPACE@[25; 29)
33 EXPR_STMT@[29; 45)
34 ATTR@[29; 33)
35 POUND@[29; 30)
36 TOKEN_TREE@[30; 33)
37 L_BRACK@[30; 31)
38 IDENT@[31; 32) "B"
39 R_BRACK@[32; 33)
40 WHITESPACE@[33; 34)
41 IF_EXPR@[34; 44)
42 IF_KW@[34; 36)
43 WHITESPACE@[36; 37)
44 CONDITION@[37; 41)
45 LITERAL@[37; 41)
46 TRUE_KW@[37; 41)
47 WHITESPACE@[41; 42)
48 BLOCK@[42; 44)
49 L_CURLY@[42; 43)
50 R_CURLY@[43; 44)
51 err: `attributes are not allowed on IF_EXPR`
52 SEMI@[44; 45)
53 WHITESPACE@[45; 46)
54 R_CURLY@[46; 47)
55 WHITESPACE@[47; 48)
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs
new file mode 100644
index 000000000..b28c078f9
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs
@@ -0,0 +1,6 @@
1fn foo() {
2 #[A] foo();
3 #[B] bar!{}
4 #[C] #[D] {}
5 #[D] return ();
6}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt
new file mode 100644
index 000000000..7cd525cc7
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt
@@ -0,0 +1,88 @@
1SOURCE_FILE@[0; 82)
2 FN_DEF@[0; 81)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 81)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 EXPR_STMT@[15; 26)
15 ATTR@[15; 19)
16 POUND@[15; 16)
17 TOKEN_TREE@[16; 19)
18 L_BRACK@[16; 17)
19 IDENT@[17; 18) "A"
20 R_BRACK@[18; 19)
21 WHITESPACE@[19; 20)
22 CALL_EXPR@[20; 25)
23 PATH_EXPR@[20; 23)
24 PATH@[20; 23)
25 PATH_SEGMENT@[20; 23)
26 NAME_REF@[20; 23)
27 IDENT@[20; 23) "foo"
28 ARG_LIST@[23; 25)
29 L_PAREN@[23; 24)
30 R_PAREN@[24; 25)
31 SEMI@[25; 26)
32 WHITESPACE@[26; 31)
33 EXPR_STMT@[31; 42)
34 ATTR@[31; 35)
35 POUND@[31; 32)
36 TOKEN_TREE@[32; 35)
37 L_BRACK@[32; 33)
38 IDENT@[33; 34) "B"
39 R_BRACK@[34; 35)
40 WHITESPACE@[35; 36)
41 MACRO_CALL@[36; 42)
42 PATH@[36; 39)
43 PATH_SEGMENT@[36; 39)
44 NAME_REF@[36; 39)
45 IDENT@[36; 39) "bar"
46 EXCL@[39; 40)
47 TOKEN_TREE@[40; 42)
48 L_CURLY@[40; 41)
49 R_CURLY@[41; 42)
50 WHITESPACE@[42; 47)
51 EXPR_STMT@[47; 59)
52 ATTR@[47; 51)
53 POUND@[47; 48)
54 TOKEN_TREE@[48; 51)
55 L_BRACK@[48; 49)
56 IDENT@[49; 50) "C"
57 R_BRACK@[50; 51)
58 WHITESPACE@[51; 52)
59 ATTR@[52; 56)
60 POUND@[52; 53)
61 TOKEN_TREE@[53; 56)
62 L_BRACK@[53; 54)
63 IDENT@[54; 55) "D"
64 R_BRACK@[55; 56)
65 WHITESPACE@[56; 57)
66 BLOCK_EXPR@[57; 59)
67 BLOCK@[57; 59)
68 L_CURLY@[57; 58)
69 R_CURLY@[58; 59)
70 WHITESPACE@[59; 64)
71 EXPR_STMT@[64; 79)
72 ATTR@[64; 68)
73 POUND@[64; 65)
74 TOKEN_TREE@[65; 68)
75 L_BRACK@[65; 66)
76 IDENT@[66; 67) "D"
77 R_BRACK@[67; 68)
78 WHITESPACE@[68; 69)
79 RETURN_EXPR@[69; 78)
80 RETURN_KW@[69; 75)
81 WHITESPACE@[75; 76)
82 TUPLE_EXPR@[76; 78)
83 L_PAREN@[76; 77)
84 R_PAREN@[77; 78)
85 SEMI@[78; 79)
86 WHITESPACE@[79; 80)
87 R_CURLY@[80; 81)
88 WHITESPACE@[81; 82)
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs
new file mode 100644
index 000000000..9c5c8eb36
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs
@@ -0,0 +1,4 @@
1fn foo() {
2 { #[A] bar!()? }
3 #[B] &()
4}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt
new file mode 100644
index 000000000..4af64559c
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt
@@ -0,0 +1,54 @@
1SOURCE_FILE@[0; 47)
2 FN_DEF@[0; 46)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 46)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 EXPR_STMT@[15; 31)
15 BLOCK_EXPR@[15; 31)
16 BLOCK@[15; 31)
17 L_CURLY@[15; 16)
18 WHITESPACE@[16; 17)
19 TRY_EXPR@[17; 29)
20 ATTR@[17; 21)
21 POUND@[17; 18)
22 TOKEN_TREE@[18; 21)
23 L_BRACK@[18; 19)
24 IDENT@[19; 20) "A"
25 R_BRACK@[20; 21)
26 WHITESPACE@[21; 22)
27 MACRO_CALL@[22; 28)
28 PATH@[22; 25)
29 PATH_SEGMENT@[22; 25)
30 NAME_REF@[22; 25)
31 IDENT@[22; 25) "bar"
32 EXCL@[25; 26)
33 TOKEN_TREE@[26; 28)
34 L_PAREN@[26; 27)
35 R_PAREN@[27; 28)
36 QUESTION@[28; 29)
37 WHITESPACE@[29; 30)
38 R_CURLY@[30; 31)
39 WHITESPACE@[31; 36)
40 REF_EXPR@[36; 44)
41 ATTR@[36; 40)
42 POUND@[36; 37)
43 TOKEN_TREE@[37; 40)
44 L_BRACK@[37; 38)
45 IDENT@[38; 39) "B"
46 R_BRACK@[39; 40)
47 WHITESPACE@[40; 41)
48 AMP@[41; 42)
49 TUPLE_EXPR@[42; 44)
50 L_PAREN@[42; 43)
51 R_PAREN@[43; 44)
52 WHITESPACE@[44; 45)
53 R_CURLY@[45; 46)
54 WHITESPACE@[46; 47)