aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir/src/diagnostics.rs232
-rw-r--r--crates/hir/src/lib.rs176
-rw-r--r--crates/hir_def/src/body.rs25
-rw-r--r--crates/hir_def/src/body/diagnostics.rs32
-rw-r--r--crates/hir_def/src/body/lower.rs45
-rw-r--r--crates/hir_def/src/body/tests.rs16
-rw-r--r--crates/hir_def/src/diagnostics.rs227
-rw-r--r--crates/hir_def/src/lib.rs17
-rw-r--r--crates/hir_def/src/nameres.rs252
-rw-r--r--crates/hir_def/src/nameres/collector.rs7
-rw-r--r--crates/hir_def/src/nameres/diagnostics.rs90
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs57
-rw-r--r--crates/hir_def/src/path.rs2
-rw-r--r--crates/hir_def/src/path/lower.rs27
-rw-r--r--crates/hir_def/src/resolver.rs38
-rw-r--r--crates/hir_def/src/test_db.rs83
-rw-r--r--crates/hir_expand/src/db.rs2
-rw-r--r--crates/hir_expand/src/lib.rs7
-rw-r--r--crates/hir_ty/src/diagnostics.rs12
-rw-r--r--crates/hir_ty/src/diagnostics/decl_check.rs6
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs3
-rw-r--r--crates/hir_ty/src/diagnostics/unsafe_check.rs4
-rw-r--r--crates/hir_ty/src/diagnostics_sink.rs (renamed from crates/hir_expand/src/diagnostics.rs)3
-rw-r--r--crates/hir_ty/src/display.rs6
-rw-r--r--crates/hir_ty/src/infer.rs12
-rw-r--r--crates/hir_ty/src/infer/expr.rs2
-rw-r--r--crates/hir_ty/src/lib.rs1
-rw-r--r--crates/hir_ty/src/tests/coercion.rs101
-rw-r--r--crates/hir_ty/src/tests/traits.rs93
-rw-r--r--crates/ide/src/annotations.rs66
-rw-r--r--crates/ide/src/diagnostics.rs65
-rw-r--r--crates/ide/src/goto_implementation.rs70
-rw-r--r--crates/ide/src/goto_type_definition.rs32
-rw-r--r--crates/ide/src/syntax_highlighting.rs24
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs23
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html2
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs8
-rw-r--r--crates/ide_completion/src/completions/record.rs10
-rw-r--r--crates/ide_completion/src/completions/snippet.rs6
-rw-r--r--crates/rust-analyzer/src/caps.rs4
-rw-r--r--crates/rust-analyzer/src/config.rs13
-rw-r--r--crates/rust-analyzer/src/handlers.rs239
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/rust-analyzer/src/reload.rs6
-rw-r--r--docs/user/generated_config.adoc7
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/run.ts4
48 files changed, 1295 insertions, 870 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 414c3f35e..22ec7c6ac 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -3,13 +3,227 @@
3//! 3//!
4//! This probably isn't the best way to do this -- ideally, diagnistics should 4//! This probably isn't the best way to do this -- ideally, diagnistics should
5//! be expressed in terms of hir types themselves. 5//! be expressed in terms of hir types themselves.
6pub use hir_def::diagnostics::{ 6use std::any::Any;
7 InactiveCode, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro, 7
8}; 8use cfg::{CfgExpr, CfgOptions, DnfExpr};
9pub use hir_expand::diagnostics::{ 9use hir_def::path::ModPath;
10 Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder, 10use hir_expand::{HirFileId, InFile};
11}; 11use stdx::format_to;
12pub use hir_ty::diagnostics::{ 12use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
13 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, 13
14 NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, 14pub use hir_ty::{
15 diagnostics::{
16 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms,
17 MissingOkOrSomeInTailExpr, NoSuchField, RemoveThisSemicolon,
18 ReplaceFilterMapNextWithFindMap,
19 },
20 diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder},
15}; 21};
22
23// Diagnostic: unresolved-module
24//
25// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
26#[derive(Debug)]
27pub struct UnresolvedModule {
28 pub file: HirFileId,
29 pub decl: AstPtr<ast::Module>,
30 pub candidate: String,
31}
32
33impl Diagnostic for UnresolvedModule {
34 fn code(&self) -> DiagnosticCode {
35 DiagnosticCode("unresolved-module")
36 }
37 fn message(&self) -> String {
38 "unresolved module".to_string()
39 }
40 fn display_source(&self) -> InFile<SyntaxNodePtr> {
41 InFile::new(self.file, self.decl.clone().into())
42 }
43 fn as_any(&self) -> &(dyn Any + Send + 'static) {
44 self
45 }
46}
47
48// Diagnostic: unresolved-extern-crate
49//
50// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
51#[derive(Debug)]
52pub struct UnresolvedExternCrate {
53 pub file: HirFileId,
54 pub item: AstPtr<ast::ExternCrate>,
55}
56
57impl Diagnostic for UnresolvedExternCrate {
58 fn code(&self) -> DiagnosticCode {
59 DiagnosticCode("unresolved-extern-crate")
60 }
61 fn message(&self) -> String {
62 "unresolved extern crate".to_string()
63 }
64 fn display_source(&self) -> InFile<SyntaxNodePtr> {
65 InFile::new(self.file, self.item.clone().into())
66 }
67 fn as_any(&self) -> &(dyn Any + Send + 'static) {
68 self
69 }
70}
71
72#[derive(Debug)]
73pub struct UnresolvedImport {
74 pub file: HirFileId,
75 pub node: AstPtr<ast::UseTree>,
76}
77
78impl Diagnostic for UnresolvedImport {
79 fn code(&self) -> DiagnosticCode {
80 DiagnosticCode("unresolved-import")
81 }
82 fn message(&self) -> String {
83 "unresolved import".to_string()
84 }
85 fn display_source(&self) -> InFile<SyntaxNodePtr> {
86 InFile::new(self.file, self.node.clone().into())
87 }
88 fn as_any(&self) -> &(dyn Any + Send + 'static) {
89 self
90 }
91 fn is_experimental(&self) -> bool {
92 // This currently results in false positives in the following cases:
93 // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
94 // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
95 // - proc macros and/or proc macro generated code
96 true
97 }
98}
99
100// Diagnostic: unresolved-macro-call
101//
102// This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
103// macro in a macro invocation.
104#[derive(Debug, Clone, Eq, PartialEq)]
105pub struct UnresolvedMacroCall {
106 pub file: HirFileId,
107 pub node: AstPtr<ast::MacroCall>,
108 pub path: ModPath,
109}
110
111impl Diagnostic for UnresolvedMacroCall {
112 fn code(&self) -> DiagnosticCode {
113 DiagnosticCode("unresolved-macro-call")
114 }
115 fn message(&self) -> String {
116 format!("unresolved macro `{}!`", self.path)
117 }
118 fn display_source(&self) -> InFile<SyntaxNodePtr> {
119 InFile::new(self.file, self.node.clone().into())
120 }
121 fn as_any(&self) -> &(dyn Any + Send + 'static) {
122 self
123 }
124 fn is_experimental(&self) -> bool {
125 true
126 }
127}
128
129// Diagnostic: inactive-code
130//
131// This diagnostic is shown for code with inactive `#[cfg]` attributes.
132#[derive(Debug, Clone, Eq, PartialEq)]
133pub struct InactiveCode {
134 pub file: HirFileId,
135 pub node: SyntaxNodePtr,
136 pub cfg: CfgExpr,
137 pub opts: CfgOptions,
138}
139
140impl Diagnostic for InactiveCode {
141 fn code(&self) -> DiagnosticCode {
142 DiagnosticCode("inactive-code")
143 }
144 fn message(&self) -> String {
145 let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
146 let mut buf = "code is inactive due to #[cfg] directives".to_string();
147
148 if let Some(inactive) = inactive {
149 format_to!(buf, ": {}", inactive);
150 }
151
152 buf
153 }
154 fn display_source(&self) -> InFile<SyntaxNodePtr> {
155 InFile::new(self.file, self.node.clone())
156 }
157 fn as_any(&self) -> &(dyn Any + Send + 'static) {
158 self
159 }
160}
161
162// Diagnostic: unresolved-proc-macro
163//
164// This diagnostic is shown when a procedural macro can not be found. This usually means that
165// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
166// but can also indicate project setup problems.
167//
168// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
169// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
170// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
171#[derive(Debug, Clone, Eq, PartialEq)]
172pub struct UnresolvedProcMacro {
173 pub file: HirFileId,
174 pub node: SyntaxNodePtr,
175 /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
176 /// to use instead.
177 pub precise_location: Option<TextRange>,
178 pub macro_name: Option<String>,
179}
180
181impl Diagnostic for UnresolvedProcMacro {
182 fn code(&self) -> DiagnosticCode {
183 DiagnosticCode("unresolved-proc-macro")
184 }
185
186 fn message(&self) -> String {
187 match &self.macro_name {
188 Some(name) => format!("proc macro `{}` not expanded", name),
189 None => "proc macro not expanded".to_string(),
190 }
191 }
192
193 fn display_source(&self) -> InFile<SyntaxNodePtr> {
194 InFile::new(self.file, self.node.clone())
195 }
196
197 fn as_any(&self) -> &(dyn Any + Send + 'static) {
198 self
199 }
200}
201
202// Diagnostic: macro-error
203//
204// This diagnostic is shown for macro expansion errors.
205#[derive(Debug, Clone, Eq, PartialEq)]
206pub struct MacroError {
207 pub file: HirFileId,
208 pub node: SyntaxNodePtr,
209 pub message: String,
210}
211
212impl Diagnostic for MacroError {
213 fn code(&self) -> DiagnosticCode {
214 DiagnosticCode("macro-error")
215 }
216 fn message(&self) -> String {
217 self.message.clone()
218 }
219 fn display_source(&self) -> InFile<SyntaxNodePtr> {
220 InFile::new(self.file, self.node.clone())
221 }
222 fn as_any(&self) -> &(dyn Any + Send + 'static) {
223 self
224 }
225 fn is_experimental(&self) -> bool {
226 // Newly added and not very well-tested, might contain false positives.
227 true
228 }
229}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index cdf65a044..1ecd2391b 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -35,12 +35,18 @@ use std::{iter, sync::Arc};
35 35
36use arrayvec::ArrayVec; 36use arrayvec::ArrayVec;
37use base_db::{CrateDisplayName, CrateId, Edition, FileId}; 37use base_db::{CrateDisplayName, CrateId, Edition, FileId};
38use diagnostics::{
39 InactiveCode, MacroError, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
40 UnresolvedModule, UnresolvedProcMacro,
41};
38use either::Either; 42use either::Either;
39use hir_def::{ 43use hir_def::{
40 adt::{ReprKind, VariantData}, 44 adt::{ReprKind, VariantData},
45 body::BodyDiagnostic,
41 expr::{BindingAnnotation, LabelId, Pat, PatId}, 46 expr::{BindingAnnotation, LabelId, Pat, PatId},
42 item_tree::ItemTreeNode, 47 item_tree::ItemTreeNode,
43 lang_item::LangItemTarget, 48 lang_item::LangItemTarget,
49 nameres,
44 per_ns::PerNs, 50 per_ns::PerNs,
45 resolver::{HasResolver, Resolver}, 51 resolver::{HasResolver, Resolver},
46 src::HasSource as _, 52 src::HasSource as _,
@@ -50,11 +56,12 @@ use hir_def::{
50 LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId, 56 LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId,
51 TypeParamId, UnionId, 57 TypeParamId, UnionId,
52}; 58};
53use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind}; 59use hir_expand::{name::name, MacroCallKind, MacroDefKind};
54use hir_ty::{ 60use hir_ty::{
55 autoderef, 61 autoderef,
56 consteval::ConstExt, 62 consteval::ConstExt,
57 could_unify, 63 could_unify,
64 diagnostics_sink::DiagnosticSink,
58 method_resolution::{self, def_crates, TyFingerprint}, 65 method_resolution::{self, def_crates, TyFingerprint},
59 primitive::UintTy, 66 primitive::UintTy,
60 subst_prefix, 67 subst_prefix,
@@ -65,11 +72,12 @@ use hir_ty::{
65 WhereClause, 72 WhereClause,
66}; 73};
67use itertools::Itertools; 74use itertools::Itertools;
75use nameres::diagnostics::DefDiagnosticKind;
68use rustc_hash::FxHashSet; 76use rustc_hash::FxHashSet;
69use stdx::{format_to, impl_from}; 77use stdx::{format_to, impl_from};
70use syntax::{ 78use syntax::{
71 ast::{self, AttrsOwner, NameOwner}, 79 ast::{self, AttrsOwner, NameOwner},
72 AstNode, SmolStr, 80 AstNode, AstPtr, SmolStr, SyntaxKind, SyntaxNodePtr,
73}; 81};
74use tt::{Ident, Leaf, Literal, TokenTree}; 82use tt::{Ident, Leaf, Literal, TokenTree};
75 83
@@ -442,7 +450,137 @@ impl Module {
442 format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string())) 450 format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string()))
443 }); 451 });
444 let def_map = self.id.def_map(db.upcast()); 452 let def_map = self.id.def_map(db.upcast());
445 def_map.add_diagnostics(db.upcast(), self.id.local_id, sink); 453 for diag in def_map.diagnostics() {
454 if diag.in_module != self.id.local_id {
455 // FIXME: This is accidentally quadratic.
456 continue;
457 }
458 match &diag.kind {
459 DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate } => {
460 let decl = declaration.to_node(db.upcast());
461 sink.push(UnresolvedModule {
462 file: declaration.file_id,
463 decl: AstPtr::new(&decl),
464 candidate: candidate.clone(),
465 })
466 }
467 DefDiagnosticKind::UnresolvedExternCrate { ast } => {
468 let item = ast.to_node(db.upcast());
469 sink.push(UnresolvedExternCrate {
470 file: ast.file_id,
471 item: AstPtr::new(&item),
472 });
473 }
474
475 DefDiagnosticKind::UnresolvedImport { ast, index } => {
476 let use_item = ast.to_node(db.upcast());
477 let hygiene = Hygiene::new(db.upcast(), ast.file_id);
478 let mut cur = 0;
479 let mut tree = None;
480 ModPath::expand_use_item(
481 db.upcast(),
482 InFile::new(ast.file_id, use_item),
483 &hygiene,
484 |_mod_path, use_tree, _is_glob, _alias| {
485 if cur == *index {
486 tree = Some(use_tree.clone());
487 }
488
489 cur += 1;
490 },
491 );
492
493 if let Some(tree) = tree {
494 sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
495 }
496 }
497
498 DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
499 let item = ast.to_node(db.upcast());
500 sink.push(InactiveCode {
501 file: ast.file_id,
502 node: AstPtr::new(&item).into(),
503 cfg: cfg.clone(),
504 opts: opts.clone(),
505 });
506 }
507
508 DefDiagnosticKind::UnresolvedProcMacro { ast } => {
509 let mut precise_location = None;
510 let (file, ast, name) = match ast {
511 MacroCallKind::FnLike { ast_id, .. } => {
512 let node = ast_id.to_node(db.upcast());
513 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
514 }
515 MacroCallKind::Derive { ast_id, derive_name, .. } => {
516 let node = ast_id.to_node(db.upcast());
517
518 // Compute the precise location of the macro name's token in the derive
519 // list.
520 // FIXME: This does not handle paths to the macro, but neither does the
521 // rest of r-a.
522 let derive_attrs =
523 node.attrs().filter_map(|attr| match attr.as_simple_call() {
524 Some((name, args)) if name == "derive" => Some(args),
525 _ => None,
526 });
527 'outer: for attr in derive_attrs {
528 let tokens =
529 attr.syntax().children_with_tokens().filter_map(|elem| {
530 match elem {
531 syntax::NodeOrToken::Node(_) => None,
532 syntax::NodeOrToken::Token(tok) => Some(tok),
533 }
534 });
535 for token in tokens {
536 if token.kind() == SyntaxKind::IDENT
537 && token.text() == derive_name.as_str()
538 {
539 precise_location = Some(token.text_range());
540 break 'outer;
541 }
542 }
543 }
544
545 (
546 ast_id.file_id,
547 SyntaxNodePtr::from(AstPtr::new(&node)),
548 Some(derive_name.clone()),
549 )
550 }
551 };
552 sink.push(UnresolvedProcMacro {
553 file,
554 node: ast,
555 precise_location,
556 macro_name: name,
557 });
558 }
559
560 DefDiagnosticKind::UnresolvedMacroCall { ast, path } => {
561 let node = ast.to_node(db.upcast());
562 sink.push(UnresolvedMacroCall {
563 file: ast.file_id,
564 node: AstPtr::new(&node),
565 path: path.clone(),
566 });
567 }
568
569 DefDiagnosticKind::MacroError { ast, message } => {
570 let (file, ast) = match ast {
571 MacroCallKind::FnLike { ast_id, .. } => {
572 let node = ast_id.to_node(db.upcast());
573 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
574 }
575 MacroCallKind::Derive { ast_id, .. } => {
576 let node = ast_id.to_node(db.upcast());
577 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
578 }
579 };
580 sink.push(MacroError { file, node: ast, message: message.clone() });
581 }
582 }
583 }
446 for decl in self.declarations(db) { 584 for decl in self.declarations(db) {
447 match decl { 585 match decl {
448 crate::ModuleDef::Function(f) => f.diagnostics(db, sink), 586 crate::ModuleDef::Function(f) => f.diagnostics(db, sink),
@@ -865,7 +1003,37 @@ impl Function {
865 1003
866 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 1004 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
867 let krate = self.module(db).id.krate(); 1005 let krate = self.module(db).id.krate();
868 hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink); 1006
1007 let source_map = db.body_with_source_map(self.id.into()).1;
1008 for diag in source_map.diagnostics() {
1009 match diag {
1010 BodyDiagnostic::InactiveCode { node, cfg, opts } => sink.push(InactiveCode {
1011 file: node.file_id,
1012 node: node.value.clone(),
1013 cfg: cfg.clone(),
1014 opts: opts.clone(),
1015 }),
1016 BodyDiagnostic::MacroError { node, message } => sink.push(MacroError {
1017 file: node.file_id,
1018 node: node.value.clone().into(),
1019 message: message.to_string(),
1020 }),
1021 BodyDiagnostic::UnresolvedProcMacro { node } => sink.push(UnresolvedProcMacro {
1022 file: node.file_id,
1023 node: node.value.clone().into(),
1024 precise_location: None,
1025 macro_name: None,
1026 }),
1027 BodyDiagnostic::UnresolvedMacroCall { node, path } => {
1028 sink.push(UnresolvedMacroCall {
1029 file: node.file_id,
1030 node: node.value.clone(),
1031 path: path.clone(),
1032 })
1033 }
1034 }
1035 }
1036
869 hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink); 1037 hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink);
870 hir_ty::diagnostics::validate_body(db, self.id.into(), sink); 1038 hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
871 } 1039 }
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index 98b485b60..c521879c8 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -1,7 +1,6 @@
1//! Defines `Body`: a lowered representation of bodies of functions, statics and 1//! Defines `Body`: a lowered representation of bodies of functions, statics and
2//! consts. 2//! consts.
3mod lower; 3mod lower;
4mod diagnostics;
5#[cfg(test)] 4#[cfg(test)]
6mod tests; 5mod tests;
7pub mod scope; 6pub mod scope;
@@ -9,17 +8,16 @@ pub mod scope;
9use std::{mem, ops::Index, sync::Arc}; 8use std::{mem, ops::Index, sync::Arc};
10 9
11use base_db::CrateId; 10use base_db::CrateId;
12use cfg::CfgOptions; 11use cfg::{CfgExpr, CfgOptions};
13use drop_bomb::DropBomb; 12use drop_bomb::DropBomb;
14use either::Either; 13use either::Either;
15use hir_expand::{ 14use hir_expand::{
16 ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, ExpandResult, 15 ast_id_map::AstIdMap, hygiene::Hygiene, AstId, ExpandResult, HirFileId, InFile, MacroDefId,
17 HirFileId, InFile, MacroDefId,
18}; 16};
19use la_arena::{Arena, ArenaMap}; 17use la_arena::{Arena, ArenaMap};
20use profile::Count; 18use profile::Count;
21use rustc_hash::FxHashMap; 19use rustc_hash::FxHashMap;
22use syntax::{ast, AstNode, AstPtr}; 20use syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
23 21
24use crate::{ 22use crate::{
25 attr::{Attrs, RawAttrs}, 23 attr::{Attrs, RawAttrs},
@@ -273,12 +271,20 @@ pub struct BodySourceMap {
273 271
274 /// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in 272 /// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
275 /// the source map (since they're just as volatile). 273 /// the source map (since they're just as volatile).
276 diagnostics: Vec<diagnostics::BodyDiagnostic>, 274 diagnostics: Vec<BodyDiagnostic>,
277} 275}
278 276
279#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] 277#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
280pub struct SyntheticSyntax; 278pub struct SyntheticSyntax;
281 279
280#[derive(Debug, Eq, PartialEq)]
281pub enum BodyDiagnostic {
282 InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
283 MacroError { node: InFile<AstPtr<ast::MacroCall>>, message: String },
284 UnresolvedProcMacro { node: InFile<AstPtr<ast::MacroCall>> },
285 UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
286}
287
282impl Body { 288impl Body {
283 pub(crate) fn body_with_source_map_query( 289 pub(crate) fn body_with_source_map_query(
284 db: &dyn DefDatabase, 290 db: &dyn DefDatabase,
@@ -416,9 +422,8 @@ impl BodySourceMap {
416 self.field_map.get(&src).cloned() 422 self.field_map.get(&src).cloned()
417 } 423 }
418 424
419 pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) { 425 /// Get a reference to the body source map's diagnostics.
420 for diag in &self.diagnostics { 426 pub fn diagnostics(&self) -> &[BodyDiagnostic] {
421 diag.add_to(sink); 427 &self.diagnostics
422 }
423 } 428 }
424} 429}
diff --git a/crates/hir_def/src/body/diagnostics.rs b/crates/hir_def/src/body/diagnostics.rs
deleted file mode 100644
index f6992c9a8..000000000
--- a/crates/hir_def/src/body/diagnostics.rs
+++ /dev/null
@@ -1,32 +0,0 @@
1//! Diagnostics emitted during body lowering.
2
3use hir_expand::diagnostics::DiagnosticSink;
4
5use crate::diagnostics::{InactiveCode, MacroError, UnresolvedMacroCall, UnresolvedProcMacro};
6
7#[derive(Debug, Eq, PartialEq)]
8pub(crate) enum BodyDiagnostic {
9 InactiveCode(InactiveCode),
10 MacroError(MacroError),
11 UnresolvedProcMacro(UnresolvedProcMacro),
12 UnresolvedMacroCall(UnresolvedMacroCall),
13}
14
15impl BodyDiagnostic {
16 pub(crate) fn add_to(&self, sink: &mut DiagnosticSink<'_>) {
17 match self {
18 BodyDiagnostic::InactiveCode(diag) => {
19 sink.push(diag.clone());
20 }
21 BodyDiagnostic::MacroError(diag) => {
22 sink.push(diag.clone());
23 }
24 BodyDiagnostic::UnresolvedProcMacro(diag) => {
25 sink.push(diag.clone());
26 }
27 BodyDiagnostic::UnresolvedMacroCall(diag) => {
28 sink.push(diag.clone());
29 }
30 }
31 }
32}
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 2a7e0205f..da1fdac33 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -8,7 +8,7 @@ use hir_expand::{
8 ast_id_map::{AstIdMap, FileAstId}, 8 ast_id_map::{AstIdMap, FileAstId},
9 hygiene::Hygiene, 9 hygiene::Hygiene,
10 name::{name, AsName, Name}, 10 name::{name, AsName, Name},
11 ExpandError, HirFileId, 11 ExpandError, HirFileId, InFile,
12}; 12};
13use la_arena::Arena; 13use la_arena::Arena;
14use profile::Count; 14use profile::Count;
@@ -23,9 +23,9 @@ use syntax::{
23use crate::{ 23use crate::{
24 adt::StructKind, 24 adt::StructKind,
25 body::{Body, BodySourceMap, Expander, LabelSource, PatPtr, SyntheticSyntax}, 25 body::{Body, BodySourceMap, Expander, LabelSource, PatPtr, SyntheticSyntax},
26 body::{BodyDiagnostic, ExprSource, PatSource},
26 builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, 27 builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
27 db::DefDatabase, 28 db::DefDatabase,
28 diagnostics::{InactiveCode, MacroError, UnresolvedMacroCall, UnresolvedProcMacro},
29 expr::{ 29 expr::{
30 dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Label, 30 dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Label,
31 LabelId, Literal, LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, 31 LabelId, Literal, LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField,
@@ -38,8 +38,6 @@ use crate::{
38 AdtId, BlockLoc, ModuleDefId, UnresolvedMacro, 38 AdtId, BlockLoc, ModuleDefId, UnresolvedMacro,
39}; 39};
40 40
41use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource};
42
43pub struct LowerCtx<'a> { 41pub struct LowerCtx<'a> {
44 pub db: &'a dyn DefDatabase, 42 pub db: &'a dyn DefDatabase,
45 hygiene: Hygiene, 43 hygiene: Hygiene,
@@ -592,13 +590,10 @@ impl ExprCollector<'_> {
592 let res = match res { 590 let res = match res {
593 Ok(res) => res, 591 Ok(res) => res,
594 Err(UnresolvedMacro { path }) => { 592 Err(UnresolvedMacro { path }) => {
595 self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall( 593 self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall {
596 UnresolvedMacroCall { 594 node: InFile::new(outer_file, syntax_ptr),
597 file: outer_file, 595 path,
598 node: syntax_ptr.cast().unwrap(), 596 });
599 path,
600 },
601 ));
602 collector(self, None); 597 collector(self, None);
603 return; 598 return;
604 } 599 }
@@ -606,21 +601,15 @@ impl ExprCollector<'_> {
606 601
607 match &res.err { 602 match &res.err {
608 Some(ExpandError::UnresolvedProcMacro) => { 603 Some(ExpandError::UnresolvedProcMacro) => {
609 self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro( 604 self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro {
610 UnresolvedProcMacro { 605 node: InFile::new(outer_file, syntax_ptr),
611 file: outer_file, 606 });
612 node: syntax_ptr.into(),
613 precise_location: None,
614 macro_name: None,
615 },
616 ));
617 } 607 }
618 Some(err) => { 608 Some(err) => {
619 self.source_map.diagnostics.push(BodyDiagnostic::MacroError(MacroError { 609 self.source_map.diagnostics.push(BodyDiagnostic::MacroError {
620 file: outer_file, 610 node: InFile::new(outer_file, syntax_ptr),
621 node: syntax_ptr.into(),
622 message: err.to_string(), 611 message: err.to_string(),
623 })); 612 });
624 } 613 }
625 None => {} 614 None => {}
626 } 615 }
@@ -945,12 +934,14 @@ impl ExprCollector<'_> {
945 return Some(()); 934 return Some(());
946 } 935 }
947 936
948 self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode(InactiveCode { 937 self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode {
949 file: self.expander.current_file_id, 938 node: InFile::new(
950 node: SyntaxNodePtr::new(owner.syntax()), 939 self.expander.current_file_id,
940 SyntaxNodePtr::new(owner.syntax()),
941 ),
951 cfg, 942 cfg,
952 opts: self.expander.cfg_options().clone(), 943 opts: self.expander.cfg_options().clone(),
953 })); 944 });
954 945
955 None 946 None
956 } 947 }
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
index 3e8f16306..d4fae05a6 100644
--- a/crates/hir_def/src/body/tests.rs
+++ b/crates/hir_def/src/body/tests.rs
@@ -96,26 +96,26 @@ fn f() {
96 // The three g̶e̶n̶d̶e̶r̶s̶ statements: 96 // The three g̶e̶n̶d̶e̶r̶s̶ statements:
97 97
98 #[cfg(a)] fn f() {} // Item statement 98 #[cfg(a)] fn f() {} // Item statement
99 //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled 99 //^^^^^^^^^^^^^^^^^^^ InactiveCode
100 #[cfg(a)] {} // Expression statement 100 #[cfg(a)] {} // Expression statement
101 //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled 101 //^^^^^^^^^^^^ InactiveCode
102 #[cfg(a)] let x = 0; // let statement 102 #[cfg(a)] let x = 0; // let statement
103 //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled 103 //^^^^^^^^^^^^^^^^^^^^ InactiveCode
104 104
105 abc(#[cfg(a)] 0); 105 abc(#[cfg(a)] 0);
106 //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled 106 //^^^^^^^^^^^ InactiveCode
107 let x = Struct { 107 let x = Struct {
108 #[cfg(a)] f: 0, 108 #[cfg(a)] f: 0,
109 //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled 109 //^^^^^^^^^^^^^^ InactiveCode
110 }; 110 };
111 match () { 111 match () {
112 () => (), 112 () => (),
113 #[cfg(a)] () => (), 113 #[cfg(a)] () => (),
114 //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled 114 //^^^^^^^^^^^^^^^^^^ InactiveCode
115 } 115 }
116 116
117 #[cfg(a)] 0 // Trailing expression of block 117 #[cfg(a)] 0 // Trailing expression of block
118 //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled 118 //^^^^^^^^^^^ InactiveCode
119} 119}
120 ", 120 ",
121 ); 121 );
@@ -188,7 +188,7 @@ fn unresolved_macro_diag() {
188 r#" 188 r#"
189fn f() { 189fn f() {
190 m!(); 190 m!();
191 //^^^^ unresolved macro `m!` 191 //^^^^ UnresolvedMacroCall
192} 192}
193 "#, 193 "#,
194 ); 194 );
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs
deleted file mode 100644
index a71ae2668..000000000
--- a/crates/hir_def/src/diagnostics.rs
+++ /dev/null
@@ -1,227 +0,0 @@
1//! Diagnostics produced by `hir_def`.
2
3use std::any::Any;
4use stdx::format_to;
5
6use cfg::{CfgExpr, CfgOptions, DnfExpr};
7use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
8use hir_expand::{HirFileId, InFile};
9use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
10
11use crate::{db::DefDatabase, path::ModPath, DefWithBodyId};
12
13pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
14 let source_map = db.body_with_source_map(owner).1;
15 source_map.add_diagnostics(db, sink);
16}
17
18// Diagnostic: unresolved-module
19//
20// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
21#[derive(Debug)]
22pub struct UnresolvedModule {
23 pub file: HirFileId,
24 pub decl: AstPtr<ast::Module>,
25 pub candidate: String,
26}
27
28impl Diagnostic for UnresolvedModule {
29 fn code(&self) -> DiagnosticCode {
30 DiagnosticCode("unresolved-module")
31 }
32 fn message(&self) -> String {
33 "unresolved module".to_string()
34 }
35 fn display_source(&self) -> InFile<SyntaxNodePtr> {
36 InFile::new(self.file, self.decl.clone().into())
37 }
38 fn as_any(&self) -> &(dyn Any + Send + 'static) {
39 self
40 }
41}
42
43// Diagnostic: unresolved-extern-crate
44//
45// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
46#[derive(Debug)]
47pub struct UnresolvedExternCrate {
48 pub file: HirFileId,
49 pub item: AstPtr<ast::ExternCrate>,
50}
51
52impl Diagnostic for UnresolvedExternCrate {
53 fn code(&self) -> DiagnosticCode {
54 DiagnosticCode("unresolved-extern-crate")
55 }
56 fn message(&self) -> String {
57 "unresolved extern crate".to_string()
58 }
59 fn display_source(&self) -> InFile<SyntaxNodePtr> {
60 InFile::new(self.file, self.item.clone().into())
61 }
62 fn as_any(&self) -> &(dyn Any + Send + 'static) {
63 self
64 }
65}
66
67// Diagnostic: unresolved-import
68//
69// This diagnostic is triggered if rust-analyzer is unable to discover imported module.
70#[derive(Debug)]
71pub struct UnresolvedImport {
72 pub file: HirFileId,
73 pub node: AstPtr<ast::UseTree>,
74}
75
76impl Diagnostic for UnresolvedImport {
77 fn code(&self) -> DiagnosticCode {
78 DiagnosticCode("unresolved-import")
79 }
80 fn message(&self) -> String {
81 "unresolved import".to_string()
82 }
83 fn display_source(&self) -> InFile<SyntaxNodePtr> {
84 InFile::new(self.file, self.node.clone().into())
85 }
86 fn as_any(&self) -> &(dyn Any + Send + 'static) {
87 self
88 }
89 fn is_experimental(&self) -> bool {
90 // This currently results in false positives in the following cases:
91 // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
92 // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
93 // - proc macros and/or proc macro generated code
94 true
95 }
96}
97
98// Diagnostic: unresolved-macro-call
99//
100// This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
101// macro in a macro invocation.
102#[derive(Debug, Clone, Eq, PartialEq)]
103pub struct UnresolvedMacroCall {
104 pub file: HirFileId,
105 pub node: AstPtr<ast::MacroCall>,
106 pub path: ModPath,
107}
108
109impl Diagnostic for UnresolvedMacroCall {
110 fn code(&self) -> DiagnosticCode {
111 DiagnosticCode("unresolved-macro-call")
112 }
113 fn message(&self) -> String {
114 format!("unresolved macro `{}!`", self.path)
115 }
116 fn display_source(&self) -> InFile<SyntaxNodePtr> {
117 InFile::new(self.file, self.node.clone().into())
118 }
119 fn as_any(&self) -> &(dyn Any + Send + 'static) {
120 self
121 }
122 fn is_experimental(&self) -> bool {
123 true
124 }
125}
126
127// Diagnostic: inactive-code
128//
129// This diagnostic is shown for code with inactive `#[cfg]` attributes.
130#[derive(Debug, Clone, Eq, PartialEq)]
131pub struct InactiveCode {
132 pub file: HirFileId,
133 pub node: SyntaxNodePtr,
134 pub cfg: CfgExpr,
135 pub opts: CfgOptions,
136}
137
138impl Diagnostic for InactiveCode {
139 fn code(&self) -> DiagnosticCode {
140 DiagnosticCode("inactive-code")
141 }
142 fn message(&self) -> String {
143 let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
144 let mut buf = "code is inactive due to #[cfg] directives".to_string();
145
146 if let Some(inactive) = inactive {
147 format_to!(buf, ": {}", inactive);
148 }
149
150 buf
151 }
152 fn display_source(&self) -> InFile<SyntaxNodePtr> {
153 InFile::new(self.file, self.node.clone())
154 }
155 fn as_any(&self) -> &(dyn Any + Send + 'static) {
156 self
157 }
158}
159
160// Diagnostic: unresolved-proc-macro
161//
162// This diagnostic is shown when a procedural macro can not be found. This usually means that
163// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
164// but can also indicate project setup problems.
165//
166// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
167// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
168// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
169#[derive(Debug, Clone, Eq, PartialEq)]
170pub struct UnresolvedProcMacro {
171 pub file: HirFileId,
172 pub node: SyntaxNodePtr,
173 /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
174 /// to use instead.
175 pub precise_location: Option<TextRange>,
176 pub macro_name: Option<String>,
177}
178
179impl Diagnostic for UnresolvedProcMacro {
180 fn code(&self) -> DiagnosticCode {
181 DiagnosticCode("unresolved-proc-macro")
182 }
183
184 fn message(&self) -> String {
185 match &self.macro_name {
186 Some(name) => format!("proc macro `{}` not expanded", name),
187 None => "proc macro not expanded".to_string(),
188 }
189 }
190
191 fn display_source(&self) -> InFile<SyntaxNodePtr> {
192 InFile::new(self.file, self.node.clone())
193 }
194
195 fn as_any(&self) -> &(dyn Any + Send + 'static) {
196 self
197 }
198}
199
200// Diagnostic: macro-error
201//
202// This diagnostic is shown for macro expansion errors.
203#[derive(Debug, Clone, Eq, PartialEq)]
204pub struct MacroError {
205 pub file: HirFileId,
206 pub node: SyntaxNodePtr,
207 pub message: String,
208}
209
210impl Diagnostic for MacroError {
211 fn code(&self) -> DiagnosticCode {
212 DiagnosticCode("macro-error")
213 }
214 fn message(&self) -> String {
215 self.message.clone()
216 }
217 fn display_source(&self) -> InFile<SyntaxNodePtr> {
218 InFile::new(self.file, self.node.clone())
219 }
220 fn as_any(&self) -> &(dyn Any + Send + 'static) {
221 self
222 }
223 fn is_experimental(&self) -> bool {
224 // Newly added and not very well-tested, might contain false positives.
225 true
226 }
227}
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index 70001cac8..9aa95720a 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -19,7 +19,6 @@ pub mod path;
19pub mod type_ref; 19pub mod type_ref;
20pub mod builtin_type; 20pub mod builtin_type;
21pub mod builtin_attr; 21pub mod builtin_attr;
22pub mod diagnostics;
23pub mod per_ns; 22pub mod per_ns;
24pub mod item_scope; 23pub mod item_scope;
25 24
@@ -56,7 +55,6 @@ use std::{
56 sync::Arc, 55 sync::Arc,
57}; 56};
58 57
59use adt::VariantData;
60use base_db::{impl_intern_key, salsa, CrateId}; 58use base_db::{impl_intern_key, salsa, CrateId};
61use hir_expand::{ 59use hir_expand::{
62 ast_id_map::FileAstId, 60 ast_id_map::FileAstId,
@@ -67,15 +65,18 @@ use hir_expand::{
67use la_arena::Idx; 65use la_arena::Idx;
68use nameres::DefMap; 66use nameres::DefMap;
69use path::ModPath; 67use path::ModPath;
68use stdx::impl_from;
70use syntax::ast; 69use syntax::ast;
71 70
72use crate::attr::AttrId; 71use crate::{
73use crate::builtin_type::BuiltinType; 72 adt::VariantData,
74use item_tree::{ 73 attr::AttrId,
75 Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait, 74 builtin_type::BuiltinType,
76 TypeAlias, Union, 75 item_tree::{
76 Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait,
77 TypeAlias, Union,
78 },
77}; 79};
78use stdx::impl_from;
79 80
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
81pub struct ModuleId { 82pub struct ModuleId {
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index 249af6fc8..ebfcc26c4 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -47,18 +47,19 @@
47//! path and, upon success, we run macro expansion and "collect module" phase on 47//! path and, upon success, we run macro expansion and "collect module" phase on
48//! the result 48//! the result
49 49
50pub mod diagnostics;
50mod collector; 51mod collector;
51mod mod_resolution; 52mod mod_resolution;
52mod path_resolution; 53mod path_resolution;
54mod proc_macro;
53 55
54#[cfg(test)] 56#[cfg(test)]
55mod tests; 57mod tests;
56mod proc_macro;
57 58
58use std::sync::Arc; 59use std::sync::Arc;
59 60
60use base_db::{CrateId, Edition, FileId}; 61use base_db::{CrateId, Edition, FileId};
61use hir_expand::{diagnostics::DiagnosticSink, name::Name, InFile, MacroDefId}; 62use hir_expand::{name::Name, InFile, MacroDefId};
62use la_arena::Arena; 63use la_arena::Arena;
63use profile::Count; 64use profile::Count;
64use rustc_hash::FxHashMap; 65use rustc_hash::FxHashMap;
@@ -254,15 +255,6 @@ impl DefMap {
254 } 255 }
255 } 256 }
256 257
257 pub fn add_diagnostics(
258 &self,
259 db: &dyn DefDatabase,
260 module: LocalModuleId,
261 sink: &mut DiagnosticSink,
262 ) {
263 self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink))
264 }
265
266 pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator<Item = LocalModuleId> + '_ { 258 pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator<Item = LocalModuleId> + '_ {
267 self.modules 259 self.modules
268 .iter() 260 .iter()
@@ -448,6 +440,11 @@ impl DefMap {
448 module.scope.shrink_to_fit(); 440 module.scope.shrink_to_fit();
449 } 441 }
450 } 442 }
443
444 /// Get a reference to the def map's diagnostics.
445 pub fn diagnostics(&self) -> &[DefDiagnostic] {
446 self.diagnostics.as_slice()
447 }
451} 448}
452 449
453impl ModuleData { 450impl ModuleData {
@@ -471,236 +468,3 @@ pub enum ModuleSource {
471 Module(ast::Module), 468 Module(ast::Module),
472 BlockExpr(ast::BlockExpr), 469 BlockExpr(ast::BlockExpr),
473} 470}
474
475mod diagnostics {
476 use cfg::{CfgExpr, CfgOptions};
477 use hir_expand::diagnostics::DiagnosticSink;
478 use hir_expand::hygiene::Hygiene;
479 use hir_expand::{InFile, MacroCallKind};
480 use syntax::ast::AttrsOwner;
481 use syntax::{ast, AstNode, AstPtr, SyntaxKind, SyntaxNodePtr};
482
483 use crate::path::ModPath;
484 use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId};
485
486 #[derive(Debug, PartialEq, Eq)]
487 enum DiagnosticKind {
488 UnresolvedModule { declaration: AstId<ast::Module>, candidate: String },
489
490 UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
491
492 UnresolvedImport { ast: AstId<ast::Use>, index: usize },
493
494 UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
495
496 UnresolvedProcMacro { ast: MacroCallKind },
497
498 UnresolvedMacroCall { ast: AstId<ast::MacroCall>, path: ModPath },
499
500 MacroError { ast: MacroCallKind, message: String },
501 }
502
503 #[derive(Debug, PartialEq, Eq)]
504 pub(super) struct DefDiagnostic {
505 in_module: LocalModuleId,
506 kind: DiagnosticKind,
507 }
508
509 impl DefDiagnostic {
510 pub(super) fn unresolved_module(
511 container: LocalModuleId,
512 declaration: AstId<ast::Module>,
513 candidate: String,
514 ) -> Self {
515 Self {
516 in_module: container,
517 kind: DiagnosticKind::UnresolvedModule { declaration, candidate },
518 }
519 }
520
521 pub(super) fn unresolved_extern_crate(
522 container: LocalModuleId,
523 declaration: AstId<ast::ExternCrate>,
524 ) -> Self {
525 Self {
526 in_module: container,
527 kind: DiagnosticKind::UnresolvedExternCrate { ast: declaration },
528 }
529 }
530
531 pub(super) fn unresolved_import(
532 container: LocalModuleId,
533 ast: AstId<ast::Use>,
534 index: usize,
535 ) -> Self {
536 Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } }
537 }
538
539 pub(super) fn unconfigured_code(
540 container: LocalModuleId,
541 ast: AstId<ast::Item>,
542 cfg: CfgExpr,
543 opts: CfgOptions,
544 ) -> Self {
545 Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
546 }
547
548 pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self {
549 Self { in_module: container, kind: DiagnosticKind::UnresolvedProcMacro { ast } }
550 }
551
552 pub(super) fn macro_error(
553 container: LocalModuleId,
554 ast: MacroCallKind,
555 message: String,
556 ) -> Self {
557 Self { in_module: container, kind: DiagnosticKind::MacroError { ast, message } }
558 }
559
560 pub(super) fn unresolved_macro_call(
561 container: LocalModuleId,
562 ast: AstId<ast::MacroCall>,
563 path: ModPath,
564 ) -> Self {
565 Self { in_module: container, kind: DiagnosticKind::UnresolvedMacroCall { ast, path } }
566 }
567
568 pub(super) fn add_to(
569 &self,
570 db: &dyn DefDatabase,
571 target_module: LocalModuleId,
572 sink: &mut DiagnosticSink,
573 ) {
574 if self.in_module != target_module {
575 return;
576 }
577
578 match &self.kind {
579 DiagnosticKind::UnresolvedModule { declaration, candidate } => {
580 let decl = declaration.to_node(db.upcast());
581 sink.push(UnresolvedModule {
582 file: declaration.file_id,
583 decl: AstPtr::new(&decl),
584 candidate: candidate.clone(),
585 })
586 }
587
588 DiagnosticKind::UnresolvedExternCrate { ast } => {
589 let item = ast.to_node(db.upcast());
590 sink.push(UnresolvedExternCrate {
591 file: ast.file_id,
592 item: AstPtr::new(&item),
593 });
594 }
595
596 DiagnosticKind::UnresolvedImport { ast, index } => {
597 let use_item = ast.to_node(db.upcast());
598 let hygiene = Hygiene::new(db.upcast(), ast.file_id);
599 let mut cur = 0;
600 let mut tree = None;
601 ModPath::expand_use_item(
602 db,
603 InFile::new(ast.file_id, use_item),
604 &hygiene,
605 |_mod_path, use_tree, _is_glob, _alias| {
606 if cur == *index {
607 tree = Some(use_tree.clone());
608 }
609
610 cur += 1;
611 },
612 );
613
614 if let Some(tree) = tree {
615 sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
616 }
617 }
618
619 DiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
620 let item = ast.to_node(db.upcast());
621 sink.push(InactiveCode {
622 file: ast.file_id,
623 node: AstPtr::new(&item).into(),
624 cfg: cfg.clone(),
625 opts: opts.clone(),
626 });
627 }
628
629 DiagnosticKind::UnresolvedProcMacro { ast } => {
630 let mut precise_location = None;
631 let (file, ast, name) = match ast {
632 MacroCallKind::FnLike { ast_id, .. } => {
633 let node = ast_id.to_node(db.upcast());
634 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
635 }
636 MacroCallKind::Derive { ast_id, derive_name, .. } => {
637 let node = ast_id.to_node(db.upcast());
638
639 // Compute the precise location of the macro name's token in the derive
640 // list.
641 // FIXME: This does not handle paths to the macro, but neither does the
642 // rest of r-a.
643 let derive_attrs =
644 node.attrs().filter_map(|attr| match attr.as_simple_call() {
645 Some((name, args)) if name == "derive" => Some(args),
646 _ => None,
647 });
648 'outer: for attr in derive_attrs {
649 let tokens =
650 attr.syntax().children_with_tokens().filter_map(|elem| {
651 match elem {
652 syntax::NodeOrToken::Node(_) => None,
653 syntax::NodeOrToken::Token(tok) => Some(tok),
654 }
655 });
656 for token in tokens {
657 if token.kind() == SyntaxKind::IDENT
658 && token.text() == derive_name.as_str()
659 {
660 precise_location = Some(token.text_range());
661 break 'outer;
662 }
663 }
664 }
665
666 (
667 ast_id.file_id,
668 SyntaxNodePtr::from(AstPtr::new(&node)),
669 Some(derive_name.clone()),
670 )
671 }
672 };
673 sink.push(UnresolvedProcMacro {
674 file,
675 node: ast,
676 precise_location,
677 macro_name: name,
678 });
679 }
680
681 DiagnosticKind::UnresolvedMacroCall { ast, path } => {
682 let node = ast.to_node(db.upcast());
683 sink.push(UnresolvedMacroCall {
684 file: ast.file_id,
685 node: AstPtr::new(&node),
686 path: path.clone(),
687 });
688 }
689
690 DiagnosticKind::MacroError { ast, message } => {
691 let (file, ast) = match ast {
692 MacroCallKind::FnLike { ast_id, .. } => {
693 let node = ast_id.to_node(db.upcast());
694 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
695 }
696 MacroCallKind::Derive { ast_id, .. } => {
697 let node = ast_id.to_node(db.upcast());
698 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
699 }
700 };
701 sink.push(MacroError { file, node: ast, message: message.clone() });
702 }
703 }
704 }
705 }
706}
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 2ae740d0e..3ea472908 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -33,7 +33,10 @@ use crate::{
33 }, 33 },
34 macro_call_as_call_id, 34 macro_call_as_call_id,
35 nameres::{ 35 nameres::{
36 diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint, 36 diagnostics::DefDiagnostic,
37 mod_resolution::ModDir,
38 path_resolution::ReachedFixedPoint,
39 proc_macro::{ProcMacroDef, ProcMacroKind},
37 BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode, 40 BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode,
38 }, 41 },
39 path::{ImportAlias, ModPath, PathKind}, 42 path::{ImportAlias, ModPath, PathKind},
@@ -44,8 +47,6 @@ use crate::{
44 UnresolvedMacro, 47 UnresolvedMacro,
45}; 48};
46 49
47use super::proc_macro::{ProcMacroDef, ProcMacroKind};
48
49const GLOB_RECURSION_LIMIT: usize = 100; 50const GLOB_RECURSION_LIMIT: usize = 100;
50const EXPANSION_DEPTH_LIMIT: usize = 128; 51const EXPANSION_DEPTH_LIMIT: usize = 128;
51const FIXED_POINT_LIMIT: usize = 8192; 52const FIXED_POINT_LIMIT: usize = 8192;
diff --git a/crates/hir_def/src/nameres/diagnostics.rs b/crates/hir_def/src/nameres/diagnostics.rs
new file mode 100644
index 000000000..8f2f0ff9f
--- /dev/null
+++ b/crates/hir_def/src/nameres/diagnostics.rs
@@ -0,0 +1,90 @@
1//! Diagnostics emitted during DefMap construction.
2
3use cfg::{CfgExpr, CfgOptions};
4use hir_expand::MacroCallKind;
5use syntax::ast;
6
7use crate::{nameres::LocalModuleId, path::ModPath, AstId};
8
9#[derive(Debug, PartialEq, Eq)]
10pub enum DefDiagnosticKind {
11 UnresolvedModule { ast: AstId<ast::Module>, candidate: String },
12
13 UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
14
15 UnresolvedImport { ast: AstId<ast::Use>, index: usize },
16
17 UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
18
19 UnresolvedProcMacro { ast: MacroCallKind },
20
21 UnresolvedMacroCall { ast: AstId<ast::MacroCall>, path: ModPath },
22
23 MacroError { ast: MacroCallKind, message: String },
24}
25
26#[derive(Debug, PartialEq, Eq)]
27pub struct DefDiagnostic {
28 pub in_module: LocalModuleId,
29 pub kind: DefDiagnosticKind,
30}
31
32impl DefDiagnostic {
33 pub(super) fn unresolved_module(
34 container: LocalModuleId,
35 declaration: AstId<ast::Module>,
36 candidate: String,
37 ) -> Self {
38 Self {
39 in_module: container,
40 kind: DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate },
41 }
42 }
43
44 pub(super) fn unresolved_extern_crate(
45 container: LocalModuleId,
46 declaration: AstId<ast::ExternCrate>,
47 ) -> Self {
48 Self {
49 in_module: container,
50 kind: DefDiagnosticKind::UnresolvedExternCrate { ast: declaration },
51 }
52 }
53
54 pub(super) fn unresolved_import(
55 container: LocalModuleId,
56 ast: AstId<ast::Use>,
57 index: usize,
58 ) -> Self {
59 Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { ast, index } }
60 }
61
62 pub(super) fn unconfigured_code(
63 container: LocalModuleId,
64 ast: AstId<ast::Item>,
65 cfg: CfgExpr,
66 opts: CfgOptions,
67 ) -> Self {
68 Self { in_module: container, kind: DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
69 }
70
71 pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self {
72 Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast } }
73 }
74
75 pub(super) fn macro_error(
76 container: LocalModuleId,
77 ast: MacroCallKind,
78 message: String,
79 ) -> Self {
80 Self { in_module: container, kind: DefDiagnosticKind::MacroError { ast, message } }
81 }
82
83 pub(super) fn unresolved_macro_call(
84 container: LocalModuleId,
85 ast: AstId<ast::MacroCall>,
86 path: ModPath,
87 ) -> Self {
88 Self { in_module: container, kind: DefDiagnosticKind::UnresolvedMacroCall { ast, path } }
89 }
90}
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
index 75147d973..ec6670952 100644
--- a/crates/hir_def/src/nameres/tests/diagnostics.rs
+++ b/crates/hir_def/src/nameres/tests/diagnostics.rs
@@ -18,7 +18,7 @@ fn unresolved_import() {
18 r" 18 r"
19 use does_exist; 19 use does_exist;
20 use does_not_exist; 20 use does_not_exist;
21 //^^^^^^^^^^^^^^ unresolved import 21 //^^^^^^^^^^^^^^^^^^^ UnresolvedImport
22 22
23 mod does_exist {} 23 mod does_exist {}
24 ", 24 ",
@@ -26,40 +26,13 @@ fn unresolved_import() {
26} 26}
27 27
28#[test] 28#[test]
29fn unresolved_import_in_use_tree() {
30 // Only the relevant part of a nested `use` item should be highlighted.
31 check_diagnostics(
32 r"
33 use does_exist::{Exists, DoesntExist};
34 //^^^^^^^^^^^ unresolved import
35
36 use {does_not_exist::*, does_exist};
37 //^^^^^^^^^^^^^^^^^ unresolved import
38
39 use does_not_exist::{
40 a,
41 //^ unresolved import
42 b,
43 //^ unresolved import
44 c,
45 //^ unresolved import
46 };
47
48 mod does_exist {
49 pub struct Exists;
50 }
51 ",
52 );
53}
54
55#[test]
56fn unresolved_extern_crate() { 29fn unresolved_extern_crate() {
57 check_diagnostics( 30 check_diagnostics(
58 r" 31 r"
59 //- /main.rs crate:main deps:core 32 //- /main.rs crate:main deps:core
60 extern crate core; 33 extern crate core;
61 extern crate doesnotexist; 34 extern crate doesnotexist;
62 //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate 35 //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
63 //- /lib.rs crate:core 36 //- /lib.rs crate:core
64 ", 37 ",
65 ); 38 );
@@ -72,7 +45,7 @@ fn extern_crate_self_as() {
72 r" 45 r"
73 //- /lib.rs 46 //- /lib.rs
74 extern crate doesnotexist; 47 extern crate doesnotexist;
75 //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate 48 //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
76 // Should not error. 49 // Should not error.
77 extern crate self as foo; 50 extern crate self as foo;
78 struct Foo; 51 struct Foo;
@@ -88,18 +61,18 @@ fn dedup_unresolved_import_from_unresolved_crate() {
88 //- /main.rs crate:main 61 //- /main.rs crate:main
89 mod a { 62 mod a {
90 extern crate doesnotexist; 63 extern crate doesnotexist;
91 //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate 64 //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
92 65
93 // Should not error, since we already errored for the missing crate. 66 // Should not error, since we already errored for the missing crate.
94 use doesnotexist::{self, bla, *}; 67 use doesnotexist::{self, bla, *};
95 68
96 use crate::doesnotexist; 69 use crate::doesnotexist;
97 //^^^^^^^^^^^^^^^^^^^ unresolved import 70 //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
98 } 71 }
99 72
100 mod m { 73 mod m {
101 use super::doesnotexist; 74 use super::doesnotexist;
102 //^^^^^^^^^^^^^^^^^^^ unresolved import 75 //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
103 } 76 }
104 ", 77 ",
105 ); 78 );
@@ -112,7 +85,7 @@ fn unresolved_module() {
112 //- /lib.rs 85 //- /lib.rs
113 mod foo; 86 mod foo;
114 mod bar; 87 mod bar;
115 //^^^^^^^^ unresolved module 88 //^^^^^^^^ UnresolvedModule
116 mod baz {} 89 mod baz {}
117 //- /foo.rs 90 //- /foo.rs
118 ", 91 ",
@@ -127,16 +100,16 @@ fn inactive_item() {
127 r#" 100 r#"
128 //- /lib.rs 101 //- /lib.rs
129 #[cfg(no)] pub fn f() {} 102 #[cfg(no)] pub fn f() {}
130 //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled 103 //^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
131 104
132 #[cfg(no)] #[cfg(no2)] mod m; 105 #[cfg(no)] #[cfg(no2)] mod m;
133 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled 106 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
134 107
135 #[cfg(all(not(a), b))] enum E {} 108 #[cfg(all(not(a), b))] enum E {}
136 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled 109 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
137 110
138 #[cfg(feature = "std")] use std; 111 #[cfg(feature = "std")] use std;
139 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled 112 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
140 "#, 113 "#,
141 ); 114 );
142} 115}
@@ -149,14 +122,14 @@ fn inactive_via_cfg_attr() {
149 r#" 122 r#"
150 //- /lib.rs 123 //- /lib.rs
151 #[cfg_attr(not(never), cfg(no))] fn f() {} 124 #[cfg_attr(not(never), cfg(no))] fn f() {}
152 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled 125 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
153 126
154 #[cfg_attr(not(never), cfg(not(no)))] fn f() {} 127 #[cfg_attr(not(never), cfg(not(no)))] fn f() {}
155 128
156 #[cfg_attr(never, cfg(no))] fn g() {} 129 #[cfg_attr(never, cfg(no))] fn g() {}
157 130
158 #[cfg_attr(not(never), inline, cfg(no))] fn h() {} 131 #[cfg_attr(not(never), inline, cfg(no))] fn h() {}
159 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled 132 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
160 "#, 133 "#,
161 ); 134 );
162} 135}
@@ -170,7 +143,7 @@ fn unresolved_legacy_scope_macro() {
170 143
171 m!(); 144 m!();
172 m2!(); 145 m2!();
173 //^^^^^^ unresolved macro `self::m2!` 146 //^^^^^^ UnresolvedMacroCall
174 "#, 147 "#,
175 ); 148 );
176} 149}
@@ -187,7 +160,7 @@ fn unresolved_module_scope_macro() {
187 160
188 self::m!(); 161 self::m!();
189 self::m2!(); 162 self::m2!();
190 //^^^^^^^^^^^^ unresolved macro `self::m2!` 163 //^^^^^^^^^^^^ UnresolvedMacroCall
191 "#, 164 "#,
192 ); 165 );
193} 166}
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index 45ab9d0ff..d9ec03d2d 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -71,7 +71,7 @@ impl ModPath {
71 } 71 }
72 72
73 /// Calls `cb` with all paths, represented by this use item. 73 /// Calls `cb` with all paths, represented by this use item.
74 pub(crate) fn expand_use_item( 74 pub fn expand_use_item(
75 db: &dyn DefDatabase, 75 db: &dyn DefDatabase,
76 item_src: InFile<ast::Use>, 76 item_src: InFile<ast::Use>,
77 hygiene: &Hygiene, 77 hygiene: &Hygiene,
diff --git a/crates/hir_def/src/path/lower.rs b/crates/hir_def/src/path/lower.rs
index 5d5dd9c8f..54ede7393 100644
--- a/crates/hir_def/src/path/lower.rs
+++ b/crates/hir_def/src/path/lower.rs
@@ -205,15 +205,14 @@ fn lower_generic_args_from_fn_path(
205) -> Option<GenericArgs> { 205) -> Option<GenericArgs> {
206 let mut args = Vec::new(); 206 let mut args = Vec::new();
207 let mut bindings = Vec::new(); 207 let mut bindings = Vec::new();
208 if let Some(params) = params { 208 let params = params?;
209 let mut param_types = Vec::new(); 209 let mut param_types = Vec::new();
210 for param in params.params() { 210 for param in params.params() {
211 let type_ref = TypeRef::from_ast_opt(&ctx, param.ty()); 211 let type_ref = TypeRef::from_ast_opt(&ctx, param.ty());
212 param_types.push(type_ref); 212 param_types.push(type_ref);
213 }
214 let arg = GenericArg::Type(TypeRef::Tuple(param_types));
215 args.push(arg);
216 } 213 }
214 let arg = GenericArg::Type(TypeRef::Tuple(param_types));
215 args.push(arg);
217 if let Some(ret_type) = ret_type { 216 if let Some(ret_type) = ret_type {
218 let type_ref = TypeRef::from_ast_opt(&ctx, ret_type.ty()); 217 let type_ref = TypeRef::from_ast_opt(&ctx, ret_type.ty());
219 bindings.push(AssociatedTypeBinding { 218 bindings.push(AssociatedTypeBinding {
@@ -221,10 +220,14 @@ fn lower_generic_args_from_fn_path(
221 type_ref: Some(type_ref), 220 type_ref: Some(type_ref),
222 bounds: Vec::new(), 221 bounds: Vec::new(),
223 }); 222 });
224 }
225 if args.is_empty() && bindings.is_empty() {
226 None
227 } else { 223 } else {
228 Some(GenericArgs { args, has_self_type: false, bindings }) 224 // -> ()
225 let type_ref = TypeRef::Tuple(Vec::new());
226 bindings.push(AssociatedTypeBinding {
227 name: name![Output],
228 type_ref: Some(type_ref),
229 bounds: Vec::new(),
230 });
229 } 231 }
232 Some(GenericArgs { args, has_self_type: false, bindings })
230} 233}
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index 0391cc49b..fb8a6f260 100644
--- a/crates/hir_def/src/resolver.rs
+++ b/crates/hir_def/src/resolver.rs
@@ -337,22 +337,34 @@ impl Resolver {
337 pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> { 337 pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> {
338 let mut traits = FxHashSet::default(); 338 let mut traits = FxHashSet::default();
339 for scope in &self.scopes { 339 for scope in &self.scopes {
340 if let Scope::ModuleScope(m) = scope { 340 match scope {
341 if let Some(prelude) = m.def_map.prelude() { 341 Scope::ModuleScope(m) => {
342 let prelude_def_map = prelude.def_map(db); 342 if let Some(prelude) = m.def_map.prelude() {
343 traits.extend(prelude_def_map[prelude.local_id].scope.traits());
344 }
345 traits.extend(m.def_map[m.module_id].scope.traits());
346
347 // Add all traits that are in scope because of the containing DefMaps
348 m.def_map.with_ancestor_maps(db, m.module_id, &mut |def_map, module| {
349 if let Some(prelude) = def_map.prelude() {
350 let prelude_def_map = prelude.def_map(db); 343 let prelude_def_map = prelude.def_map(db);
351 traits.extend(prelude_def_map[prelude.local_id].scope.traits()); 344 traits.extend(prelude_def_map[prelude.local_id].scope.traits());
352 } 345 }
353 traits.extend(def_map[module].scope.traits()); 346 traits.extend(m.def_map[m.module_id].scope.traits());
354 None::<()> 347
355 }); 348 // Add all traits that are in scope because of the containing DefMaps
349 m.def_map.with_ancestor_maps(db, m.module_id, &mut |def_map, module| {
350 if let Some(prelude) = def_map.prelude() {
351 let prelude_def_map = prelude.def_map(db);
352 traits.extend(prelude_def_map[prelude.local_id].scope.traits());
353 }
354 traits.extend(def_map[module].scope.traits());
355 None::<()>
356 });
357 }
358 &Scope::ImplDefScope(impl_) => {
359 if let Some(target_trait) = &db.impl_data(impl_).target_trait {
360 if let Some(TypeNs::TraitId(trait_)) =
361 self.resolve_path_in_type_ns_fully(db, target_trait.path.mod_path())
362 {
363 traits.insert(trait_);
364 }
365 }
366 }
367 _ => (),
356 } 368 }
357 } 369 }
358 traits 370 traits
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index 8fa703a57..6c357c915 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -5,19 +5,20 @@ use std::{
5 sync::{Arc, Mutex}, 5 sync::{Arc, Mutex},
6}; 6};
7 7
8use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, Upcast}; 8use base_db::{
9 salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, FileRange, Upcast,
10};
9use base_db::{AnchoredPath, SourceDatabase}; 11use base_db::{AnchoredPath, SourceDatabase};
10use hir_expand::diagnostics::Diagnostic;
11use hir_expand::diagnostics::DiagnosticSinkBuilder;
12use hir_expand::{db::AstDatabase, InFile}; 12use hir_expand::{db::AstDatabase, InFile};
13use rustc_hash::FxHashMap; 13use rustc_hash::FxHashMap;
14use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
15use syntax::{algo, ast, AstNode, TextRange, TextSize}; 15use syntax::{algo, ast, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
16use test_utils::extract_annotations; 16use test_utils::extract_annotations;
17 17
18use crate::{ 18use crate::{
19 body::BodyDiagnostic,
19 db::DefDatabase, 20 db::DefDatabase,
20 nameres::{DefMap, ModuleSource}, 21 nameres::{diagnostics::DefDiagnosticKind, DefMap, ModuleSource},
21 src::HasSource, 22 src::HasSource,
22 LocalModuleId, Lookup, ModuleDefId, ModuleId, 23 LocalModuleId, Lookup, ModuleDefId, ModuleId,
23}; 24};
@@ -262,19 +263,70 @@ impl TestDB {
262 .collect() 263 .collect()
263 } 264 }
264 265
265 pub(crate) fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) { 266 pub(crate) fn diagnostics(&self, cb: &mut dyn FnMut(FileRange, String)) {
266 let crate_graph = self.crate_graph(); 267 let crate_graph = self.crate_graph();
267 for krate in crate_graph.iter() { 268 for krate in crate_graph.iter() {
268 let crate_def_map = self.crate_def_map(krate); 269 let crate_def_map = self.crate_def_map(krate);
269 270
270 let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); 271 for diag in crate_def_map.diagnostics() {
271 for (module_id, module) in crate_def_map.modules() { 272 let (node, message): (InFile<SyntaxNode>, &str) = match &diag.kind {
272 crate_def_map.add_diagnostics(self, module_id, &mut sink); 273 DefDiagnosticKind::UnresolvedModule { ast, .. } => {
274 let node = ast.to_node(self.upcast());
275 (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedModule")
276 }
277 DefDiagnosticKind::UnresolvedExternCrate { ast, .. } => {
278 let node = ast.to_node(self.upcast());
279 (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedExternCrate")
280 }
281 DefDiagnosticKind::UnresolvedImport { ast, .. } => {
282 let node = ast.to_node(self.upcast());
283 (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedImport")
284 }
285 DefDiagnosticKind::UnconfiguredCode { ast, .. } => {
286 let node = ast.to_node(self.upcast());
287 (InFile::new(ast.file_id, node.syntax().clone()), "UnconfiguredCode")
288 }
289 DefDiagnosticKind::UnresolvedProcMacro { ast, .. } => {
290 (ast.to_node(self.upcast()), "UnresolvedProcMacro")
291 }
292 DefDiagnosticKind::UnresolvedMacroCall { ast, .. } => {
293 let node = ast.to_node(self.upcast());
294 (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedMacroCall")
295 }
296 DefDiagnosticKind::MacroError { ast, message } => {
297 (ast.to_node(self.upcast()), message.as_str())
298 }
299 };
300
301 let frange = node.as_ref().original_file_range(self);
302 cb(frange, message.to_string())
303 }
273 304
305 for (_module_id, module) in crate_def_map.modules() {
274 for decl in module.scope.declarations() { 306 for decl in module.scope.declarations() {
275 if let ModuleDefId::FunctionId(it) = decl { 307 if let ModuleDefId::FunctionId(it) = decl {
276 let source_map = self.body_with_source_map(it.into()).1; 308 let source_map = self.body_with_source_map(it.into()).1;
277 source_map.add_diagnostics(self, &mut sink); 309 for diag in source_map.diagnostics() {
310 let (ptr, message): (InFile<SyntaxNodePtr>, &str) = match diag {
311 BodyDiagnostic::InactiveCode { node, .. } => {
312 (node.clone().map(|it| it.into()), "InactiveCode")
313 }
314 BodyDiagnostic::MacroError { node, message } => {
315 (node.clone().map(|it| it.into()), message.as_str())
316 }
317 BodyDiagnostic::UnresolvedProcMacro { node } => {
318 (node.clone().map(|it| it.into()), "UnresolvedProcMacro")
319 }
320 BodyDiagnostic::UnresolvedMacroCall { node, .. } => {
321 (node.clone().map(|it| it.into()), "UnresolvedMacroCall")
322 }
323 };
324
325 let root = self.parse_or_expand(ptr.file_id).unwrap();
326 let node = ptr.map(|ptr| ptr.to_node(&root));
327 let frange = node.as_ref().original_file_range(self);
328 cb(frange, message.to_string())
329 }
278 } 330 }
279 } 331 }
280 } 332 }
@@ -287,14 +339,7 @@ impl TestDB {
287 assert!(!annotations.is_empty()); 339 assert!(!annotations.is_empty());
288 340
289 let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default(); 341 let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
290 db.diagnostics(|d| { 342 db.diagnostics(&mut |frange, message| {
291 let src = d.display_source();
292 let root = db.parse_or_expand(src.file_id).unwrap();
293
294 let node = src.map(|ptr| ptr.to_node(&root));
295 let frange = node.as_ref().original_file_range(db);
296
297 let message = d.message();
298 actual.entry(frange.file_id).or_default().push((frange.range, message)); 343 actual.entry(frange.file_id).or_default().push((frange.range, message));
299 }); 344 });
300 345
@@ -319,7 +364,7 @@ impl TestDB {
319 assert!(annotations.is_empty()); 364 assert!(annotations.is_empty());
320 365
321 let mut has_diagnostics = false; 366 let mut has_diagnostics = false;
322 db.diagnostics(|_| { 367 db.diagnostics(&mut |_, _| {
323 has_diagnostics = true; 368 has_diagnostics = true;
324 }); 369 });
325 370
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index 625c26f0a..e8f4af309 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -186,7 +186,7 @@ fn parse_macro_expansion(
186 // The final goal we would like to make all parse_macro success, 186 // The final goal we would like to make all parse_macro success,
187 // such that the following log will not call anyway. 187 // such that the following log will not call anyway.
188 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); 188 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
189 let node = loc.kind.node(db); 189 let node = loc.kind.to_node(db);
190 190
191 // collect parent information for warning log 191 // collect parent information for warning log
192 let parents = 192 let parents =
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 6be4516a3..10d37234e 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -8,7 +8,6 @@ pub mod db;
8pub mod ast_id_map; 8pub mod ast_id_map;
9pub mod name; 9pub mod name;
10pub mod hygiene; 10pub mod hygiene;
11pub mod diagnostics;
12pub mod builtin_derive; 11pub mod builtin_derive;
13pub mod builtin_macro; 12pub mod builtin_macro;
14pub mod proc_macro; 13pub mod proc_macro;
@@ -108,7 +107,7 @@ impl HirFileId {
108 HirFileIdRepr::FileId(_) => None, 107 HirFileIdRepr::FileId(_) => None,
109 HirFileIdRepr::MacroFile(macro_file) => { 108 HirFileIdRepr::MacroFile(macro_file) => {
110 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); 109 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
111 Some(loc.kind.node(db)) 110 Some(loc.kind.to_node(db))
112 } 111 }
113 } 112 }
114 } 113 }
@@ -153,7 +152,7 @@ impl HirFileId {
153 HirFileIdRepr::MacroFile(macro_file) => { 152 HirFileIdRepr::MacroFile(macro_file) => {
154 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); 153 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
155 let item = match loc.def.kind { 154 let item = match loc.def.kind {
156 MacroDefKind::BuiltInDerive(..) => loc.kind.node(db), 155 MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db),
157 _ => return None, 156 _ => return None,
158 }; 157 };
159 Some(item.with_value(ast::Item::cast(item.value.clone())?)) 158 Some(item.with_value(ast::Item::cast(item.value.clone())?))
@@ -269,7 +268,7 @@ impl MacroCallKind {
269 } 268 }
270 } 269 }
271 270
272 fn node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> { 271 pub fn to_node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
273 match self { 272 match self {
274 MacroCallKind::FnLike { ast_id, .. } => { 273 MacroCallKind::FnLike { ast_id, .. } => {
275 ast_id.with_value(ast_id.to_node(db).syntax().clone()) 274 ast_id.with_value(ast_id.to_node(db).syntax().clone())
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index 84fc8ce14..7598e2193 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -8,12 +8,14 @@ use std::{any::Any, fmt};
8 8
9use base_db::CrateId; 9use base_db::CrateId;
10use hir_def::{DefWithBodyId, ModuleDefId}; 10use hir_def::{DefWithBodyId, ModuleDefId};
11use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
12use hir_expand::{name::Name, HirFileId, InFile}; 11use hir_expand::{name::Name, HirFileId, InFile};
13use stdx::format_to; 12use stdx::format_to;
14use syntax::{ast, AstPtr, SyntaxNodePtr}; 13use syntax::{ast, AstPtr, SyntaxNodePtr};
15 14
16use crate::db::HirDatabase; 15use crate::{
16 db::HirDatabase,
17 diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink},
18};
17 19
18pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields}; 20pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields};
19 21
@@ -446,15 +448,13 @@ impl Diagnostic for ReplaceFilterMapNextWithFindMap {
446mod tests { 448mod tests {
447 use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt}; 449 use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt};
448 use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId}; 450 use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId};
449 use hir_expand::{ 451 use hir_expand::db::AstDatabase;
450 db::AstDatabase,
451 diagnostics::{Diagnostic, DiagnosticSinkBuilder},
452 };
453 use rustc_hash::FxHashMap; 452 use rustc_hash::FxHashMap;
454 use syntax::{TextRange, TextSize}; 453 use syntax::{TextRange, TextSize};
455 454
456 use crate::{ 455 use crate::{
457 diagnostics::{validate_body, validate_module_item}, 456 diagnostics::{validate_body, validate_module_item},
457 diagnostics_sink::{Diagnostic, DiagnosticSinkBuilder},
458 test_db::TestDB, 458 test_db::TestDB,
459 }; 459 };
460 460
diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs
index 075dc4131..ef982cbcd 100644
--- a/crates/hir_ty/src/diagnostics/decl_check.rs
+++ b/crates/hir_ty/src/diagnostics/decl_check.rs
@@ -19,10 +19,7 @@ use hir_def::{
19 src::HasSource, 19 src::HasSource,
20 AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId, 20 AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId,
21}; 21};
22use hir_expand::{ 22use hir_expand::name::{AsName, Name};
23 diagnostics::DiagnosticSink,
24 name::{AsName, Name},
25};
26use stdx::{always, never}; 23use stdx::{always, never};
27use syntax::{ 24use syntax::{
28 ast::{self, NameOwner}, 25 ast::{self, NameOwner},
@@ -32,6 +29,7 @@ use syntax::{
32use crate::{ 29use crate::{
33 db::HirDatabase, 30 db::HirDatabase,
34 diagnostics::{decl_check::case_conv::*, CaseType, IdentType, IncorrectCase}, 31 diagnostics::{decl_check::case_conv::*, CaseType, IdentType, IncorrectCase},
32 diagnostics_sink::DiagnosticSink,
35}; 33};
36 34
37mod allow { 35mod allow {
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index d1f113e7f..86f82e3fa 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -5,7 +5,7 @@
5use std::sync::Arc; 5use std::sync::Arc;
6 6
7use hir_def::{expr::Statement, path::path, resolver::HasResolver, AssocItemId, DefWithBodyId}; 7use hir_def::{expr::Statement, path::path, resolver::HasResolver, AssocItemId, DefWithBodyId};
8use hir_expand::{diagnostics::DiagnosticSink, name}; 8use hir_expand::name;
9use rustc_hash::FxHashSet; 9use rustc_hash::FxHashSet;
10use syntax::{ast, AstPtr}; 10use syntax::{ast, AstPtr};
11 11
@@ -16,6 +16,7 @@ use crate::{
16 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, 16 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
17 MissingPatFields, RemoveThisSemicolon, 17 MissingPatFields, RemoveThisSemicolon,
18 }, 18 },
19 diagnostics_sink::DiagnosticSink,
19 AdtId, InferenceResult, Interner, TyExt, TyKind, 20 AdtId, InferenceResult, Interner, TyExt, TyKind,
20}; 21};
21 22
diff --git a/crates/hir_ty/src/diagnostics/unsafe_check.rs b/crates/hir_ty/src/diagnostics/unsafe_check.rs
index 5d13bddea..c3c483425 100644
--- a/crates/hir_ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs
@@ -9,10 +9,10 @@ use hir_def::{
9 resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, 9 resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
10 DefWithBodyId, 10 DefWithBodyId,
11}; 11};
12use hir_expand::diagnostics::DiagnosticSink;
13 12
14use crate::{ 13use crate::{
15 db::HirDatabase, diagnostics::MissingUnsafe, InferenceResult, Interner, TyExt, TyKind, 14 db::HirDatabase, diagnostics::MissingUnsafe, diagnostics_sink::DiagnosticSink, InferenceResult,
15 Interner, TyExt, TyKind,
16}; 16};
17 17
18pub(super) struct UnsafeValidator<'a, 'b: 'a> { 18pub(super) struct UnsafeValidator<'a, 'b: 'a> {
diff --git a/crates/hir_expand/src/diagnostics.rs b/crates/hir_ty/src/diagnostics_sink.rs
index bf0b85ce9..084fa8b06 100644
--- a/crates/hir_expand/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics_sink.rs
@@ -16,10 +16,9 @@
16 16
17use std::{any::Any, fmt}; 17use std::{any::Any, fmt};
18 18
19use hir_expand::InFile;
19use syntax::SyntaxNodePtr; 20use syntax::SyntaxNodePtr;
20 21
21use crate::InFile;
22
23#[derive(Copy, Clone, Debug, PartialEq)] 22#[derive(Copy, Clone, Debug, PartialEq)]
24pub struct DiagnosticCode(pub &'static str); 23pub struct DiagnosticCode(pub &'static str);
25 24
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index 637bbc634..44f843bf3 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -778,8 +778,10 @@ fn write_bounds_like_dyn_trait(
778 } 778 }
779 WhereClause::AliasEq(alias_eq) if is_fn_trait => { 779 WhereClause::AliasEq(alias_eq) if is_fn_trait => {
780 is_fn_trait = false; 780 is_fn_trait = false;
781 write!(f, " -> ")?; 781 if !alias_eq.ty.is_unit() {
782 alias_eq.ty.hir_fmt(f)?; 782 write!(f, " -> ")?;
783 alias_eq.ty.hir_fmt(f)?;
784 }
783 } 785 }
784 WhereClause::AliasEq(AliasEq { ty, alias }) => { 786 WhereClause::AliasEq(AliasEq { ty, alias }) => {
785 // in types in actual Rust, these will always come 787 // in types in actual Rust, these will always come
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index db3c937ff..8cefd80f3 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -28,13 +28,14 @@ use hir_def::{
28 AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, Lookup, 28 AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, Lookup,
29 TraitId, TypeAliasId, VariantId, 29 TraitId, TypeAliasId, VariantId,
30}; 30};
31use hir_expand::{diagnostics::DiagnosticSink, name::name}; 31use hir_expand::name::name;
32use la_arena::ArenaMap; 32use la_arena::ArenaMap;
33use rustc_hash::FxHashMap; 33use rustc_hash::FxHashMap;
34use stdx::impl_from; 34use stdx::impl_from;
35use syntax::SmolStr; 35use syntax::SmolStr;
36 36
37use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty}; 37use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty};
38use crate::diagnostics_sink::DiagnosticSink;
38use crate::{ 39use crate::{
39 db::HirDatabase, fold_tys, infer::diagnostics::InferenceDiagnostic, 40 db::HirDatabase, fold_tys, infer::diagnostics::InferenceDiagnostic,
40 lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Goal, Interner, Substitution, 41 lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Goal, Interner, Substitution,
@@ -578,9 +579,14 @@ impl<'a> InferenceContext<'a> {
578 } 579 }
579 580
580 fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> { 581 fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> {
582 // FIXME resolve via lang_item once try v2 is stable
581 let path = path![core::ops::Try]; 583 let path = path![core::ops::Try];
582 let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; 584 let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
583 self.db.trait_data(trait_).associated_type_by_name(&name![Ok]) 585 let trait_data = self.db.trait_data(trait_);
586 trait_data
587 // FIXME remove once try v2 is stable
588 .associated_type_by_name(&name![Ok])
589 .or_else(|| trait_data.associated_type_by_name(&name![Output]))
584 } 590 }
585 591
586 fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> { 592 fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> {
@@ -793,11 +799,11 @@ impl std::ops::BitOrAssign for Diverges {
793 799
794mod diagnostics { 800mod diagnostics {
795 use hir_def::{expr::ExprId, DefWithBodyId}; 801 use hir_def::{expr::ExprId, DefWithBodyId};
796 use hir_expand::diagnostics::DiagnosticSink;
797 802
798 use crate::{ 803 use crate::{
799 db::HirDatabase, 804 db::HirDatabase,
800 diagnostics::{BreakOutsideOfLoop, NoSuchField}, 805 diagnostics::{BreakOutsideOfLoop, NoSuchField},
806 diagnostics_sink::DiagnosticSink,
801 }; 807 };
802 808
803 #[derive(Debug, PartialEq, Eq, Clone)] 809 #[derive(Debug, PartialEq, Eq, Clone)]
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index c0cbe19c1..97507305c 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -804,7 +804,7 @@ impl<'a> InferenceContext<'a> {
804 None => self.table.new_float_var(), 804 None => self.table.new_float_var(),
805 }, 805 },
806 }, 806 },
807 Expr::MacroStmts { tail } => self.infer_expr(*tail, expected), 807 Expr::MacroStmts { tail } => self.infer_expr_inner(*tail, expected),
808 }; 808 };
809 // use a new type variable if we got unknown here 809 // use a new type variable if we got unknown here
810 let ty = self.insert_type_vars_shallow(ty); 810 let ty = self.insert_type_vars_shallow(ty);
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index ef021978a..50e0d6333 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -21,6 +21,7 @@ mod utils;
21mod walk; 21mod walk;
22pub mod db; 22pub mod db;
23pub mod diagnostics; 23pub mod diagnostics;
24pub mod diagnostics_sink;
24pub mod display; 25pub mod display;
25pub mod method_resolution; 26pub mod method_resolution;
26pub mod primitive; 27pub mod primitive;
diff --git a/crates/hir_ty/src/tests/coercion.rs b/crates/hir_ty/src/tests/coercion.rs
index bb568ea37..6dac7e103 100644
--- a/crates/hir_ty/src/tests/coercion.rs
+++ b/crates/hir_ty/src/tests/coercion.rs
@@ -832,11 +832,9 @@ fn coerce_unsize_super_trait_cycle() {
832 ); 832 );
833} 833}
834 834
835#[ignore]
836#[test] 835#[test]
837fn coerce_unsize_generic() { 836fn coerce_unsize_generic() {
838 // FIXME: Implement this 837 // FIXME: fix the type mismatches here
839 // https://doc.rust-lang.org/reference/type-coercions.html#unsized-coercions
840 check_infer_with_mismatches( 838 check_infer_with_mismatches(
841 r#" 839 r#"
842 #[lang = "unsize"] 840 #[lang = "unsize"]
@@ -854,8 +852,58 @@ fn coerce_unsize_generic() {
854 let _: &Bar<[usize]> = &Bar(Foo { t: [1, 2, 3] }); 852 let _: &Bar<[usize]> = &Bar(Foo { t: [1, 2, 3] });
855 } 853 }
856 "#, 854 "#,
857 expect![[r" 855 expect![[r#"
858 "]], 856 209..317 '{ ... }); }': ()
857 219..220 '_': &Foo<[usize]>
858 238..259 '&Foo {..., 3] }': &Foo<[usize]>
859 239..259 'Foo { ..., 3] }': Foo<[usize]>
860 248..257 '[1, 2, 3]': [usize; 3]
861 249..250 '1': usize
862 252..253 '2': usize
863 255..256 '3': usize
864 269..270 '_': &Bar<[usize]>
865 288..314 '&Bar(F... 3] })': &Bar<[i32; 3]>
866 289..292 'Bar': Bar<[i32; 3]>(Foo<[i32; 3]>) -> Bar<[i32; 3]>
867 289..314 'Bar(Fo... 3] })': Bar<[i32; 3]>
868 293..313 'Foo { ..., 3] }': Foo<[i32; 3]>
869 302..311 '[1, 2, 3]': [i32; 3]
870 303..304 '1': i32
871 306..307 '2': i32
872 309..310 '3': i32
873 248..257: expected [usize], got [usize; 3]
874 288..314: expected &Bar<[usize]>, got &Bar<[i32; 3]>
875 "#]],
876 );
877}
878
879#[test]
880fn coerce_unsize_apit() {
881 // FIXME: #8984
882 check_infer_with_mismatches(
883 r#"
884#[lang = "sized"]
885pub trait Sized {}
886#[lang = "unsize"]
887pub trait Unsize<T> {}
888#[lang = "coerce_unsized"]
889pub trait CoerceUnsized<T> {}
890
891impl<T: Unsize<U>, U> CoerceUnsized<&U> for &T {}
892
893trait Foo {}
894
895fn test(f: impl Foo) {
896 let _: &dyn Foo = &f;
897}
898 "#,
899 expect![[r#"
900 210..211 'f': impl Foo
901 223..252 '{ ... &f; }': ()
902 233..234 '_': &dyn Foo
903 247..249 '&f': &impl Foo
904 248..249 'f': impl Foo
905 247..249: expected &dyn Foo, got &impl Foo
906 "#]],
859 ); 907 );
860} 908}
861 909
@@ -912,3 +960,46 @@ fn test() -> i32 {
912 "#, 960 "#,
913 ) 961 )
914} 962}
963
964#[test]
965fn panic_macro() {
966 check_infer_with_mismatches(
967 r#"
968mod panic {
969 #[macro_export]
970 pub macro panic_2015 {
971 () => (
972 $crate::panicking::panic()
973 ),
974 }
975}
976
977mod panicking {
978 pub fn panic() -> ! { loop {} }
979}
980
981#[rustc_builtin_macro = "core_panic"]
982macro_rules! panic {
983 // Expands to either `$crate::panic::panic_2015` or `$crate::panic::panic_2021`
984 // depending on the edition of the caller.
985 ($($arg:tt)*) => {
986 /* compiler built-in */
987 };
988}
989
990fn main() {
991 panic!()
992}
993 "#,
994 expect![[r#"
995 174..185 '{ loop {} }': !
996 176..183 'loop {}': !
997 181..183 '{}': ()
998 !0..24 '$crate...:panic': fn panic() -> !
999 !0..26 '$crate...anic()': !
1000 !0..26 '$crate...anic()': !
1001 !0..28 '$crate...015!()': !
1002 454..470 '{ ...c!() }': ()
1003 "#]],
1004 );
1005}
diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs
index a5a2df54c..6ad96bfe3 100644
--- a/crates/hir_ty/src/tests/traits.rs
+++ b/crates/hir_ty/src/tests/traits.rs
@@ -161,6 +161,43 @@ mod result {
161} 161}
162 162
163#[test] 163#[test]
164fn infer_tryv2() {
165 check_types(
166 r#"
167//- /main.rs crate:main deps:core
168fn test() {
169 let r: Result<i32, u64> = Result::Ok(1);
170 let v = r?;
171 v;
172} //^ i32
173
174//- /core.rs crate:core
175#[prelude_import] use ops::*;
176mod ops {
177 trait Try {
178 type Output;
179 type Residual;
180 }
181}
182
183#[prelude_import] use result::*;
184mod result {
185 enum Infallible {}
186 enum Result<O, E> {
187 Ok(O),
188 Err(E)
189 }
190
191 impl<O, E> crate::ops::Try for Result<O, E> {
192 type Output = O;
193 type Error = Result<Infallible, E>;
194 }
195}
196"#,
197 );
198}
199
200#[test]
164fn infer_for_loop() { 201fn infer_for_loop() {
165 check_types( 202 check_types(
166 r#" 203 r#"
@@ -3041,7 +3078,7 @@ fn infer_fn_trait_arg() {
3041 3078
3042#[test] 3079#[test]
3043fn infer_box_fn_arg() { 3080fn infer_box_fn_arg() {
3044 // The type mismatch is a bug 3081 // The type mismatch is because we don't define Unsize and CoerceUnsized
3045 check_infer_with_mismatches( 3082 check_infer_with_mismatches(
3046 r#" 3083 r#"
3047//- /lib.rs deps:std 3084//- /lib.rs deps:std
@@ -3101,7 +3138,7 @@ fn foo() {
3101 555..557 'ps': {unknown} 3138 555..557 'ps': {unknown}
3102 559..561 '{}': () 3139 559..561 '{}': ()
3103 568..569 'f': Box<dyn FnOnce(&Option<i32>)> 3140 568..569 'f': Box<dyn FnOnce(&Option<i32>)>
3104 568..573 'f(&s)': FnOnce::Output<dyn FnOnce(&Option<i32>), (&Option<i32>,)> 3141 568..573 'f(&s)': ()
3105 570..572 '&s': &Option<i32> 3142 570..572 '&s': &Option<i32>
3106 571..572 's': Option<i32> 3143 571..572 's': Option<i32>
3107 549..562: expected Box<dyn FnOnce(&Option<i32>)>, got Box<|{unknown}| -> ()> 3144 549..562: expected Box<dyn FnOnce(&Option<i32>)>, got Box<|{unknown}| -> ()>
@@ -3571,3 +3608,55 @@ fn main() {
3571 "#]], 3608 "#]],
3572 ) 3609 )
3573} 3610}
3611
3612#[test]
3613fn fn_returning_unit() {
3614 check_infer_with_mismatches(
3615 r#"
3616#[lang = "fn_once"]
3617trait FnOnce<Args> {
3618 type Output;
3619}
3620
3621fn test<F: FnOnce()>(f: F) {
3622 let _: () = f();
3623}"#,
3624 expect![[r#"
3625 82..83 'f': F
3626 88..112 '{ ...f(); }': ()
3627 98..99 '_': ()
3628 106..107 'f': F
3629 106..109 'f()': ()
3630 "#]],
3631 );
3632}
3633
3634#[test]
3635fn trait_in_scope_of_trait_impl() {
3636 check_infer(
3637 r#"
3638mod foo {
3639 pub trait Foo {
3640 fn foo(self);
3641 fn bar(self) -> usize { 0 }
3642 }
3643}
3644impl foo::Foo for u32 {
3645 fn foo(self) {
3646 let _x = self.bar();
3647 }
3648}
3649 "#,
3650 expect![[r#"
3651 45..49 'self': Self
3652 67..71 'self': Self
3653 82..87 '{ 0 }': usize
3654 84..85 '0': usize
3655 131..135 'self': u32
3656 137..173 '{ ... }': ()
3657 151..153 '_x': usize
3658 156..160 'self': u32
3659 156..166 'self.bar()': usize
3660 "#]],
3661 );
3662}
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
index 5ebe7fd0e..b0c4ed60a 100644
--- a/crates/ide/src/annotations.rs
+++ b/crates/ide/src/annotations.rs
@@ -5,7 +5,7 @@ use ide_db::{
5 helpers::visit_file_defs, 5 helpers::visit_file_defs,
6 RootDatabase, 6 RootDatabase,
7}; 7};
8use syntax::{ast::NameOwner, AstNode, TextRange, TextSize}; 8use syntax::{ast::NameOwner, AstNode, TextRange};
9 9
10use crate::{ 10use crate::{
11 fn_references::find_all_methods, 11 fn_references::find_all_methods,
@@ -80,26 +80,26 @@ pub(crate) fn annotations(
80 80
81 visit_file_defs(&Semantics::new(db), file_id, &mut |def| match def { 81 visit_file_defs(&Semantics::new(db), file_id, &mut |def| match def {
82 Either::Left(def) => { 82 Either::Left(def) => {
83 let node = match def { 83 let range = match def {
84 hir::ModuleDef::Const(konst) => { 84 hir::ModuleDef::Const(konst) => {
85 konst.source(db).and_then(|node| range_and_position_of(&node, file_id)) 85 konst.source(db).and_then(|node| name_range(&node, file_id))
86 } 86 }
87 hir::ModuleDef::Trait(trait_) => { 87 hir::ModuleDef::Trait(trait_) => {
88 trait_.source(db).and_then(|node| range_and_position_of(&node, file_id)) 88 trait_.source(db).and_then(|node| name_range(&node, file_id))
89 } 89 }
90 hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => { 90 hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
91 strukt.source(db).and_then(|node| range_and_position_of(&node, file_id)) 91 strukt.source(db).and_then(|node| name_range(&node, file_id))
92 } 92 }
93 hir::ModuleDef::Adt(hir::Adt::Enum(enum_)) => { 93 hir::ModuleDef::Adt(hir::Adt::Enum(enum_)) => {
94 enum_.source(db).and_then(|node| range_and_position_of(&node, file_id)) 94 enum_.source(db).and_then(|node| name_range(&node, file_id))
95 } 95 }
96 hir::ModuleDef::Adt(hir::Adt::Union(union)) => { 96 hir::ModuleDef::Adt(hir::Adt::Union(union)) => {
97 union.source(db).and_then(|node| range_and_position_of(&node, file_id)) 97 union.source(db).and_then(|node| name_range(&node, file_id))
98 } 98 }
99 _ => None, 99 _ => None,
100 }; 100 };
101 let (offset, range) = match node { 101 let (range, offset) = match range {
102 Some(node) => node, 102 Some(range) => (range, range.start()),
103 None => return, 103 None => return,
104 }; 104 };
105 105
@@ -122,18 +122,12 @@ pub(crate) fn annotations(
122 }); 122 });
123 } 123 }
124 124
125 fn range_and_position_of<T: NameOwner>( 125 fn name_range<T: NameOwner>(node: &InFile<T>, file_id: FileId) -> Option<TextRange> {
126 node: &InFile<T>, 126 if node.file_id == file_id.into() {
127 file_id: FileId, 127 node.value.name().map(|it| it.syntax().text_range())
128 ) -> Option<(TextSize, TextRange)> { 128 } else {
129 if node.file_id != file_id.into() {
130 // Node is outside the file we are adding annotations to (e.g. macros). 129 // Node is outside the file we are adding annotations to (e.g. macros).
131 None 130 None
132 } else {
133 Some((
134 node.value.name()?.syntax().text_range().start(),
135 node.value.syntax().text_range(),
136 ))
137 } 131 }
138 } 132 }
139 } 133 }
@@ -141,13 +135,15 @@ pub(crate) fn annotations(
141 }); 135 });
142 136
143 if config.annotate_method_references { 137 if config.annotate_method_references {
144 annotations.extend(find_all_methods(db, file_id).into_iter().map(|method| Annotation { 138 annotations.extend(find_all_methods(db, file_id).into_iter().map(
145 range: method.range, 139 |FileRange { file_id, range }| Annotation {
146 kind: AnnotationKind::HasReferences { 140 range,
147 position: FilePosition { file_id, offset: method.range.start() }, 141 kind: AnnotationKind::HasReferences {
148 data: None, 142 position: FilePosition { file_id, offset: range.start() },
143 data: None,
144 },
149 }, 145 },
150 })); 146 ));
151 } 147 }
152 148
153 annotations 149 annotations
@@ -266,7 +262,7 @@ fn main() {
266 }, 262 },
267 }, 263 },
268 Annotation { 264 Annotation {
269 range: 0..22, 265 range: 6..10,
270 kind: HasReferences { 266 kind: HasReferences {
271 position: FilePosition { 267 position: FilePosition {
272 file_id: FileId( 268 file_id: FileId(
@@ -287,7 +283,7 @@ fn main() {
287 }, 283 },
288 }, 284 },
289 Annotation { 285 Annotation {
290 range: 24..48, 286 range: 30..36,
291 kind: HasReferences { 287 kind: HasReferences {
292 position: FilePosition { 288 position: FilePosition {
293 file_id: FileId( 289 file_id: FileId(
@@ -370,7 +366,7 @@ fn main() {
370 }, 366 },
371 }, 367 },
372 Annotation { 368 Annotation {
373 range: 0..12, 369 range: 7..11,
374 kind: HasImpls { 370 kind: HasImpls {
375 position: FilePosition { 371 position: FilePosition {
376 file_id: FileId( 372 file_id: FileId(
@@ -384,7 +380,7 @@ fn main() {
384 }, 380 },
385 }, 381 },
386 Annotation { 382 Annotation {
387 range: 0..12, 383 range: 7..11,
388 kind: HasReferences { 384 kind: HasReferences {
389 position: FilePosition { 385 position: FilePosition {
390 file_id: FileId( 386 file_id: FileId(
@@ -478,7 +474,7 @@ fn main() {
478 }, 474 },
479 }, 475 },
480 Annotation { 476 Annotation {
481 range: 0..12, 477 range: 7..11,
482 kind: HasImpls { 478 kind: HasImpls {
483 position: FilePosition { 479 position: FilePosition {
484 file_id: FileId( 480 file_id: FileId(
@@ -502,7 +498,7 @@ fn main() {
502 }, 498 },
503 }, 499 },
504 Annotation { 500 Annotation {
505 range: 0..12, 501 range: 7..11,
506 kind: HasReferences { 502 kind: HasReferences {
507 position: FilePosition { 503 position: FilePosition {
508 file_id: FileId( 504 file_id: FileId(
@@ -529,7 +525,7 @@ fn main() {
529 }, 525 },
530 }, 526 },
531 Annotation { 527 Annotation {
532 range: 14..34, 528 range: 20..31,
533 kind: HasImpls { 529 kind: HasImpls {
534 position: FilePosition { 530 position: FilePosition {
535 file_id: FileId( 531 file_id: FileId(
@@ -553,7 +549,7 @@ fn main() {
553 }, 549 },
554 }, 550 },
555 Annotation { 551 Annotation {
556 range: 14..34, 552 range: 20..31,
557 kind: HasReferences { 553 kind: HasReferences {
558 position: FilePosition { 554 position: FilePosition {
559 file_id: FileId( 555 file_id: FileId(
@@ -712,7 +708,7 @@ fn main() {
712 }, 708 },
713 }, 709 },
714 Annotation { 710 Annotation {
715 range: 0..12, 711 range: 7..11,
716 kind: HasImpls { 712 kind: HasImpls {
717 position: FilePosition { 713 position: FilePosition {
718 file_id: FileId( 714 file_id: FileId(
@@ -736,7 +732,7 @@ fn main() {
736 }, 732 },
737 }, 733 },
738 Annotation { 734 Annotation {
739 range: 0..12, 735 range: 7..11,
740 kind: HasReferences { 736 kind: HasReferences {
741 position: FilePosition { 737 position: FilePosition {
742 file_id: FileId( 738 file_id: FileId(
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 27d347dbd..dcac7c76d 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -299,10 +299,10 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
299 299
300#[cfg(test)] 300#[cfg(test)]
301mod tests { 301mod tests {
302 use expect_test::{expect, Expect}; 302 use expect_test::Expect;
303 use ide_assists::AssistResolveStrategy; 303 use ide_assists::AssistResolveStrategy;
304 use stdx::trim_indent; 304 use stdx::trim_indent;
305 use test_utils::assert_eq_text; 305 use test_utils::{assert_eq_text, extract_annotations};
306 306
307 use crate::{fixture, DiagnosticsConfig}; 307 use crate::{fixture, DiagnosticsConfig};
308 308
@@ -396,26 +396,51 @@ mod tests {
396 expect.assert_debug_eq(&diagnostics) 396 expect.assert_debug_eq(&diagnostics)
397 } 397 }
398 398
399 pub(crate) fn check_diagnostics(ra_fixture: &str) {
400 let (analysis, file_id) = fixture::file(ra_fixture);
401 let diagnostics = analysis
402 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
403 .unwrap();
404
405 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
406 let actual = diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>();
407 assert_eq!(expected, actual);
408 }
409
399 #[test] 410 #[test]
400 fn test_unresolved_macro_range() { 411 fn test_unresolved_macro_range() {
401 check_expect( 412 check_diagnostics(
402 r#"foo::bar!(92);"#, 413 r#"
403 expect![[r#" 414foo::bar!(92);
404 [ 415 //^^^ unresolved macro `foo::bar!`
405 Diagnostic { 416"#,
406 message: "unresolved macro `foo::bar!`", 417 );
407 range: 5..8, 418 }
408 severity: Error, 419
409 fixes: None, 420 #[test]
410 unused: false, 421 fn unresolved_import_in_use_tree() {
411 code: Some( 422 // Only the relevant part of a nested `use` item should be highlighted.
412 DiagnosticCode( 423 check_diagnostics(
413 "unresolved-macro-call", 424 r#"
414 ), 425use does_exist::{Exists, DoesntExist};
415 ), 426 //^^^^^^^^^^^ unresolved import
416 }, 427
417 ] 428use {does_not_exist::*, does_exist};
418 "#]], 429 //^^^^^^^^^^^^^^^^^ unresolved import
430
431use does_not_exist::{
432 a,
433 //^ unresolved import
434 b,
435 //^ unresolved import
436 c,
437 //^ unresolved import
438};
439
440mod does_exist {
441 pub struct Exists;
442}
443"#,
419 ); 444 );
420 } 445 }
421 446
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 05130a237..43356a94e 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -1,4 +1,4 @@
1use hir::{Impl, Semantics}; 1use hir::{AsAssocItem, Impl, Semantics};
2use ide_db::{ 2use ide_db::{
3 defs::{Definition, NameClass, NameRefClass}, 3 defs::{Definition, NameClass, NameRefClass},
4 RootDatabase, 4 RootDatabase,
@@ -36,6 +36,7 @@ pub(crate) fn goto_implementation(
36 } 36 }
37 ast::NameLike::Lifetime(_) => None, 37 ast::NameLike::Lifetime(_) => None,
38 }?; 38 }?;
39
39 let def = match def { 40 let def = match def {
40 Definition::ModuleDef(def) => def, 41 Definition::ModuleDef(def) => def,
41 _ => return None, 42 _ => return None,
@@ -48,6 +49,18 @@ pub(crate) fn goto_implementation(
48 let module = sema.to_module_def(position.file_id)?; 49 let module = sema.to_module_def(position.file_id)?;
49 impls_for_ty(&sema, builtin.ty(sema.db, module)) 50 impls_for_ty(&sema, builtin.ty(sema.db, module))
50 } 51 }
52 hir::ModuleDef::Function(f) => {
53 let assoc = f.as_assoc_item(sema.db)?;
54 let name = assoc.name(sema.db)?;
55 let trait_ = assoc.containing_trait(sema.db)?;
56 impls_for_trait_item(&sema, trait_, name)
57 }
58 hir::ModuleDef::Const(c) => {
59 let assoc = c.as_assoc_item(sema.db)?;
60 let name = assoc.name(sema.db)?;
61 let trait_ = assoc.containing_trait(sema.db)?;
62 impls_for_trait_item(&sema, trait_, name)
63 }
51 _ => return None, 64 _ => return None,
52 }; 65 };
53 Some(RangeInfo { range: node.syntax().text_range(), info: navs }) 66 Some(RangeInfo { range: node.syntax().text_range(), info: navs })
@@ -64,6 +77,23 @@ fn impls_for_trait(sema: &Semantics<RootDatabase>, trait_: hir::Trait) -> Vec<Na
64 .collect() 77 .collect()
65} 78}
66 79
80fn impls_for_trait_item(
81 sema: &Semantics<RootDatabase>,
82 trait_: hir::Trait,
83 fun_name: hir::Name,
84) -> Vec<NavigationTarget> {
85 Impl::all_for_trait(sema.db, trait_)
86 .into_iter()
87 .filter_map(|imp| {
88 let item = imp.items(sema.db).iter().find_map(|itm| {
89 let itm_name = itm.name(sema.db)?;
90 (itm_name == fun_name).then(|| itm.clone())
91 })?;
92 item.try_to_nav(sema.db)
93 })
94 .collect()
95}
96
67#[cfg(test)] 97#[cfg(test)]
68mod tests { 98mod tests {
69 use ide_db::base_db::FileRange; 99 use ide_db::base_db::FileRange;
@@ -262,4 +292,42 @@ impl bool {}
262"#, 292"#,
263 ); 293 );
264 } 294 }
295
296 #[test]
297 fn goto_implementation_trait_functions() {
298 check(
299 r#"
300trait Tr {
301 fn f$0();
302}
303
304struct S;
305
306impl Tr for S {
307 fn f() {
308 //^
309 println!("Hello, world!");
310 }
311}
312"#,
313 );
314 }
315
316 #[test]
317 fn goto_implementation_trait_assoc_const() {
318 check(
319 r#"
320trait Tr {
321 const C$0: usize;
322}
323
324struct S;
325
326impl Tr for S {
327 const C: usize = 4;
328 //^
329}
330"#,
331 );
332 }
265} 333}
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
index f3284bb96..004d9cb68 100644
--- a/crates/ide/src/goto_type_definition.rs
+++ b/crates/ide/src/goto_type_definition.rs
@@ -1,3 +1,4 @@
1use ide_db::base_db::Upcast;
1use ide_db::RootDatabase; 2use ide_db::RootDatabase;
2use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; 3use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
3 4
@@ -31,6 +32,7 @@ pub(crate) fn goto_type_definition(
31 ast::Pat(it) => sema.type_of_pat(&it)?, 32 ast::Pat(it) => sema.type_of_pat(&it)?,
32 ast::SelfParam(it) => sema.type_of_self(&it)?, 33 ast::SelfParam(it) => sema.type_of_self(&it)?,
33 ast::Type(it) => sema.resolve_type(&it)?, 34 ast::Type(it) => sema.resolve_type(&it)?,
35 ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?,
34 _ => return None, 36 _ => return None,
35 } 37 }
36 }; 38 };
@@ -161,4 +163,34 @@ impl Foo$0 {}
161"#, 163"#,
162 ) 164 )
163 } 165 }
166
167 #[test]
168 fn goto_def_for_struct_field() {
169 check(
170 r#"
171struct Bar;
172 //^^^
173
174struct Foo {
175 bar$0: Bar,
176}
177"#,
178 );
179 }
180
181 #[test]
182 fn goto_def_for_enum_struct_field() {
183 check(
184 r#"
185struct Bar;
186 //^^^
187
188enum Foo {
189 Bar {
190 bar$0: Bar
191 },
192}
193"#,
194 );
195 }
164} 196}
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index cf1a8bad7..7f7f4d38a 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -49,6 +49,30 @@ pub struct HlRange {
49// The general rule is that a reference to an entity gets colored the same way as the entity itself. 49// The general rule is that a reference to an entity gets colored the same way as the entity itself.
50// We also give special modifier for `mut` and `&mut` local variables. 50// We also give special modifier for `mut` and `&mut` local variables.
51// 51//
52// .Token Modifiers
53//
54// Token modifiers allow to style some elements in the source code more precisely.
55//
56// Rust-analyzer currently emits the following token modifiers:
57//
58// [horizontal]
59// associated:: Emitted for associated items.
60// async:: Emitted for async functions and the `async` and `await` keywords.
61// attribute:: Emitted for tokens inside attributes.
62// callable:: Emitted for locals whose types implements one of the `Fn*` traits.
63// consuming:: Emitted for locals that are being consumed when use in a function call.
64// controlFlow:: Emitted for control-flow related tokens, this includes the `?` operator.
65// declaration:: Emitted for names of definitions, like `foo` in `fn foo() {}`.
66// documentation:: Emitted for documentation comments.
67// injected:: Emitted for doc-string injected highlighting like rust source blocks in documentation.
68// intraDocLink:: Emitted for intra doc links in doc-strings.
69// library:: Emitted for items that are defined outside of the current crate.
70// mutable:: Emitted for mutable locals and statics.
71// static:: Emitted for "static" functions, also known as functions that do not take a `self` param.
72// trait:: Emitted for associated trait items.
73// unsafe:: Emitted for unsafe operations, like unsafe function calls, as well as the `unsafe` token.
74//
75//
52// image::https://user-images.githubusercontent.com/48062697/113164457-06cfb980-9239-11eb-819b-0f93e646acf8.png[] 76// image::https://user-images.githubusercontent.com/48062697/113164457-06cfb980-9239-11eb-819b-0f93e646acf8.png[]
53// image::https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png[] 77// image::https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png[]
54pub(crate) fn highlight( 78pub(crate) fn highlight(
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index b4a3d39c9..9503c936d 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -71,7 +71,7 @@ pub(super) fn element(
71 } 71 }
72 NAME_REF => { 72 NAME_REF => {
73 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); 73 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
74 highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { 74 highlight_func_by_name_ref(sema, krate, &name_ref).unwrap_or_else(|| {
75 let is_self = name_ref.self_token().is_some(); 75 let is_self = name_ref.self_token().is_some();
76 let h = match NameRefClass::classify(sema, &name_ref) { 76 let h = match NameRefClass::classify(sema, &name_ref) {
77 Some(name_kind) => match name_kind { 77 Some(name_kind) => match name_kind {
@@ -108,7 +108,7 @@ pub(super) fn element(
108 NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(), 108 NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(),
109 }, 109 },
110 None if syntactic_name_ref_highlighting => { 110 None if syntactic_name_ref_highlighting => {
111 highlight_name_ref_by_syntax(name_ref, sema) 111 highlight_name_ref_by_syntax(name_ref, sema, krate)
112 } 112 }
113 None => HlTag::UnresolvedReference.into(), 113 None => HlTag::UnresolvedReference.into(),
114 }; 114 };
@@ -434,19 +434,23 @@ fn highlight_def(db: &RootDatabase, krate: Option<hir::Crate>, def: Definition)
434 434
435fn highlight_func_by_name_ref( 435fn highlight_func_by_name_ref(
436 sema: &Semantics<RootDatabase>, 436 sema: &Semantics<RootDatabase>,
437 krate: Option<hir::Crate>,
437 name_ref: &ast::NameRef, 438 name_ref: &ast::NameRef,
438) -> Option<Highlight> { 439) -> Option<Highlight> {
439 let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?; 440 let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
440 highlight_method_call(sema, &mc) 441 highlight_method_call(sema, krate, &mc)
441} 442}
442 443
443fn highlight_method_call( 444fn highlight_method_call(
444 sema: &Semantics<RootDatabase>, 445 sema: &Semantics<RootDatabase>,
446 krate: Option<hir::Crate>,
445 method_call: &ast::MethodCallExpr, 447 method_call: &ast::MethodCallExpr,
446) -> Option<Highlight> { 448) -> Option<Highlight> {
447 let func = sema.resolve_method_call(&method_call)?; 449 let func = sema.resolve_method_call(&method_call)?;
450
448 let mut h = SymbolKind::Function.into(); 451 let mut h = SymbolKind::Function.into();
449 h |= HlMod::Associated; 452 h |= HlMod::Associated;
453
450 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { 454 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
451 h |= HlMod::Unsafe; 455 h |= HlMod::Unsafe;
452 } 456 }
@@ -454,7 +458,10 @@ fn highlight_method_call(
454 h |= HlMod::Async; 458 h |= HlMod::Async;
455 } 459 }
456 if func.as_assoc_item(sema.db).and_then(|it| it.containing_trait(sema.db)).is_some() { 460 if func.as_assoc_item(sema.db).and_then(|it| it.containing_trait(sema.db)).is_some() {
457 h |= HlMod::Trait 461 h |= HlMod::Trait;
462 }
463 if Some(func.module(sema.db).krate()) != krate {
464 h |= HlMod::Library;
458 } 465 }
459 466
460 if let Some(self_param) = func.self_param(sema.db) { 467 if let Some(self_param) = func.self_param(sema.db) {
@@ -503,7 +510,11 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
503 tag.into() 510 tag.into()
504} 511}
505 512
506fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight { 513fn highlight_name_ref_by_syntax(
514 name: ast::NameRef,
515 sema: &Semantics<RootDatabase>,
516 krate: Option<hir::Crate>,
517) -> Highlight {
507 let default = HlTag::UnresolvedReference; 518 let default = HlTag::UnresolvedReference;
508 519
509 let parent = match name.syntax().parent() { 520 let parent = match name.syntax().parent() {
@@ -514,7 +525,7 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
514 match parent.kind() { 525 match parent.kind() {
515 METHOD_CALL_EXPR => { 526 METHOD_CALL_EXPR => {
516 return ast::MethodCallExpr::cast(parent) 527 return ast::MethodCallExpr::cast(parent)
517 .and_then(|it| highlight_method_call(sema, &it)) 528 .and_then(|it| highlight_method_call(sema, krate, &it))
518 .unwrap_or_else(|| SymbolKind::Function.into()); 529 .unwrap_or_else(|| SymbolKind::Function.into());
519 } 530 }
520 FIELD_EXPR => { 531 FIELD_EXPR => {
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index e94f17cd9..9d481deae 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -37,6 +37,8 @@ pub enum HlTag {
37 None, 37 None,
38} 38}
39 39
40// Don't forget to adjust the feature description in crates/ide/src/syntax_highlighting.rs.
41// And make sure to use the lsp strings used when converting to the protocol in crates\rust-analyzer\src\semantic_tokens.rs, not the names of the variants here.
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 42#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
41#[repr(u8)] 43#[repr(u8)]
42pub enum HlMod { 44pub enum HlMod {
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 055d21109..0264e39a3 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -258,7 +258,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
258 258
259 <span class="keyword">let</span> <span class="variable declaration">control_flow</span> <span class="operator">=</span> <span class="module library">foo</span><span class="operator">::</span><span class="function library">identity</span><span class="parenthesis">(</span><span class="module library">foo</span><span class="operator">::</span><span class="enum library">ControlFlow</span><span class="operator">::</span><span class="enum_variant library">Continue</span><span class="parenthesis">)</span><span class="semicolon">;</span> 259 <span class="keyword">let</span> <span class="variable declaration">control_flow</span> <span class="operator">=</span> <span class="module library">foo</span><span class="operator">::</span><span class="function library">identity</span><span class="parenthesis">(</span><span class="module library">foo</span><span class="operator">::</span><span class="enum library">ControlFlow</span><span class="operator">::</span><span class="enum_variant library">Continue</span><span class="parenthesis">)</span><span class="semicolon">;</span>
260 260
261 <span class="keyword control">if</span> <span class="keyword">let</span> <span class="module library">foo</span><span class="operator">::</span><span class="enum library">ControlFlow</span><span class="operator">::</span><span class="enum_variant library">Die</span> <span class="operator">=</span> <span class="variable">control_flow</span> <span class="brace">{</span> 261 <span class="keyword control">if</span> <span class="variable">control_flow</span><span class="operator">.</span><span class="function associated consuming library">should_die</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
262 foo::<span class="macro">die!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> 262 foo::<span class="macro">die!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
263 <span class="brace">}</span> 263 <span class="brace">}</span>
264<span class="brace">}</span> 264<span class="brace">}</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index be4447ebb..662b53481 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -232,7 +232,7 @@ fn use_foo_items() {
232 232
233 let control_flow = foo::identity(foo::ControlFlow::Continue); 233 let control_flow = foo::identity(foo::ControlFlow::Continue);
234 234
235 if let foo::ControlFlow::Die = control_flow { 235 if control_flow.should_die() {
236 foo::die!(); 236 foo::die!();
237 } 237 }
238} 238}
@@ -249,6 +249,12 @@ pub enum ControlFlow {
249 Die, 249 Die,
250} 250}
251 251
252impl ControlFlow {
253 pub fn should_die(self) -> bool {
254 matches!(self, ControlFlow::Die)
255 }
256}
257
252pub fn identity<T>(x: T) -> T { x } 258pub fn identity<T>(x: T) -> T { x }
253 259
254pub mod consts { 260pub mod consts {
diff --git a/crates/ide_completion/src/completions/record.rs b/crates/ide_completion/src/completions/record.rs
index 2f95b8687..40006fb74 100644
--- a/crates/ide_completion/src/completions/record.rs
+++ b/crates/ide_completion/src/completions/record.rs
@@ -108,8 +108,6 @@ fn process(f: S) {
108 check_snippet( 108 check_snippet(
109 test_code, 109 test_code,
110 expect![[r#" 110 expect![[r#"
111 sn pd
112 sn ppd
113 fd ..Default::default() 111 fd ..Default::default()
114 "#]], 112 "#]],
115 ); 113 );
@@ -179,13 +177,7 @@ fn process(f: S) {
179 "#]], 177 "#]],
180 ); 178 );
181 179
182 check_snippet( 180 check_snippet(test_code, expect![[r#""#]]);
183 test_code,
184 expect![[r#"
185 sn pd
186 sn ppd
187 "#]],
188 );
189 } 181 }
190 182
191 #[test] 183 #[test]
diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs
index 7f7830976..14cfb61de 100644
--- a/crates/ide_completion/src/completions/snippet.rs
+++ b/crates/ide_completion/src/completions/snippet.rs
@@ -22,8 +22,10 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte
22 None => return, 22 None => return,
23 }; 23 };
24 24
25 snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); 25 if ctx.can_be_stmt {
26 snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); 26 snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
27 snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
28 }
27} 29}
28 30
29pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { 31pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index b2317618a..4d88932ca 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -1,4 +1,4 @@
1//! Advertizes the capabilities of the LSP Server. 1//! Advertises the capabilities of the LSP Server.
2use std::env; 2use std::env;
3 3
4use lsp_types::{ 4use lsp_types::{
@@ -54,7 +54,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
54 code_action_provider: Some(code_action_capabilities(client_caps)), 54 code_action_provider: Some(code_action_capabilities(client_caps)),
55 code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }), 55 code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
56 document_formatting_provider: Some(OneOf::Left(true)), 56 document_formatting_provider: Some(OneOf::Left(true)),
57 document_range_formatting_provider: None, 57 document_range_formatting_provider: Some(OneOf::Left(true)),
58 document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { 58 document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
59 first_trigger_character: "=".to_string(), 59 first_trigger_character: "=".to_string(),
60 more_trigger_character: Some(vec![".".to_string(), ">".to_string(), "{".to_string()]), 60 more_trigger_character: Some(vec![".".to_string(), ">".to_string(), "{".to_string()]),
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 7c02a507c..7620a2fe1 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -218,6 +218,10 @@ config_data! {
218 /// Advanced option, fully override the command rust-analyzer uses for 218 /// Advanced option, fully override the command rust-analyzer uses for
219 /// formatting. 219 /// formatting.
220 rustfmt_overrideCommand: Option<Vec<String>> = "null", 220 rustfmt_overrideCommand: Option<Vec<String>> = "null",
221 /// Enables the use of rustfmt's unstable range formatting command for the
222 /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
223 /// available on a nightly build.
224 rustfmt_enableRangeFormatting: bool = "false",
221 225
222 /// Workspace symbol search scope. 226 /// Workspace symbol search scope.
223 workspace_symbol_search_scope: WorskpaceSymbolSearchScopeDef = "\"workspace\"", 227 workspace_symbol_search_scope: WorskpaceSymbolSearchScopeDef = "\"workspace\"",
@@ -305,7 +309,7 @@ pub struct NotificationsConfig {
305 309
306#[derive(Debug, Clone)] 310#[derive(Debug, Clone)]
307pub enum RustfmtConfig { 311pub enum RustfmtConfig {
308 Rustfmt { extra_args: Vec<String> }, 312 Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
309 CustomCommand { command: String, args: Vec<String> }, 313 CustomCommand { command: String, args: Vec<String> },
310} 314}
311 315
@@ -584,9 +588,10 @@ impl Config {
584 let command = args.remove(0); 588 let command = args.remove(0);
585 RustfmtConfig::CustomCommand { command, args } 589 RustfmtConfig::CustomCommand { command, args }
586 } 590 }
587 Some(_) | None => { 591 Some(_) | None => RustfmtConfig::Rustfmt {
588 RustfmtConfig::Rustfmt { extra_args: self.data.rustfmt_extraArgs.clone() } 592 extra_args: self.data.rustfmt_extraArgs.clone(),
589 } 593 enable_range_formatting: self.data.rustfmt_enableRangeFormatting,
594 },
590 } 595 }
591 } 596 }
592 pub fn flycheck(&self) -> Option<FlycheckConfig> { 597 pub fn flycheck(&self) -> Option<FlycheckConfig> {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index f48210424..456744603 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -27,7 +27,7 @@ use lsp_types::{
27}; 27};
28use project_model::TargetKind; 28use project_model::TargetKind;
29use serde::{Deserialize, Serialize}; 29use serde::{Deserialize, Serialize};
30use serde_json::to_value; 30use serde_json::{json, to_value};
31use stdx::format_to; 31use stdx::format_to;
32use syntax::{algo, ast, AstNode, TextRange, TextSize}; 32use syntax::{algo, ast, AstNode, TextRange, TextSize};
33 33
@@ -955,104 +955,17 @@ pub(crate) fn handle_formatting(
955 params: DocumentFormattingParams, 955 params: DocumentFormattingParams,
956) -> Result<Option<Vec<lsp_types::TextEdit>>> { 956) -> Result<Option<Vec<lsp_types::TextEdit>>> {
957 let _p = profile::span("handle_formatting"); 957 let _p = profile::span("handle_formatting");
958 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
959 let file = snap.analysis.file_text(file_id)?;
960 let crate_ids = snap.analysis.crate_for(file_id)?;
961
962 let line_index = snap.file_line_index(file_id)?;
963
964 let mut rustfmt = match snap.config.rustfmt() {
965 RustfmtConfig::Rustfmt { extra_args } => {
966 let mut cmd = process::Command::new(toolchain::rustfmt());
967 cmd.args(extra_args);
968 // try to chdir to the file so we can respect `rustfmt.toml`
969 // FIXME: use `rustfmt --config-path` once
970 // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
971 match params.text_document.uri.to_file_path() {
972 Ok(mut path) => {
973 // pop off file name
974 if path.pop() && path.is_dir() {
975 cmd.current_dir(path);
976 }
977 }
978 Err(_) => {
979 log::error!(
980 "Unable to get file path for {}, rustfmt.toml might be ignored",
981 params.text_document.uri
982 );
983 }
984 }
985 if let Some(&crate_id) = crate_ids.first() {
986 // Assume all crates are in the same edition
987 let edition = snap.analysis.crate_edition(crate_id)?;
988 cmd.arg("--edition");
989 cmd.arg(edition.to_string());
990 }
991 cmd
992 }
993 RustfmtConfig::CustomCommand { command, args } => {
994 let mut cmd = process::Command::new(command);
995 cmd.args(args);
996 cmd
997 }
998 };
999 958
1000 let mut rustfmt = 959 run_rustfmt(&snap, params.text_document, None)
1001 rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?; 960}
1002
1003 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
1004
1005 let output = rustfmt.wait_with_output()?;
1006 let captured_stdout = String::from_utf8(output.stdout)?;
1007 let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
1008
1009 if !output.status.success() {
1010 let rustfmt_not_installed =
1011 captured_stderr.contains("not installed") || captured_stderr.contains("not available");
1012
1013 return match output.status.code() {
1014 Some(1) if !rustfmt_not_installed => {
1015 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
1016 // likely cause exiting with 1. Most Language Servers swallow parse errors on
1017 // formatting because otherwise an error is surfaced to the user on top of the
1018 // syntax error diagnostics they're already receiving. This is especially jarring
1019 // if they have format on save enabled.
1020 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
1021 Ok(None)
1022 }
1023 _ => {
1024 // Something else happened - e.g. `rustfmt` is missing or caught a signal
1025 Err(LspError::new(
1026 -32900,
1027 format!(
1028 r#"rustfmt exited with:
1029 Status: {}
1030 stdout: {}
1031 stderr: {}"#,
1032 output.status, captured_stdout, captured_stderr,
1033 ),
1034 )
1035 .into())
1036 }
1037 };
1038 }
1039 961
1040 let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout); 962pub(crate) fn handle_range_formatting(
963 snap: GlobalStateSnapshot,
964 params: lsp_types::DocumentRangeFormattingParams,
965) -> Result<Option<Vec<lsp_types::TextEdit>>> {
966 let _p = profile::span("handle_range_formatting");
1041 967
1042 if line_index.endings != new_line_endings { 968 run_rustfmt(&snap, params.text_document, Some(params.range))
1043 // If line endings are different, send the entire file.
1044 // Diffing would not work here, as the line endings might be the only
1045 // difference.
1046 Ok(Some(to_proto::text_edit_vec(
1047 &line_index,
1048 TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text),
1049 )))
1050 } else if *file == new_text {
1051 // The document is already formatted correctly -- no edits needed.
1052 Ok(None)
1053 } else {
1054 Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
1055 }
1056} 969}
1057 970
1058pub(crate) fn handle_code_action( 971pub(crate) fn handle_code_action(
@@ -1675,6 +1588,140 @@ fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>)
1675 } 1588 }
1676} 1589}
1677 1590
1591fn run_rustfmt(
1592 snap: &GlobalStateSnapshot,
1593 text_document: TextDocumentIdentifier,
1594 range: Option<lsp_types::Range>,
1595) -> Result<Option<Vec<lsp_types::TextEdit>>> {
1596 let file_id = from_proto::file_id(&snap, &text_document.uri)?;
1597 let file = snap.analysis.file_text(file_id)?;
1598 let crate_ids = snap.analysis.crate_for(file_id)?;
1599
1600 let line_index = snap.file_line_index(file_id)?;
1601
1602 let mut rustfmt = match snap.config.rustfmt() {
1603 RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
1604 let mut cmd = process::Command::new(toolchain::rustfmt());
1605 cmd.args(extra_args);
1606 // try to chdir to the file so we can respect `rustfmt.toml`
1607 // FIXME: use `rustfmt --config-path` once
1608 // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
1609 match text_document.uri.to_file_path() {
1610 Ok(mut path) => {
1611 // pop off file name
1612 if path.pop() && path.is_dir() {
1613 cmd.current_dir(path);
1614 }
1615 }
1616 Err(_) => {
1617 log::error!(
1618 "Unable to get file path for {}, rustfmt.toml might be ignored",
1619 text_document.uri
1620 );
1621 }
1622 }
1623 if let Some(&crate_id) = crate_ids.first() {
1624 // Assume all crates are in the same edition
1625 let edition = snap.analysis.crate_edition(crate_id)?;
1626 cmd.arg("--edition");
1627 cmd.arg(edition.to_string());
1628 }
1629
1630 if let Some(range) = range {
1631 if !enable_range_formatting {
1632 return Err(LspError::new(
1633 ErrorCode::InvalidRequest as i32,
1634 String::from(
1635 "rustfmt range formatting is unstable. \
1636 Opt-in by using a nightly build of rustfmt and setting \
1637 `rustfmt.enableRangeFormatting` to true in your LSP configuration",
1638 ),
1639 )
1640 .into());
1641 }
1642
1643 let frange = from_proto::file_range(&snap, text_document.clone(), range)?;
1644 let start_line = line_index.index.line_col(frange.range.start()).line;
1645 let end_line = line_index.index.line_col(frange.range.end()).line;
1646
1647 cmd.arg("--unstable-features");
1648 cmd.arg("--file-lines");
1649 cmd.arg(
1650 json!([{
1651 "file": "stdin",
1652 "range": [start_line, end_line]
1653 }])
1654 .to_string(),
1655 );
1656 }
1657
1658 cmd
1659 }
1660 RustfmtConfig::CustomCommand { command, args } => {
1661 let mut cmd = process::Command::new(command);
1662 cmd.args(args);
1663 cmd
1664 }
1665 };
1666
1667 let mut rustfmt =
1668 rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
1669
1670 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
1671
1672 let output = rustfmt.wait_with_output()?;
1673 let captured_stdout = String::from_utf8(output.stdout)?;
1674 let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
1675
1676 if !output.status.success() {
1677 let rustfmt_not_installed =
1678 captured_stderr.contains("not installed") || captured_stderr.contains("not available");
1679
1680 return match output.status.code() {
1681 Some(1) if !rustfmt_not_installed => {
1682 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
1683 // likely cause exiting with 1. Most Language Servers swallow parse errors on
1684 // formatting because otherwise an error is surfaced to the user on top of the
1685 // syntax error diagnostics they're already receiving. This is especially jarring
1686 // if they have format on save enabled.
1687 log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
1688 Ok(None)
1689 }
1690 _ => {
1691 // Something else happened - e.g. `rustfmt` is missing or caught a signal
1692 Err(LspError::new(
1693 -32900,
1694 format!(
1695 r#"rustfmt exited with:
1696 Status: {}
1697 stdout: {}
1698 stderr: {}"#,
1699 output.status, captured_stdout, captured_stderr,
1700 ),
1701 )
1702 .into())
1703 }
1704 };
1705 }
1706
1707 let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout);
1708
1709 if line_index.endings != new_line_endings {
1710 // If line endings are different, send the entire file.
1711 // Diffing would not work here, as the line endings might be the only
1712 // difference.
1713 Ok(Some(to_proto::text_edit_vec(
1714 &line_index,
1715 TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text),
1716 )))
1717 } else if *file == new_text {
1718 // The document is already formatted correctly -- no edits needed.
1719 Ok(None)
1720 } else {
1721 Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
1722 }
1723}
1724
1678#[derive(Debug, Serialize, Deserialize)] 1725#[derive(Debug, Serialize, Deserialize)]
1679struct CompletionResolveData { 1726struct CompletionResolveData {
1680 position: lsp_types::TextDocumentPositionParams, 1727 position: lsp_types::TextDocumentPositionParams,
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index cb002f700..008758ea0 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -543,6 +543,7 @@ impl GlobalState {
543 .on::<lsp_types::request::Rename>(handlers::handle_rename) 543 .on::<lsp_types::request::Rename>(handlers::handle_rename)
544 .on::<lsp_types::request::References>(handlers::handle_references) 544 .on::<lsp_types::request::References>(handlers::handle_references)
545 .on::<lsp_types::request::Formatting>(handlers::handle_formatting) 545 .on::<lsp_types::request::Formatting>(handlers::handle_formatting)
546 .on::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting)
546 .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight) 547 .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight)
547 .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare) 548 .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
548 .on::<lsp_types::request::CallHierarchyIncomingCalls>( 549 .on::<lsp_types::request::CallHierarchyIncomingCalls>(
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 7a53e4a8b..93b5ff55f 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -455,7 +455,11 @@ impl ProjectFolders {
455 dirs.include.extend(root.include); 455 dirs.include.extend(root.include);
456 dirs.exclude.extend(root.exclude); 456 dirs.exclude.extend(root.exclude);
457 for excl in global_excludes { 457 for excl in global_excludes {
458 if dirs.include.iter().any(|incl| incl.starts_with(excl)) { 458 if dirs
459 .include
460 .iter()
461 .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
462 {
459 dirs.exclude.push(excl.clone()); 463 dirs.exclude.push(excl.clone());
460 } 464 }
461 } 465 }
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index c02bab7cc..f3da82feb 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -346,6 +346,13 @@ Additional arguments to `rustfmt`.
346Advanced option, fully override the command rust-analyzer uses for 346Advanced option, fully override the command rust-analyzer uses for
347formatting. 347formatting.
348-- 348--
349[[rust-analyzer.rustfmt.enableRangeFormatting]]rust-analyzer.rustfmt.enableRangeFormatting (default: `false`)::
350+
351--
352Enables the use of rustfmt's unstable range formatting command for the
353`textDocument/rangeFormatting` request. The rustfmt option is unstable and only
354available on a nightly build.
355--
349[[rust-analyzer.workspace.symbol.search.scope]]rust-analyzer.workspace.symbol.search.scope (default: `"workspace"`):: 356[[rust-analyzer.workspace.symbol.search.scope]]rust-analyzer.workspace.symbol.search.scope (default: `"workspace"`)::
350+ 357+
351-- 358--
diff --git a/editors/code/package.json b/editors/code/package.json
index 17d9281ff..05cbccf94 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -795,6 +795,11 @@
795 "type": "string" 795 "type": "string"
796 } 796 }
797 }, 797 },
798 "rust-analyzer.rustfmt.enableRangeFormatting": {
799 "markdownDescription": "Enables the use of rustfmt's unstable range formatting command for the\n`textDocument/rangeFormatting` request. The rustfmt option is unstable and only\navailable on a nightly build.",
800 "default": false,
801 "type": "boolean"
802 },
798 "rust-analyzer.workspace.symbol.search.scope": { 803 "rust-analyzer.workspace.symbol.search.scope": {
799 "markdownDescription": "Workspace symbol search scope.", 804 "markdownDescription": "Workspace symbol search scope.",
800 "default": "workspace", 805 "default": "workspace",
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index 138e3f686..d0be84068 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -142,7 +142,11 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
142 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion 142 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
143 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() 143 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
144 const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true); 144 const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true);
145
145 cargoTask.presentationOptions.clear = true; 146 cargoTask.presentationOptions.clear = true;
147 // Sadly, this doesn't prevent focus stealing if the terminal is currently
148 // hidden, and will become revealed due to task exucution.
149 cargoTask.presentationOptions.focus = false;
146 150
147 return cargoTask; 151 return cargoTask;
148} 152}