aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_hir/src/source_analyzer.rs16
-rw-r--r--crates/ra_hir_def/src/body.rs54
-rw-r--r--crates/ra_hir_def/src/lib.rs63
-rw-r--r--crates/ra_hir_def/src/nameres/collector.rs77
-rw-r--r--crates/ra_ide/src/lib.rs2
-rw-r--r--crates/ra_ide/src/runnables.rs109
-rw-r--r--crates/ra_lsp_server/src/cargo_target_spec.rs16
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs4
-rw-r--r--crates/ra_lsp_server/tests/heavy_tests/main.rs2
-rw-r--r--docs/dev/debugging.md64
-rw-r--r--editors/code/src/config.ts1
-rw-r--r--editors/code/src/ctx.ts12
-rw-r--r--editors/code/src/inlay_hints.ts49
13 files changed, 304 insertions, 165 deletions
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs
index bb9a35c5d..94d5b4cfd 100644
--- a/crates/ra_hir/src/source_analyzer.rs
+++ b/crates/ra_hir/src/source_analyzer.rs
@@ -15,11 +15,9 @@ use hir_def::{
15 }, 15 },
16 expr::{ExprId, PatId}, 16 expr::{ExprId, PatId},
17 resolver::{self, resolver_for_scope, Resolver, TypeNs, ValueNs}, 17 resolver::{self, resolver_for_scope, Resolver, TypeNs, ValueNs},
18 DefWithBodyId, TraitId, 18 AsMacroCall, DefWithBodyId, TraitId,
19};
20use hir_expand::{
21 hygiene::Hygiene, name::AsName, AstId, HirFileId, InFile, MacroCallId, MacroCallKind,
22}; 19};
20use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile, MacroCallId};
23use hir_ty::{InEnvironment, InferenceResult, TraitEnvironment}; 21use hir_ty::{InEnvironment, InferenceResult, TraitEnvironment};
24use ra_syntax::{ 22use ra_syntax::{
25 ast::{self, AstNode}, 23 ast::{self, AstNode},
@@ -363,12 +361,10 @@ impl SourceAnalyzer {
363 db: &impl HirDatabase, 361 db: &impl HirDatabase,
364 macro_call: InFile<&ast::MacroCall>, 362 macro_call: InFile<&ast::MacroCall>,
365 ) -> Option<Expansion> { 363 ) -> Option<Expansion> {
366 let def = self.resolve_macro_call(db, macro_call)?.id; 364 let macro_call_id = macro_call.as_call_id(db, |path| {
367 let ast_id = AstId::new( 365 self.resolver.resolve_path_as_macro(db, &path).map(|it| it.into())
368 macro_call.file_id, 366 })?;
369 db.ast_id_map(macro_call.file_id).ast_id(macro_call.value), 367 Some(Expansion { macro_call_id })
370 );
371 Some(Expansion { macro_call_id: def.as_call_id(db, MacroCallKind::FnLike(ast_id)) })
372 } 368 }
373} 369}
374 370
diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs
index 142c52d35..010d35e55 100644
--- a/crates/ra_hir_def/src/body.rs
+++ b/crates/ra_hir_def/src/body.rs
@@ -7,9 +7,7 @@ use std::{mem, ops::Index, sync::Arc};
7 7
8use drop_bomb::DropBomb; 8use drop_bomb::DropBomb;
9use either::Either; 9use either::Either;
10use hir_expand::{ 10use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId};
11 ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroCallKind, MacroDefId,
12};
13use ra_arena::{map::ArenaMap, Arena}; 11use ra_arena::{map::ArenaMap, Arena};
14use ra_prof::profile; 12use ra_prof::profile;
15use ra_syntax::{ast, AstNode, AstPtr}; 13use ra_syntax::{ast, AstNode, AstPtr};
@@ -23,7 +21,7 @@ use crate::{
23 nameres::CrateDefMap, 21 nameres::CrateDefMap,
24 path::{ModPath, Path}, 22 path::{ModPath, Path},
25 src::HasSource, 23 src::HasSource,
26 DefWithBodyId, HasModule, Lookup, ModuleId, 24 AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId,
27}; 25};
28 26
29pub(crate) struct Expander { 27pub(crate) struct Expander {
@@ -51,30 +49,26 @@ impl Expander {
51 db: &DB, 49 db: &DB,
52 macro_call: ast::MacroCall, 50 macro_call: ast::MacroCall,
53 ) -> Option<(Mark, T)> { 51 ) -> Option<(Mark, T)> {
54 let ast_id = AstId::new( 52 let macro_call = InFile::new(self.current_file_id, &macro_call);
55 self.current_file_id, 53
56 db.ast_id_map(self.current_file_id).ast_id(&macro_call), 54 if let Some(call_id) =
57 ); 55 macro_call.as_call_id(db, |path| self.resolve_path_as_macro(db, &path))
58 56 {
59 if let Some(path) = macro_call.path().and_then(|path| self.parse_mod_path(path)) { 57 let file_id = call_id.as_file();
60 if let Some(def) = self.resolve_path_as_macro(db, &path) { 58 if let Some(node) = db.parse_or_expand(file_id) {
61 let call_id = def.as_call_id(db, MacroCallKind::FnLike(ast_id)); 59 if let Some(expr) = T::cast(node) {
62 let file_id = call_id.as_file(); 60 log::debug!("macro expansion {:#?}", expr.syntax());
63 if let Some(node) = db.parse_or_expand(file_id) { 61
64 if let Some(expr) = T::cast(node) { 62 let mark = Mark {
65 log::debug!("macro expansion {:#?}", expr.syntax()); 63 file_id: self.current_file_id,
66 64 ast_id_map: mem::take(&mut self.ast_id_map),
67 let mark = Mark { 65 bomb: DropBomb::new("expansion mark dropped"),
68 file_id: self.current_file_id, 66 };
69 ast_id_map: mem::take(&mut self.ast_id_map), 67 self.hygiene = Hygiene::new(db, file_id);
70 bomb: DropBomb::new("expansion mark dropped"), 68 self.current_file_id = file_id;
71 }; 69 self.ast_id_map = db.ast_id_map(file_id);
72 self.hygiene = Hygiene::new(db, file_id); 70
73 self.current_file_id = file_id; 71 return Some((mark, expr));
74 self.ast_id_map = db.ast_id_map(file_id);
75
76 return Some((mark, expr));
77 }
78 } 72 }
79 } 73 }
80 } 74 }
@@ -99,10 +93,6 @@ impl Expander {
99 Path::from_src(path, &self.hygiene) 93 Path::from_src(path, &self.hygiene)
100 } 94 }
101 95
102 fn parse_mod_path(&mut self, path: ast::Path) -> Option<ModPath> {
103 ModPath::from_src(path, &self.hygiene)
104 }
105
106 fn resolve_path_as_macro(&self, db: &impl DefDatabase, path: &ModPath) -> Option<MacroDefId> { 96 fn resolve_path_as_macro(&self, db: &impl DefDatabase, path: &ModPath) -> Option<MacroDefId> {
107 self.crate_def_map 97 self.crate_def_map
108 .resolve_path(db, self.module.local_id, path, BuiltinShadowMode::Other) 98 .resolve_path(db, self.module.local_id, path, BuiltinShadowMode::Other)
diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs
index feb3a300d..aa0b558b8 100644
--- a/crates/ra_hir_def/src/lib.rs
+++ b/crates/ra_hir_def/src/lib.rs
@@ -46,7 +46,10 @@ mod marks;
46 46
47use std::hash::Hash; 47use std::hash::Hash;
48 48
49use hir_expand::{ast_id_map::FileAstId, AstId, HirFileId, InFile, MacroDefId}; 49use hir_expand::{
50 ast_id_map::FileAstId, db::AstDatabase, hygiene::Hygiene, AstId, HirFileId, InFile,
51 MacroCallId, MacroCallKind, MacroDefId,
52};
50use ra_arena::{impl_arena_id, RawId}; 53use ra_arena::{impl_arena_id, RawId};
51use ra_db::{impl_intern_key, salsa, CrateId}; 54use ra_db::{impl_intern_key, salsa, CrateId};
52use ra_syntax::{ast, AstNode}; 55use ra_syntax::{ast, AstNode};
@@ -413,3 +416,61 @@ impl HasModule for StaticLoc {
413 self.container.module(db) 416 self.container.module(db)
414 } 417 }
415} 418}
419
420/// A helper trait for converting to MacroCallId
421pub trait AsMacroCall {
422 fn as_call_id(
423 &self,
424 db: &(impl db::DefDatabase + AstDatabase),
425 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
426 ) -> Option<MacroCallId>;
427}
428
429impl AsMacroCall for InFile<&ast::MacroCall> {
430 fn as_call_id(
431 &self,
432 db: &(impl db::DefDatabase + AstDatabase),
433 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
434 ) -> Option<MacroCallId> {
435 let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value));
436 let h = Hygiene::new(db, self.file_id);
437 let path = path::ModPath::from_src(self.value.path()?, &h)?;
438
439 AstIdWithPath::new(ast_id.file_id, ast_id.value, path).as_call_id(db, resolver)
440 }
441}
442
443/// Helper wrapper for `AstId` with `ModPath`
444#[derive(Clone, Debug, Eq, PartialEq)]
445struct AstIdWithPath<T: ast::AstNode> {
446 pub ast_id: AstId<T>,
447 pub path: path::ModPath,
448}
449
450impl<T: ast::AstNode> AstIdWithPath<T> {
451 pub fn new(file_id: HirFileId, ast_id: FileAstId<T>, path: path::ModPath) -> AstIdWithPath<T> {
452 AstIdWithPath { ast_id: AstId::new(file_id, ast_id), path }
453 }
454}
455
456impl AsMacroCall for AstIdWithPath<ast::MacroCall> {
457 fn as_call_id(
458 &self,
459 db: &impl AstDatabase,
460 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
461 ) -> Option<MacroCallId> {
462 let def = resolver(self.path.clone())?;
463 Some(def.as_call_id(db, MacroCallKind::FnLike(self.ast_id.clone())))
464 }
465}
466
467impl AsMacroCall for AstIdWithPath<ast::ModuleItem> {
468 fn as_call_id(
469 &self,
470 db: &impl AstDatabase,
471 resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
472 ) -> Option<MacroCallId> {
473 let def = resolver(self.path.clone())?;
474 Some(def.as_call_id(db, MacroCallKind::Attr(self.ast_id.clone())))
475 }
476}
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs
index b1f3f525d..51c65a5d7 100644
--- a/crates/ra_hir_def/src/nameres/collector.rs
+++ b/crates/ra_hir_def/src/nameres/collector.rs
@@ -7,7 +7,7 @@ use hir_expand::{
7 builtin_derive::find_builtin_derive, 7 builtin_derive::find_builtin_derive,
8 builtin_macro::find_builtin_macro, 8 builtin_macro::find_builtin_macro,
9 name::{name, AsName, Name}, 9 name::{name, AsName, Name},
10 HirFileId, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, 10 HirFileId, MacroCallId, MacroDefId, MacroDefKind,
11}; 11};
12use ra_cfg::CfgOptions; 12use ra_cfg::CfgOptions;
13use ra_db::{CrateId, FileId}; 13use ra_db::{CrateId, FileId};
@@ -25,8 +25,9 @@ use crate::{
25 path::{ImportAlias, ModPath, PathKind}, 25 path::{ImportAlias, ModPath, PathKind},
26 per_ns::PerNs, 26 per_ns::PerNs,
27 visibility::Visibility, 27 visibility::Visibility,
28 AdtId, AstId, ConstLoc, ContainerId, EnumLoc, EnumVariantId, FunctionLoc, ImplLoc, Intern, 28 AdtId, AsMacroCall, AstId, AstIdWithPath, ConstLoc, ContainerId, EnumLoc, EnumVariantId,
29 LocalModuleId, ModuleDefId, ModuleId, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, 29 FunctionLoc, ImplLoc, Intern, LocalModuleId, ModuleDefId, ModuleId, StaticLoc, StructLoc,
30 TraitLoc, TypeAliasLoc, UnionLoc,
30}; 31};
31 32
32pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap { 33pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap {
@@ -99,11 +100,16 @@ struct ImportDirective {
99#[derive(Clone, Debug, Eq, PartialEq)] 100#[derive(Clone, Debug, Eq, PartialEq)]
100struct MacroDirective { 101struct MacroDirective {
101 module_id: LocalModuleId, 102 module_id: LocalModuleId,
102 ast_id: AstId<ast::MacroCall>, 103 ast_id: AstIdWithPath<ast::MacroCall>,
103 path: ModPath,
104 legacy: Option<MacroCallId>, 104 legacy: Option<MacroCallId>,
105} 105}
106 106
107#[derive(Clone, Debug, Eq, PartialEq)]
108struct DeriveDirective {
109 module_id: LocalModuleId,
110 ast_id: AstIdWithPath<ast::ModuleItem>,
111}
112
107/// Walks the tree of module recursively 113/// Walks the tree of module recursively
108struct DefCollector<'a, DB> { 114struct DefCollector<'a, DB> {
109 db: &'a DB, 115 db: &'a DB,
@@ -112,7 +118,7 @@ struct DefCollector<'a, DB> {
112 unresolved_imports: Vec<ImportDirective>, 118 unresolved_imports: Vec<ImportDirective>,
113 resolved_imports: Vec<ImportDirective>, 119 resolved_imports: Vec<ImportDirective>,
114 unexpanded_macros: Vec<MacroDirective>, 120 unexpanded_macros: Vec<MacroDirective>,
115 unexpanded_attribute_macros: Vec<(LocalModuleId, AstId<ast::ModuleItem>, ModPath)>, 121 unexpanded_attribute_macros: Vec<DeriveDirective>,
116 mod_dirs: FxHashMap<LocalModuleId, ModDir>, 122 mod_dirs: FxHashMap<LocalModuleId, ModDir>,
117 cfg_options: &'a CfgOptions, 123 cfg_options: &'a CfgOptions,
118} 124}
@@ -515,16 +521,16 @@ where
515 return false; 521 return false;
516 } 522 }
517 523
518 let resolved_res = self.def_map.resolve_path_fp_with_macro( 524 if let Some(call_id) = directive.ast_id.as_call_id(self.db, |path| {
519 self.db, 525 let resolved_res = self.def_map.resolve_path_fp_with_macro(
520 ResolveMode::Other, 526 self.db,
521 directive.module_id, 527 ResolveMode::Other,
522 &directive.path, 528 directive.module_id,
523 BuiltinShadowMode::Module, 529 &path,
524 ); 530 BuiltinShadowMode::Module,
525 531 );
526 if let Some(def) = resolved_res.resolved_def.take_macros() { 532 resolved_res.resolved_def.take_macros()
527 let call_id = def.as_call_id(self.db, MacroCallKind::FnLike(directive.ast_id)); 533 }) {
528 resolved.push((directive.module_id, call_id)); 534 resolved.push((directive.module_id, call_id));
529 res = ReachedFixedPoint::No; 535 res = ReachedFixedPoint::No;
530 return false; 536 return false;
@@ -532,12 +538,11 @@ where
532 538
533 true 539 true
534 }); 540 });
535 attribute_macros.retain(|(module_id, ast_id, path)| { 541 attribute_macros.retain(|directive| {
536 let resolved_res = self.resolve_attribute_macro(path); 542 if let Some(call_id) =
537 543 directive.ast_id.as_call_id(self.db, |path| self.resolve_attribute_macro(&path))
538 if let Some(def) = resolved_res { 544 {
539 let call_id = def.as_call_id(self.db, MacroCallKind::Attr(*ast_id)); 545 resolved.push((directive.module_id, call_id));
540 resolved.push((*module_id, call_id));
541 res = ReachedFixedPoint::No; 546 res = ReachedFixedPoint::No;
542 return false; 547 return false;
543 } 548 }
@@ -833,20 +838,22 @@ where
833 }; 838 };
834 let path = ModPath::from_tt_ident(ident); 839 let path = ModPath::from_tt_ident(ident);
835 840
836 let ast_id = AstId::new(self.file_id, def.kind.ast_id()); 841 let ast_id = AstIdWithPath::new(self.file_id, def.kind.ast_id(), path);
837 self.def_collector.unexpanded_attribute_macros.push((self.module_id, ast_id, path)); 842 self.def_collector
843 .unexpanded_attribute_macros
844 .push(DeriveDirective { module_id: self.module_id, ast_id });
838 } 845 }
839 } 846 }
840 } 847 }
841 848
842 fn collect_macro(&mut self, mac: &raw::MacroData) { 849 fn collect_macro(&mut self, mac: &raw::MacroData) {
843 let ast_id = AstId::new(self.file_id, mac.ast_id); 850 let mut ast_id = AstIdWithPath::new(self.file_id, mac.ast_id, mac.path.clone());
844 851
845 // Case 0: builtin macros 852 // Case 0: builtin macros
846 if mac.builtin { 853 if mac.builtin {
847 if let Some(name) = &mac.name { 854 if let Some(name) = &mac.name {
848 let krate = self.def_collector.def_map.krate; 855 let krate = self.def_collector.def_map.krate;
849 if let Some(macro_id) = find_builtin_macro(name, krate, ast_id) { 856 if let Some(macro_id) = find_builtin_macro(name, krate, ast_id.ast_id) {
850 self.def_collector.define_macro( 857 self.def_collector.define_macro(
851 self.module_id, 858 self.module_id,
852 name.clone(), 859 name.clone(),
@@ -862,7 +869,7 @@ where
862 if is_macro_rules(&mac.path) { 869 if is_macro_rules(&mac.path) {
863 if let Some(name) = &mac.name { 870 if let Some(name) = &mac.name {
864 let macro_id = MacroDefId { 871 let macro_id = MacroDefId {
865 ast_id: Some(ast_id), 872 ast_id: Some(ast_id.ast_id),
866 krate: Some(self.def_collector.def_map.krate), 873 krate: Some(self.def_collector.def_map.krate),
867 kind: MacroDefKind::Declarative, 874 kind: MacroDefKind::Declarative,
868 }; 875 };
@@ -872,15 +879,13 @@ where
872 } 879 }
873 880
874 // Case 2: try to resolve in legacy scope and expand macro_rules 881 // Case 2: try to resolve in legacy scope and expand macro_rules
875 if let Some(macro_def) = mac.path.as_ident().and_then(|name| { 882 if let Some(macro_call_id) = ast_id.as_call_id(self.def_collector.db, |path| {
876 self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name) 883 path.as_ident().and_then(|name| {
884 self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name)
885 })
877 }) { 886 }) {
878 let macro_call_id =
879 macro_def.as_call_id(self.def_collector.db, MacroCallKind::FnLike(ast_id));
880
881 self.def_collector.unexpanded_macros.push(MacroDirective { 887 self.def_collector.unexpanded_macros.push(MacroDirective {
882 module_id: self.module_id, 888 module_id: self.module_id,
883 path: mac.path.clone(),
884 ast_id, 889 ast_id,
885 legacy: Some(macro_call_id), 890 legacy: Some(macro_call_id),
886 }); 891 });
@@ -890,14 +895,12 @@ where
890 895
891 // Case 3: resolve in module scope, expand during name resolution. 896 // Case 3: resolve in module scope, expand during name resolution.
892 // We rewrite simple path `macro_name` to `self::macro_name` to force resolve in module scope only. 897 // We rewrite simple path `macro_name` to `self::macro_name` to force resolve in module scope only.
893 let mut path = mac.path.clone(); 898 if ast_id.path.is_ident() {
894 if path.is_ident() { 899 ast_id.path.kind = PathKind::Super(0);
895 path.kind = PathKind::Super(0);
896 } 900 }
897 901
898 self.def_collector.unexpanded_macros.push(MacroDirective { 902 self.def_collector.unexpanded_macros.push(MacroDirective {
899 module_id: self.module_id, 903 module_id: self.module_id,
900 path,
901 ast_id, 904 ast_id,
902 legacy: None, 905 legacy: None,
903 }); 906 });
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 689921f3f..9d66c365b 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -71,7 +71,7 @@ pub use crate::{
71 references::{ 71 references::{
72 Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope, 72 Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope,
73 }, 73 },
74 runnables::{Runnable, RunnableKind}, 74 runnables::{Runnable, RunnableKind, TestId},
75 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 75 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
76 syntax_highlighting::HighlightedRange, 76 syntax_highlighting::HighlightedRange,
77}; 77};
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index b6b0c70f9..be2a67d0a 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::InFile; 3use hir::{InFile, SourceBinder};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_db::SourceDatabase; 5use ra_db::SourceDatabase;
6use ra_ide_db::RootDatabase; 6use ra_ide_db::RootDatabase;
@@ -10,6 +10,7 @@ use ra_syntax::{
10}; 10};
11 11
12use crate::FileId; 12use crate::FileId;
13use std::fmt::Display;
13 14
14#[derive(Debug)] 15#[derive(Debug)]
15pub struct Runnable { 16pub struct Runnable {
@@ -18,38 +19,84 @@ pub struct Runnable {
18} 19}
19 20
20#[derive(Debug)] 21#[derive(Debug)]
22pub enum TestId {
23 Name(String),
24 Path(String),
25}
26
27impl Display for TestId {
28 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
29 match self {
30 TestId::Name(name) => write!(f, "{}", name),
31 TestId::Path(path) => write!(f, "{}", path),
32 }
33 }
34}
35
36#[derive(Debug)]
21pub enum RunnableKind { 37pub enum RunnableKind {
22 Test { name: String }, 38 Test { test_id: TestId },
23 TestMod { path: String }, 39 TestMod { path: String },
24 Bench { name: String }, 40 Bench { test_id: TestId },
25 Bin, 41 Bin,
26} 42}
27 43
28pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 44pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
29 let parse = db.parse(file_id); 45 let parse = db.parse(file_id);
30 parse.tree().syntax().descendants().filter_map(|i| runnable(db, file_id, i)).collect() 46 let mut sb = SourceBinder::new(db);
47 parse.tree().syntax().descendants().filter_map(|i| runnable(db, &mut sb, file_id, i)).collect()
31} 48}
32 49
33fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNode) -> Option<Runnable> { 50fn runnable(
51 db: &RootDatabase,
52 source_binder: &mut SourceBinder<RootDatabase>,
53 file_id: FileId,
54 item: SyntaxNode,
55) -> Option<Runnable> {
34 match_ast! { 56 match_ast! {
35 match item { 57 match item {
36 ast::FnDef(it) => { runnable_fn(it) }, 58 ast::FnDef(it) => { runnable_fn(db, source_binder, file_id, it) },
37 ast::Module(it) => { runnable_mod(db, file_id, it) }, 59 ast::Module(it) => { runnable_mod(db, source_binder, file_id, it) },
38 _ => { None }, 60 _ => { None },
39 } 61 }
40 } 62 }
41} 63}
42 64
43fn runnable_fn(fn_def: ast::FnDef) -> Option<Runnable> { 65fn runnable_fn(
44 let name = fn_def.name()?.text().clone(); 66 db: &RootDatabase,
45 let kind = if name == "main" { 67 source_binder: &mut SourceBinder<RootDatabase>,
68 file_id: FileId,
69 fn_def: ast::FnDef,
70) -> Option<Runnable> {
71 let name_string = fn_def.name()?.text().to_string();
72
73 let kind = if name_string == "main" {
46 RunnableKind::Bin 74 RunnableKind::Bin
47 } else if has_test_related_attribute(&fn_def) {
48 RunnableKind::Test { name: name.to_string() }
49 } else if fn_def.has_atom_attr("bench") {
50 RunnableKind::Bench { name: name.to_string() }
51 } else { 75 } else {
52 return None; 76 let test_id = if let Some(module) = source_binder
77 .to_def(InFile::new(file_id.into(), fn_def.clone()))
78 .map(|def| def.module(db))
79 {
80 let path = module
81 .path_to_root(db)
82 .into_iter()
83 .rev()
84 .filter_map(|it| it.name(db))
85 .map(|name| name.to_string())
86 .chain(std::iter::once(name_string))
87 .join("::");
88 TestId::Path(path)
89 } else {
90 TestId::Name(name_string)
91 };
92
93 if has_test_related_attribute(&fn_def) {
94 RunnableKind::Test { test_id }
95 } else if fn_def.has_atom_attr("bench") {
96 RunnableKind::Bench { test_id }
97 } else {
98 return None;
99 }
53 }; 100 };
54 Some(Runnable { range: fn_def.syntax().text_range(), kind }) 101 Some(Runnable { range: fn_def.syntax().text_range(), kind })
55} 102}
@@ -68,7 +115,12 @@ fn has_test_related_attribute(fn_def: &ast::FnDef) -> bool {
68 .any(|attribute_text| attribute_text.contains("test")) 115 .any(|attribute_text| attribute_text.contains("test"))
69} 116}
70 117
71fn runnable_mod(db: &RootDatabase, file_id: FileId, module: ast::Module) -> Option<Runnable> { 118fn runnable_mod(
119 db: &RootDatabase,
120 source_binder: &mut SourceBinder<RootDatabase>,
121 file_id: FileId,
122 module: ast::Module,
123) -> Option<Runnable> {
72 let has_test_function = module 124 let has_test_function = module
73 .item_list()? 125 .item_list()?
74 .items() 126 .items()
@@ -76,13 +128,12 @@ fn runnable_mod(db: &RootDatabase, file_id: FileId, module: ast::Module) -> Opti
76 ast::ModuleItem::FnDef(it) => Some(it), 128 ast::ModuleItem::FnDef(it) => Some(it),
77 _ => None, 129 _ => None,
78 }) 130 })
79 .any(|f| f.has_atom_attr("test")); 131 .any(|f| has_test_related_attribute(&f));
80 if !has_test_function { 132 if !has_test_function {
81 return None; 133 return None;
82 } 134 }
83 let range = module.syntax().text_range(); 135 let range = module.syntax().text_range();
84 let mut sb = hir::SourceBinder::new(db); 136 let module = source_binder.to_def(InFile::new(file_id.into(), module))?;
85 let module = sb.to_def(InFile::new(file_id.into(), module))?;
86 137
87 let path = module.path_to_root(db).into_iter().rev().filter_map(|it| it.name(db)).join("::"); 138 let path = module.path_to_root(db).into_iter().rev().filter_map(|it| it.name(db)).join("::");
88 Some(Runnable { range, kind: RunnableKind::TestMod { path } }) 139 Some(Runnable { range, kind: RunnableKind::TestMod { path } })
@@ -121,13 +172,17 @@ mod tests {
121 Runnable { 172 Runnable {
122 range: [22; 46), 173 range: [22; 46),
123 kind: Test { 174 kind: Test {
124 name: "test_foo", 175 test_id: Path(
176 "test_foo",
177 ),
125 }, 178 },
126 }, 179 },
127 Runnable { 180 Runnable {
128 range: [47; 81), 181 range: [47; 81),
129 kind: Test { 182 kind: Test {
130 name: "test_foo", 183 test_id: Path(
184 "test_foo",
185 ),
131 }, 186 },
132 }, 187 },
133 ] 188 ]
@@ -160,7 +215,9 @@ mod tests {
160 Runnable { 215 Runnable {
161 range: [28; 57), 216 range: [28; 57),
162 kind: Test { 217 kind: Test {
163 name: "test_foo1", 218 test_id: Path(
219 "test_mod::test_foo1",
220 ),
164 }, 221 },
165 }, 222 },
166 ] 223 ]
@@ -195,7 +252,9 @@ mod tests {
195 Runnable { 252 Runnable {
196 range: [46; 79), 253 range: [46; 79),
197 kind: Test { 254 kind: Test {
198 name: "test_foo1", 255 test_id: Path(
256 "foo::test_mod::test_foo1",
257 ),
199 }, 258 },
200 }, 259 },
201 ] 260 ]
@@ -232,7 +291,9 @@ mod tests {
232 Runnable { 291 Runnable {
233 range: [68; 105), 292 range: [68; 105),
234 kind: Test { 293 kind: Test {
235 name: "test_foo1", 294 test_id: Path(
295 "foo::bar::test_mod::test_foo1",
296 ),
236 }, 297 },
237 }, 298 },
238 ] 299 ]
diff --git a/crates/ra_lsp_server/src/cargo_target_spec.rs b/crates/ra_lsp_server/src/cargo_target_spec.rs
index 594caffe2..5fd1e7b6b 100644
--- a/crates/ra_lsp_server/src/cargo_target_spec.rs
+++ b/crates/ra_lsp_server/src/cargo_target_spec.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use ra_ide::{FileId, RunnableKind}; 3use ra_ide::{FileId, RunnableKind, TestId};
4use ra_project_model::{self, ProjectWorkspace, TargetKind}; 4use ra_project_model::{self, ProjectWorkspace, TargetKind};
5 5
6use crate::{world::WorldSnapshot, Result}; 6use crate::{world::WorldSnapshot, Result};
@@ -13,13 +13,16 @@ pub(crate) fn runnable_args(
13 let spec = CargoTargetSpec::for_file(world, file_id)?; 13 let spec = CargoTargetSpec::for_file(world, file_id)?;
14 let mut res = Vec::new(); 14 let mut res = Vec::new();
15 match kind { 15 match kind {
16 RunnableKind::Test { name } => { 16 RunnableKind::Test { test_id } => {
17 res.push("test".to_string()); 17 res.push("test".to_string());
18 if let Some(spec) = spec { 18 if let Some(spec) = spec {
19 spec.push_to(&mut res); 19 spec.push_to(&mut res);
20 } 20 }
21 res.push("--".to_string()); 21 res.push("--".to_string());
22 res.push(name.to_string()); 22 res.push(test_id.to_string());
23 if let TestId::Path(_) = test_id {
24 res.push("--exact".to_string());
25 }
23 res.push("--nocapture".to_string()); 26 res.push("--nocapture".to_string());
24 } 27 }
25 RunnableKind::TestMod { path } => { 28 RunnableKind::TestMod { path } => {
@@ -31,13 +34,16 @@ pub(crate) fn runnable_args(
31 res.push(path.to_string()); 34 res.push(path.to_string());
32 res.push("--nocapture".to_string()); 35 res.push("--nocapture".to_string());
33 } 36 }
34 RunnableKind::Bench { name } => { 37 RunnableKind::Bench { test_id } => {
35 res.push("bench".to_string()); 38 res.push("bench".to_string());
36 if let Some(spec) = spec { 39 if let Some(spec) = spec {
37 spec.push_to(&mut res); 40 spec.push_to(&mut res);
38 } 41 }
39 res.push("--".to_string()); 42 res.push("--".to_string());
40 res.push(name.to_string()); 43 res.push(test_id.to_string());
44 if let TestId::Path(_) = test_id {
45 res.push("--exact".to_string());
46 }
41 res.push("--nocapture".to_string()); 47 res.push("--nocapture".to_string());
42 } 48 }
43 RunnableKind::Bin => { 49 RunnableKind::Bin => {
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 1da2d1814..3893430c0 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -919,9 +919,9 @@ fn to_lsp_runnable(
919 let args = runnable_args(world, file_id, &runnable.kind)?; 919 let args = runnable_args(world, file_id, &runnable.kind)?;
920 let line_index = world.analysis().file_line_index(file_id)?; 920 let line_index = world.analysis().file_line_index(file_id)?;
921 let label = match &runnable.kind { 921 let label = match &runnable.kind {
922 RunnableKind::Test { name } => format!("test {}", name), 922 RunnableKind::Test { test_id } => format!("test {}", test_id),
923 RunnableKind::TestMod { path } => format!("test-mod {}", path), 923 RunnableKind::TestMod { path } => format!("test-mod {}", path),
924 RunnableKind::Bench { name } => format!("bench {}", name), 924 RunnableKind::Bench { test_id } => format!("bench {}", test_id),
925 RunnableKind::Bin => "run binary".to_string(), 925 RunnableKind::Bin => "run binary".to_string(),
926 }; 926 };
927 Ok(req::Runnable { 927 Ok(req::Runnable {
diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs
index dff63a12d..9ca31cbcc 100644
--- a/crates/ra_lsp_server/tests/heavy_tests/main.rs
+++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs
@@ -147,7 +147,7 @@ fn main() {}
147 }, 147 },
148 json!([ 148 json!([
149 { 149 {
150 "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--nocapture" ], 150 "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--exact", "--nocapture" ],
151 "bin": "cargo", 151 "bin": "cargo",
152 "env": { "RUST_BACKTRACE": "short" }, 152 "env": { "RUST_BACKTRACE": "short" },
153 "label": "test test_eggs", 153 "label": "test test_eggs",
diff --git a/docs/dev/debugging.md b/docs/dev/debugging.md
index 1ccf4dca2..e6b082156 100644
--- a/docs/dev/debugging.md
+++ b/docs/dev/debugging.md
@@ -1,44 +1,66 @@
1# Debugging vs Code plugin and the Language Server 1# Debugging VSCode plugin and the language server
2 2
3**NOTE:** the information here is mostly obsolete 3## Prerequisites
4 4
5Install [LLDB](https://lldb.llvm.org/) and the [LLDB Extension](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb). 5- Install [LLDB](https://lldb.llvm.org/) and the [LLDB Extension](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb).
6- Open the root folder in VSCode. Here you can access the preconfigured debug setups.
6 7
7Checkout rust rust-analyzer and open it in vscode. 8 <img height=150px src="https://user-images.githubusercontent.com/36276403/74611090-92ec5380-5101-11ea-8a41-598f51f3f3e3.png" alt="Debug options view">
9
10- Install all TypeScript dependencies
11 ```bash
12 cd editors/code
13 npm install
14 ```
15
16## Common knowledge
17
18* All debug configurations open a new `[Extension Development Host]` VSCode instance
19where **only** the `rust-analyzer` extension being debugged is enabled.
20* To activate the extension you need to open any Rust project folder in `[Extension Development Host]`.
8 21
9```
10$ git clone https://github.com/rust-analyzer/rust-analyzer.git --depth 1
11$ cd rust-analyzer
12$ code .
13```
14 22
15- To attach to the `lsp server` in linux you'll have to run: 23## Debug TypeScript VSCode extension
16 24
17 `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope` 25- `Run Extension` - runs the extension with the globally installed `ra_lsp_server` binary.
26- `Run Extension (Dev Server)` - runs extension with the locally built LSP server (`target/debug/ra_lsp_server`).
27
28TypeScript debugging is configured to watch your source edits and recompile.
29To apply changes to an already running debug process press <kbd>Ctrl+Shift+P</kbd> and run the following command in your `[Extension Development Host]`
30
31```
32> Developer: Reload Window
33```
18 34
19 This enables ptrace on non forked processes 35## Debug Rust LSP server
20 36
21- Ensure the dependencies for the extension are installed, run the `npm: install - editors/code` task in vscode. 37- When attaching a debugger to an already running `rust-analyzer` server on Linux you might need to enable `ptrace` for unrelated processes by running:
22 38
23- Launch the `Debug Extension`, this will build the extension and the `lsp server`. 39 ```
40 echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
41 ```
24 42
25- A new instance of vscode with `[Extension Development Host]` in the title.
26 43
27 Don't worry about disabling `rls` all other extensions will be disabled but this one. 44- By default, the LSP server is built without debug information. To enable it, you'll need to change `Cargo.toml`:
45 ```toml
46 [profile.dev]
47 debug = 2
48 ```
28 49
29- In the new vscode instance open a rust project, and navigate to a rust file 50- Select `Run Extension (Dev Server)` to run your locally built `target/debug/ra_lsp_server`.
30 51
31- In the original vscode start an additional debug session (the three periods in the launch) and select `Debug Lsp Server`. 52- In the original VSCode window once again select the `Attach To Server` debug configuration.
32 53
33- A list of running processes should appear select the `ra_lsp_server` from this repo. 54- A list of running processes should appear. Select the `ra_lsp_server` from this repo.
34 55
35- Navigate to `crates/ra_lsp_server/src/main_loop.rs` and add a breakpoint to the `on_task` function. 56- Navigate to `crates/ra_lsp_server/src/main_loop.rs` and add a breakpoint to the `on_task` function.
36 57
37- Go back to the `[Extension Development Host]` instance and hover over a rust variable and your breakpoint should hit. 58- Go back to the `[Extension Development Host]` instance and hover over a Rust variable and your breakpoint should hit.
38 59
39## Demo 60## Demo
40 61
41![demonstration of debugging](https://user-images.githubusercontent.com/1711539/51384036-254fab80-1b2c-11e9-824d-95f9a6e9cf4f.gif) 62- [Debugging TypeScript VScode extension](https://www.youtube.com/watch?v=T-hvpK6s4wM).
63- [Debugging Rust LSP server](https://www.youtube.com/watch?v=EaNb5rg4E0M).
42 64
43## Troubleshooting 65## Troubleshooting
44 66
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 5fffd1fa7..c3fa788c7 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -44,7 +44,6 @@ export class Config {
44 this.refreshConfig(); 44 this.refreshConfig();
45 } 45 }
46 46
47
48 private refreshConfig() { 47 private refreshConfig() {
49 this.cfg = vscode.workspace.getConfiguration(Config.rootSection); 48 this.cfg = vscode.workspace.getConfiguration(Config.rootSection);
50 console.log("Using configuration:", this.cfg); 49 console.log("Using configuration:", this.cfg);
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 9fcf2ec38..ff6245f78 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -91,15 +91,11 @@ export async function sendRequestWithRetry<R>(
91 for (const delay of [2, 4, 6, 8, 10, null]) { 91 for (const delay of [2, 4, 6, 8, 10, null]) {
92 try { 92 try {
93 return await (token ? client.sendRequest(method, param, token) : client.sendRequest(method, param)); 93 return await (token ? client.sendRequest(method, param, token) : client.sendRequest(method, param));
94 } catch (e) { 94 } catch (err) {
95 if ( 95 if (delay === null || err.code !== lc.ErrorCodes.ContentModified) {
96 e.code === lc.ErrorCodes.ContentModified && 96 throw err;
97 delay !== null
98 ) {
99 await sleep(10 * (1 << delay));
100 continue;
101 } 97 }
102 throw e; 98 await sleep(10 * (1 << delay));
103 } 99 }
104 } 100 }
105 throw 'unreachable'; 101 throw 'unreachable';
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts
index c317a9213..3896878cd 100644
--- a/editors/code/src/inlay_hints.ts
+++ b/editors/code/src/inlay_hints.ts
@@ -27,7 +27,9 @@ export function activateInlayHints(ctx: Ctx) {
27 ctx.subscriptions 27 ctx.subscriptions
28 ); 28 );
29 29
30 ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints)); 30 // We pass async function though it will not be awaited when called,
31 // thus Promise rejections won't be handled, but this should never throw in fact...
32 ctx.onDidRestart(async _ => hintsUpdater.setEnabled(ctx.config.displayInlayHints));
31} 33}
32 34
33interface InlayHintsParams { 35interface InlayHintsParams {
@@ -36,7 +38,7 @@ interface InlayHintsParams {
36 38
37interface InlayHint { 39interface InlayHint {
38 range: vscode.Range; 40 range: vscode.Range;
39 kind: string; 41 kind: "TypeHint" | "ParameterHint";
40 label: string; 42 label: string;
41} 43}
42 44
@@ -53,7 +55,7 @@ const parameterHintDecorationType = vscode.window.createTextEditorDecorationType
53}); 55});
54 56
55class HintsUpdater { 57class HintsUpdater {
56 private pending: Map<string, vscode.CancellationTokenSource> = new Map(); 58 private pending = new Map<string, vscode.CancellationTokenSource>();
57 private ctx: Ctx; 59 private ctx: Ctx;
58 private enabled: boolean; 60 private enabled: boolean;
59 61
@@ -62,30 +64,36 @@ class HintsUpdater {
62 this.enabled = ctx.config.displayInlayHints; 64 this.enabled = ctx.config.displayInlayHints;
63 } 65 }
64 66
65 async setEnabled(enabled: boolean) { 67 async setEnabled(enabled: boolean): Promise<void> {
66 if (this.enabled == enabled) return; 68 if (this.enabled == enabled) return;
67 this.enabled = enabled; 69 this.enabled = enabled;
68 70
69 if (this.enabled) { 71 if (this.enabled) {
70 await this.refresh(); 72 return await this.refresh();
71 } else {
72 this.allEditors.forEach(it => {
73 this.setTypeDecorations(it, []);
74 this.setParameterDecorations(it, []);
75 });
76 } 73 }
74 this.allEditors.forEach(it => {
75 this.setTypeDecorations(it, []);
76 this.setParameterDecorations(it, []);
77 });
77 } 78 }
78 79
79 async refresh() { 80 async refresh() {
80 if (!this.enabled) return; 81 if (!this.enabled) return;
81 const promises = this.allEditors.map(it => this.refreshEditor(it)); 82 await Promise.all(this.allEditors.map(it => this.refreshEditor(it)));
82 await Promise.all(promises); 83 }
84
85 private get allEditors(): vscode.TextEditor[] {
86 return vscode.window.visibleTextEditors.filter(
87 editor => editor.document.languageId === 'rust',
88 );
83 } 89 }
84 90
85 private async refreshEditor(editor: vscode.TextEditor): Promise<void> { 91 private async refreshEditor(editor: vscode.TextEditor): Promise<void> {
86 const newHints = await this.queryHints(editor.document.uri.toString()); 92 const newHints = await this.queryHints(editor.document.uri.toString());
87 if (newHints == null) return; 93 if (newHints == null) return;
88 const newTypeDecorations = newHints.filter(hint => hint.kind === 'TypeHint') 94
95 const newTypeDecorations = newHints
96 .filter(hint => hint.kind === 'TypeHint')
89 .map(hint => ({ 97 .map(hint => ({
90 range: hint.range, 98 range: hint.range,
91 renderOptions: { 99 renderOptions: {
@@ -96,7 +104,8 @@ class HintsUpdater {
96 })); 104 }));
97 this.setTypeDecorations(editor, newTypeDecorations); 105 this.setTypeDecorations(editor, newTypeDecorations);
98 106
99 const newParameterDecorations = newHints.filter(hint => hint.kind === 'ParameterHint') 107 const newParameterDecorations = newHints
108 .filter(hint => hint.kind === 'ParameterHint')
100 .map(hint => ({ 109 .map(hint => ({
101 range: hint.range, 110 range: hint.range,
102 renderOptions: { 111 renderOptions: {
@@ -108,12 +117,6 @@ class HintsUpdater {
108 this.setParameterDecorations(editor, newParameterDecorations); 117 this.setParameterDecorations(editor, newParameterDecorations);
109 } 118 }
110 119
111 private get allEditors(): vscode.TextEditor[] {
112 return vscode.window.visibleTextEditors.filter(
113 editor => editor.document.languageId === 'rust',
114 );
115 }
116
117 private setTypeDecorations( 120 private setTypeDecorations(
118 editor: vscode.TextEditor, 121 editor: vscode.TextEditor,
119 decorations: vscode.DecorationOptions[], 122 decorations: vscode.DecorationOptions[],
@@ -137,12 +140,14 @@ class HintsUpdater {
137 private async queryHints(documentUri: string): Promise<InlayHint[] | null> { 140 private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
138 const client = this.ctx.client; 141 const client = this.ctx.client;
139 if (!client) return null; 142 if (!client) return null;
143
140 const request: InlayHintsParams = { 144 const request: InlayHintsParams = {
141 textDocument: { uri: documentUri }, 145 textDocument: { uri: documentUri },
142 }; 146 };
143 const tokenSource = new vscode.CancellationTokenSource(); 147 const tokenSource = new vscode.CancellationTokenSource();
144 const prev = this.pending.get(documentUri); 148 const prevHintsRequest = this.pending.get(documentUri);
145 if (prev) prev.cancel(); 149 prevHintsRequest?.cancel();
150
146 this.pending.set(documentUri, tokenSource); 151 this.pending.set(documentUri, tokenSource);
147 try { 152 try {
148 return await sendRequestWithRetry<InlayHint[] | null>( 153 return await sendRequestWithRetry<InlayHint[] | null>(