aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock12
-rw-r--r--crates/ra_hir/src/semantics.rs8
-rw-r--r--crates/ra_hir/src/source_analyzer.rs99
-rw-r--r--crates/ra_hir_def/src/body.rs4
-rw-r--r--crates/ra_hir_def/src/body/lower.rs16
-rw-r--r--crates/ra_hir_def/src/diagnostics.rs2
-rw-r--r--crates/ra_hir_expand/src/diagnostics.rs5
-rw-r--r--crates/ra_hir_ty/src/diagnostics.rs2
-rw-r--r--crates/ra_hir_ty/src/infer.rs4
-rw-r--r--crates/ra_hir_ty/src/infer/coerce.rs4
-rw-r--r--crates/ra_hir_ty/src/infer/unify.rs42
-rw-r--r--crates/ra_hir_ty/src/tests/regression.rs49
-rw-r--r--crates/ra_hir_ty/src/tests/traits.rs198
-rw-r--r--crates/ra_ide/src/completion/complete_trait_impl.rs19
-rw-r--r--crates/ra_ide/src/completion/complete_unqualified_path.rs40
-rw-r--r--crates/ra_ide/src/diagnostics.rs72
-rw-r--r--crates/ra_ide/src/marks.rs1
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs3
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs12
-rw-r--r--crates/ra_proc_macro/src/lib.rs12
-rw-r--r--crates/ra_proc_macro/src/process.rs19
-rw-r--r--crates/ra_proc_macro_srv/Cargo.toml1
-rw-r--r--crates/ra_proc_macro_srv/src/cli.rs (renamed from crates/ra_proc_macro_srv/src/main.rs)5
-rw-r--r--crates/ra_proc_macro_srv/src/dylib.rs77
-rw-r--r--crates/ra_proc_macro_srv/src/lib.rs6
-rw-r--r--crates/ra_project_model/src/lib.rs231
-rw-r--r--crates/rust-analyzer/Cargo.toml2
-rw-r--r--crates/rust-analyzer/src/bin/args.rs24
-rw-r--r--crates/rust-analyzer/src/bin/main.rs22
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs3
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs3
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs9
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs18
-rw-r--r--crates/rust-analyzer/src/config.rs12
-rw-r--r--crates/rust-analyzer/src/main_loop.rs60
-rw-r--r--crates/rust-analyzer/src/world.rs8
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs91
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/config.ts1
-rw-r--r--xtask/tests/tidy-tests/main.rs6
40 files changed, 905 insertions, 302 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2a12d9f5b..3826ae1c6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -676,6 +676,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
676checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 676checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
677 677
678[[package]] 678[[package]]
679name = "memmap"
680version = "0.7.0"
681source = "registry+https://github.com/rust-lang/crates.io-index"
682checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
683dependencies = [
684 "libc",
685 "winapi 0.3.8",
686]
687
688[[package]]
679name = "memoffset" 689name = "memoffset"
680version = "0.5.4" 690version = "0.5.4"
681source = "registry+https://github.com/rust-lang/crates.io-index" 691source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1112,6 +1122,7 @@ dependencies = [
1112 "difference", 1122 "difference",
1113 "goblin", 1123 "goblin",
1114 "libloading", 1124 "libloading",
1125 "memmap",
1115 "ra_mbe", 1126 "ra_mbe",
1116 "ra_proc_macro", 1127 "ra_proc_macro",
1117 "ra_tt", 1128 "ra_tt",
@@ -1341,6 +1352,7 @@ dependencies = [
1341 "ra_hir_def", 1352 "ra_hir_def",
1342 "ra_hir_ty", 1353 "ra_hir_ty",
1343 "ra_ide", 1354 "ra_ide",
1355 "ra_proc_macro_srv",
1344 "ra_prof", 1356 "ra_prof",
1345 "ra_project_model", 1357 "ra_project_model",
1346 "ra_syntax", 1358 "ra_syntax",
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs
index 2707e422d..0b477f0e9 100644
--- a/crates/ra_hir/src/semantics.rs
+++ b/crates/ra_hir/src/semantics.rs
@@ -20,6 +20,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
20 20
21use crate::{ 21use crate::{
22 db::HirDatabase, 22 db::HirDatabase,
23 diagnostics::Diagnostic,
23 semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, 24 semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
24 source_analyzer::{resolve_hir_path, SourceAnalyzer}, 25 source_analyzer::{resolve_hir_path, SourceAnalyzer},
25 AssocItem, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name, 26 AssocItem, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name,
@@ -126,6 +127,13 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
126 original_range(self.db, node.as_ref()) 127 original_range(self.db, node.as_ref())
127 } 128 }
128 129
130 pub fn diagnostics_range(&self, diagnostics: &dyn Diagnostic) -> FileRange {
131 let src = diagnostics.source();
132 let root = self.db.parse_or_expand(src.file_id).unwrap();
133 let node = src.value.to_node(&root);
134 original_range(self.db, src.with_value(&node))
135 }
136
129 pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ { 137 pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
130 let node = self.find_file(node); 138 let node = self.find_file(node);
131 node.ancestors_with_macros(self.db).map(|it| it.value) 139 node.ancestors_with_macros(self.db).map(|it| it.value)
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs
index 58ae6ce41..23af400b8 100644
--- a/crates/ra_hir/src/source_analyzer.rs
+++ b/crates/ra_hir/src/source_analyzer.rs
@@ -23,7 +23,7 @@ use hir_ty::{
23}; 23};
24use ra_syntax::{ 24use ra_syntax::{
25 ast::{self, AstNode}, 25 ast::{self, AstNode},
26 SyntaxNode, SyntaxNodePtr, TextUnit, 26 SyntaxNode, TextRange, TextUnit,
27}; 27};
28 28
29use crate::{ 29use crate::{
@@ -56,7 +56,7 @@ impl SourceAnalyzer {
56 let scopes = db.expr_scopes(def); 56 let scopes = db.expr_scopes(def);
57 let scope = match offset { 57 let scope = match offset {
58 None => scope_for(&scopes, &source_map, node), 58 None => scope_for(&scopes, &source_map, node),
59 Some(offset) => scope_for_offset(&scopes, &source_map, node.with_value(offset)), 59 Some(offset) => scope_for_offset(db, &scopes, &source_map, node.with_value(offset)),
60 }; 60 };
61 let resolver = resolver_for_scope(db.upcast(), def, scope); 61 let resolver = resolver_for_scope(db.upcast(), def, scope);
62 SourceAnalyzer { 62 SourceAnalyzer {
@@ -304,6 +304,7 @@ fn scope_for(
304} 304}
305 305
306fn scope_for_offset( 306fn scope_for_offset(
307 db: &dyn HirDatabase,
307 scopes: &ExprScopes, 308 scopes: &ExprScopes,
308 source_map: &BodySourceMap, 309 source_map: &BodySourceMap,
309 offset: InFile<TextUnit>, 310 offset: InFile<TextUnit>,
@@ -317,21 +318,63 @@ fn scope_for_offset(
317 if source.file_id != offset.file_id { 318 if source.file_id != offset.file_id {
318 return None; 319 return None;
319 } 320 }
320 let syntax_node_ptr = source.value.syntax_node_ptr(); 321 let root = source.file_syntax(db.upcast());
321 Some((syntax_node_ptr, scope)) 322 let node = source.value.to_node(&root);
323 Some((node.syntax().text_range(), scope))
322 }) 324 })
323 // find containing scope 325 // find containing scope
324 .min_by_key(|(ptr, _scope)| { 326 .min_by_key(|(expr_range, _scope)| {
325 ( 327 (
326 !(ptr.range().start() <= offset.value && offset.value <= ptr.range().end()), 328 !(expr_range.start() <= offset.value && offset.value <= expr_range.end()),
327 ptr.range().len(), 329 expr_range.len(),
328 ) 330 )
329 }) 331 })
330 .map(|(ptr, scope)| { 332 .map(|(expr_range, scope)| {
331 adjust(scopes, source_map, ptr, offset.file_id, offset.value).unwrap_or(*scope) 333 adjust(db, scopes, source_map, expr_range, offset.file_id, offset.value)
334 .unwrap_or(*scope)
332 }) 335 })
333} 336}
334 337
338// XXX: during completion, cursor might be outside of any particular
339// expression. Try to figure out the correct scope...
340fn adjust(
341 db: &dyn HirDatabase,
342 scopes: &ExprScopes,
343 source_map: &BodySourceMap,
344 expr_range: TextRange,
345 file_id: HirFileId,
346 offset: TextUnit,
347) -> Option<ScopeId> {
348 let child_scopes = scopes
349 .scope_by_expr()
350 .iter()
351 .filter_map(|(id, scope)| {
352 let source = source_map.expr_syntax(*id).ok()?;
353 // FIXME: correctly handle macro expansion
354 if source.file_id != file_id {
355 return None;
356 }
357 let root = source.file_syntax(db.upcast());
358 let node = source.value.to_node(&root);
359 Some((node.syntax().text_range(), scope))
360 })
361 .filter(|(range, _)| {
362 range.start() <= offset && range.is_subrange(&expr_range) && *range != expr_range
363 });
364
365 child_scopes
366 .max_by(|(r1, _), (r2, _)| {
367 if r2.is_subrange(&r1) {
368 std::cmp::Ordering::Greater
369 } else if r1.is_subrange(&r2) {
370 std::cmp::Ordering::Less
371 } else {
372 r1.start().cmp(&r2.start())
373 }
374 })
375 .map(|(_ptr, scope)| *scope)
376}
377
335pub(crate) fn resolve_hir_path( 378pub(crate) fn resolve_hir_path(
336 db: &dyn HirDatabase, 379 db: &dyn HirDatabase,
337 resolver: &Resolver, 380 resolver: &Resolver,
@@ -376,41 +419,3 @@ pub(crate) fn resolve_hir_path(
376 .map(|def| PathResolution::Macro(def.into())) 419 .map(|def| PathResolution::Macro(def.into()))
377 }) 420 })
378} 421}
379
380// XXX: during completion, cursor might be outside of any particular
381// expression. Try to figure out the correct scope...
382fn adjust(
383 scopes: &ExprScopes,
384 source_map: &BodySourceMap,
385 ptr: SyntaxNodePtr,
386 file_id: HirFileId,
387 offset: TextUnit,
388) -> Option<ScopeId> {
389 let r = ptr.range();
390 let child_scopes = scopes
391 .scope_by_expr()
392 .iter()
393 .filter_map(|(id, scope)| {
394 let source = source_map.expr_syntax(*id).ok()?;
395 // FIXME: correctly handle macro expansion
396 if source.file_id != file_id {
397 return None;
398 }
399 let syntax_node_ptr = source.value.syntax_node_ptr();
400 Some((syntax_node_ptr, scope))
401 })
402 .map(|(ptr, scope)| (ptr.range(), scope))
403 .filter(|(range, _)| range.start() <= offset && range.is_subrange(&r) && *range != r);
404
405 child_scopes
406 .max_by(|(r1, _), (r2, _)| {
407 if r2.is_subrange(&r1) {
408 std::cmp::Ordering::Greater
409 } else if r1.is_subrange(&r2) {
410 std::cmp::Ordering::Less
411 } else {
412 r1.start().cmp(&r2.start())
413 }
414 })
415 .map(|(_ptr, scope)| *scope)
416}
diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs
index eafaf48c1..3b169440a 100644
--- a/crates/ra_hir_def/src/body.rs
+++ b/crates/ra_hir_def/src/body.rs
@@ -210,7 +210,7 @@ pub struct BodySourceMap {
210 expr_map_back: ArenaMap<ExprId, Result<ExprSource, SyntheticSyntax>>, 210 expr_map_back: ArenaMap<ExprId, Result<ExprSource, SyntheticSyntax>>,
211 pat_map: FxHashMap<PatSource, PatId>, 211 pat_map: FxHashMap<PatSource, PatId>,
212 pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, 212 pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>,
213 field_map: FxHashMap<(ExprId, usize), AstPtr<ast::RecordField>>, 213 field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordField>>>,
214 expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>, 214 expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
215} 215}
216 216
@@ -303,7 +303,7 @@ impl BodySourceMap {
303 self.pat_map.get(&src).cloned() 303 self.pat_map.get(&src).cloned()
304 } 304 }
305 305
306 pub fn field_syntax(&self, expr: ExprId, field: usize) -> AstPtr<ast::RecordField> { 306 pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordField>> {
307 self.field_map[&(expr, field)].clone() 307 self.field_map[&(expr, field)].clone()
308 } 308 }
309} 309}
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index 79abe55ce..82a52804d 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -320,7 +320,8 @@ impl ExprCollector<'_> {
320 320
321 let res = self.alloc_expr(record_lit, syntax_ptr); 321 let res = self.alloc_expr(record_lit, syntax_ptr);
322 for (i, ptr) in field_ptrs.into_iter().enumerate() { 322 for (i, ptr) in field_ptrs.into_iter().enumerate() {
323 self.source_map.field_map.insert((res, i), ptr); 323 let src = self.expander.to_source(ptr);
324 self.source_map.field_map.insert((res, i), src);
324 } 325 }
325 res 326 res
326 } 327 }
@@ -650,6 +651,7 @@ impl ExprCollector<'_> {
650 ast::Pat::SlicePat(p) => { 651 ast::Pat::SlicePat(p) => {
651 let SlicePatComponents { prefix, slice, suffix } = p.components(); 652 let SlicePatComponents { prefix, slice, suffix } = p.components();
652 653
654 // FIXME properly handle `DotDotPat`
653 Pat::Slice { 655 Pat::Slice {
654 prefix: prefix.into_iter().map(|p| self.collect_pat(p)).collect(), 656 prefix: prefix.into_iter().map(|p| self.collect_pat(p)).collect(),
655 slice: slice.map(|p| self.collect_pat(p)), 657 slice: slice.map(|p| self.collect_pat(p)),
@@ -666,9 +668,15 @@ impl ExprCollector<'_> {
666 Pat::Missing 668 Pat::Missing
667 } 669 }
668 } 670 }
669 ast::Pat::DotDotPat(_) => unreachable!( 671 ast::Pat::DotDotPat(_) => {
670 "`DotDotPat` requires special handling and should not be mapped to a Pat." 672 // `DotDotPat` requires special handling and should not be mapped
671 ), 673 // to a Pat. Here we are using `Pat::Missing` as a fallback for
674 // when `DotDotPat` is mapped to `Pat`, which can easily happen
675 // when the source code being analyzed has a malformed pattern
676 // which includes `..` in a place where it isn't valid.
677
678 Pat::Missing
679 }
672 // FIXME: implement 680 // FIXME: implement
673 ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing, 681 ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
674 }; 682 };
diff --git a/crates/ra_hir_def/src/diagnostics.rs b/crates/ra_hir_def/src/diagnostics.rs
index cfa0f2f76..510c5e064 100644
--- a/crates/ra_hir_def/src/diagnostics.rs
+++ b/crates/ra_hir_def/src/diagnostics.rs
@@ -20,7 +20,7 @@ impl Diagnostic for UnresolvedModule {
20 "unresolved module".to_string() 20 "unresolved module".to_string()
21 } 21 }
22 fn source(&self) -> InFile<SyntaxNodePtr> { 22 fn source(&self) -> InFile<SyntaxNodePtr> {
23 InFile { file_id: self.file, value: self.decl.clone().into() } 23 InFile::new(self.file, self.decl.clone().into())
24 } 24 }
25 fn as_any(&self) -> &(dyn Any + Send + 'static) { 25 fn as_any(&self) -> &(dyn Any + Send + 'static) {
26 self 26 self
diff --git a/crates/ra_hir_expand/src/diagnostics.rs b/crates/ra_hir_expand/src/diagnostics.rs
index 108c1e38c..99209c6e8 100644
--- a/crates/ra_hir_expand/src/diagnostics.rs
+++ b/crates/ra_hir_expand/src/diagnostics.rs
@@ -16,16 +16,13 @@
16 16
17use std::{any::Any, fmt}; 17use std::{any::Any, fmt};
18 18
19use ra_syntax::{SyntaxNode, SyntaxNodePtr, TextRange}; 19use ra_syntax::{SyntaxNode, SyntaxNodePtr};
20 20
21use crate::{db::AstDatabase, InFile}; 21use crate::{db::AstDatabase, InFile};
22 22
23pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { 23pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static {
24 fn message(&self) -> String; 24 fn message(&self) -> String;
25 fn source(&self) -> InFile<SyntaxNodePtr>; 25 fn source(&self) -> InFile<SyntaxNodePtr>;
26 fn highlight_range(&self) -> TextRange {
27 self.source().value.range()
28 }
29 fn as_any(&self) -> &(dyn Any + Send + 'static); 26 fn as_any(&self) -> &(dyn Any + Send + 'static);
30} 27}
31 28
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs
index 927896d6f..c8fd54861 100644
--- a/crates/ra_hir_ty/src/diagnostics.rs
+++ b/crates/ra_hir_ty/src/diagnostics.rs
@@ -21,7 +21,7 @@ impl Diagnostic for NoSuchField {
21 } 21 }
22 22
23 fn source(&self) -> InFile<SyntaxNodePtr> { 23 fn source(&self) -> InFile<SyntaxNodePtr> {
24 InFile { file_id: self.file, value: self.field.clone().into() } 24 InFile::new(self.file, self.field.clone().into())
25 } 25 }
26 26
27 fn as_any(&self) -> &(dyn Any + Send + 'static) { 27 fn as_any(&self) -> &(dyn Any + Send + 'static) {
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs
index 246b0e9be..b6d9b3438 100644
--- a/crates/ra_hir_ty/src/infer.rs
+++ b/crates/ra_hir_ty/src/infer.rs
@@ -682,10 +682,10 @@ mod diagnostics {
682 ) { 682 ) {
683 match self { 683 match self {
684 InferenceDiagnostic::NoSuchField { expr, field } => { 684 InferenceDiagnostic::NoSuchField { expr, field } => {
685 let file = owner.lookup(db.upcast()).source(db.upcast()).file_id; 685 let source = owner.lookup(db.upcast()).source(db.upcast());
686 let (_, source_map) = db.body_with_source_map(owner.into()); 686 let (_, source_map) = db.body_with_source_map(owner.into());
687 let field = source_map.field_syntax(*expr, *field); 687 let field = source_map.field_syntax(*expr, *field);
688 sink.push(NoSuchField { file, field }) 688 sink.push(NoSuchField { file: source.file_id, field: field.value })
689 } 689 }
690 } 690 }
691 } 691 }
diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs
index 959b1e212..89200255a 100644
--- a/crates/ra_hir_ty/src/infer/coerce.rs
+++ b/crates/ra_hir_ty/src/infer/coerce.rs
@@ -51,7 +51,7 @@ impl<'a> InferenceContext<'a> {
51 // Trivial cases, this should go after `never` check to 51 // Trivial cases, this should go after `never` check to
52 // avoid infer result type to be never 52 // avoid infer result type to be never
53 _ => { 53 _ => {
54 if self.table.unify_inner_trivial(&from_ty, &to_ty) { 54 if self.table.unify_inner_trivial(&from_ty, &to_ty, 0) {
55 return true; 55 return true;
56 } 56 }
57 } 57 }
@@ -175,7 +175,7 @@ impl<'a> InferenceContext<'a> {
175 return self.table.unify_substs(st1, st2, 0); 175 return self.table.unify_substs(st1, st2, 0);
176 } 176 }
177 _ => { 177 _ => {
178 if self.table.unify_inner_trivial(&derefed_ty, &to_ty) { 178 if self.table.unify_inner_trivial(&derefed_ty, &to_ty, 0) {
179 return true; 179 return true;
180 } 180 }
181 } 181 }
diff --git a/crates/ra_hir_ty/src/infer/unify.rs b/crates/ra_hir_ty/src/infer/unify.rs
index 5f6cea8d3..ab0bc8b70 100644
--- a/crates/ra_hir_ty/src/infer/unify.rs
+++ b/crates/ra_hir_ty/src/infer/unify.rs
@@ -8,7 +8,8 @@ use test_utils::tested_by;
8 8
9use super::{InferenceContext, Obligation}; 9use super::{InferenceContext, Obligation};
10use crate::{ 10use crate::{
11 BoundVar, Canonical, DebruijnIndex, InEnvironment, InferTy, Substs, Ty, TypeCtor, TypeWalk, 11 BoundVar, Canonical, DebruijnIndex, GenericPredicate, InEnvironment, InferTy, Substs, Ty,
12 TypeCtor, TypeWalk,
12}; 13};
13 14
14impl<'a> InferenceContext<'a> { 15impl<'a> InferenceContext<'a> {
@@ -226,16 +227,26 @@ impl InferenceTable {
226 (Ty::Apply(a_ty1), Ty::Apply(a_ty2)) if a_ty1.ctor == a_ty2.ctor => { 227 (Ty::Apply(a_ty1), Ty::Apply(a_ty2)) if a_ty1.ctor == a_ty2.ctor => {
227 self.unify_substs(&a_ty1.parameters, &a_ty2.parameters, depth + 1) 228 self.unify_substs(&a_ty1.parameters, &a_ty2.parameters, depth + 1)
228 } 229 }
229 _ => self.unify_inner_trivial(&ty1, &ty2), 230
231 _ => self.unify_inner_trivial(&ty1, &ty2, depth),
230 } 232 }
231 } 233 }
232 234
233 pub(super) fn unify_inner_trivial(&mut self, ty1: &Ty, ty2: &Ty) -> bool { 235 pub(super) fn unify_inner_trivial(&mut self, ty1: &Ty, ty2: &Ty, depth: usize) -> bool {
234 match (ty1, ty2) { 236 match (ty1, ty2) {
235 (Ty::Unknown, _) | (_, Ty::Unknown) => true, 237 (Ty::Unknown, _) | (_, Ty::Unknown) => true,
236 238
237 (Ty::Placeholder(p1), Ty::Placeholder(p2)) if *p1 == *p2 => true, 239 (Ty::Placeholder(p1), Ty::Placeholder(p2)) if *p1 == *p2 => true,
238 240
241 (Ty::Dyn(dyn1), Ty::Dyn(dyn2)) if dyn1.len() == dyn2.len() => {
242 for (pred1, pred2) in dyn1.iter().zip(dyn2.iter()) {
243 if !self.unify_preds(pred1, pred2, depth + 1) {
244 return false;
245 }
246 }
247 true
248 }
249
239 (Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2))) 250 (Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2)))
240 | (Ty::Infer(InferTy::IntVar(tv1)), Ty::Infer(InferTy::IntVar(tv2))) 251 | (Ty::Infer(InferTy::IntVar(tv1)), Ty::Infer(InferTy::IntVar(tv2)))
241 | (Ty::Infer(InferTy::FloatVar(tv1)), Ty::Infer(InferTy::FloatVar(tv2))) 252 | (Ty::Infer(InferTy::FloatVar(tv1)), Ty::Infer(InferTy::FloatVar(tv2)))
@@ -268,6 +279,31 @@ impl InferenceTable {
268 } 279 }
269 } 280 }
270 281
282 fn unify_preds(
283 &mut self,
284 pred1: &GenericPredicate,
285 pred2: &GenericPredicate,
286 depth: usize,
287 ) -> bool {
288 match (pred1, pred2) {
289 (GenericPredicate::Implemented(tr1), GenericPredicate::Implemented(tr2))
290 if tr1.trait_ == tr2.trait_ =>
291 {
292 self.unify_substs(&tr1.substs, &tr2.substs, depth + 1)
293 }
294 (GenericPredicate::Projection(proj1), GenericPredicate::Projection(proj2))
295 if proj1.projection_ty.associated_ty == proj2.projection_ty.associated_ty =>
296 {
297 self.unify_substs(
298 &proj1.projection_ty.parameters,
299 &proj2.projection_ty.parameters,
300 depth + 1,
301 ) && self.unify_inner(&proj1.ty, &proj2.ty, depth + 1)
302 }
303 _ => false,
304 }
305 }
306
271 /// If `ty` is a type variable with known type, returns that type; 307 /// If `ty` is a type variable with known type, returns that type;
272 /// otherwise, return ty. 308 /// otherwise, return ty.
273 pub fn resolve_ty_shallow<'b>(&mut self, ty: &'b Ty) -> Cow<'b, Ty> { 309 pub fn resolve_ty_shallow<'b>(&mut self, ty: &'b Ty) -> Cow<'b, Ty> {
diff --git a/crates/ra_hir_ty/src/tests/regression.rs b/crates/ra_hir_ty/src/tests/regression.rs
index d69115a2f..61284d672 100644
--- a/crates/ra_hir_ty/src/tests/regression.rs
+++ b/crates/ra_hir_ty/src/tests/regression.rs
@@ -484,3 +484,52 @@ fn main() {
484 484
485 assert_eq!("()", super::type_at_pos(&db, pos)); 485 assert_eq!("()", super::type_at_pos(&db, pos));
486} 486}
487
488#[test]
489fn issue_3999_slice() {
490 assert_snapshot!(
491 infer(r#"
492fn foo(params: &[usize]) {
493 match params {
494 [ps @ .., _] => {}
495 }
496}
497"#),
498 @r###"
499 [8; 14) 'params': &[usize]
500 [26; 81) '{ ... } }': ()
501 [32; 79) 'match ... }': ()
502 [38; 44) 'params': &[usize]
503 [55; 67) '[ps @ .., _]': [usize]
504 [65; 66) '_': usize
505 [71; 73) '{}': ()
506 "###
507 );
508}
509
510#[test]
511fn issue_3999_struct() {
512 // rust-analyzer should not panic on seeing this malformed
513 // record pattern.
514 assert_snapshot!(
515 infer(r#"
516struct Bar {
517 a: bool,
518}
519fn foo(b: Bar) {
520 match b {
521 Bar { a: .. } => {},
522 }
523}
524"#),
525 @r###"
526 [36; 37) 'b': Bar
527 [44; 96) '{ ... } }': ()
528 [50; 94) 'match ... }': ()
529 [56; 57) 'b': Bar
530 [68; 81) 'Bar { a: .. }': Bar
531 [77; 79) '..': bool
532 [85; 87) '{}': ()
533 "###
534 );
535}
diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs
index 36f53b264..a46f03b7f 100644
--- a/crates/ra_hir_ty/src/tests/traits.rs
+++ b/crates/ra_hir_ty/src/tests/traits.rs
@@ -2240,3 +2240,201 @@ fn test(x: Box<dyn Trait>) {
2240 ); 2240 );
2241 assert_eq!(t, "()"); 2241 assert_eq!(t, "()");
2242} 2242}
2243
2244#[test]
2245fn string_to_owned() {
2246 let t = type_at(
2247 r#"
2248//- /main.rs
2249struct String {}
2250pub trait ToOwned {
2251 type Owned;
2252 fn to_owned(&self) -> Self::Owned;
2253}
2254impl ToOwned for str {
2255 type Owned = String;
2256}
2257fn test() {
2258 "foo".to_owned()<|>;
2259}
2260"#,
2261 );
2262 assert_eq!(t, "String");
2263}
2264
2265#[test]
2266fn iterator_chain() {
2267 assert_snapshot!(
2268 infer(r#"
2269//- /main.rs
2270#[lang = "fn_once"]
2271trait FnOnce<Args> {
2272 type Output;
2273}
2274#[lang = "fn_mut"]
2275trait FnMut<Args>: FnOnce<Args> { }
2276
2277enum Option<T> { Some(T), None }
2278use Option::*;
2279
2280pub trait Iterator {
2281 type Item;
2282
2283 fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F>
2284 where
2285 F: FnMut(Self::Item) -> Option<B>,
2286 { loop {} }
2287
2288 fn for_each<F>(self, f: F)
2289 where
2290 F: FnMut(Self::Item),
2291 { loop {} }
2292}
2293
2294pub trait IntoIterator {
2295 type Item;
2296 type IntoIter: Iterator<Item = Self::Item>;
2297 fn into_iter(self) -> Self::IntoIter;
2298}
2299
2300pub struct FilterMap<I, F> { }
2301impl<B, I: Iterator, F> Iterator for FilterMap<I, F>
2302where
2303 F: FnMut(I::Item) -> Option<B>,
2304{
2305 type Item = B;
2306}
2307
2308#[stable(feature = "rust1", since = "1.0.0")]
2309impl<I: Iterator> IntoIterator for I {
2310 type Item = I::Item;
2311 type IntoIter = I;
2312
2313 fn into_iter(self) -> I {
2314 self
2315 }
2316}
2317
2318struct Vec<T> {}
2319impl<T> Vec<T> {
2320 fn new() -> Self { loop {} }
2321}
2322
2323impl<T> IntoIterator for Vec<T> {
2324 type Item = T;
2325 type IntoIter = IntoIter<T>;
2326}
2327
2328pub struct IntoIter<T> { }
2329impl<T> Iterator for IntoIter<T> {
2330 type Item = T;
2331}
2332
2333fn main() {
2334 Vec::<i32>::new().into_iter()
2335 .filter_map(|x| if x > 0 { Some(x as u32) } else { None })
2336 .for_each(|y| { y; });
2337}
2338"#),
2339 @r###"
2340 [240; 244) 'self': Self
2341 [246; 247) 'f': F
2342 [331; 342) '{ loop {} }': FilterMap<Self, F>
2343 [333; 340) 'loop {}': !
2344 [338; 340) '{}': ()
2345 [363; 367) 'self': Self
2346 [369; 370) 'f': F
2347 [419; 430) '{ loop {} }': ()
2348 [421; 428) 'loop {}': !
2349 [426; 428) '{}': ()
2350 [539; 543) 'self': Self
2351 [868; 872) 'self': I
2352 [879; 899) '{ ... }': I
2353 [889; 893) 'self': I
2354 [958; 969) '{ loop {} }': Vec<T>
2355 [960; 967) 'loop {}': !
2356 [965; 967) '{}': ()
2357 [1156; 1287) '{ ... }); }': ()
2358 [1162; 1177) 'Vec::<i32>::new': fn new<i32>() -> Vec<i32>
2359 [1162; 1179) 'Vec::<...:new()': Vec<i32>
2360 [1162; 1191) 'Vec::<...iter()': IntoIter<i32>
2361 [1162; 1256) 'Vec::<...one })': FilterMap<IntoIter<i32>, |i32| -> Option<u32>>
2362 [1162; 1284) 'Vec::<... y; })': ()
2363 [1210; 1255) '|x| if...None }': |i32| -> Option<u32>
2364 [1211; 1212) 'x': i32
2365 [1214; 1255) 'if x >...None }': Option<u32>
2366 [1217; 1218) 'x': i32
2367 [1217; 1222) 'x > 0': bool
2368 [1221; 1222) '0': i32
2369 [1223; 1241) '{ Some...u32) }': Option<u32>
2370 [1225; 1229) 'Some': Some<u32>(u32) -> Option<u32>
2371 [1225; 1239) 'Some(x as u32)': Option<u32>
2372 [1230; 1231) 'x': i32
2373 [1230; 1238) 'x as u32': u32
2374 [1247; 1255) '{ None }': Option<u32>
2375 [1249; 1253) 'None': Option<u32>
2376 [1273; 1283) '|y| { y; }': |u32| -> ()
2377 [1274; 1275) 'y': u32
2378 [1277; 1283) '{ y; }': ()
2379 [1279; 1280) 'y': u32
2380 "###
2381 );
2382}
2383
2384#[test]
2385fn nested_assoc() {
2386 let t = type_at(
2387 r#"
2388//- /main.rs
2389struct Bar;
2390struct Foo;
2391
2392trait A {
2393 type OutputA;
2394}
2395
2396impl A for Bar {
2397 type OutputA = Foo;
2398}
2399
2400trait B {
2401 type Output;
2402 fn foo() -> Self::Output;
2403}
2404
2405impl<T:A> B for T {
2406 type Output = T::OutputA;
2407 fn foo() -> Self::Output { loop {} }
2408}
2409
2410fn main() {
2411 Bar::foo()<|>;
2412}
2413"#,
2414 );
2415 assert_eq!(t, "Foo");
2416}
2417
2418#[test]
2419fn trait_object_no_coercion() {
2420 assert_snapshot!(
2421 infer_with_mismatches(r#"
2422trait Foo {}
2423
2424fn foo(x: &dyn Foo) {}
2425
2426fn test(x: &dyn Foo) {
2427 foo(x);
2428}
2429"#, true),
2430 @r###"
2431 [22; 23) 'x': &dyn Foo
2432 [35; 37) '{}': ()
2433 [47; 48) 'x': &dyn Foo
2434 [60; 75) '{ foo(x); }': ()
2435 [66; 69) 'foo': fn foo(&dyn Foo)
2436 [66; 72) 'foo(x)': ()
2437 [70; 71) 'x': &dyn Foo
2438 "###
2439 );
2440}
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs
index fab02945c..2ec0e7ce9 100644
--- a/crates/ra_ide/src/completion/complete_trait_impl.rs
+++ b/crates/ra_ide/src/completion/complete_trait_impl.rs
@@ -142,11 +142,11 @@ fn add_function_impl(
142 CompletionItemKind::Function 142 CompletionItemKind::Function
143 }; 143 };
144 144
145 let snippet = format!("{} {{}}", display); 145 let snippet = format!("{} {{\n $0\n}}", display);
146 146
147 let range = TextRange::from_to(fn_def_node.text_range().start(), ctx.source_range().end()); 147 let range = TextRange::from_to(fn_def_node.text_range().start(), ctx.source_range().end());
148 148
149 builder.text_edit(TextEdit::replace(range, snippet)).kind(completion_kind).add_to(acc); 149 builder.snippet_edit(TextEdit::replace(range, snippet)).kind(completion_kind).add_to(acc);
150} 150}
151 151
152fn add_type_alias_impl( 152fn add_type_alias_impl(
@@ -217,9 +217,10 @@ fn make_const_compl_syntax(const_: &ast::ConstDef) -> String {
217 217
218#[cfg(test)] 218#[cfg(test)]
219mod tests { 219mod tests {
220 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
221 use insta::assert_debug_snapshot; 220 use insta::assert_debug_snapshot;
222 221
222 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
223
223 fn complete(code: &str) -> Vec<CompletionItem> { 224 fn complete(code: &str) -> Vec<CompletionItem> {
224 do_completion(code, CompletionKind::Magic) 225 do_completion(code, CompletionKind::Magic)
225 } 226 }
@@ -255,7 +256,7 @@ mod tests {
255 label: "fn test()", 256 label: "fn test()",
256 source_range: [209; 210), 257 source_range: [209; 210),
257 delete: [209; 210), 258 delete: [209; 210),
258 insert: "fn test() {}", 259 insert: "fn test() {\n $0\n}",
259 kind: Function, 260 kind: Function,
260 lookup: "test", 261 lookup: "test",
261 }, 262 },
@@ -313,7 +314,7 @@ mod tests {
313 label: "fn test()", 314 label: "fn test()",
314 source_range: [139; 140), 315 source_range: [139; 140),
315 delete: [139; 140), 316 delete: [139; 140),
316 insert: "fn test() {}", 317 insert: "fn test() {\n $0\n}",
317 kind: Function, 318 kind: Function,
318 lookup: "test", 319 lookup: "test",
319 }, 320 },
@@ -342,7 +343,7 @@ mod tests {
342 label: "fn foo()", 343 label: "fn foo()",
343 source_range: [141; 142), 344 source_range: [141; 142),
344 delete: [138; 142), 345 delete: [138; 142),
345 insert: "fn foo() {}", 346 insert: "fn foo() {\n $0\n}",
346 kind: Function, 347 kind: Function,
347 lookup: "foo", 348 lookup: "foo",
348 }, 349 },
@@ -374,7 +375,7 @@ mod tests {
374 label: "fn foo_bar()", 375 label: "fn foo_bar()",
375 source_range: [200; 201), 376 source_range: [200; 201),
376 delete: [197; 201), 377 delete: [197; 201),
377 insert: "fn foo_bar() {}", 378 insert: "fn foo_bar() {\n $0\n}",
378 kind: Function, 379 kind: Function,
379 lookup: "foo_bar", 380 lookup: "foo_bar",
380 }, 381 },
@@ -425,7 +426,7 @@ mod tests {
425 label: "fn foo()", 426 label: "fn foo()",
426 source_range: [144; 145), 427 source_range: [144; 145),
427 delete: [141; 145), 428 delete: [141; 145),
428 insert: "fn foo<T>() {}", 429 insert: "fn foo<T>() {\n $0\n}",
429 kind: Function, 430 kind: Function,
430 lookup: "foo", 431 lookup: "foo",
431 }, 432 },
@@ -454,7 +455,7 @@ mod tests {
454 label: "fn foo()", 455 label: "fn foo()",
455 source_range: [166; 167), 456 source_range: [166; 167),
456 delete: [163; 167), 457 delete: [163; 167),
457 insert: "fn foo<T>()\nwhere T: Into<String> {}", 458 insert: "fn foo<T>()\nwhere T: Into<String> {\n $0\n}",
458 kind: Function, 459 kind: Function,
459 lookup: "foo", 460 lookup: "foo",
460 }, 461 },
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs
index 2d8e0776c..ad5fdcc4e 100644
--- a/crates/ra_ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs
@@ -1,6 +1,10 @@
1//! Completion of names from the current scope, e.g. locals and imported items. 1//! Completion of names from the current scope, e.g. locals and imported items.
2 2
3use hir::ScopeDef;
4use test_utils::tested_by;
5
3use crate::completion::{CompletionContext, Completions}; 6use crate::completion::{CompletionContext, Completions};
7use ra_syntax::AstNode;
4 8
5pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { 9pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
6 if !ctx.is_trivial_path { 10 if !ctx.is_trivial_path {
@@ -14,12 +18,23 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
14 return; 18 return;
15 } 19 }
16 20
17 ctx.scope().process_all_names(&mut |name, res| acc.add_resolution(ctx, name.to_string(), &res)); 21 ctx.scope().process_all_names(&mut |name, res| {
22 if ctx.use_item_syntax.is_some() {
23 if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) {
24 if name_ref.syntax().text() == name.to_string().as_str() {
25 tested_by!(self_fulfilling_completion);
26 return;
27 }
28 }
29 }
30 acc.add_resolution(ctx, name.to_string(), &res)
31 });
18} 32}
19 33
20#[cfg(test)] 34#[cfg(test)]
21mod tests { 35mod tests {
22 use insta::assert_debug_snapshot; 36 use insta::assert_debug_snapshot;
37 use test_utils::covers;
23 38
24 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; 39 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
25 40
@@ -28,6 +43,29 @@ mod tests {
28 } 43 }
29 44
30 #[test] 45 #[test]
46 fn self_fulfilling_completion() {
47 covers!(self_fulfilling_completion);
48 assert_debug_snapshot!(
49 do_reference_completion(
50 r#"
51 use foo<|>
52 use std::collections;
53 "#,
54 ),
55 @r###"
56 [
57 CompletionItem {
58 label: "collections",
59 source_range: [21; 24),
60 delete: [21; 24),
61 insert: "collections",
62 },
63 ]
64 "###
65 );
66 }
67
68 #[test]
31 fn bind_pat_and_path_ignore_at() { 69 fn bind_pat_and_path_ignore_at() {
32 assert_debug_snapshot!( 70 assert_debug_snapshot!(
33 do_reference_completion( 71 do_reference_completion(
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index 901ad104c..e7e201709 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -1,4 +1,8 @@
1//! FIXME: write short doc here 1//! Collects diagnostics & fixits for a single file.
2//!
3//! The tricky bit here is that diagnostics are produced by hir in terms of
4//! macro-expanded files, but we need to present them to the users in terms of
5//! original files. So we need to map the ranges.
2 6
3use std::cell::RefCell; 7use std::cell::RefCell;
4 8
@@ -46,7 +50,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
46 let mut sink = DiagnosticSink::new(|d| { 50 let mut sink = DiagnosticSink::new(|d| {
47 res.borrow_mut().push(Diagnostic { 51 res.borrow_mut().push(Diagnostic {
48 message: d.message(), 52 message: d.message(),
49 range: d.highlight_range(), 53 range: sema.diagnostics_range(d).range,
50 severity: Severity::Error, 54 severity: Severity::Error,
51 fix: None, 55 fix: None,
52 }) 56 })
@@ -62,7 +66,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
62 let create_file = FileSystemEdit::CreateFile { source_root, path }; 66 let create_file = FileSystemEdit::CreateFile { source_root, path };
63 let fix = SourceChange::file_system_edit("create module", create_file); 67 let fix = SourceChange::file_system_edit("create module", create_file);
64 res.borrow_mut().push(Diagnostic { 68 res.borrow_mut().push(Diagnostic {
65 range: d.highlight_range(), 69 range: sema.diagnostics_range(d).range,
66 message: d.message(), 70 message: d.message(),
67 severity: Severity::Error, 71 severity: Severity::Error,
68 fix: Some(fix), 72 fix: Some(fix),
@@ -95,7 +99,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
95 }; 99 };
96 100
97 res.borrow_mut().push(Diagnostic { 101 res.borrow_mut().push(Diagnostic {
98 range: d.highlight_range(), 102 range: sema.diagnostics_range(d).range,
99 message: d.message(), 103 message: d.message(),
100 severity: Severity::Error, 104 severity: Severity::Error,
101 fix, 105 fix,
@@ -103,7 +107,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
103 }) 107 })
104 .on::<hir::diagnostics::MissingMatchArms, _>(|d| { 108 .on::<hir::diagnostics::MissingMatchArms, _>(|d| {
105 res.borrow_mut().push(Diagnostic { 109 res.borrow_mut().push(Diagnostic {
106 range: d.highlight_range(), 110 range: sema.diagnostics_range(d).range,
107 message: d.message(), 111 message: d.message(),
108 severity: Severity::Error, 112 severity: Severity::Error,
109 fix: None, 113 fix: None,
@@ -115,7 +119,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
115 let edit = TextEdit::replace(node.syntax().text_range(), replacement); 119 let edit = TextEdit::replace(node.syntax().text_range(), replacement);
116 let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); 120 let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit);
117 res.borrow_mut().push(Diagnostic { 121 res.borrow_mut().push(Diagnostic {
118 range: d.highlight_range(), 122 range: sema.diagnostics_range(d).range,
119 message: d.message(), 123 message: d.message(),
120 severity: Severity::Error, 124 severity: Severity::Error,
121 fix: Some(fix), 125 fix: Some(fix),
@@ -622,6 +626,62 @@ mod tests {
622 } 626 }
623 627
624 #[test] 628 #[test]
629 fn range_mapping_out_of_macros() {
630 let (analysis, file_id) = single_file(
631 r"
632 fn some() {}
633 fn items() {}
634 fn here() {}
635
636 macro_rules! id {
637 ($($tt:tt)*) => { $($tt)*};
638 }
639
640 fn main() {
641 let _x = id![Foo { a: 42 }];
642 }
643
644 pub struct Foo {
645 pub a: i32,
646 pub b: i32,
647 }
648 ",
649 );
650 let diagnostics = analysis.diagnostics(file_id).unwrap();
651 assert_debug_snapshot!(diagnostics, @r###"
652 [
653 Diagnostic {
654 message: "Missing structure fields:\n- b",
655 range: [224; 233),
656 fix: Some(
657 SourceChange {
658 label: "fill struct fields",
659 source_file_edits: [
660 SourceFileEdit {
661 file_id: FileId(
662 1,
663 ),
664 edit: TextEdit {
665 atoms: [
666 AtomTextEdit {
667 delete: [3; 9),
668 insert: "{a:42, b: ()}",
669 },
670 ],
671 },
672 },
673 ],
674 file_system_edits: [],
675 cursor_position: None,
676 },
677 ),
678 severity: Error,
679 },
680 ]
681 "###);
682 }
683
684 #[test]
625 fn test_check_unnecessary_braces_in_use_statement() { 685 fn test_check_unnecessary_braces_in_use_statement() {
626 check_not_applicable( 686 check_not_applicable(
627 " 687 "
diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs
index 5e1f135c5..eee44e886 100644
--- a/crates/ra_ide/src/marks.rs
+++ b/crates/ra_ide/src/marks.rs
@@ -8,4 +8,5 @@ test_utils::marks!(
8 test_resolve_parent_module_on_module_decl 8 test_resolve_parent_module_on_module_decl
9 search_filters_by_range 9 search_filters_by_range
10 dont_insert_macro_call_parens_unncessary 10 dont_insert_macro_call_parens_unncessary
11 self_fulfilling_completion
11); 12);
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 83d161f45..7b15b82bd 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -174,7 +174,8 @@ pub(crate) fn highlight(
174 } 174 }
175 175
176 assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element"); 176 assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element");
177 let res = res.pop().unwrap(); 177 let mut res = res.pop().unwrap();
178 res.sort_by_key(|range| range.range.start());
178 // Check that ranges are sorted and disjoint 179 // Check that ranges are sorted and disjoint
179 assert!(res 180 assert!(res
180 .iter() 181 .iter()
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index 110887c2a..73611e23a 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -156,3 +156,15 @@ fn main() {
156 fs::write(dst_file, &actual_html).unwrap(); 156 fs::write(dst_file, &actual_html).unwrap();
157 assert_eq_text!(expected_html, actual_html); 157 assert_eq_text!(expected_html, actual_html);
158} 158}
159
160#[test]
161fn ranges_sorted() {
162 let (analysis, file_id) = single_file(
163 r#"
164#[foo(bar = "bar")]
165macro_rules! test {}
166}"#
167 .trim(),
168 );
169 let _ = analysis.highlight(file_id).unwrap();
170}
diff --git a/crates/ra_proc_macro/src/lib.rs b/crates/ra_proc_macro/src/lib.rs
index 63da9f1b4..b200fd126 100644
--- a/crates/ra_proc_macro/src/lib.rs
+++ b/crates/ra_proc_macro/src/lib.rs
@@ -12,6 +12,7 @@ pub mod msg;
12use process::{ProcMacroProcessSrv, ProcMacroProcessThread}; 12use process::{ProcMacroProcessSrv, ProcMacroProcessThread};
13use ra_tt::{SmolStr, Subtree}; 13use ra_tt::{SmolStr, Subtree};
14use std::{ 14use std::{
15 ffi::OsStr,
15 path::{Path, PathBuf}, 16 path::{Path, PathBuf},
16 sync::Arc, 17 sync::Arc,
17}; 18};
@@ -56,8 +57,15 @@ pub struct ProcMacroClient {
56} 57}
57 58
58impl ProcMacroClient { 59impl ProcMacroClient {
59 pub fn extern_process(process_path: &Path) -> Result<ProcMacroClient, std::io::Error> { 60 pub fn extern_process<I, S>(
60 let (thread, process) = ProcMacroProcessSrv::run(process_path)?; 61 process_path: &Path,
62 args: I,
63 ) -> Result<ProcMacroClient, std::io::Error>
64 where
65 I: IntoIterator<Item = S>,
66 S: AsRef<OsStr>,
67 {
68 let (thread, process) = ProcMacroProcessSrv::run(process_path, args)?;
61 Ok(ProcMacroClient { 69 Ok(ProcMacroClient {
62 kind: ProcMacroClientKind::Process { process: Arc::new(process), thread }, 70 kind: ProcMacroClientKind::Process { process: Arc::new(process), thread },
63 }) 71 })
diff --git a/crates/ra_proc_macro/src/process.rs b/crates/ra_proc_macro/src/process.rs
index e8c85be38..f851570bc 100644
--- a/crates/ra_proc_macro/src/process.rs
+++ b/crates/ra_proc_macro/src/process.rs
@@ -9,6 +9,7 @@ use crate::rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTas
9use io::{BufRead, BufReader}; 9use io::{BufRead, BufReader};
10use std::{ 10use std::{
11 convert::{TryFrom, TryInto}, 11 convert::{TryFrom, TryInto},
12 ffi::OsStr,
12 io::{self, Write}, 13 io::{self, Write},
13 path::{Path, PathBuf}, 14 path::{Path, PathBuf},
14 process::{Child, Command, Stdio}, 15 process::{Child, Command, Stdio},
@@ -44,8 +45,13 @@ impl Drop for Process {
44} 45}
45 46
46impl Process { 47impl Process {
47 fn run(process_path: &Path) -> Result<Process, io::Error> { 48 fn run<I, S>(process_path: &Path, args: I) -> Result<Process, io::Error>
49 where
50 I: IntoIterator<Item = S>,
51 S: AsRef<OsStr>,
52 {
48 let child = Command::new(process_path.clone()) 53 let child = Command::new(process_path.clone())
54 .args(args)
49 .stdin(Stdio::piped()) 55 .stdin(Stdio::piped())
50 .stdout(Stdio::piped()) 56 .stdout(Stdio::piped())
51 .stderr(Stdio::null()) 57 .stderr(Stdio::null())
@@ -74,10 +80,15 @@ impl Process {
74} 80}
75 81
76impl ProcMacroProcessSrv { 82impl ProcMacroProcessSrv {
77 pub fn run( 83 pub fn run<I, S>(
78 process_path: &Path, 84 process_path: &Path,
79 ) -> Result<(ProcMacroProcessThread, ProcMacroProcessSrv), io::Error> { 85 args: I,
80 let process = Process::run(process_path)?; 86 ) -> Result<(ProcMacroProcessThread, ProcMacroProcessSrv), io::Error>
87 where
88 I: IntoIterator<Item = S>,
89 S: AsRef<OsStr>,
90 {
91 let process = Process::run(process_path, args)?;
81 92
82 let (task_tx, task_rx) = bounded(0); 93 let (task_tx, task_rx) = bounded(0);
83 let handle = jod_thread::spawn(move || { 94 let handle = jod_thread::spawn(move || {
diff --git a/crates/ra_proc_macro_srv/Cargo.toml b/crates/ra_proc_macro_srv/Cargo.toml
index 1e0f50339..ac2d156dc 100644
--- a/crates/ra_proc_macro_srv/Cargo.toml
+++ b/crates/ra_proc_macro_srv/Cargo.toml
@@ -14,6 +14,7 @@ ra_mbe = { path = "../ra_mbe" }
14ra_proc_macro = { path = "../ra_proc_macro" } 14ra_proc_macro = { path = "../ra_proc_macro" }
15goblin = "0.2.1" 15goblin = "0.2.1"
16libloading = "0.6.0" 16libloading = "0.6.0"
17memmap = "0.7"
17test_utils = { path = "../test_utils" } 18test_utils = { path = "../test_utils" }
18 19
19[dev-dependencies] 20[dev-dependencies]
diff --git a/crates/ra_proc_macro_srv/src/main.rs b/crates/ra_proc_macro_srv/src/cli.rs
index 70743c1f4..c771f2b38 100644
--- a/crates/ra_proc_macro_srv/src/main.rs
+++ b/crates/ra_proc_macro_srv/src/cli.rs
@@ -1,7 +1,7 @@
1//! Driver for proc macro server 1//! Driver for proc macro server
2 2
3use crate::{expand_task, list_macros};
3use ra_proc_macro::msg::{self, Message}; 4use ra_proc_macro::msg::{self, Message};
4use ra_proc_macro_srv::{expand_task, list_macros};
5 5
6use std::io; 6use std::io;
7 7
@@ -24,7 +24,8 @@ fn write_response(res: Result<msg::Response, String>) -> Result<(), io::Error> {
24 let mut stdout = stdout.lock(); 24 let mut stdout = stdout.lock();
25 msg.write(&mut stdout) 25 msg.write(&mut stdout)
26} 26}
27fn main() { 27
28pub fn run() {
28 loop { 29 loop {
29 let req = match read_request() { 30 let req = match read_request() {
30 Err(err) => { 31 Err(err) => {
diff --git a/crates/ra_proc_macro_srv/src/dylib.rs b/crates/ra_proc_macro_srv/src/dylib.rs
index ec63d587b..16bd7466e 100644
--- a/crates/ra_proc_macro_srv/src/dylib.rs
+++ b/crates/ra_proc_macro_srv/src/dylib.rs
@@ -1,10 +1,12 @@
1//! Handles dynamic library loading for proc macro 1//! Handles dynamic library loading for proc macro
2 2
3use crate::{proc_macro::bridge, rustc_server::TokenStream}; 3use crate::{proc_macro::bridge, rustc_server::TokenStream};
4use std::fs::File;
4use std::path::Path; 5use std::path::Path;
5 6
6use goblin::{mach::Mach, Object}; 7use goblin::{mach::Mach, Object};
7use libloading::Library; 8use libloading::Library;
9use memmap::Mmap;
8use ra_proc_macro::ProcMacroKind; 10use ra_proc_macro::ProcMacroKind;
9 11
10use std::io::Error as IoError; 12use std::io::Error as IoError;
@@ -16,55 +18,54 @@ fn invalid_data_err(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> I
16 IoError::new(IoErrorKind::InvalidData, e) 18 IoError::new(IoErrorKind::InvalidData, e)
17} 19}
18 20
19fn get_symbols_from_lib(file: &Path) -> Result<Vec<String>, IoError> { 21fn is_derive_registrar_symbol(symbol: &str) -> bool {
20 let buffer = std::fs::read(file)?; 22 symbol.contains(NEW_REGISTRAR_SYMBOL)
23}
24
25fn find_registrar_symbol(file: &Path) -> Result<Option<String>, IoError> {
26 let file = File::open(file)?;
27 let buffer = unsafe { Mmap::map(&file)? };
21 let object = Object::parse(&buffer).map_err(invalid_data_err)?; 28 let object = Object::parse(&buffer).map_err(invalid_data_err)?;
22 29
23 match object { 30 match object {
24 Object::Elf(elf) => { 31 Object::Elf(elf) => {
25 let symbols = elf.dynstrtab.to_vec().map_err(invalid_data_err)?; 32 let symbols = elf.dynstrtab.to_vec().map_err(invalid_data_err)?;
26 let names = symbols.iter().map(|s| s.to_string()).collect(); 33 let name =
27 Ok(names) 34 symbols.iter().find(|s| is_derive_registrar_symbol(s)).map(|s| s.to_string());
35 Ok(name)
28 } 36 }
29 Object::PE(pe) => { 37 Object::PE(pe) => {
30 let symbol_names = 38 let name = pe
31 pe.exports.iter().flat_map(|s| s.name).map(|n| n.to_string()).collect(); 39 .exports
32 Ok(symbol_names) 40 .iter()
41 .flat_map(|s| s.name)
42 .find(|s| is_derive_registrar_symbol(s))
43 .map(|s| s.to_string());
44 Ok(name)
33 } 45 }
34 Object::Mach(mach) => match mach { 46 Object::Mach(Mach::Binary(binary)) => {
35 Mach::Binary(binary) => { 47 let exports = binary.exports().map_err(invalid_data_err)?;
36 let exports = binary.exports().map_err(invalid_data_err)?; 48 let name = exports
37 let names = exports 49 .iter()
38 .into_iter() 50 .map(|s| {
39 .map(|s| { 51 // In macos doc:
40 // In macos doc: 52 // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html
41 // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html 53 // Unlike other dyld API's, the symbol name passed to dlsym() must NOT be
42 // Unlike other dyld API's, the symbol name passed to dlsym() must NOT be 54 // prepended with an underscore.
43 // prepended with an underscore. 55 if s.name.starts_with("_") {
44 if s.name.starts_with("_") { 56 &s.name[1..]
45 s.name[1..].to_string() 57 } else {
46 } else { 58 &s.name
47 s.name 59 }
48 } 60 })
49 }) 61 .find(|s| is_derive_registrar_symbol(s))
50 .collect(); 62 .map(|s| s.to_string());
51 Ok(names) 63 Ok(name)
52 } 64 }
53 Mach::Fat(_) => Ok(vec![]), 65 _ => Ok(None),
54 },
55 Object::Archive(_) | Object::Unknown(_) => Ok(vec![]),
56 } 66 }
57} 67}
58 68
59fn is_derive_registrar_symbol(symbol: &str) -> bool {
60 symbol.contains(NEW_REGISTRAR_SYMBOL)
61}
62
63fn find_registrar_symbol(file: &Path) -> Result<Option<String>, IoError> {
64 let symbols = get_symbols_from_lib(file)?;
65 Ok(symbols.into_iter().find(|s| is_derive_registrar_symbol(s)))
66}
67
68/// Loads dynamic library in platform dependent manner. 69/// Loads dynamic library in platform dependent manner.
69/// 70///
70/// For unix, you have to use RTLD_DEEPBIND flag to escape problems described 71/// For unix, you have to use RTLD_DEEPBIND flag to escape problems described
diff --git a/crates/ra_proc_macro_srv/src/lib.rs b/crates/ra_proc_macro_srv/src/lib.rs
index 59716cbb3..c62b0ed89 100644
--- a/crates/ra_proc_macro_srv/src/lib.rs
+++ b/crates/ra_proc_macro_srv/src/lib.rs
@@ -22,7 +22,7 @@ mod dylib;
22use proc_macro::bridge::client::TokenStream; 22use proc_macro::bridge::client::TokenStream;
23use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask}; 23use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask};
24 24
25pub fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> { 25pub(crate) fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> {
26 let expander = dylib::Expander::new(&task.lib) 26 let expander = dylib::Expander::new(&task.lib)
27 .expect(&format!("Cannot expand with provided libraries: ${:?}", &task.lib)); 27 .expect(&format!("Cannot expand with provided libraries: ${:?}", &task.lib));
28 28
@@ -39,7 +39,7 @@ pub fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> {
39 } 39 }
40} 40}
41 41
42pub fn list_macros(task: &ListMacrosTask) -> Result<ListMacrosResult, String> { 42pub(crate) fn list_macros(task: &ListMacrosTask) -> Result<ListMacrosResult, String> {
43 let expander = dylib::Expander::new(&task.lib) 43 let expander = dylib::Expander::new(&task.lib)
44 .expect(&format!("Cannot expand with provided libraries: ${:?}", &task.lib)); 44 .expect(&format!("Cannot expand with provided libraries: ${:?}", &task.lib));
45 45
@@ -53,5 +53,7 @@ pub fn list_macros(task: &ListMacrosTask) -> Result<ListMacrosResult, String> {
53 } 53 }
54} 54}
55 55
56pub mod cli;
57
56#[cfg(test)] 58#[cfg(test)]
57mod tests; 59mod tests;
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 0ab64a1e0..03f2629da 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -5,9 +5,8 @@ mod json_project;
5mod sysroot; 5mod sysroot;
6 6
7use std::{ 7use std::{
8 error::Error,
9 fs::{read_dir, File, ReadDir}, 8 fs::{read_dir, File, ReadDir},
10 io::BufReader, 9 io::{self, BufReader},
11 path::{Path, PathBuf}, 10 path::{Path, PathBuf},
12 process::Command, 11 process::Command,
13}; 12};
@@ -25,25 +24,6 @@ pub use crate::{
25}; 24};
26pub use ra_proc_macro::ProcMacroClient; 25pub use ra_proc_macro::ProcMacroClient;
27 26
28#[derive(Clone, PartialEq, Eq, Hash, Debug)]
29pub struct CargoTomlNotFoundError {
30 pub searched_at: PathBuf,
31 pub reason: String,
32}
33
34impl std::fmt::Display for CargoTomlNotFoundError {
35 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 write!(
37 fmt,
38 "can't find Cargo.toml at {}, due to {}",
39 self.searched_at.display(),
40 self.reason
41 )
42 }
43}
44
45impl Error for CargoTomlNotFoundError {}
46
47#[derive(Debug, Clone)] 27#[derive(Debug, Clone)]
48pub enum ProjectWorkspace { 28pub enum ProjectWorkspace {
49 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. 29 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
@@ -77,31 +57,119 @@ impl PackageRoot {
77 } 57 }
78} 58}
79 59
80impl ProjectWorkspace { 60#[derive(Debug, Clone, PartialEq, Eq, Hash)]
81 pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> { 61pub enum ProjectRoot {
82 ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) 62 ProjectJson(PathBuf),
63 CargoToml(PathBuf),
64}
65
66impl ProjectRoot {
67 pub fn from_manifest_file(path: PathBuf) -> Result<ProjectRoot> {
68 if path.ends_with("rust-project.json") {
69 return Ok(ProjectRoot::ProjectJson(path));
70 }
71 if path.ends_with("Cargo.toml") {
72 return Ok(ProjectRoot::CargoToml(path));
73 }
74 bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display())
83 } 75 }
84 76
85 pub fn discover_with_sysroot( 77 pub fn discover_single(path: &Path) -> Result<ProjectRoot> {
86 path: &Path, 78 let mut candidates = ProjectRoot::discover(path)?;
87 with_sysroot: bool, 79 let res = match candidates.pop() {
80 None => bail!("no projects"),
81 Some(it) => it,
82 };
83
84 if !candidates.is_empty() {
85 bail!("more than one project")
86 }
87 Ok(res)
88 }
89
90 pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> {
91 if let Some(project_json) = find_rust_project_json(path) {
92 return Ok(vec![ProjectRoot::ProjectJson(project_json)]);
93 }
94 return find_cargo_toml(path)
95 .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect());
96
97 fn find_rust_project_json(path: &Path) -> Option<PathBuf> {
98 if path.ends_with("rust-project.json") {
99 return Some(path.to_path_buf());
100 }
101
102 let mut curr = Some(path);
103 while let Some(path) = curr {
104 let candidate = path.join("rust-project.json");
105 if candidate.exists() {
106 return Some(candidate);
107 }
108 curr = path.parent();
109 }
110
111 None
112 }
113
114 fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> {
115 if path.ends_with("Cargo.toml") {
116 return Ok(vec![path.to_path_buf()]);
117 }
118
119 if let Some(p) = find_cargo_toml_in_parent_dir(path) {
120 return Ok(vec![p]);
121 }
122
123 let entities = read_dir(path)?;
124 Ok(find_cargo_toml_in_child_dir(entities))
125 }
126
127 fn find_cargo_toml_in_parent_dir(path: &Path) -> Option<PathBuf> {
128 let mut curr = Some(path);
129 while let Some(path) = curr {
130 let candidate = path.join("Cargo.toml");
131 if candidate.exists() {
132 return Some(candidate);
133 }
134 curr = path.parent();
135 }
136
137 None
138 }
139
140 fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> {
141 // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
142 let mut valid_canditates = vec![];
143 for entity in entities.filter_map(Result::ok) {
144 let candidate = entity.path().join("Cargo.toml");
145 if candidate.exists() {
146 valid_canditates.push(candidate)
147 }
148 }
149 valid_canditates
150 }
151 }
152}
153
154impl ProjectWorkspace {
155 pub fn load(
156 root: ProjectRoot,
88 cargo_features: &CargoConfig, 157 cargo_features: &CargoConfig,
158 with_sysroot: bool,
89 ) -> Result<ProjectWorkspace> { 159 ) -> Result<ProjectWorkspace> {
90 match find_rust_project_json(path) { 160 let res = match root {
91 Some(json_path) => { 161 ProjectRoot::ProjectJson(project_json) => {
92 let file = File::open(&json_path) 162 let file = File::open(&project_json).with_context(|| {
93 .with_context(|| format!("Failed to open json file {}", json_path.display()))?; 163 format!("Failed to open json file {}", project_json.display())
164 })?;
94 let reader = BufReader::new(file); 165 let reader = BufReader::new(file);
95 Ok(ProjectWorkspace::Json { 166 ProjectWorkspace::Json {
96 project: from_reader(reader).with_context(|| { 167 project: from_reader(reader).with_context(|| {
97 format!("Failed to deserialize json file {}", json_path.display()) 168 format!("Failed to deserialize json file {}", project_json.display())
98 })?, 169 })?,
99 }) 170 }
100 } 171 }
101 None => { 172 ProjectRoot::CargoToml(cargo_toml) => {
102 let cargo_toml = find_cargo_toml(path).with_context(|| {
103 format!("Failed to find Cargo.toml for path {}", path.display())
104 })?;
105 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) 173 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
106 .with_context(|| { 174 .with_context(|| {
107 format!( 175 format!(
@@ -119,9 +187,11 @@ impl ProjectWorkspace {
119 } else { 187 } else {
120 Sysroot::default() 188 Sysroot::default()
121 }; 189 };
122 Ok(ProjectWorkspace::Cargo { cargo, sysroot }) 190 ProjectWorkspace::Cargo { cargo, sysroot }
123 } 191 }
124 } 192 };
193
194 Ok(res)
125 } 195 }
126 196
127 /// Returns the roots for the current `ProjectWorkspace` 197 /// Returns the roots for the current `ProjectWorkspace`
@@ -469,87 +539,6 @@ impl ProjectWorkspace {
469 } 539 }
470} 540}
471 541
472fn find_rust_project_json(path: &Path) -> Option<PathBuf> {
473 if path.ends_with("rust-project.json") {
474 return Some(path.to_path_buf());
475 }
476
477 let mut curr = Some(path);
478 while let Some(path) = curr {
479 let candidate = path.join("rust-project.json");
480 if candidate.exists() {
481 return Some(candidate);
482 }
483 curr = path.parent();
484 }
485
486 None
487}
488
489fn find_cargo_toml_in_parent_dir(path: &Path) -> Option<PathBuf> {
490 let mut curr = Some(path);
491 while let Some(path) = curr {
492 let candidate = path.join("Cargo.toml");
493 if candidate.exists() {
494 return Some(candidate);
495 }
496 curr = path.parent();
497 }
498
499 None
500}
501
502fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> {
503 // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
504 let mut valid_canditates = vec![];
505 for entity in entities.filter_map(Result::ok) {
506 let candidate = entity.path().join("Cargo.toml");
507 if candidate.exists() {
508 valid_canditates.push(candidate)
509 }
510 }
511 valid_canditates
512}
513
514fn find_cargo_toml(path: &Path) -> Result<PathBuf> {
515 if path.ends_with("Cargo.toml") {
516 return Ok(path.to_path_buf());
517 }
518
519 if let Some(p) = find_cargo_toml_in_parent_dir(path) {
520 return Ok(p);
521 }
522
523 let entities = match read_dir(path) {
524 Ok(entities) => entities,
525 Err(e) => {
526 return Err(CargoTomlNotFoundError {
527 searched_at: path.to_path_buf(),
528 reason: format!("file system error: {}", e),
529 }
530 .into());
531 }
532 };
533
534 let mut valid_canditates = find_cargo_toml_in_child_dir(entities);
535 match valid_canditates.len() {
536 1 => Ok(valid_canditates.remove(0)),
537 0 => Err(CargoTomlNotFoundError {
538 searched_at: path.to_path_buf(),
539 reason: "no Cargo.toml file found".to_string(),
540 }
541 .into()),
542 _ => Err(CargoTomlNotFoundError {
543 searched_at: path.to_path_buf(),
544 reason: format!(
545 "multiple equally valid Cargo.toml files found: {:?}",
546 valid_canditates
547 ),
548 }
549 .into()),
550 }
551}
552
553pub fn get_rustc_cfg_options() -> CfgOptions { 542pub fn get_rustc_cfg_options() -> CfgOptions {
554 let mut cfg_options = CfgOptions::default(); 543 let mut cfg_options = CfgOptions::default();
555 544
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index f5f773432..cee0248b6 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -46,7 +46,7 @@ ra_db = { path = "../ra_db" }
46hir = { path = "../ra_hir", package = "ra_hir" } 46hir = { path = "../ra_hir", package = "ra_hir" }
47hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } 47hir_def = { path = "../ra_hir_def", package = "ra_hir_def" }
48hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" } 48hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" }
49 49ra_proc_macro_srv = { path = "../ra_proc_macro_srv" }
50 50
51[target.'cfg(windows)'.dependencies] 51[target.'cfg(windows)'.dependencies]
52winapi = "0.3.8" 52winapi = "0.3.8"
diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs
index f5981588a..5e19253a6 100644
--- a/crates/rust-analyzer/src/bin/args.rs
+++ b/crates/rust-analyzer/src/bin/args.rs
@@ -29,19 +29,23 @@ pub(crate) enum Command {
29 with_deps: bool, 29 with_deps: bool,
30 path: PathBuf, 30 path: PathBuf,
31 load_output_dirs: bool, 31 load_output_dirs: bool,
32 with_proc_macro: bool,
32 }, 33 },
33 Bench { 34 Bench {
34 path: PathBuf, 35 path: PathBuf,
35 what: BenchWhat, 36 what: BenchWhat,
36 load_output_dirs: bool, 37 load_output_dirs: bool,
38 with_proc_macro: bool,
37 }, 39 },
38 Diagnostics { 40 Diagnostics {
39 path: PathBuf, 41 path: PathBuf,
40 load_output_dirs: bool, 42 load_output_dirs: bool,
43 with_proc_macro: bool,
41 /// Include files which are not modules. In rust-analyzer 44 /// Include files which are not modules. In rust-analyzer
42 /// this would include the parser test files. 45 /// this would include the parser test files.
43 all: bool, 46 all: bool,
44 }, 47 },
48 ProcMacro,
45 RunServer, 49 RunServer,
46 Version, 50 Version,
47} 51}
@@ -148,6 +152,7 @@ FLAGS:
148 -h, --help Prints help information 152 -h, --help Prints help information
149 --memory-usage 153 --memory-usage
150 --load-output-dirs Load OUT_DIR values by running `cargo check` before analysis 154 --load-output-dirs Load OUT_DIR values by running `cargo check` before analysis
155 --with-proc-macro Use ra-proc-macro-srv for proc-macro expanding
151 -v, --verbose 156 -v, --verbose
152 -q, --quiet 157 -q, --quiet
153 158
@@ -165,6 +170,7 @@ ARGS:
165 let only: Option<String> = matches.opt_value_from_str(["-o", "--only"])?; 170 let only: Option<String> = matches.opt_value_from_str(["-o", "--only"])?;
166 let with_deps: bool = matches.contains("--with-deps"); 171 let with_deps: bool = matches.contains("--with-deps");
167 let load_output_dirs = matches.contains("--load-output-dirs"); 172 let load_output_dirs = matches.contains("--load-output-dirs");
173 let with_proc_macro = matches.contains("--with-proc-macro");
168 let path = { 174 let path = {
169 let mut trailing = matches.free()?; 175 let mut trailing = matches.free()?;
170 if trailing.len() != 1 { 176 if trailing.len() != 1 {
@@ -173,7 +179,15 @@ ARGS:
173 trailing.pop().unwrap().into() 179 trailing.pop().unwrap().into()
174 }; 180 };
175 181
176 Command::Stats { randomize, memory_usage, only, with_deps, path, load_output_dirs } 182 Command::Stats {
183 randomize,
184 memory_usage,
185 only,
186 with_deps,
187 path,
188 load_output_dirs,
189 with_proc_macro,
190 }
177 } 191 }
178 "analysis-bench" => { 192 "analysis-bench" => {
179 if matches.contains(["-h", "--help"]) { 193 if matches.contains(["-h", "--help"]) {
@@ -187,6 +201,7 @@ USAGE:
187FLAGS: 201FLAGS:
188 -h, --help Prints help information 202 -h, --help Prints help information
189 --load-output-dirs Load OUT_DIR values by running `cargo check` before analysis 203 --load-output-dirs Load OUT_DIR values by running `cargo check` before analysis
204 --with-proc-macro Use ra-proc-macro-srv for proc-macro expanding
190 -v, --verbose 205 -v, --verbose
191 206
192OPTIONS: 207OPTIONS:
@@ -214,7 +229,8 @@ ARGS:
214 ), 229 ),
215 }; 230 };
216 let load_output_dirs = matches.contains("--load-output-dirs"); 231 let load_output_dirs = matches.contains("--load-output-dirs");
217 Command::Bench { path, what, load_output_dirs } 232 let with_proc_macro = matches.contains("--with-proc-macro");
233 Command::Bench { path, what, load_output_dirs, with_proc_macro }
218 } 234 }
219 "diagnostics" => { 235 "diagnostics" => {
220 if matches.contains(["-h", "--help"]) { 236 if matches.contains(["-h", "--help"]) {
@@ -237,6 +253,7 @@ ARGS:
237 } 253 }
238 254
239 let load_output_dirs = matches.contains("--load-output-dirs"); 255 let load_output_dirs = matches.contains("--load-output-dirs");
256 let with_proc_macro = matches.contains("--with-proc-macro");
240 let all = matches.contains("--all"); 257 let all = matches.contains("--all");
241 let path = { 258 let path = {
242 let mut trailing = matches.free()?; 259 let mut trailing = matches.free()?;
@@ -246,8 +263,9 @@ ARGS:
246 trailing.pop().unwrap().into() 263 trailing.pop().unwrap().into()
247 }; 264 };
248 265
249 Command::Diagnostics { path, load_output_dirs, all } 266 Command::Diagnostics { path, load_output_dirs, with_proc_macro, all }
250 } 267 }
268 "proc-macro" => Command::ProcMacro,
251 _ => { 269 _ => {
252 eprintln!( 270 eprintln!(
253 "\ 271 "\
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 7cfc44f01..28b67cfe2 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -25,6 +25,7 @@ fn main() -> Result<()> {
25 with_deps, 25 with_deps,
26 path, 26 path,
27 load_output_dirs, 27 load_output_dirs,
28 with_proc_macro,
28 } => cli::analysis_stats( 29 } => cli::analysis_stats(
29 args.verbosity, 30 args.verbosity,
30 memory_usage, 31 memory_usage,
@@ -33,16 +34,24 @@ fn main() -> Result<()> {
33 with_deps, 34 with_deps,
34 randomize, 35 randomize,
35 load_output_dirs, 36 load_output_dirs,
37 with_proc_macro,
36 )?, 38 )?,
37 39
38 args::Command::Bench { path, what, load_output_dirs } => { 40 args::Command::Bench { path, what, load_output_dirs, with_proc_macro } => {
39 cli::analysis_bench(args.verbosity, path.as_ref(), what, load_output_dirs)? 41 cli::analysis_bench(
42 args.verbosity,
43 path.as_ref(),
44 what,
45 load_output_dirs,
46 with_proc_macro,
47 )?
40 } 48 }
41 49
42 args::Command::Diagnostics { path, load_output_dirs, all } => { 50 args::Command::Diagnostics { path, load_output_dirs, with_proc_macro, all } => {
43 cli::diagnostics(path.as_ref(), load_output_dirs, all)? 51 cli::diagnostics(path.as_ref(), load_output_dirs, with_proc_macro, all)?
44 } 52 }
45 53
54 args::Command::ProcMacro => run_proc_macro_sv()?,
46 args::Command::RunServer => run_server()?, 55 args::Command::RunServer => run_server()?,
47 args::Command::Version => println!("rust-analyzer {}", env!("REV")), 56 args::Command::Version => println!("rust-analyzer {}", env!("REV")),
48 } 57 }
@@ -56,6 +65,11 @@ fn setup_logging() -> Result<()> {
56 Ok(()) 65 Ok(())
57} 66}
58 67
68fn run_proc_macro_sv() -> Result<()> {
69 ra_proc_macro_srv::cli::run();
70 Ok(())
71}
72
59fn run_server() -> Result<()> { 73fn run_server() -> Result<()> {
60 log::info!("lifecycle: server started"); 74 log::info!("lifecycle: server started");
61 75
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 7667873d5..6147ae207 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -47,12 +47,13 @@ pub fn analysis_bench(
47 path: &Path, 47 path: &Path,
48 what: BenchWhat, 48 what: BenchWhat,
49 load_output_dirs: bool, 49 load_output_dirs: bool,
50 with_proc_macro: bool,
50) -> Result<()> { 51) -> Result<()> {
51 ra_prof::init(); 52 ra_prof::init();
52 53
53 let start = Instant::now(); 54 let start = Instant::now();
54 eprint!("loading: "); 55 eprint!("loading: ");
55 let (mut host, roots) = load_cargo(path, load_output_dirs)?; 56 let (mut host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?;
56 let db = host.raw_database(); 57 let db = host.raw_database();
57 eprintln!("{:?}\n", start.elapsed()); 58 eprintln!("{:?}\n", start.elapsed());
58 59
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index e9ee0b888..d442cbd63 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -25,9 +25,10 @@ pub fn analysis_stats(
25 with_deps: bool, 25 with_deps: bool,
26 randomize: bool, 26 randomize: bool,
27 load_output_dirs: bool, 27 load_output_dirs: bool,
28 with_proc_macro: bool,
28) -> Result<()> { 29) -> Result<()> {
29 let db_load_time = Instant::now(); 30 let db_load_time = Instant::now();
30 let (mut host, roots) = load_cargo(path, load_output_dirs)?; 31 let (mut host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?;
31 let db = host.raw_database(); 32 let db = host.raw_database();
32 println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed()); 33 println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
33 let analysis_time = Instant::now(); 34 let analysis_time = Instant::now();
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 92664b415..60daefa3e 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -9,8 +9,13 @@ use std::{collections::HashSet, path::Path};
9use crate::cli::{load_cargo::load_cargo, Result}; 9use crate::cli::{load_cargo::load_cargo, Result};
10use hir::Semantics; 10use hir::Semantics;
11 11
12pub fn diagnostics(path: &Path, load_output_dirs: bool, all: bool) -> Result<()> { 12pub fn diagnostics(
13 let (host, roots) = load_cargo(path, load_output_dirs)?; 13 path: &Path,
14 load_output_dirs: bool,
15 with_proc_macro: bool,
16 all: bool,
17) -> Result<()> {
18 let (host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?;
14 let db = host.raw_database(); 19 let db = host.raw_database();
15 let analysis = host.analysis(); 20 let analysis = host.analysis();
16 let semantics = Semantics::new(db); 21 let semantics = Semantics::new(db);
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 43062ea10..762f776fe 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -8,7 +8,7 @@ use crossbeam_channel::{unbounded, Receiver};
8use ra_db::{ExternSourceId, FileId, SourceRootId}; 8use ra_db::{ExternSourceId, FileId, SourceRootId};
9use ra_ide::{AnalysisChange, AnalysisHost}; 9use ra_ide::{AnalysisChange, AnalysisHost};
10use ra_project_model::{ 10use ra_project_model::{
11 get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectWorkspace, 11 get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace,
12}; 12};
13use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; 13use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
14use rustc_hash::{FxHashMap, FxHashSet}; 14use rustc_hash::{FxHashMap, FxHashSet};
@@ -25,11 +25,14 @@ fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
25pub(crate) fn load_cargo( 25pub(crate) fn load_cargo(
26 root: &Path, 26 root: &Path,
27 load_out_dirs_from_check: bool, 27 load_out_dirs_from_check: bool,
28 with_proc_macro: bool,
28) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> { 29) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
29 let root = std::env::current_dir()?.join(root); 30 let root = std::env::current_dir()?.join(root);
30 let ws = ProjectWorkspace::discover( 31 let root = ProjectRoot::discover_single(&root)?;
31 root.as_ref(), 32 let ws = ProjectWorkspace::load(
33 root,
32 &CargoConfig { load_out_dirs_from_check, ..Default::default() }, 34 &CargoConfig { load_out_dirs_from_check, ..Default::default() },
35 true,
33 )?; 36 )?;
34 37
35 let mut extern_dirs = FxHashSet::default(); 38 let mut extern_dirs = FxHashSet::default();
@@ -69,7 +72,12 @@ pub(crate) fn load_cargo(
69 }) 72 })
70 .collect::<FxHashMap<_, _>>(); 73 .collect::<FxHashMap<_, _>>();
71 74
72 let proc_macro_client = ProcMacroClient::dummy(); 75 let proc_macro_client = if !with_proc_macro {
76 ProcMacroClient::dummy()
77 } else {
78 let path = std::env::current_exe()?;
79 ProcMacroClient::extern_process(&path, &["proc-macro"]).unwrap()
80 };
73 let host = load(&source_roots, ws, &mut vfs, receiver, extern_dirs, &proc_macro_client); 81 let host = load(&source_roots, ws, &mut vfs, receiver, extern_dirs, &proc_macro_client);
74 Ok((host, source_roots)) 82 Ok((host, source_roots))
75} 83}
@@ -175,7 +183,7 @@ mod tests {
175 #[test] 183 #[test]
176 fn test_loading_rust_analyzer() { 184 fn test_loading_rust_analyzer() {
177 let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap(); 185 let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
178 let (host, _roots) = load_cargo(path, false).unwrap(); 186 let (host, _roots) = load_cargo(path, false, false).unwrap();
179 let n_crates = Crate::all(host.raw_database()).len(); 187 let n_crates = Crate::all(host.raw_database()).len();
180 // RA has quite a few crates, but the exact count doesn't matter 188 // RA has quite a few crates, but the exact count doesn't matter
181 assert!(n_crates > 20); 189 assert!(n_crates > 20);
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 4734df16a..3597a14e3 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -20,7 +20,7 @@ pub struct Config {
20 pub with_sysroot: bool, 20 pub with_sysroot: bool,
21 pub publish_diagnostics: bool, 21 pub publish_diagnostics: bool,
22 pub lru_capacity: Option<usize>, 22 pub lru_capacity: Option<usize>,
23 pub proc_macro_srv: Option<String>, 23 pub proc_macro_srv: Option<(String, Vec<String>)>,
24 pub files: FilesConfig, 24 pub files: FilesConfig,
25 pub notifications: NotificationsConfig, 25 pub notifications: NotificationsConfig,
26 26
@@ -131,6 +131,16 @@ impl Config {
131 set(value, "/cargo/allFeatures", &mut self.cargo.all_features); 131 set(value, "/cargo/allFeatures", &mut self.cargo.all_features);
132 set(value, "/cargo/features", &mut self.cargo.features); 132 set(value, "/cargo/features", &mut self.cargo.features);
133 set(value, "/cargo/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check); 133 set(value, "/cargo/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check);
134
135 match get::<bool>(value, "/procMacro/enabled") {
136 Some(true) => {
137 if let Ok(path) = std::env::current_exe() {
138 self.proc_macro_srv = Some((path.to_string_lossy().to_string(), vec!["proc-macro".to_string()]));
139 }
140 }
141 _ => self.proc_macro_srv = None,
142 }
143
134 match get::<Vec<String>>(value, "/rustfmt/overrideCommand") { 144 match get::<Vec<String>>(value, "/rustfmt/overrideCommand") {
135 Some(mut args) if !args.is_empty() => { 145 Some(mut args) if !args.is_empty() => {
136 let command = args.remove(0); 146 let command = args.remove(0);
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 8d1429196..fc4c77f8a 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -15,6 +15,7 @@ use std::{
15}; 15};
16 16
17use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; 17use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
18use itertools::Itertools;
18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 19use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
19use lsp_types::{ 20use lsp_types::{
20 NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, 21 NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams,
@@ -88,37 +89,46 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
88 89
89 let mut loop_state = LoopState::default(); 90 let mut loop_state = LoopState::default();
90 let mut world_state = { 91 let mut world_state = {
91 // FIXME: support dynamic workspace loading.
92 let workspaces = { 92 let workspaces = {
93 let mut loaded_workspaces = Vec::new(); 93 // FIXME: support dynamic workspace loading.
94 for ws_root in &ws_roots { 94 let mut visited = FxHashSet::default();
95 let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot( 95 let project_roots = ws_roots
96 ws_root.as_path(), 96 .iter()
97 config.with_sysroot, 97 .filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok())
98 &config.cargo, 98 .flatten()
99 ); 99 .filter(|it| visited.insert(it.clone()))
100 match workspace { 100 .collect::<Vec<_>>();
101 Ok(workspace) => loaded_workspaces.push(workspace), 101
102 Err(e) => { 102 if project_roots.is_empty() && config.notifications.cargo_toml_not_found {
103 log::error!("loading workspace failed: {:?}", e); 103 show_message(
104 104 req::MessageType::Error,
105 if let Some(ra_project_model::CargoTomlNotFoundError { .. }) = 105 format!(
106 e.downcast_ref() 106 "rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}",
107 { 107 ws_roots.iter().format_with(", ", |it, f| f(&it.display()))
108 if !config.notifications.cargo_toml_not_found { 108 ),
109 continue; 109 &connection.sender,
110 } 110 );
111 } 111 };
112 112
113 project_roots
114 .into_iter()
115 .filter_map(|root| {
116 ra_project_model::ProjectWorkspace::load(
117 root,
118 &config.cargo,
119 config.with_sysroot,
120 )
121 .map_err(|err| {
122 log::error!("failed to load workspace: {:#}", err);
113 show_message( 123 show_message(
114 req::MessageType::Error, 124 req::MessageType::Error,
115 format!("rust-analyzer failed to load workspace: {:?}", e), 125 format!("rust-analyzer failed to load workspace: {:#}", err),
116 &connection.sender, 126 &connection.sender,
117 ); 127 );
118 } 128 })
119 } 129 .ok()
120 } 130 })
121 loaded_workspaces 131 .collect::<Vec<_>>()
122 }; 132 };
123 133
124 let globs = config 134 let globs = config
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index 6c42e1d76..f2ad453fa 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -64,6 +64,7 @@ pub struct WorldState {
64 pub latest_requests: Arc<RwLock<LatestRequests>>, 64 pub latest_requests: Arc<RwLock<LatestRequests>>,
65 pub flycheck: Option<Flycheck>, 65 pub flycheck: Option<Flycheck>,
66 pub diagnostics: DiagnosticCollection, 66 pub diagnostics: DiagnosticCollection,
67 pub proc_macro_client: ProcMacroClient,
67} 68}
68 69
69/// An immutable snapshot of the world's state at a point in time. 70/// An immutable snapshot of the world's state at a point in time.
@@ -147,9 +148,9 @@ impl WorldState {
147 148
148 let proc_macro_client = match &config.proc_macro_srv { 149 let proc_macro_client = match &config.proc_macro_srv {
149 None => ProcMacroClient::dummy(), 150 None => ProcMacroClient::dummy(),
150 Some(srv) => { 151 Some((path, args)) => {
151 let path = Path::new(&srv); 152 let path = std::path::Path::new(path);
152 match ProcMacroClient::extern_process(path) { 153 match ProcMacroClient::extern_process(path, args) {
153 Ok(it) => it, 154 Ok(it) => it,
154 Err(err) => { 155 Err(err) => {
155 log::error!( 156 log::error!(
@@ -192,6 +193,7 @@ impl WorldState {
192 latest_requests: Default::default(), 193 latest_requests: Default::default(),
193 flycheck, 194 flycheck,
194 diagnostics: Default::default(), 195 diagnostics: Default::default(),
196 proc_macro_client,
195 } 197 }
196 } 198 }
197 199
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 638813311..1dd2676b6 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -9,7 +9,7 @@ use lsp_types::{
9}; 9};
10use rust_analyzer::req::{ 10use rust_analyzer::req::{
11 CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, 11 CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
12 Formatting, GotoDefinition, OnEnter, Runnables, RunnablesParams, 12 Formatting, GotoDefinition, HoverRequest, OnEnter, Runnables, RunnablesParams,
13}; 13};
14use serde_json::json; 14use serde_json::json;
15use tempfile::TempDir; 15use tempfile::TempDir;
@@ -625,3 +625,92 @@ fn main() { message(); }
625 )); 625 ));
626 assert!(format!("{}", res).contains("hello.rs")); 626 assert!(format!("{}", res).contains("hello.rs"));
627} 627}
628
629#[test]
630fn resolve_proc_macro() {
631 if skip_slow_tests() {
632 return;
633 }
634 let server = Project::with_fixture(
635 r###"
636//- foo/Cargo.toml
637[package]
638name = "foo"
639version = "0.0.0"
640edition = "2018"
641[dependencies]
642bar = {path = "../bar"}
643
644//- foo/src/main.rs
645use bar::Bar;
646trait Bar {
647 fn bar();
648}
649#[derive(Bar)]
650struct Foo {}
651fn main() {
652 Foo::bar();
653}
654
655//- bar/Cargo.toml
656[package]
657name = "bar"
658version = "0.0.0"
659edition = "2018"
660
661[lib]
662proc-macro = true
663
664//- bar/src/lib.rs
665extern crate proc_macro;
666use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
667macro_rules! t {
668 ($n:literal) => {
669 TokenTree::from(Ident::new($n, Span::call_site()))
670 };
671 ({}) => {
672 TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
673 };
674 (()) => {
675 TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
676 };
677}
678#[proc_macro_derive(Bar)]
679pub fn foo(_input: TokenStream) -> TokenStream {
680 // We hard code the output here for preventing to use any deps
681 let mut res = TokenStream::new();
682
683 // impl Bar for Foo { fn bar() {} }
684 let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")];
685 let mut fn_stream = TokenStream::new();
686 fn_stream.extend(vec![t!("fn"), t!("bar"), t!(()), t!({})]);
687 tokens.push(Group::new(Delimiter::Brace, fn_stream).into());
688 res.extend(tokens);
689 res
690}
691
692"###,
693 )
694 .with_config(|config| {
695 // FIXME: Use env!("CARGO_BIN_EXE_ra-analyzer") instead after
696 // https://github.com/rust-lang/cargo/pull/7697 landed
697 let macro_srv_path = std::path::Path::new(std::env!("CARGO_MANIFEST_DIR"))
698 .join("../../target/debug/rust-analyzer")
699 .to_string_lossy()
700 .to_string();
701
702 config.cargo.load_out_dirs_from_check = true;
703 config.proc_macro_srv = Some((macro_srv_path, vec!["proc-macro".to_string()]));
704 })
705 .root("foo")
706 .root("bar")
707 .server();
708 server.wait_until_workspace_is_loaded();
709 let res = server.send_request::<HoverRequest>(TextDocumentPositionParams::new(
710 server.doc_id("foo/src/main.rs"),
711 Position::new(7, 9),
712 ));
713
714 let value = res.get("contents").unwrap().get("value").unwrap().to_string();
715 assert_eq!(value, r#""```rust\nfoo::Bar\nfn bar()\n```""#)
716}
diff --git a/editors/code/package.json b/editors/code/package.json
index 5f73c8d83..5ce59e54a 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -388,6 +388,11 @@
388 "description": "Enable logging of VS Code extensions itself", 388 "description": "Enable logging of VS Code extensions itself",
389 "type": "boolean", 389 "type": "boolean",
390 "default": false 390 "default": false
391 },
392 "rust-analyzer.procMacro.enabled": {
393 "description": "Enable Proc macro support, cargo.loadOutDirsFromCheck must be enabled.",
394 "type": "boolean",
395 "default": false
391 } 396 }
392 } 397 }
393 }, 398 },
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 35a05131c..3b2eec8ba 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -12,6 +12,7 @@ export class Config {
12 private readonly requiresReloadOpts = [ 12 private readonly requiresReloadOpts = [
13 "serverPath", 13 "serverPath",
14 "cargo", 14 "cargo",
15 "procMacro",
15 "files", 16 "files",
16 "highlighting", 17 "highlighting",
17 "updates.channel", 18 "updates.channel",
diff --git a/xtask/tests/tidy-tests/main.rs b/xtask/tests/tidy-tests/main.rs
index 101ae19bd..ead642acc 100644
--- a/xtask/tests/tidy-tests/main.rs
+++ b/xtask/tests/tidy-tests/main.rs
@@ -35,7 +35,7 @@ fn check_todo(path: &Path, text: &str) {
35 } 35 }
36 if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") { 36 if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") {
37 panic!( 37 panic!(
38 "\nTODO markers should not be committed to the master branch,\n\ 38 "\nTODO markers or todo! macros should not be committed to the master branch,\n\
39 use FIXME instead\n\ 39 use FIXME instead\n\
40 {}\n", 40 {}\n",
41 path.display(), 41 path.display(),
@@ -47,9 +47,9 @@ fn check_trailing_ws(path: &Path, text: &str) {
47 if is_exclude_dir(path, &["test_data"]) { 47 if is_exclude_dir(path, &["test_data"]) {
48 return; 48 return;
49 } 49 }
50 for line in text.lines() { 50 for (line_number, line) in text.lines().enumerate() {
51 if line.chars().last().map(char::is_whitespace) == Some(true) { 51 if line.chars().last().map(char::is_whitespace) == Some(true) {
52 panic!("Trailing whitespace in {}", path.display()) 52 panic!("Trailing whitespace in {} at line {}", path.display(), line_number)
53 } 53 }
54 } 54 }
55} 55}