diff options
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" | |||
676 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" | 676 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" |
677 | 677 | ||
678 | [[package]] | 678 | [[package]] |
679 | name = "memmap" | ||
680 | version = "0.7.0" | ||
681 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
682 | checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" | ||
683 | dependencies = [ | ||
684 | "libc", | ||
685 | "winapi 0.3.8", | ||
686 | ] | ||
687 | |||
688 | [[package]] | ||
679 | name = "memoffset" | 689 | name = "memoffset" |
680 | version = "0.5.4" | 690 | version = "0.5.4" |
681 | source = "registry+https://github.com/rust-lang/crates.io-index" | 691 | source = "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 | ||
21 | use crate::{ | 21 | use 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 | }; |
24 | use ra_syntax::{ | 24 | use ra_syntax::{ |
25 | ast::{self, AstNode}, | 25 | ast::{self, AstNode}, |
26 | SyntaxNode, SyntaxNodePtr, TextUnit, | 26 | SyntaxNode, TextRange, TextUnit, |
27 | }; | 27 | }; |
28 | 28 | ||
29 | use crate::{ | 29 | use 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 | ||
306 | fn scope_for_offset( | 306 | fn 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... | ||
340 | fn 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 | |||
335 | pub(crate) fn resolve_hir_path( | 378 | pub(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... | ||
382 | fn 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 | ||
17 | use std::{any::Any, fmt}; | 17 | use std::{any::Any, fmt}; |
18 | 18 | ||
19 | use ra_syntax::{SyntaxNode, SyntaxNodePtr, TextRange}; | 19 | use ra_syntax::{SyntaxNode, SyntaxNodePtr}; |
20 | 20 | ||
21 | use crate::{db::AstDatabase, InFile}; | 21 | use crate::{db::AstDatabase, InFile}; |
22 | 22 | ||
23 | pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { | 23 | pub 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 | ||
9 | use super::{InferenceContext, Obligation}; | 9 | use super::{InferenceContext, Obligation}; |
10 | use crate::{ | 10 | use 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 | ||
14 | impl<'a> InferenceContext<'a> { | 15 | impl<'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] | ||
489 | fn issue_3999_slice() { | ||
490 | assert_snapshot!( | ||
491 | infer(r#" | ||
492 | fn 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] | ||
511 | fn issue_3999_struct() { | ||
512 | // rust-analyzer should not panic on seeing this malformed | ||
513 | // record pattern. | ||
514 | assert_snapshot!( | ||
515 | infer(r#" | ||
516 | struct Bar { | ||
517 | a: bool, | ||
518 | } | ||
519 | fn 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] | ||
2245 | fn string_to_owned() { | ||
2246 | let t = type_at( | ||
2247 | r#" | ||
2248 | //- /main.rs | ||
2249 | struct String {} | ||
2250 | pub trait ToOwned { | ||
2251 | type Owned; | ||
2252 | fn to_owned(&self) -> Self::Owned; | ||
2253 | } | ||
2254 | impl ToOwned for str { | ||
2255 | type Owned = String; | ||
2256 | } | ||
2257 | fn test() { | ||
2258 | "foo".to_owned()<|>; | ||
2259 | } | ||
2260 | "#, | ||
2261 | ); | ||
2262 | assert_eq!(t, "String"); | ||
2263 | } | ||
2264 | |||
2265 | #[test] | ||
2266 | fn iterator_chain() { | ||
2267 | assert_snapshot!( | ||
2268 | infer(r#" | ||
2269 | //- /main.rs | ||
2270 | #[lang = "fn_once"] | ||
2271 | trait FnOnce<Args> { | ||
2272 | type Output; | ||
2273 | } | ||
2274 | #[lang = "fn_mut"] | ||
2275 | trait FnMut<Args>: FnOnce<Args> { } | ||
2276 | |||
2277 | enum Option<T> { Some(T), None } | ||
2278 | use Option::*; | ||
2279 | |||
2280 | pub 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 | |||
2294 | pub trait IntoIterator { | ||
2295 | type Item; | ||
2296 | type IntoIter: Iterator<Item = Self::Item>; | ||
2297 | fn into_iter(self) -> Self::IntoIter; | ||
2298 | } | ||
2299 | |||
2300 | pub struct FilterMap<I, F> { } | ||
2301 | impl<B, I: Iterator, F> Iterator for FilterMap<I, F> | ||
2302 | where | ||
2303 | F: FnMut(I::Item) -> Option<B>, | ||
2304 | { | ||
2305 | type Item = B; | ||
2306 | } | ||
2307 | |||
2308 | #[stable(feature = "rust1", since = "1.0.0")] | ||
2309 | impl<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 | |||
2318 | struct Vec<T> {} | ||
2319 | impl<T> Vec<T> { | ||
2320 | fn new() -> Self { loop {} } | ||
2321 | } | ||
2322 | |||
2323 | impl<T> IntoIterator for Vec<T> { | ||
2324 | type Item = T; | ||
2325 | type IntoIter = IntoIter<T>; | ||
2326 | } | ||
2327 | |||
2328 | pub struct IntoIter<T> { } | ||
2329 | impl<T> Iterator for IntoIter<T> { | ||
2330 | type Item = T; | ||
2331 | } | ||
2332 | |||
2333 | fn 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] | ||
2385 | fn nested_assoc() { | ||
2386 | let t = type_at( | ||
2387 | r#" | ||
2388 | //- /main.rs | ||
2389 | struct Bar; | ||
2390 | struct Foo; | ||
2391 | |||
2392 | trait A { | ||
2393 | type OutputA; | ||
2394 | } | ||
2395 | |||
2396 | impl A for Bar { | ||
2397 | type OutputA = Foo; | ||
2398 | } | ||
2399 | |||
2400 | trait B { | ||
2401 | type Output; | ||
2402 | fn foo() -> Self::Output; | ||
2403 | } | ||
2404 | |||
2405 | impl<T:A> B for T { | ||
2406 | type Output = T::OutputA; | ||
2407 | fn foo() -> Self::Output { loop {} } | ||
2408 | } | ||
2409 | |||
2410 | fn main() { | ||
2411 | Bar::foo()<|>; | ||
2412 | } | ||
2413 | "#, | ||
2414 | ); | ||
2415 | assert_eq!(t, "Foo"); | ||
2416 | } | ||
2417 | |||
2418 | #[test] | ||
2419 | fn trait_object_no_coercion() { | ||
2420 | assert_snapshot!( | ||
2421 | infer_with_mismatches(r#" | ||
2422 | trait Foo {} | ||
2423 | |||
2424 | fn foo(x: &dyn Foo) {} | ||
2425 | |||
2426 | fn 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 | ||
152 | fn add_type_alias_impl( | 152 | fn 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)] |
219 | mod tests { | 219 | mod 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 | ||
3 | use hir::ScopeDef; | ||
4 | use test_utils::tested_by; | ||
5 | |||
3 | use crate::completion::{CompletionContext, Completions}; | 6 | use crate::completion::{CompletionContext, Completions}; |
7 | use ra_syntax::AstNode; | ||
4 | 8 | ||
5 | pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 9 | pub(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)] |
21 | mod tests { | 35 | mod 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 | ||
3 | use std::cell::RefCell; | 7 | use 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] | ||
161 | fn ranges_sorted() { | ||
162 | let (analysis, file_id) = single_file( | ||
163 | r#" | ||
164 | #[foo(bar = "bar")] | ||
165 | macro_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; | |||
12 | use process::{ProcMacroProcessSrv, ProcMacroProcessThread}; | 12 | use process::{ProcMacroProcessSrv, ProcMacroProcessThread}; |
13 | use ra_tt::{SmolStr, Subtree}; | 13 | use ra_tt::{SmolStr, Subtree}; |
14 | use std::{ | 14 | use 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 | ||
58 | impl ProcMacroClient { | 59 | impl 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 | |||
9 | use io::{BufRead, BufReader}; | 9 | use io::{BufRead, BufReader}; |
10 | use std::{ | 10 | use 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 | ||
46 | impl Process { | 47 | impl 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 | ||
76 | impl ProcMacroProcessSrv { | 82 | impl 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" } | |||
14 | ra_proc_macro = { path = "../ra_proc_macro" } | 14 | ra_proc_macro = { path = "../ra_proc_macro" } |
15 | goblin = "0.2.1" | 15 | goblin = "0.2.1" |
16 | libloading = "0.6.0" | 16 | libloading = "0.6.0" |
17 | memmap = "0.7" | ||
17 | test_utils = { path = "../test_utils" } | 18 | test_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 | ||
3 | use crate::{expand_task, list_macros}; | ||
3 | use ra_proc_macro::msg::{self, Message}; | 4 | use ra_proc_macro::msg::{self, Message}; |
4 | use ra_proc_macro_srv::{expand_task, list_macros}; | ||
5 | 5 | ||
6 | use std::io; | 6 | use 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 | } |
27 | fn main() { | 27 | |
28 | pub 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 | ||
3 | use crate::{proc_macro::bridge, rustc_server::TokenStream}; | 3 | use crate::{proc_macro::bridge, rustc_server::TokenStream}; |
4 | use std::fs::File; | ||
4 | use std::path::Path; | 5 | use std::path::Path; |
5 | 6 | ||
6 | use goblin::{mach::Mach, Object}; | 7 | use goblin::{mach::Mach, Object}; |
7 | use libloading::Library; | 8 | use libloading::Library; |
9 | use memmap::Mmap; | ||
8 | use ra_proc_macro::ProcMacroKind; | 10 | use ra_proc_macro::ProcMacroKind; |
9 | 11 | ||
10 | use std::io::Error as IoError; | 12 | use 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 | ||
19 | fn get_symbols_from_lib(file: &Path) -> Result<Vec<String>, IoError> { | 21 | fn is_derive_registrar_symbol(symbol: &str) -> bool { |
20 | let buffer = std::fs::read(file)?; | 22 | symbol.contains(NEW_REGISTRAR_SYMBOL) |
23 | } | ||
24 | |||
25 | fn 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 | ||
59 | fn is_derive_registrar_symbol(symbol: &str) -> bool { | ||
60 | symbol.contains(NEW_REGISTRAR_SYMBOL) | ||
61 | } | ||
62 | |||
63 | fn 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; | |||
22 | use proc_macro::bridge::client::TokenStream; | 22 | use proc_macro::bridge::client::TokenStream; |
23 | use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask}; | 23 | use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask}; |
24 | 24 | ||
25 | pub fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> { | 25 | pub(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 | ||
42 | pub fn list_macros(task: &ListMacrosTask) -> Result<ListMacrosResult, String> { | 42 | pub(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 | ||
56 | pub mod cli; | ||
57 | |||
56 | #[cfg(test)] | 58 | #[cfg(test)] |
57 | mod tests; | 59 | mod 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; | |||
5 | mod sysroot; | 5 | mod sysroot; |
6 | 6 | ||
7 | use std::{ | 7 | use 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 | }; |
26 | pub use ra_proc_macro::ProcMacroClient; | 25 | pub use ra_proc_macro::ProcMacroClient; |
27 | 26 | ||
28 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] | ||
29 | pub struct CargoTomlNotFoundError { | ||
30 | pub searched_at: PathBuf, | ||
31 | pub reason: String, | ||
32 | } | ||
33 | |||
34 | impl 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 | |||
45 | impl Error for CargoTomlNotFoundError {} | ||
46 | |||
47 | #[derive(Debug, Clone)] | 27 | #[derive(Debug, Clone)] |
48 | pub enum ProjectWorkspace { | 28 | pub 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 | ||
80 | impl ProjectWorkspace { | 60 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
81 | pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> { | 61 | pub enum ProjectRoot { |
82 | ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) | 62 | ProjectJson(PathBuf), |
63 | CargoToml(PathBuf), | ||
64 | } | ||
65 | |||
66 | impl 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 | |||
154 | impl 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 | ||
472 | fn 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 | |||
489 | fn 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 | |||
502 | fn 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 | |||
514 | fn 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 | |||
553 | pub fn get_rustc_cfg_options() -> CfgOptions { | 542 | pub 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" } | |||
46 | hir = { path = "../ra_hir", package = "ra_hir" } | 46 | hir = { path = "../ra_hir", package = "ra_hir" } |
47 | hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } | 47 | hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } |
48 | hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" } | 48 | hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" } |
49 | 49 | ra_proc_macro_srv = { path = "../ra_proc_macro_srv" } | |
50 | 50 | ||
51 | [target.'cfg(windows)'.dependencies] | 51 | [target.'cfg(windows)'.dependencies] |
52 | winapi = "0.3.8" | 52 | winapi = "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: | |||
187 | FLAGS: | 201 | FLAGS: |
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 | ||
192 | OPTIONS: | 207 | OPTIONS: |
@@ -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 | ||
68 | fn run_proc_macro_sv() -> Result<()> { | ||
69 | ra_proc_macro_srv::cli::run(); | ||
70 | Ok(()) | ||
71 | } | ||
72 | |||
59 | fn run_server() -> Result<()> { | 73 | fn 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}; | |||
9 | use crate::cli::{load_cargo::load_cargo, Result}; | 9 | use crate::cli::{load_cargo::load_cargo, Result}; |
10 | use hir::Semantics; | 10 | use hir::Semantics; |
11 | 11 | ||
12 | pub fn diagnostics(path: &Path, load_output_dirs: bool, all: bool) -> Result<()> { | 12 | pub 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}; | |||
8 | use ra_db::{ExternSourceId, FileId, SourceRootId}; | 8 | use ra_db::{ExternSourceId, FileId, SourceRootId}; |
9 | use ra_ide::{AnalysisChange, AnalysisHost}; | 9 | use ra_ide::{AnalysisChange, AnalysisHost}; |
10 | use ra_project_model::{ | 10 | use ra_project_model::{ |
11 | get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectWorkspace, | 11 | get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace, |
12 | }; | 12 | }; |
13 | use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; | 13 | use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; |
14 | use rustc_hash::{FxHashMap, FxHashSet}; | 14 | use rustc_hash::{FxHashMap, FxHashSet}; |
@@ -25,11 +25,14 @@ fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId { | |||
25 | pub(crate) fn load_cargo( | 25 | pub(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 | ||
17 | use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; | 17 | use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; |
18 | use itertools::Itertools; | ||
18 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; | 19 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; |
19 | use lsp_types::{ | 20 | use 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 | }; |
10 | use rust_analyzer::req::{ | 10 | use 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 | }; |
14 | use serde_json::json; | 14 | use serde_json::json; |
15 | use tempfile::TempDir; | 15 | use 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] | ||
630 | fn 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] | ||
638 | name = "foo" | ||
639 | version = "0.0.0" | ||
640 | edition = "2018" | ||
641 | [dependencies] | ||
642 | bar = {path = "../bar"} | ||
643 | |||
644 | //- foo/src/main.rs | ||
645 | use bar::Bar; | ||
646 | trait Bar { | ||
647 | fn bar(); | ||
648 | } | ||
649 | #[derive(Bar)] | ||
650 | struct Foo {} | ||
651 | fn main() { | ||
652 | Foo::bar(); | ||
653 | } | ||
654 | |||
655 | //- bar/Cargo.toml | ||
656 | [package] | ||
657 | name = "bar" | ||
658 | version = "0.0.0" | ||
659 | edition = "2018" | ||
660 | |||
661 | [lib] | ||
662 | proc-macro = true | ||
663 | |||
664 | //- bar/src/lib.rs | ||
665 | extern crate proc_macro; | ||
666 | use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; | ||
667 | macro_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)] | ||
679 | pub 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 | } |