aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/assists/src/handlers/generate_impl.rs42
-rw-r--r--crates/cfg/Cargo.toml1
-rw-r--r--crates/cfg/src/cfg_expr.rs113
-rw-r--r--crates/cfg/src/dnf.rs320
-rw-r--r--crates/cfg/src/lib.rs130
-rw-r--r--crates/cfg/src/tests.rs193
-rw-r--r--crates/completion/src/completion_context.rs13
-rw-r--r--crates/completion/src/completion_item.rs21
-rw-r--r--crates/completion/src/presentation.rs121
-rw-r--r--crates/hir/src/code_model.rs26
-rw-r--r--crates/hir_def/src/attr.rs14
-rw-r--r--crates/hir_def/src/body.rs63
-rw-r--r--crates/hir_def/src/body/diagnostics.rs20
-rw-r--r--crates/hir_def/src/body/lower.rs60
-rw-r--r--crates/hir_def/src/body/tests.rs75
-rw-r--r--crates/hir_def/src/diagnostics.rs28
-rw-r--r--crates/hir_def/src/item_tree/lower.rs47
-rw-r--r--crates/hir_def/src/nameres.rs16
-rw-r--r--crates/hir_def/src/nameres/collector.rs29
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs56
-rw-r--r--crates/hir_def/src/test_db.rs44
-rw-r--r--crates/hir_expand/src/name.rs36
-rw-r--r--crates/hir_ty/src/diagnostics/decl_check.rs28
-rw-r--r--crates/hir_ty/src/diagnostics/decl_check/case_conv.rs9
-rw-r--r--crates/hir_ty/src/diagnostics/unsafe_check.rs18
-rw-r--r--crates/hir_ty/src/traits.rs30
-rw-r--r--crates/ide/src/doc_links.rs28
-rw-r--r--crates/ide/src/hover.rs4
-rw-r--r--crates/ide/src/inlay_hints.rs29
-rw-r--r--crates/ide/src/runnables.rs84
-rw-r--r--crates/ide/src/syntax_highlighting.rs3
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs3
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html19
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs19
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs10
-rw-r--r--crates/rust-analyzer/src/dispatch.rs6
-rw-r--r--crates/rust-analyzer/src/handlers.rs2
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs1
-rw-r--r--crates/rust-analyzer/src/to_proto.rs74
-rw-r--r--crates/stdx/src/panic_context.rs4
-rw-r--r--docs/dev/debugging.md2
-rw-r--r--docs/user/generated_diagnostic.adoc18
-rw-r--r--docs/user/manual.adoc7
-rw-r--r--editors/code/package.json4
-rw-r--r--editors/code/rust.tmGrammar.json53
46 files changed, 1601 insertions, 323 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 59f743efa..fe09e4afe 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -162,6 +162,7 @@ checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
162name = "cfg" 162name = "cfg"
163version = "0.0.0" 163version = "0.0.0"
164dependencies = [ 164dependencies = [
165 "expect-test",
165 "mbe", 166 "mbe",
166 "rustc-hash", 167 "rustc-hash",
167 "syntax", 168 "syntax",
diff --git a/crates/assists/src/handlers/generate_impl.rs b/crates/assists/src/handlers/generate_impl.rs
index 9989109b5..114974465 100644
--- a/crates/assists/src/handlers/generate_impl.rs
+++ b/crates/assists/src/handlers/generate_impl.rs
@@ -1,6 +1,6 @@
1use itertools::Itertools; 1use itertools::Itertools;
2use stdx::format_to; 2use stdx::format_to;
3use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner}; 3use syntax::ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner};
4 4
5use crate::{AssistContext, AssistId, AssistKind, Assists}; 5use crate::{AssistContext, AssistId, AssistKind, Assists};
6 6
@@ -27,6 +27,7 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()
27 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?; 27 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
28 let name = nominal.name()?; 28 let name = nominal.name()?;
29 let target = nominal.syntax().text_range(); 29 let target = nominal.syntax().text_range();
30
30 acc.add( 31 acc.add(
31 AssistId("generate_impl", AssistKind::Generate), 32 AssistId("generate_impl", AssistKind::Generate),
32 format!("Generate impl for `{}`", name), 33 format!("Generate impl for `{}`", name),
@@ -35,7 +36,15 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()
35 let type_params = nominal.generic_param_list(); 36 let type_params = nominal.generic_param_list();
36 let start_offset = nominal.syntax().text_range().end(); 37 let start_offset = nominal.syntax().text_range().end();
37 let mut buf = String::new(); 38 let mut buf = String::new();
38 buf.push_str("\n\nimpl"); 39 buf.push_str("\n\n");
40 nominal
41 .attrs()
42 .filter(|attr| {
43 attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)
44 })
45 .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str()));
46
47 buf.push_str("impl");
39 if let Some(type_params) = &type_params { 48 if let Some(type_params) = &type_params {
40 format_to!(buf, "{}", type_params.syntax()); 49 format_to!(buf, "{}", type_params.syntax());
41 } 50 }
@@ -91,6 +100,35 @@ mod tests {
91 "struct Foo<'a, T: Foo<'a>> {<|>}", 100 "struct Foo<'a, T: Foo<'a>> {<|>}",
92 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", 101 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
93 ); 102 );
103 check_assist(
104 generate_impl,
105 r#"
106 #[cfg(feature = "foo")]
107 struct Foo<'a, T: Foo<'a>> {<|>}"#,
108 r#"
109 #[cfg(feature = "foo")]
110 struct Foo<'a, T: Foo<'a>> {}
111
112 #[cfg(feature = "foo")]
113 impl<'a, T: Foo<'a>> Foo<'a, T> {
114 $0
115 }"#,
116 );
117
118 check_assist(
119 generate_impl,
120 r#"
121 #[cfg(not(feature = "foo"))]
122 struct Foo<'a, T: Foo<'a>> {<|>}"#,
123 r#"
124 #[cfg(not(feature = "foo"))]
125 struct Foo<'a, T: Foo<'a>> {}
126
127 #[cfg(not(feature = "foo"))]
128 impl<'a, T: Foo<'a>> Foo<'a, T> {
129 $0
130 }"#,
131 );
94 } 132 }
95 133
96 #[test] 134 #[test]
diff --git a/crates/cfg/Cargo.toml b/crates/cfg/Cargo.toml
index a6785ee8e..c68e391c1 100644
--- a/crates/cfg/Cargo.toml
+++ b/crates/cfg/Cargo.toml
@@ -17,3 +17,4 @@ tt = { path = "../tt", version = "0.0.0" }
17[dev-dependencies] 17[dev-dependencies]
18mbe = { path = "../mbe" } 18mbe = { path = "../mbe" }
19syntax = { path = "../syntax" } 19syntax = { path = "../syntax" }
20expect-test = "1.0"
diff --git a/crates/cfg/src/cfg_expr.rs b/crates/cfg/src/cfg_expr.rs
index 336fe25bc..42327f1e1 100644
--- a/crates/cfg/src/cfg_expr.rs
+++ b/crates/cfg/src/cfg_expr.rs
@@ -2,30 +2,77 @@
2//! 2//!
3//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation 3//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation
4 4
5use std::slice::Iter as SliceIter; 5use std::{fmt, slice::Iter as SliceIter};
6 6
7use tt::SmolStr; 7use tt::SmolStr;
8 8
9/// A simple configuration value passed in from the outside.
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
11pub enum CfgAtom {
12 /// eg. `#[cfg(test)]`
13 Flag(SmolStr),
14 /// eg. `#[cfg(target_os = "linux")]`
15 ///
16 /// Note that a key can have multiple values that are all considered "active" at the same time.
17 /// For example, `#[cfg(target_feature = "sse")]` and `#[cfg(target_feature = "sse2")]`.
18 KeyValue { key: SmolStr, value: SmolStr },
19}
20
21impl CfgAtom {
22 /// Returns `true` when the atom comes from the target specification.
23 ///
24 /// If this returns `true`, then changing this atom requires changing the compilation target. If
25 /// it returns `false`, the atom might come from a build script or the build system.
26 pub fn is_target_defined(&self) -> bool {
27 match self {
28 CfgAtom::Flag(flag) => matches!(&**flag, "unix" | "windows"),
29 CfgAtom::KeyValue { key, value: _ } => matches!(
30 &**key,
31 "target_arch"
32 | "target_os"
33 | "target_env"
34 | "target_family"
35 | "target_endian"
36 | "target_pointer_width"
37 | "target_vendor" // NOTE: `target_feature` is left out since it can be configured via `-Ctarget-feature`
38 ),
39 }
40 }
41}
42
43impl fmt::Display for CfgAtom {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 CfgAtom::Flag(name) => write!(f, "{}", name),
47 CfgAtom::KeyValue { key, value } => write!(f, "{} = {:?}", key, value),
48 }
49 }
50}
51
9#[derive(Debug, Clone, PartialEq, Eq)] 52#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum CfgExpr { 53pub enum CfgExpr {
11 Invalid, 54 Invalid,
12 Atom(SmolStr), 55 Atom(CfgAtom),
13 KeyValue { key: SmolStr, value: SmolStr },
14 All(Vec<CfgExpr>), 56 All(Vec<CfgExpr>),
15 Any(Vec<CfgExpr>), 57 Any(Vec<CfgExpr>),
16 Not(Box<CfgExpr>), 58 Not(Box<CfgExpr>),
17} 59}
18 60
61impl From<CfgAtom> for CfgExpr {
62 fn from(atom: CfgAtom) -> Self {
63 CfgExpr::Atom(atom)
64 }
65}
66
19impl CfgExpr { 67impl CfgExpr {
20 pub fn parse(tt: &tt::Subtree) -> CfgExpr { 68 pub fn parse(tt: &tt::Subtree) -> CfgExpr {
21 next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid) 69 next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
22 } 70 }
23 /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates. 71 /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
24 pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> { 72 pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option<bool> {
25 match self { 73 match self {
26 CfgExpr::Invalid => None, 74 CfgExpr::Invalid => None,
27 CfgExpr::Atom(name) => Some(query(name, None)), 75 CfgExpr::Atom(atom) => Some(query(atom)),
28 CfgExpr::KeyValue { key, value } => Some(query(key, Some(value))),
29 CfgExpr::All(preds) => { 76 CfgExpr::All(preds) => {
30 preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?)) 77 preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
31 } 78 }
@@ -54,7 +101,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
54 // FIXME: escape? raw string? 101 // FIXME: escape? raw string?
55 let value = 102 let value =
56 SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"')); 103 SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
57 CfgExpr::KeyValue { key: name, value } 104 CfgAtom::KeyValue { key: name, value }.into()
58 } 105 }
59 _ => return Some(CfgExpr::Invalid), 106 _ => return Some(CfgExpr::Invalid),
60 } 107 }
@@ -70,7 +117,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
70 _ => CfgExpr::Invalid, 117 _ => CfgExpr::Invalid,
71 } 118 }
72 } 119 }
73 _ => CfgExpr::Atom(name), 120 _ => CfgAtom::Flag(name).into(),
74 }; 121 };
75 122
76 // Eat comma separator 123 // Eat comma separator
@@ -81,53 +128,3 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
81 } 128 }
82 Some(ret) 129 Some(ret)
83} 130}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 use mbe::ast_to_token_tree;
90 use syntax::ast::{self, AstNode};
91
92 fn assert_parse_result(input: &str, expected: CfgExpr) {
93 let (tt, _) = {
94 let source_file = ast::SourceFile::parse(input).ok().unwrap();
95 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
96 ast_to_token_tree(&tt).unwrap()
97 };
98 let cfg = CfgExpr::parse(&tt);
99 assert_eq!(cfg, expected);
100 }
101
102 #[test]
103 fn test_cfg_expr_parser() {
104 assert_parse_result("#![cfg(foo)]", CfgExpr::Atom("foo".into()));
105 assert_parse_result("#![cfg(foo,)]", CfgExpr::Atom("foo".into()));
106 assert_parse_result(
107 "#![cfg(not(foo))]",
108 CfgExpr::Not(Box::new(CfgExpr::Atom("foo".into()))),
109 );
110 assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
111
112 // Only take the first
113 assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgExpr::Atom("foo".into()));
114
115 assert_parse_result(
116 r#"#![cfg(all(foo, bar = "baz"))]"#,
117 CfgExpr::All(vec![
118 CfgExpr::Atom("foo".into()),
119 CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
120 ]),
121 );
122
123 assert_parse_result(
124 r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
125 CfgExpr::Any(vec![
126 CfgExpr::Not(Box::new(CfgExpr::Invalid)),
127 CfgExpr::All(vec![]),
128 CfgExpr::Invalid,
129 CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
130 ]),
131 );
132 }
133}
diff --git a/crates/cfg/src/dnf.rs b/crates/cfg/src/dnf.rs
new file mode 100644
index 000000000..580c9a9a2
--- /dev/null
+++ b/crates/cfg/src/dnf.rs
@@ -0,0 +1,320 @@
1//! Disjunctive Normal Form construction.
2//!
3//! Algorithm from <https://www.cs.drexel.edu/~jjohnson/2015-16/fall/CS270/Lectures/3/dnf.pdf>,
4//! which would have been much easier to read if it used pattern matching. It's also missing the
5//! entire "distribute ANDs over ORs" part, which is not trivial. Oh well.
6//!
7//! This is currently both messy and inefficient. Feel free to improve, there are unit tests.
8
9use std::fmt;
10
11use rustc_hash::FxHashSet;
12
13use crate::{CfgAtom, CfgDiff, CfgExpr, CfgOptions, InactiveReason};
14
15/// A `#[cfg]` directive in Disjunctive Normal Form (DNF).
16pub struct DnfExpr {
17 conjunctions: Vec<Conjunction>,
18}
19
20struct Conjunction {
21 literals: Vec<Literal>,
22}
23
24struct Literal {
25 negate: bool,
26 var: Option<CfgAtom>, // None = Invalid
27}
28
29impl DnfExpr {
30 pub fn new(expr: CfgExpr) -> Self {
31 let builder = Builder { expr: DnfExpr { conjunctions: Vec::new() } };
32
33 builder.lower(expr.clone())
34 }
35
36 /// Computes a list of present or absent atoms in `opts` that cause this expression to evaluate
37 /// to `false`.
38 ///
39 /// Note that flipping a subset of these atoms might be sufficient to make the whole expression
40 /// evaluate to `true`. For that, see `compute_enable_hints`.
41 ///
42 /// Returns `None` when `self` is already true, or contains errors.
43 pub fn why_inactive(&self, opts: &CfgOptions) -> Option<InactiveReason> {
44 let mut res = InactiveReason { enabled: Vec::new(), disabled: Vec::new() };
45
46 for conj in &self.conjunctions {
47 let mut conj_is_true = true;
48 for lit in &conj.literals {
49 let atom = lit.var.as_ref()?;
50 let enabled = opts.enabled.contains(atom);
51 if lit.negate == enabled {
52 // Literal is false, but needs to be true for this conjunction.
53 conj_is_true = false;
54
55 if enabled {
56 res.enabled.push(atom.clone());
57 } else {
58 res.disabled.push(atom.clone());
59 }
60 }
61 }
62
63 if conj_is_true {
64 // This expression is not actually inactive.
65 return None;
66 }
67 }
68
69 res.enabled.sort_unstable();
70 res.enabled.dedup();
71 res.disabled.sort_unstable();
72 res.disabled.dedup();
73 Some(res)
74 }
75
76 /// Returns `CfgDiff` objects that would enable this directive if applied to `opts`.
77 pub fn compute_enable_hints<'a>(
78 &'a self,
79 opts: &'a CfgOptions,
80 ) -> impl Iterator<Item = CfgDiff> + 'a {
81 // A cfg is enabled if any of `self.conjunctions` evaluate to `true`.
82
83 self.conjunctions.iter().filter_map(move |conj| {
84 let mut enable = FxHashSet::default();
85 let mut disable = FxHashSet::default();
86 for lit in &conj.literals {
87 let atom = lit.var.as_ref()?;
88 let enabled = opts.enabled.contains(atom);
89 if lit.negate && enabled {
90 disable.insert(atom.clone());
91 }
92 if !lit.negate && !enabled {
93 enable.insert(atom.clone());
94 }
95 }
96
97 // Check that this actually makes `conj` true.
98 for lit in &conj.literals {
99 let atom = lit.var.as_ref()?;
100 let enabled = enable.contains(atom)
101 || (opts.enabled.contains(atom) && !disable.contains(atom));
102 if enabled == lit.negate {
103 return None;
104 }
105 }
106
107 if enable.is_empty() && disable.is_empty() {
108 return None;
109 }
110
111 let mut diff = CfgDiff {
112 enable: enable.into_iter().collect(),
113 disable: disable.into_iter().collect(),
114 };
115
116 // Undo the FxHashMap randomization for consistent output.
117 diff.enable.sort_unstable();
118 diff.disable.sort_unstable();
119
120 Some(diff)
121 })
122 }
123}
124
125impl fmt::Display for DnfExpr {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 if self.conjunctions.len() != 1 {
128 write!(f, "any(")?;
129 }
130 for (i, conj) in self.conjunctions.iter().enumerate() {
131 if i != 0 {
132 f.write_str(", ")?;
133 }
134
135 write!(f, "{}", conj)?;
136 }
137 if self.conjunctions.len() != 1 {
138 write!(f, ")")?;
139 }
140
141 Ok(())
142 }
143}
144
145impl Conjunction {
146 fn new(parts: Vec<CfgExpr>) -> Self {
147 let mut literals = Vec::new();
148 for part in parts {
149 match part {
150 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
151 literals.push(Literal::new(part));
152 }
153 CfgExpr::All(conj) => {
154 // Flatten.
155 literals.extend(Conjunction::new(conj).literals);
156 }
157 CfgExpr::Any(_) => unreachable!("disjunction in conjunction"),
158 }
159 }
160
161 Self { literals }
162 }
163}
164
165impl fmt::Display for Conjunction {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 if self.literals.len() != 1 {
168 write!(f, "all(")?;
169 }
170 for (i, lit) in self.literals.iter().enumerate() {
171 if i != 0 {
172 f.write_str(", ")?;
173 }
174
175 write!(f, "{}", lit)?;
176 }
177 if self.literals.len() != 1 {
178 write!(f, ")")?;
179 }
180
181 Ok(())
182 }
183}
184
185impl Literal {
186 fn new(expr: CfgExpr) -> Self {
187 match expr {
188 CfgExpr::Invalid => Self { negate: false, var: None },
189 CfgExpr::Atom(atom) => Self { negate: false, var: Some(atom) },
190 CfgExpr::Not(expr) => match *expr {
191 CfgExpr::Invalid => Self { negate: true, var: None },
192 CfgExpr::Atom(atom) => Self { negate: true, var: Some(atom) },
193 _ => unreachable!("non-atom {:?}", expr),
194 },
195 CfgExpr::Any(_) | CfgExpr::All(_) => unreachable!("non-literal {:?}", expr),
196 }
197 }
198}
199
200impl fmt::Display for Literal {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 if self.negate {
203 write!(f, "not(")?;
204 }
205
206 match &self.var {
207 Some(var) => write!(f, "{}", var)?,
208 None => f.write_str("<invalid>")?,
209 }
210
211 if self.negate {
212 write!(f, ")")?;
213 }
214
215 Ok(())
216 }
217}
218
219struct Builder {
220 expr: DnfExpr,
221}
222
223impl Builder {
224 fn lower(mut self, expr: CfgExpr) -> DnfExpr {
225 let expr = make_nnf(expr);
226 let expr = make_dnf(expr);
227
228 match expr {
229 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
230 self.expr.conjunctions.push(Conjunction::new(vec![expr]));
231 }
232 CfgExpr::All(conj) => {
233 self.expr.conjunctions.push(Conjunction::new(conj));
234 }
235 CfgExpr::Any(mut disj) => {
236 disj.reverse();
237 while let Some(conj) = disj.pop() {
238 match conj {
239 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::All(_) | CfgExpr::Not(_) => {
240 self.expr.conjunctions.push(Conjunction::new(vec![conj]));
241 }
242 CfgExpr::Any(inner_disj) => {
243 // Flatten.
244 disj.extend(inner_disj.into_iter().rev());
245 }
246 }
247 }
248 }
249 }
250
251 self.expr
252 }
253}
254
255fn make_dnf(expr: CfgExpr) -> CfgExpr {
256 match expr {
257 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => expr,
258 CfgExpr::Any(e) => CfgExpr::Any(e.into_iter().map(|expr| make_dnf(expr)).collect()),
259 CfgExpr::All(e) => {
260 let e = e.into_iter().map(|expr| make_nnf(expr)).collect::<Vec<_>>();
261
262 CfgExpr::Any(distribute_conj(&e))
263 }
264 }
265}
266
267/// Turns a conjunction of expressions into a disjunction of expressions.
268fn distribute_conj(conj: &[CfgExpr]) -> Vec<CfgExpr> {
269 fn go(out: &mut Vec<CfgExpr>, with: &mut Vec<CfgExpr>, rest: &[CfgExpr]) {
270 match rest {
271 [head, tail @ ..] => match head {
272 CfgExpr::Any(disj) => {
273 for part in disj {
274 with.push(part.clone());
275 go(out, with, tail);
276 with.pop();
277 }
278 }
279 _ => {
280 with.push(head.clone());
281 go(out, with, tail);
282 with.pop();
283 }
284 },
285 _ => {
286 // Turn accumulated parts into a new conjunction.
287 out.push(CfgExpr::All(with.clone()));
288 }
289 }
290 }
291
292 let mut out = Vec::new();
293 let mut with = Vec::new();
294
295 go(&mut out, &mut with, conj);
296
297 out
298}
299
300fn make_nnf(expr: CfgExpr) -> CfgExpr {
301 match expr {
302 CfgExpr::Invalid | CfgExpr::Atom(_) => expr,
303 CfgExpr::Any(expr) => CfgExpr::Any(expr.into_iter().map(|expr| make_nnf(expr)).collect()),
304 CfgExpr::All(expr) => CfgExpr::All(expr.into_iter().map(|expr| make_nnf(expr)).collect()),
305 CfgExpr::Not(operand) => match *operand {
306 CfgExpr::Invalid | CfgExpr::Atom(_) => CfgExpr::Not(operand.clone()), // Original negated expr
307 CfgExpr::Not(expr) => {
308 // Remove double negation.
309 make_nnf(*expr)
310 }
311 // Convert negated conjunction/disjunction using DeMorgan's Law.
312 CfgExpr::Any(inner) => CfgExpr::All(
313 inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
314 ),
315 CfgExpr::All(inner) => CfgExpr::Any(
316 inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
317 ),
318 },
319 }
320}
diff --git a/crates/cfg/src/lib.rs b/crates/cfg/src/lib.rs
index a9d50e698..d0e08cf5f 100644
--- a/crates/cfg/src/lib.rs
+++ b/crates/cfg/src/lib.rs
@@ -1,11 +1,17 @@
1//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator 1//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator
2 2
3mod cfg_expr; 3mod cfg_expr;
4mod dnf;
5#[cfg(test)]
6mod tests;
7
8use std::fmt;
4 9
5use rustc_hash::FxHashSet; 10use rustc_hash::FxHashSet;
6use tt::SmolStr; 11use tt::SmolStr;
7 12
8pub use cfg_expr::CfgExpr; 13pub use cfg_expr::{CfgAtom, CfgExpr};
14pub use dnf::DnfExpr;
9 15
10/// Configuration options used for conditional compilition on items with `cfg` attributes. 16/// Configuration options used for conditional compilition on items with `cfg` attributes.
11/// We have two kind of options in different namespaces: atomic options like `unix`, and 17/// We have two kind of options in different namespaces: atomic options like `unix`, and
@@ -19,33 +25,131 @@ pub use cfg_expr::CfgExpr;
19/// See: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options 25/// See: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options
20#[derive(Debug, Clone, PartialEq, Eq, Default)] 26#[derive(Debug, Clone, PartialEq, Eq, Default)]
21pub struct CfgOptions { 27pub struct CfgOptions {
22 atoms: FxHashSet<SmolStr>, 28 enabled: FxHashSet<CfgAtom>,
23 key_values: FxHashSet<(SmolStr, SmolStr)>,
24} 29}
25 30
26impl CfgOptions { 31impl CfgOptions {
27 pub fn check(&self, cfg: &CfgExpr) -> Option<bool> { 32 pub fn check(&self, cfg: &CfgExpr) -> Option<bool> {
28 cfg.fold(&|key, value| match value { 33 cfg.fold(&|atom| self.enabled.contains(atom))
29 None => self.atoms.contains(key),
30 Some(value) => self.key_values.contains(&(key.clone(), value.clone())),
31 })
32 } 34 }
33 35
34 pub fn insert_atom(&mut self, key: SmolStr) { 36 pub fn insert_atom(&mut self, key: SmolStr) {
35 self.atoms.insert(key); 37 self.enabled.insert(CfgAtom::Flag(key));
36 } 38 }
37 39
38 pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) { 40 pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) {
39 self.key_values.insert((key, value)); 41 self.enabled.insert(CfgAtom::KeyValue { key, value });
40 } 42 }
41 43
42 pub fn append(&mut self, other: &CfgOptions) { 44 pub fn append(&mut self, other: &CfgOptions) {
43 for atom in &other.atoms { 45 for atom in &other.enabled {
44 self.atoms.insert(atom.clone()); 46 self.enabled.insert(atom.clone());
47 }
48 }
49
50 pub fn apply_diff(&mut self, diff: CfgDiff) {
51 for atom in diff.enable {
52 self.enabled.insert(atom);
45 } 53 }
46 54
47 for (key, value) in &other.key_values { 55 for atom in diff.disable {
48 self.key_values.insert((key.clone(), value.clone())); 56 self.enabled.remove(&atom);
57 }
58 }
59}
60
61pub struct CfgDiff {
62 // Invariants: No duplicates, no atom that's both in `enable` and `disable`.
63 enable: Vec<CfgAtom>,
64 disable: Vec<CfgAtom>,
65}
66
67impl CfgDiff {
68 /// Returns the total number of atoms changed by this diff.
69 pub fn len(&self) -> usize {
70 self.enable.len() + self.disable.len()
71 }
72}
73
74impl fmt::Display for CfgDiff {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 if !self.enable.is_empty() {
77 f.write_str("enable ")?;
78 for (i, atom) in self.enable.iter().enumerate() {
79 let sep = match i {
80 0 => "",
81 _ if i == self.enable.len() - 1 => " and ",
82 _ => ", ",
83 };
84 f.write_str(sep)?;
85
86 write!(f, "{}", atom)?;
87 }
88
89 if !self.disable.is_empty() {
90 f.write_str("; ")?;
91 }
49 } 92 }
93
94 if !self.disable.is_empty() {
95 f.write_str("disable ")?;
96 for (i, atom) in self.disable.iter().enumerate() {
97 let sep = match i {
98 0 => "",
99 _ if i == self.enable.len() - 1 => " and ",
100 _ => ", ",
101 };
102 f.write_str(sep)?;
103
104 write!(f, "{}", atom)?;
105 }
106 }
107
108 Ok(())
109 }
110}
111
112pub struct InactiveReason {
113 enabled: Vec<CfgAtom>,
114 disabled: Vec<CfgAtom>,
115}
116
117impl fmt::Display for InactiveReason {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 if !self.enabled.is_empty() {
120 for (i, atom) in self.enabled.iter().enumerate() {
121 let sep = match i {
122 0 => "",
123 _ if i == self.enabled.len() - 1 => " and ",
124 _ => ", ",
125 };
126 f.write_str(sep)?;
127
128 write!(f, "{}", atom)?;
129 }
130 let is_are = if self.enabled.len() == 1 { "is" } else { "are" };
131 write!(f, " {} enabled", is_are)?;
132
133 if !self.disabled.is_empty() {
134 f.write_str(" and ")?;
135 }
136 }
137
138 if !self.disabled.is_empty() {
139 for (i, atom) in self.disabled.iter().enumerate() {
140 let sep = match i {
141 0 => "",
142 _ if i == self.disabled.len() - 1 => " and ",
143 _ => ", ",
144 };
145 f.write_str(sep)?;
146
147 write!(f, "{}", atom)?;
148 }
149 let is_are = if self.disabled.len() == 1 { "is" } else { "are" };
150 write!(f, " {} disabled", is_are)?;
151 }
152
153 Ok(())
50 } 154 }
51} 155}
diff --git a/crates/cfg/src/tests.rs b/crates/cfg/src/tests.rs
new file mode 100644
index 000000000..bd0f9ec48
--- /dev/null
+++ b/crates/cfg/src/tests.rs
@@ -0,0 +1,193 @@
1use expect_test::{expect, Expect};
2use mbe::ast_to_token_tree;
3use syntax::{ast, AstNode};
4
5use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr};
6
7fn assert_parse_result(input: &str, expected: CfgExpr) {
8 let (tt, _) = {
9 let source_file = ast::SourceFile::parse(input).ok().unwrap();
10 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
11 ast_to_token_tree(&tt).unwrap()
12 };
13 let cfg = CfgExpr::parse(&tt);
14 assert_eq!(cfg, expected);
15}
16
17fn check_dnf(input: &str, expect: Expect) {
18 let (tt, _) = {
19 let source_file = ast::SourceFile::parse(input).ok().unwrap();
20 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
21 ast_to_token_tree(&tt).unwrap()
22 };
23 let cfg = CfgExpr::parse(&tt);
24 let actual = format!("#![cfg({})]", DnfExpr::new(cfg));
25 expect.assert_eq(&actual);
26}
27
28fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) {
29 let (tt, _) = {
30 let source_file = ast::SourceFile::parse(input).ok().unwrap();
31 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
32 ast_to_token_tree(&tt).unwrap()
33 };
34 let cfg = CfgExpr::parse(&tt);
35 let dnf = DnfExpr::new(cfg);
36 let why_inactive = dnf.why_inactive(opts).unwrap().to_string();
37 expect.assert_eq(&why_inactive);
38}
39
40#[track_caller]
41fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) {
42 let (tt, _) = {
43 let source_file = ast::SourceFile::parse(input).ok().unwrap();
44 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
45 ast_to_token_tree(&tt).unwrap()
46 };
47 let cfg = CfgExpr::parse(&tt);
48 let dnf = DnfExpr::new(cfg);
49 let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::<Vec<_>>();
50 assert_eq!(hints, expected_hints);
51}
52
53#[test]
54fn test_cfg_expr_parser() {
55 assert_parse_result("#![cfg(foo)]", CfgAtom::Flag("foo".into()).into());
56 assert_parse_result("#![cfg(foo,)]", CfgAtom::Flag("foo".into()).into());
57 assert_parse_result(
58 "#![cfg(not(foo))]",
59 CfgExpr::Not(Box::new(CfgAtom::Flag("foo".into()).into())),
60 );
61 assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
62
63 // Only take the first
64 assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgAtom::Flag("foo".into()).into());
65
66 assert_parse_result(
67 r#"#![cfg(all(foo, bar = "baz"))]"#,
68 CfgExpr::All(vec![
69 CfgAtom::Flag("foo".into()).into(),
70 CfgAtom::KeyValue { key: "bar".into(), value: "baz".into() }.into(),
71 ]),
72 );
73
74 assert_parse_result(
75 r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
76 CfgExpr::Any(vec![
77 CfgExpr::Not(Box::new(CfgExpr::Invalid)),
78 CfgExpr::All(vec![]),
79 CfgExpr::Invalid,
80 CfgAtom::KeyValue { key: "bar".into(), value: "baz".into() }.into(),
81 ]),
82 );
83}
84
85#[test]
86fn smoke() {
87 check_dnf("#![cfg(test)]", expect![[r#"#![cfg(test)]"#]]);
88 check_dnf("#![cfg(not(test))]", expect![[r#"#![cfg(not(test))]"#]]);
89 check_dnf("#![cfg(not(not(test)))]", expect![[r#"#![cfg(test)]"#]]);
90
91 check_dnf("#![cfg(all(a, b))]", expect![[r#"#![cfg(all(a, b))]"#]]);
92 check_dnf("#![cfg(any(a, b))]", expect![[r#"#![cfg(any(a, b))]"#]]);
93
94 check_dnf("#![cfg(not(a))]", expect![[r#"#![cfg(not(a))]"#]]);
95}
96
97#[test]
98fn distribute() {
99 check_dnf("#![cfg(all(any(a, b), c))]", expect![[r#"#![cfg(any(all(a, c), all(b, c)))]"#]]);
100 check_dnf("#![cfg(all(c, any(a, b)))]", expect![[r#"#![cfg(any(all(c, a), all(c, b)))]"#]]);
101 check_dnf(
102 "#![cfg(all(any(a, b), any(c, d)))]",
103 expect![[r#"#![cfg(any(all(a, c), all(a, d), all(b, c), all(b, d)))]"#]],
104 );
105
106 check_dnf(
107 "#![cfg(all(any(a, b, c), any(d, e, f), g))]",
108 expect![[
109 r#"#![cfg(any(all(a, d, g), all(a, e, g), all(a, f, g), all(b, d, g), all(b, e, g), all(b, f, g), all(c, d, g), all(c, e, g), all(c, f, g)))]"#
110 ]],
111 );
112}
113
114#[test]
115fn demorgan() {
116 check_dnf("#![cfg(not(all(a, b)))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
117 check_dnf("#![cfg(not(any(a, b)))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
118
119 check_dnf("#![cfg(not(all(not(a), b)))]", expect![[r#"#![cfg(any(a, not(b)))]"#]]);
120 check_dnf("#![cfg(not(any(a, not(b))))]", expect![[r#"#![cfg(all(not(a), b))]"#]]);
121}
122
123#[test]
124fn nested() {
125 check_dnf("#![cfg(all(any(a), not(all(any(b)))))]", expect![[r#"#![cfg(all(a, not(b)))]"#]]);
126
127 check_dnf("#![cfg(any(any(a, b)))]", expect![[r#"#![cfg(any(a, b))]"#]]);
128 check_dnf("#![cfg(not(any(any(a, b))))]", expect![[r#"#![cfg(all(not(a), not(b)))]"#]]);
129 check_dnf("#![cfg(all(all(a, b)))]", expect![[r#"#![cfg(all(a, b))]"#]]);
130 check_dnf("#![cfg(not(all(all(a, b))))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
131}
132
133#[test]
134fn hints() {
135 let mut opts = CfgOptions::default();
136
137 check_enable_hints("#![cfg(test)]", &opts, &["enable test"]);
138 check_enable_hints("#![cfg(not(test))]", &opts, &[]);
139
140 check_enable_hints("#![cfg(any(a, b))]", &opts, &["enable a", "enable b"]);
141 check_enable_hints("#![cfg(any(b, a))]", &opts, &["enable b", "enable a"]);
142
143 check_enable_hints("#![cfg(all(a, b))]", &opts, &["enable a and b"]);
144
145 opts.insert_atom("test".into());
146
147 check_enable_hints("#![cfg(test)]", &opts, &[]);
148 check_enable_hints("#![cfg(not(test))]", &opts, &["disable test"]);
149}
150
151/// Tests that we don't suggest hints for cfgs that express an inconsistent formula.
152#[test]
153fn hints_impossible() {
154 let mut opts = CfgOptions::default();
155
156 check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
157
158 opts.insert_atom("test".into());
159
160 check_enable_hints("#![cfg(all(test, not(test)))]", &opts, &[]);
161}
162
163#[test]
164fn why_inactive() {
165 let mut opts = CfgOptions::default();
166 opts.insert_atom("test".into());
167 opts.insert_atom("test2".into());
168
169 check_why_inactive("#![cfg(a)]", &opts, expect![["a is disabled"]]);
170 check_why_inactive("#![cfg(not(test))]", &opts, expect![["test is enabled"]]);
171
172 check_why_inactive(
173 "#![cfg(all(not(test), not(test2)))]",
174 &opts,
175 expect![["test and test2 are enabled"]],
176 );
177 check_why_inactive("#![cfg(all(a, b))]", &opts, expect![["a and b are disabled"]]);
178 check_why_inactive(
179 "#![cfg(all(not(test), a))]",
180 &opts,
181 expect![["test is enabled and a is disabled"]],
182 );
183 check_why_inactive(
184 "#![cfg(all(not(test), test2, a))]",
185 &opts,
186 expect![["test is enabled and a is disabled"]],
187 );
188 check_why_inactive(
189 "#![cfg(all(not(test), not(test2), a))]",
190 &opts,
191 expect![["test and test2 are enabled and a is disabled"]],
192 );
193}
diff --git a/crates/completion/src/completion_context.rs b/crates/completion/src/completion_context.rs
index dc4e136c6..e4f86d0e0 100644
--- a/crates/completion/src/completion_context.rs
+++ b/crates/completion/src/completion_context.rs
@@ -246,6 +246,19 @@ impl<'a> CompletionContext<'a> {
246 } 246 }
247 } 247 }
248 248
249 pub(crate) fn active_name_and_type(&self) -> Option<(String, Type)> {
250 if let Some(record_field) = &self.record_field_syntax {
251 mark::hit!(record_field_type_match);
252 let (struct_field, _local) = self.sema.resolve_record_field(record_field)?;
253 Some((struct_field.name(self.db).to_string(), struct_field.signature_ty(self.db)))
254 } else if let Some(active_parameter) = &self.active_parameter {
255 mark::hit!(active_param_type_match);
256 Some((active_parameter.name.clone(), active_parameter.ty.clone()))
257 } else {
258 None
259 }
260 }
261
249 fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { 262 fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
250 let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); 263 let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
251 let syntax_element = NodeOrToken::Token(fake_ident_token); 264 let syntax_element = NodeOrToken::Token(fake_ident_token);
diff --git a/crates/completion/src/completion_item.rs b/crates/completion/src/completion_item.rs
index f8be0ad2b..2e1ca0e59 100644
--- a/crates/completion/src/completion_item.rs
+++ b/crates/completion/src/completion_item.rs
@@ -2,7 +2,7 @@
2 2
3use std::fmt; 3use std::fmt;
4 4
5use hir::Documentation; 5use hir::{Documentation, Mutability};
6use syntax::TextRange; 6use syntax::TextRange;
7use text_edit::TextEdit; 7use text_edit::TextEdit;
8 8
@@ -56,6 +56,10 @@ pub struct CompletionItem {
56 56
57 /// Score is useful to pre select or display in better order completion items 57 /// Score is useful to pre select or display in better order completion items
58 score: Option<CompletionScore>, 58 score: Option<CompletionScore>,
59
60 /// Indicates that a reference or mutable reference to this variable is a
61 /// possible match.
62 ref_match: Option<(Mutability, CompletionScore)>,
59} 63}
60 64
61// We use custom debug for CompletionItem to make snapshot tests more readable. 65// We use custom debug for CompletionItem to make snapshot tests more readable.
@@ -194,6 +198,7 @@ impl CompletionItem {
194 deprecated: None, 198 deprecated: None,
195 trigger_call_info: None, 199 trigger_call_info: None,
196 score: None, 200 score: None,
201 ref_match: None,
197 } 202 }
198 } 203 }
199 /// What user sees in pop-up in the UI. 204 /// What user sees in pop-up in the UI.
@@ -240,10 +245,15 @@ impl CompletionItem {
240 pub fn trigger_call_info(&self) -> bool { 245 pub fn trigger_call_info(&self) -> bool {
241 self.trigger_call_info 246 self.trigger_call_info
242 } 247 }
248
249 pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> {
250 self.ref_match
251 }
243} 252}
244 253
245/// A helper to make `CompletionItem`s. 254/// A helper to make `CompletionItem`s.
246#[must_use] 255#[must_use]
256#[derive(Clone)]
247pub(crate) struct Builder { 257pub(crate) struct Builder {
248 source_range: TextRange, 258 source_range: TextRange,
249 completion_kind: CompletionKind, 259 completion_kind: CompletionKind,
@@ -258,6 +268,7 @@ pub(crate) struct Builder {
258 deprecated: Option<bool>, 268 deprecated: Option<bool>,
259 trigger_call_info: Option<bool>, 269 trigger_call_info: Option<bool>,
260 score: Option<CompletionScore>, 270 score: Option<CompletionScore>,
271 ref_match: Option<(Mutability, CompletionScore)>,
261} 272}
262 273
263impl Builder { 274impl Builder {
@@ -288,6 +299,7 @@ impl Builder {
288 deprecated: self.deprecated.unwrap_or(false), 299 deprecated: self.deprecated.unwrap_or(false),
289 trigger_call_info: self.trigger_call_info.unwrap_or(false), 300 trigger_call_info: self.trigger_call_info.unwrap_or(false),
290 score: self.score, 301 score: self.score,
302 ref_match: self.ref_match,
291 } 303 }
292 } 304 }
293 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { 305 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
@@ -350,6 +362,13 @@ impl Builder {
350 self.trigger_call_info = Some(true); 362 self.trigger_call_info = Some(true);
351 self 363 self
352 } 364 }
365 pub(crate) fn set_ref_match(
366 mut self,
367 ref_match: Option<(Mutability, CompletionScore)>,
368 ) -> Builder {
369 self.ref_match = ref_match;
370 self
371 }
353} 372}
354 373
355impl<'a> Into<CompletionItem> for Builder { 374impl<'a> Into<CompletionItem> for Builder {
diff --git a/crates/completion/src/presentation.rs b/crates/completion/src/presentation.rs
index 0a0dc1ce5..0a6f5a1ea 100644
--- a/crates/completion/src/presentation.rs
+++ b/crates/completion/src/presentation.rs
@@ -1,7 +1,7 @@
1//! This modules takes care of rendering various definitions as completion items. 1//! This modules takes care of rendering various definitions as completion items.
2//! It also handles scoring (sorting) completions. 2//! It also handles scoring (sorting) completions.
3 3
4use hir::{HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; 4use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type};
5use itertools::Itertools; 5use itertools::Itertools;
6use syntax::{ast::NameOwner, display::*}; 6use syntax::{ast::NameOwner, display::*};
7use test_utils::mark; 7use test_utils::mark;
@@ -107,9 +107,16 @@ impl Completions {
107 } 107 }
108 }; 108 };
109 109
110 let mut ref_match = None;
110 if let ScopeDef::Local(local) = resolution { 111 if let ScopeDef::Local(local) = resolution {
111 if let Some(score) = compute_score(ctx, &local.ty(ctx.db), &local_name) { 112 if let Some((active_name, active_type)) = ctx.active_name_and_type() {
112 completion_item = completion_item.set_score(score); 113 let ty = local.ty(ctx.db);
114 if let Some(score) =
115 compute_score_from_active(&active_type, &active_name, &ty, &local_name)
116 {
117 completion_item = completion_item.set_score(score);
118 }
119 ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name);
113 } 120 }
114 } 121 }
115 122
@@ -131,7 +138,7 @@ impl Completions {
131 } 138 }
132 } 139 }
133 140
134 completion_item.kind(kind).set_documentation(docs).add_to(self) 141 completion_item.kind(kind).set_documentation(docs).set_ref_match(ref_match).add_to(self)
135 } 142 }
136 143
137 pub(crate) fn add_macro( 144 pub(crate) fn add_macro(
@@ -297,9 +304,14 @@ impl Completions {
297 ) { 304 ) {
298 let is_deprecated = is_deprecated(variant, ctx.db); 305 let is_deprecated = is_deprecated(variant, ctx.db);
299 let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string()); 306 let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string());
300 let qualified_name = match &path { 307 let (qualified_name, short_qualified_name) = match &path {
301 Some(it) => it.to_string(), 308 Some(path) => {
302 None => name.to_string(), 309 let full = path.to_string();
310 let short =
311 path.segments[path.segments.len().saturating_sub(2)..].iter().join("::");
312 (full, short)
313 }
314 None => (name.to_string(), name.to_string()),
303 }; 315 };
304 let detail_types = variant 316 let detail_types = variant
305 .fields(ctx.db) 317 .fields(ctx.db)
@@ -328,39 +340,27 @@ impl Completions {
328 .set_deprecated(is_deprecated) 340 .set_deprecated(is_deprecated)
329 .detail(detail); 341 .detail(detail);
330 342
331 if path.is_some() {
332 res = res.lookup_by(name);
333 }
334
335 if variant_kind == StructKind::Tuple { 343 if variant_kind == StructKind::Tuple {
336 mark::hit!(inserts_parens_for_tuple_enums); 344 mark::hit!(inserts_parens_for_tuple_enums);
337 let params = Params::Anonymous(variant.fields(ctx.db).len()); 345 let params = Params::Anonymous(variant.fields(ctx.db).len());
338 res = res.add_call_parens(ctx, qualified_name, params) 346 res = res.add_call_parens(ctx, short_qualified_name, params)
347 } else if path.is_some() {
348 res = res.lookup_by(short_qualified_name);
339 } 349 }
340 350
341 res.add_to(self); 351 res.add_to(self);
342 } 352 }
343} 353}
344 354
345pub(crate) fn compute_score( 355fn compute_score_from_active(
346 ctx: &CompletionContext, 356 active_type: &Type,
357 active_name: &str,
347 ty: &Type, 358 ty: &Type,
348 name: &str, 359 name: &str,
349) -> Option<CompletionScore> { 360) -> Option<CompletionScore> {
350 let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax {
351 mark::hit!(record_field_type_match);
352 let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?;
353 (struct_field.name(ctx.db).to_string(), struct_field.signature_ty(ctx.db))
354 } else if let Some(active_parameter) = &ctx.active_parameter {
355 mark::hit!(active_param_type_match);
356 (active_parameter.name.clone(), active_parameter.ty.clone())
357 } else {
358 return None;
359 };
360
361 // Compute score 361 // Compute score
362 // For the same type 362 // For the same type
363 if &active_type != ty { 363 if active_type != ty {
364 return None; 364 return None;
365 } 365 }
366 366
@@ -373,6 +373,24 @@ pub(crate) fn compute_score(
373 373
374 Some(res) 374 Some(res)
375} 375}
376fn refed_type_matches(
377 active_type: &Type,
378 active_name: &str,
379 ty: &Type,
380 name: &str,
381) -> Option<(Mutability, CompletionScore)> {
382 let derefed_active = active_type.remove_ref()?;
383 let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?;
384 Some((
385 if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared },
386 score,
387 ))
388}
389
390fn compute_score(ctx: &CompletionContext, ty: &Type, name: &str) -> Option<CompletionScore> {
391 let (active_name, active_type) = ctx.active_name_and_type()?;
392 compute_score_from_active(&active_type, &active_name, ty, name)
393}
376 394
377enum Params { 395enum Params {
378 Named(Vec<String>), 396 Named(Vec<String>),
@@ -592,6 +610,57 @@ fn main() { Foo::Fo<|> }
592 } 610 }
593 611
594 #[test] 612 #[test]
613 fn lookup_enums_by_two_qualifiers() {
614 check(
615 r#"
616mod m {
617 pub enum Spam { Foo, Bar(i32) }
618}
619fn main() { let _: m::Spam = S<|> }
620"#,
621 expect![[r#"
622 [
623 CompletionItem {
624 label: "Spam::Bar(…)",
625 source_range: 75..76,
626 delete: 75..76,
627 insert: "Spam::Bar($0)",
628 kind: EnumVariant,
629 lookup: "Spam::Bar",
630 detail: "(i32)",
631 trigger_call_info: true,
632 },
633 CompletionItem {
634 label: "m",
635 source_range: 75..76,
636 delete: 75..76,
637 insert: "m",
638 kind: Module,
639 },
640 CompletionItem {
641 label: "m::Spam::Foo",
642 source_range: 75..76,
643 delete: 75..76,
644 insert: "m::Spam::Foo",
645 kind: EnumVariant,
646 lookup: "Spam::Foo",
647 detail: "()",
648 },
649 CompletionItem {
650 label: "main()",
651 source_range: 75..76,
652 delete: 75..76,
653 insert: "main()$0",
654 kind: Function,
655 lookup: "main",
656 detail: "fn main()",
657 },
658 ]
659 "#]],
660 )
661 }
662
663 #[test]
595 fn sets_deprecated_flag_in_completion_items() { 664 fn sets_deprecated_flag_in_completion_items() {
596 check( 665 check(
597 r#" 666 r#"
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 7f169ccd2..63c1a8ebf 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -31,8 +31,7 @@ use hir_ty::{
31 autoderef, 31 autoderef,
32 display::{HirDisplayError, HirFormatter}, 32 display::{HirDisplayError, HirFormatter},
33 method_resolution, 33 method_resolution,
34 traits::Solution, 34 traits::{FnTrait, Solution, SolutionVariables},
35 traits::SolutionVariables,
36 ApplicationTy, BoundVar, CallableDefId, Canonical, DebruijnIndex, FnSig, GenericPredicate, 35 ApplicationTy, BoundVar, CallableDefId, Canonical, DebruijnIndex, FnSig, GenericPredicate,
37 InEnvironment, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, Ty, 36 InEnvironment, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, Ty,
38 TyDefId, TyKind, TypeCtor, 37 TyDefId, TyKind, TypeCtor,
@@ -781,6 +780,7 @@ impl Function {
781 } 780 }
782 781
783 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 782 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
783 hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink);
784 hir_ty::diagnostics::validate_module_item(db, self.id.into(), sink); 784 hir_ty::diagnostics::validate_module_item(db, self.id.into(), sink);
785 hir_ty::diagnostics::validate_body(db, self.id.into(), sink); 785 hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
786 } 786 }
@@ -1385,6 +1385,28 @@ impl Type {
1385 ) 1385 )
1386 } 1386 }
1387 1387
1388 /// Checks that particular type `ty` implements `std::ops::FnOnce`.
1389 ///
1390 /// This function can be used to check if a particular type is callable, since FnOnce is a
1391 /// supertrait of Fn and FnMut, so all callable types implements at least FnOnce.
1392 pub fn impls_fnonce(&self, db: &dyn HirDatabase) -> bool {
1393 let krate = self.krate;
1394
1395 let fnonce_trait = match FnTrait::FnOnce.get_id(db, krate) {
1396 Some(it) => it,
1397 None => return false,
1398 };
1399
1400 let canonical_ty = Canonical { value: self.ty.value.clone(), kinds: Arc::new([]) };
1401 method_resolution::implements_trait(
1402 &canonical_ty,
1403 db,
1404 self.ty.environment.clone(),
1405 krate,
1406 fnonce_trait,
1407 )
1408 }
1409
1388 pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> bool { 1410 pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> bool {
1389 let trait_ref = hir_ty::TraitRef { 1411 let trait_ref = hir_ty::TraitRef {
1390 trait_: trait_.id, 1412 trait_: trait_.id,
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index dea552a60..b2ce7ca3c 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -125,12 +125,20 @@ impl Attrs {
125 AttrQuery { attrs: self, key } 125 AttrQuery { attrs: self, key }
126 } 126 }
127 127
128 pub fn cfg(&self) -> impl Iterator<Item = CfgExpr> + '_ { 128 pub fn cfg(&self) -> Option<CfgExpr> {
129 // FIXME: handle cfg_attr :-) 129 // FIXME: handle cfg_attr :-)
130 self.by_key("cfg").tt_values().map(CfgExpr::parse) 130 let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse).collect::<Vec<_>>();
131 match cfgs.len() {
132 0 => None,
133 1 => Some(cfgs.pop().unwrap()),
134 _ => Some(CfgExpr::All(cfgs)),
135 }
131 } 136 }
132 pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool { 137 pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
133 self.cfg().all(|cfg| cfg_options.check(&cfg) != Some(false)) 138 match self.cfg() {
139 None => true,
140 Some(cfg) => cfg_options.check(&cfg) != Some(false),
141 }
134 } 142 }
135} 143}
136 144
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index d51036e4f..d10b1af01 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -1,6 +1,9 @@
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)]
6mod tests;
4pub mod scope; 7pub mod scope;
5 8
6use std::{mem, ops::Index, sync::Arc}; 9use std::{mem, ops::Index, sync::Arc};
@@ -10,7 +13,10 @@ use base_db::CrateId;
10use cfg::CfgOptions; 13use cfg::CfgOptions;
11use drop_bomb::DropBomb; 14use drop_bomb::DropBomb;
12use either::Either; 15use either::Either;
13use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId}; 16use hir_expand::{
17 ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, HirFileId, InFile,
18 MacroDefId,
19};
14use rustc_hash::FxHashMap; 20use rustc_hash::FxHashMap;
15use syntax::{ast, AstNode, AstPtr}; 21use syntax::{ast, AstNode, AstPtr};
16use test_utils::mark; 22use test_utils::mark;
@@ -150,8 +156,12 @@ impl Expander {
150 InFile { file_id: self.current_file_id, value } 156 InFile { file_id: self.current_file_id, value }
151 } 157 }
152 158
153 pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool { 159 pub(crate) fn parse_attrs(&self, owner: &dyn ast::AttrsOwner) -> Attrs {
154 self.cfg_expander.is_cfg_enabled(owner) 160 self.cfg_expander.parse_attrs(owner)
161 }
162
163 pub(crate) fn cfg_options(&self) -> &CfgOptions {
164 &self.cfg_expander.cfg_options
155 } 165 }
156 166
157 fn parse_path(&mut self, path: ast::Path) -> Option<Path> { 167 fn parse_path(&mut self, path: ast::Path) -> Option<Path> {
@@ -219,6 +229,10 @@ pub struct BodySourceMap {
219 pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, 229 pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>,
220 field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>, 230 field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>,
221 expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>, 231 expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
232
233 /// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
234 /// the source map (since they're just as volatile).
235 diagnostics: Vec<diagnostics::BodyDiagnostic>,
222} 236}
223 237
224#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] 238#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
@@ -318,45 +332,10 @@ impl BodySourceMap {
318 pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> { 332 pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> {
319 self.field_map[&(expr, field)].clone() 333 self.field_map[&(expr, field)].clone()
320 } 334 }
321}
322
323#[cfg(test)]
324mod tests {
325 use base_db::{fixture::WithFixture, SourceDatabase};
326 use test_utils::mark;
327 335
328 use crate::ModuleDefId; 336 pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) {
329 337 for diag in &self.diagnostics {
330 use super::*; 338 diag.add_to(sink);
331 339 }
332 fn lower(ra_fixture: &str) -> Arc<Body> {
333 let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture);
334
335 let krate = db.crate_graph().iter().next().unwrap();
336 let def_map = db.crate_def_map(krate);
337 let module = def_map.modules_for_file(file_id).next().unwrap();
338 let module = &def_map[module];
339 let fn_def = match module.scope.declarations().next().unwrap() {
340 ModuleDefId::FunctionId(it) => it,
341 _ => panic!(),
342 };
343
344 db.body(fn_def.into())
345 }
346
347 #[test]
348 fn your_stack_belongs_to_me() {
349 mark::check!(your_stack_belongs_to_me);
350 lower(
351 "
352macro_rules! n_nuple {
353 ($e:tt) => ();
354 ($($rest:tt)*) => {{
355 (n_nuple!($($rest)*)None,)
356 }};
357}
358fn main() { n_nuple!(1,2,3); }
359",
360 );
361 } 340 }
362} 341}
diff --git a/crates/hir_def/src/body/diagnostics.rs b/crates/hir_def/src/body/diagnostics.rs
new file mode 100644
index 000000000..cfa47d189
--- /dev/null
+++ b/crates/hir_def/src/body/diagnostics.rs
@@ -0,0 +1,20 @@
1//! Diagnostics emitted during body lowering.
2
3use hir_expand::diagnostics::DiagnosticSink;
4
5use crate::diagnostics::InactiveCode;
6
7#[derive(Debug, Eq, PartialEq)]
8pub enum BodyDiagnostic {
9 InactiveCode(InactiveCode),
10}
11
12impl BodyDiagnostic {
13 pub fn add_to(&self, sink: &mut DiagnosticSink<'_>) {
14 match self {
15 BodyDiagnostic::InactiveCode(diag) => {
16 sink.push(diag.clone());
17 }
18 }
19 }
20}
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 01e72690a..ddc267b83 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -16,7 +16,7 @@ use syntax::{
16 self, ArgListOwner, ArrayExprKind, AstChildren, LiteralKind, LoopBodyOwner, NameOwner, 16 self, ArgListOwner, ArrayExprKind, AstChildren, LiteralKind, LoopBodyOwner, NameOwner,
17 SlicePatComponents, 17 SlicePatComponents,
18 }, 18 },
19 AstNode, AstPtr, 19 AstNode, AstPtr, SyntaxNodePtr,
20}; 20};
21use test_utils::mark; 21use test_utils::mark;
22 22
@@ -25,6 +25,7 @@ use crate::{
25 body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax}, 25 body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax},
26 builtin_type::{BuiltinFloat, BuiltinInt}, 26 builtin_type::{BuiltinFloat, BuiltinInt},
27 db::DefDatabase, 27 db::DefDatabase,
28 diagnostics::InactiveCode,
28 expr::{ 29 expr::{
29 dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, 30 dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal,
30 LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, 31 LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
@@ -37,7 +38,7 @@ use crate::{
37 StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, 38 StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc,
38}; 39};
39 40
40use super::{ExprSource, PatSource}; 41use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource};
41 42
42pub(crate) struct LowerCtx { 43pub(crate) struct LowerCtx {
43 hygiene: Hygiene, 44 hygiene: Hygiene,
@@ -176,7 +177,7 @@ impl ExprCollector<'_> {
176 177
177 fn collect_expr(&mut self, expr: ast::Expr) -> ExprId { 178 fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
178 let syntax_ptr = AstPtr::new(&expr); 179 let syntax_ptr = AstPtr::new(&expr);
179 if !self.expander.is_cfg_enabled(&expr) { 180 if self.check_cfg(&expr).is_none() {
180 return self.missing_expr(); 181 return self.missing_expr();
181 } 182 }
182 183
@@ -354,13 +355,15 @@ impl ExprCollector<'_> {
354 let arms = if let Some(match_arm_list) = e.match_arm_list() { 355 let arms = if let Some(match_arm_list) = e.match_arm_list() {
355 match_arm_list 356 match_arm_list
356 .arms() 357 .arms()
357 .map(|arm| MatchArm { 358 .filter_map(|arm| {
358 pat: self.collect_pat_opt(arm.pat()), 359 self.check_cfg(&arm).map(|()| MatchArm {
359 expr: self.collect_expr_opt(arm.expr()), 360 pat: self.collect_pat_opt(arm.pat()),
360 guard: arm 361 expr: self.collect_expr_opt(arm.expr()),
361 .guard() 362 guard: arm
362 .and_then(|guard| guard.expr()) 363 .guard()
363 .map(|e| self.collect_expr(e)), 364 .and_then(|guard| guard.expr())
365 .map(|e| self.collect_expr(e)),
366 })
364 }) 367 })
365 .collect() 368 .collect()
366 } else { 369 } else {
@@ -406,9 +409,8 @@ impl ExprCollector<'_> {
406 .fields() 409 .fields()
407 .inspect(|field| field_ptrs.push(AstPtr::new(field))) 410 .inspect(|field| field_ptrs.push(AstPtr::new(field)))
408 .filter_map(|field| { 411 .filter_map(|field| {
409 if !self.expander.is_cfg_enabled(&field) { 412 self.check_cfg(&field)?;
410 return None; 413
411 }
412 let name = field.field_name()?.as_name(); 414 let name = field.field_name()?.as_name();
413 415
414 Some(RecordLitField { 416 Some(RecordLitField {
@@ -620,15 +622,23 @@ impl ExprCollector<'_> {
620 .filter_map(|s| { 622 .filter_map(|s| {
621 let stmt = match s { 623 let stmt = match s {
622 ast::Stmt::LetStmt(stmt) => { 624 ast::Stmt::LetStmt(stmt) => {
625 self.check_cfg(&stmt)?;
626
623 let pat = self.collect_pat_opt(stmt.pat()); 627 let pat = self.collect_pat_opt(stmt.pat());
624 let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it)); 628 let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
625 let initializer = stmt.initializer().map(|e| self.collect_expr(e)); 629 let initializer = stmt.initializer().map(|e| self.collect_expr(e));
626 Statement::Let { pat, type_ref, initializer } 630 Statement::Let { pat, type_ref, initializer }
627 } 631 }
628 ast::Stmt::ExprStmt(stmt) => { 632 ast::Stmt::ExprStmt(stmt) => {
633 self.check_cfg(&stmt)?;
634
629 Statement::Expr(self.collect_expr_opt(stmt.expr())) 635 Statement::Expr(self.collect_expr_opt(stmt.expr()))
630 } 636 }
631 ast::Stmt::Item(_) => return None, 637 ast::Stmt::Item(item) => {
638 self.check_cfg(&item)?;
639
640 return None;
641 }
632 }; 642 };
633 Some(stmt) 643 Some(stmt)
634 }) 644 })
@@ -872,6 +882,28 @@ impl ExprCollector<'_> {
872 882
873 (args, ellipsis) 883 (args, ellipsis)
874 } 884 }
885
886 /// Returns `None` (and emits diagnostics) when `owner` if `#[cfg]`d out, and `Some(())` when
887 /// not.
888 fn check_cfg(&mut self, owner: &dyn ast::AttrsOwner) -> Option<()> {
889 match self.expander.parse_attrs(owner).cfg() {
890 Some(cfg) => {
891 if self.expander.cfg_options().check(&cfg) != Some(false) {
892 return Some(());
893 }
894
895 self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode(InactiveCode {
896 file: self.expander.current_file_id,
897 node: SyntaxNodePtr::new(owner.syntax()),
898 cfg,
899 opts: self.expander.cfg_options().clone(),
900 }));
901
902 None
903 }
904 None => Some(()),
905 }
906 }
875} 907}
876 908
877impl From<ast::BinOp> for BinaryOp { 909impl From<ast::BinOp> for BinaryOp {
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
new file mode 100644
index 000000000..f07df5cee
--- /dev/null
+++ b/crates/hir_def/src/body/tests.rs
@@ -0,0 +1,75 @@
1use base_db::{fixture::WithFixture, SourceDatabase};
2use test_utils::mark;
3
4use crate::{test_db::TestDB, ModuleDefId};
5
6use super::*;
7
8fn lower(ra_fixture: &str) -> Arc<Body> {
9 let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture);
10
11 let krate = db.crate_graph().iter().next().unwrap();
12 let def_map = db.crate_def_map(krate);
13 let module = def_map.modules_for_file(file_id).next().unwrap();
14 let module = &def_map[module];
15 let fn_def = match module.scope.declarations().next().unwrap() {
16 ModuleDefId::FunctionId(it) => it,
17 _ => panic!(),
18 };
19
20 db.body(fn_def.into())
21}
22
23fn check_diagnostics(ra_fixture: &str) {
24 let db: TestDB = TestDB::with_files(ra_fixture);
25 db.check_diagnostics();
26}
27
28#[test]
29fn your_stack_belongs_to_me() {
30 mark::check!(your_stack_belongs_to_me);
31 lower(
32 "
33macro_rules! n_nuple {
34 ($e:tt) => ();
35 ($($rest:tt)*) => {{
36 (n_nuple!($($rest)*)None,)
37 }};
38}
39fn main() { n_nuple!(1,2,3); }
40",
41 );
42}
43
44#[test]
45fn cfg_diagnostics() {
46 check_diagnostics(
47 r"
48fn f() {
49 // The three g̶e̶n̶d̶e̶r̶s̶ statements:
50
51 #[cfg(a)] fn f() {} // Item statement
52 //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
53 #[cfg(a)] {} // Expression statement
54 //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
55 #[cfg(a)] let x = 0; // let statement
56 //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
57
58 abc(#[cfg(a)] 0);
59 //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
60 let x = Struct {
61 #[cfg(a)] f: 0,
62 //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
63 };
64 match () {
65 () => (),
66 #[cfg(a)] () => (),
67 //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
68 }
69
70 #[cfg(a)] 0 // Trailing expression of block
71 //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
72}
73 ",
74 );
75}
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs
index c9c08b01f..b221b290c 100644
--- a/crates/hir_def/src/diagnostics.rs
+++ b/crates/hir_def/src/diagnostics.rs
@@ -1,11 +1,19 @@
1//! Diagnostics produced by `hir_def`. 1//! Diagnostics produced by `hir_def`.
2 2
3use std::any::Any; 3use std::any::Any;
4use stdx::format_to;
4 5
5use hir_expand::diagnostics::{Diagnostic, DiagnosticCode}; 6use cfg::{CfgExpr, CfgOptions, DnfExpr};
7use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
8use hir_expand::{HirFileId, InFile};
6use syntax::{ast, AstPtr, SyntaxNodePtr}; 9use syntax::{ast, AstPtr, SyntaxNodePtr};
7 10
8use hir_expand::{HirFileId, InFile}; 11use crate::{db::DefDatabase, 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}
9 17
10// Diagnostic: unresolved-module 18// Diagnostic: unresolved-module
11// 19//
@@ -87,13 +95,15 @@ impl Diagnostic for UnresolvedImport {
87 } 95 }
88} 96}
89 97
90// Diagnostic: unconfigured-code 98// Diagnostic: inactive-code
91// 99//
92// This diagnostic is shown for code with inactive `#[cfg]` attributes. 100// This diagnostic is shown for code with inactive `#[cfg]` attributes.
93#[derive(Debug)] 101#[derive(Debug, Clone, Eq, PartialEq)]
94pub struct InactiveCode { 102pub struct InactiveCode {
95 pub file: HirFileId, 103 pub file: HirFileId,
96 pub node: SyntaxNodePtr, 104 pub node: SyntaxNodePtr,
105 pub cfg: CfgExpr,
106 pub opts: CfgOptions,
97} 107}
98 108
99impl Diagnostic for InactiveCode { 109impl Diagnostic for InactiveCode {
@@ -101,8 +111,14 @@ impl Diagnostic for InactiveCode {
101 DiagnosticCode("inactive-code") 111 DiagnosticCode("inactive-code")
102 } 112 }
103 fn message(&self) -> String { 113 fn message(&self) -> String {
104 // FIXME: say *why* it is configured out 114 let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
105 "code is inactive due to #[cfg] directives".to_string() 115 let mut buf = "code is inactive due to #[cfg] directives".to_string();
116
117 if let Some(inactive) = inactive {
118 format_to!(buf, ": {}", inactive);
119 }
120
121 buf
106 } 122 }
107 fn display_source(&self) -> InFile<SyntaxNodePtr> { 123 fn display_source(&self) -> InFile<SyntaxNodePtr> {
108 InFile::new(self.file, self.node.clone()) 124 InFile::new(self.file, self.node.clone())
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index 3328639cf..ca7fb4a43 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -3,7 +3,7 @@
3use std::{collections::hash_map::Entry, mem, sync::Arc}; 3use std::{collections::hash_map::Entry, mem, sync::Arc};
4 4
5use arena::map::ArenaMap; 5use arena::map::ArenaMap;
6use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId}; 6use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, name::known, HirFileId};
7use smallvec::SmallVec; 7use smallvec::SmallVec;
8use syntax::{ 8use syntax::{
9 ast::{self, ModuleItemOwner}, 9 ast::{self, ModuleItemOwner},
@@ -555,7 +555,8 @@ impl Ctx {
555 let id: ModItem = match item { 555 let id: ModItem = match item {
556 ast::ExternItem::Fn(ast) => { 556 ast::ExternItem::Fn(ast) => {
557 let func = self.lower_function(&ast)?; 557 let func = self.lower_function(&ast)?;
558 self.data().functions[func.index].is_unsafe = true; 558 self.data().functions[func.index].is_unsafe =
559 is_intrinsic_fn_unsafe(&self.data().functions[func.index].name);
559 func.into() 560 func.into()
560 } 561 }
561 ast::ExternItem::Static(ast) => { 562 ast::ExternItem::Static(ast) => {
@@ -713,3 +714,45 @@ enum GenericsOwner<'a> {
713 TypeAlias, 714 TypeAlias,
714 Impl, 715 Impl,
715} 716}
717
718/// Returns `true` if the given intrinsic is unsafe to call, or false otherwise.
719fn is_intrinsic_fn_unsafe(name: &Name) -> bool {
720 // Should be kept in sync with https://github.com/rust-lang/rust/blob/c6e4db620a7d2f569f11dcab627430921ea8aacf/compiler/rustc_typeck/src/check/intrinsic.rs#L68
721 ![
722 known::abort,
723 known::min_align_of,
724 known::needs_drop,
725 known::caller_location,
726 known::size_of_val,
727 known::min_align_of_val,
728 known::add_with_overflow,
729 known::sub_with_overflow,
730 known::mul_with_overflow,
731 known::wrapping_add,
732 known::wrapping_sub,
733 known::wrapping_mul,
734 known::saturating_add,
735 known::saturating_sub,
736 known::rotate_left,
737 known::rotate_right,
738 known::ctpop,
739 known::ctlz,
740 known::cttz,
741 known::bswap,
742 known::bitreverse,
743 known::discriminant_value,
744 known::type_id,
745 known::likely,
746 known::unlikely,
747 known::ptr_guaranteed_eq,
748 known::ptr_guaranteed_ne,
749 known::minnumf32,
750 known::minnumf64,
751 known::maxnumf32,
752 known::rustc_peek,
753 known::maxnumf64,
754 known::type_name,
755 known::variant_count,
756 ]
757 .contains(&name)
758}
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index 01a28aeeb..eb41d324e 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -283,6 +283,7 @@ pub enum ModuleSource {
283} 283}
284 284
285mod diagnostics { 285mod diagnostics {
286 use cfg::{CfgExpr, CfgOptions};
286 use hir_expand::diagnostics::DiagnosticSink; 287 use hir_expand::diagnostics::DiagnosticSink;
287 use hir_expand::hygiene::Hygiene; 288 use hir_expand::hygiene::Hygiene;
288 use hir_expand::InFile; 289 use hir_expand::InFile;
@@ -299,7 +300,7 @@ mod diagnostics {
299 300
300 UnresolvedImport { ast: AstId<ast::Use>, index: usize }, 301 UnresolvedImport { ast: AstId<ast::Use>, index: usize },
301 302
302 UnconfiguredCode { ast: InFile<SyntaxNodePtr> }, 303 UnconfiguredCode { ast: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
303 } 304 }
304 305
305 #[derive(Debug, PartialEq, Eq)] 306 #[derive(Debug, PartialEq, Eq)]
@@ -341,8 +342,10 @@ mod diagnostics {
341 pub(super) fn unconfigured_code( 342 pub(super) fn unconfigured_code(
342 container: LocalModuleId, 343 container: LocalModuleId,
343 ast: InFile<SyntaxNodePtr>, 344 ast: InFile<SyntaxNodePtr>,
345 cfg: CfgExpr,
346 opts: CfgOptions,
344 ) -> Self { 347 ) -> Self {
345 Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast } } 348 Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
346 } 349 }
347 350
348 pub(super) fn add_to( 351 pub(super) fn add_to(
@@ -395,8 +398,13 @@ mod diagnostics {
395 } 398 }
396 } 399 }
397 400
398 DiagnosticKind::UnconfiguredCode { ast } => { 401 DiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
399 sink.push(InactiveCode { file: ast.file_id, node: ast.value.clone() }); 402 sink.push(InactiveCode {
403 file: ast.file_id,
404 node: ast.value.clone(),
405 cfg: cfg.clone(),
406 opts: opts.clone(),
407 });
400 } 408 }
401 } 409 }
402 } 410 }
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index bff8edb62..f30172d90 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -6,7 +6,7 @@
6use std::iter; 6use std::iter;
7 7
8use base_db::{CrateId, FileId, ProcMacroId}; 8use base_db::{CrateId, FileId, ProcMacroId};
9use cfg::CfgOptions; 9use cfg::{CfgExpr, CfgOptions};
10use hir_expand::InFile; 10use hir_expand::InFile;
11use hir_expand::{ 11use hir_expand::{
12 ast_id_map::FileAstId, 12 ast_id_map::FileAstId,
@@ -900,7 +900,8 @@ impl ModCollector<'_, '_> {
900 // `#[macro_use] extern crate` is hoisted to imports macros before collecting 900 // `#[macro_use] extern crate` is hoisted to imports macros before collecting
901 // any other items. 901 // any other items.
902 for item in items { 902 for item in items {
903 if self.is_cfg_enabled(self.item_tree.attrs((*item).into())) { 903 let attrs = self.item_tree.attrs((*item).into());
904 if attrs.cfg().map_or(true, |cfg| self.is_cfg_enabled(&cfg)) {
904 if let ModItem::ExternCrate(id) = item { 905 if let ModItem::ExternCrate(id) = item {
905 let import = self.item_tree[*id].clone(); 906 let import = self.item_tree[*id].clone();
906 if import.is_macro_use { 907 if import.is_macro_use {
@@ -912,9 +913,11 @@ impl ModCollector<'_, '_> {
912 913
913 for &item in items { 914 for &item in items {
914 let attrs = self.item_tree.attrs(item.into()); 915 let attrs = self.item_tree.attrs(item.into());
915 if !self.is_cfg_enabled(attrs) { 916 if let Some(cfg) = attrs.cfg() {
916 self.emit_unconfigured_diagnostic(item); 917 if !self.is_cfg_enabled(&cfg) {
917 continue; 918 self.emit_unconfigured_diagnostic(item, &cfg);
919 continue;
920 }
918 } 921 }
919 let module = 922 let module =
920 ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; 923 ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id };
@@ -1321,20 +1324,22 @@ impl ModCollector<'_, '_> {
1321 } 1324 }
1322 } 1325 }
1323 1326
1324 fn is_cfg_enabled(&self, attrs: &Attrs) -> bool { 1327 fn is_cfg_enabled(&self, cfg: &CfgExpr) -> bool {
1325 attrs.is_cfg_enabled(self.def_collector.cfg_options) 1328 self.def_collector.cfg_options.check(cfg) != Some(false)
1326 } 1329 }
1327 1330
1328 fn emit_unconfigured_diagnostic(&mut self, item: ModItem) { 1331 fn emit_unconfigured_diagnostic(&mut self, item: ModItem, cfg: &CfgExpr) {
1329 let ast_id = item.ast_id(self.item_tree); 1332 let ast_id = item.ast_id(self.item_tree);
1330 let id_map = self.def_collector.db.ast_id_map(self.file_id); 1333 let id_map = self.def_collector.db.ast_id_map(self.file_id);
1331 let syntax_ptr = id_map.get(ast_id).syntax_node_ptr(); 1334 let syntax_ptr = id_map.get(ast_id).syntax_node_ptr();
1332 1335
1333 let ast_node = InFile::new(self.file_id, syntax_ptr); 1336 let ast_node = InFile::new(self.file_id, syntax_ptr);
1334 self.def_collector 1337 self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code(
1335 .def_map 1338 self.module_id,
1336 .diagnostics 1339 ast_node,
1337 .push(DefDiagnostic::unconfigured_code(self.module_id, ast_node)); 1340 cfg.clone(),
1341 self.def_collector.cfg_options.clone(),
1342 ));
1338 } 1343 }
1339} 1344}
1340 1345
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
index 576b813d2..1a7b98831 100644
--- a/crates/hir_def/src/nameres/tests/diagnostics.rs
+++ b/crates/hir_def/src/nameres/tests/diagnostics.rs
@@ -1,42 +1,10 @@
1use base_db::fixture::WithFixture; 1use base_db::fixture::WithFixture;
2use base_db::FileId;
3use base_db::SourceDatabaseExt;
4use hir_expand::db::AstDatabase;
5use rustc_hash::FxHashMap;
6use syntax::TextRange;
7use syntax::TextSize;
8 2
9use crate::test_db::TestDB; 3use crate::test_db::TestDB;
10 4
11fn check_diagnostics(ra_fixture: &str) { 5fn check_diagnostics(ra_fixture: &str) {
12 let db: TestDB = TestDB::with_files(ra_fixture); 6 let db: TestDB = TestDB::with_files(ra_fixture);
13 let annotations = db.extract_annotations(); 7 db.check_diagnostics();
14 assert!(!annotations.is_empty());
15
16 let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
17 db.diagnostics(|d| {
18 let src = d.display_source();
19 let root = db.parse_or_expand(src.file_id).unwrap();
20 // FIXME: macros...
21 let file_id = src.file_id.original_file(&db);
22 let range = src.value.to_node(&root).text_range();
23 let message = d.message().to_owned();
24 actual.entry(file_id).or_default().push((range, message));
25 });
26
27 for (file_id, diags) in actual.iter_mut() {
28 diags.sort_by_key(|it| it.0.start());
29 let text = db.file_text(*file_id);
30 // For multiline spans, place them on line start
31 for (range, content) in diags {
32 if text[*range].contains('\n') {
33 *range = TextRange::new(range.start(), range.start() + TextSize::from(1));
34 *content = format!("... {}", content);
35 }
36 }
37 }
38
39 assert_eq!(annotations, actual);
40} 8}
41 9
42#[test] 10#[test]
@@ -129,3 +97,25 @@ fn unresolved_module() {
129 ", 97 ",
130 ); 98 );
131} 99}
100
101#[test]
102fn inactive_item() {
103 // Additional tests in `cfg` crate. This only tests disabled cfgs.
104
105 check_diagnostics(
106 r#"
107 //- /lib.rs
108 #[cfg(no)] pub fn f() {}
109 //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
110
111 #[cfg(no)] #[cfg(no2)] mod m;
112 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled
113
114 #[cfg(all(not(a), b))] enum E {}
115 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled
116
117 #[cfg(feature = "std")] use std;
118 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled
119 "#,
120 );
121}
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index fb1d3c974..2b36c824a 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -12,10 +12,10 @@ use hir_expand::diagnostics::Diagnostic;
12use hir_expand::diagnostics::DiagnosticSinkBuilder; 12use hir_expand::diagnostics::DiagnosticSinkBuilder;
13use rustc_hash::FxHashMap; 13use rustc_hash::FxHashMap;
14use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
15use syntax::TextRange; 15use syntax::{TextRange, TextSize};
16use test_utils::extract_annotations; 16use test_utils::extract_annotations;
17 17
18use crate::db::DefDatabase; 18use crate::{db::DefDatabase, ModuleDefId};
19 19
20#[salsa::database( 20#[salsa::database(
21 base_db::SourceDatabaseExtStorage, 21 base_db::SourceDatabaseExtStorage,
@@ -135,9 +135,47 @@ impl TestDB {
135 let crate_def_map = self.crate_def_map(krate); 135 let crate_def_map = self.crate_def_map(krate);
136 136
137 let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); 137 let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
138 for (module_id, _) in crate_def_map.modules.iter() { 138 for (module_id, module) in crate_def_map.modules.iter() {
139 crate_def_map.add_diagnostics(self, module_id, &mut sink); 139 crate_def_map.add_diagnostics(self, module_id, &mut sink);
140
141 for decl in module.scope.declarations() {
142 if let ModuleDefId::FunctionId(it) = decl {
143 let source_map = self.body_with_source_map(it.into()).1;
144 source_map.add_diagnostics(self, &mut sink);
145 }
146 }
140 } 147 }
141 } 148 }
142 } 149 }
150
151 pub fn check_diagnostics(&self) {
152 let db: &TestDB = self;
153 let annotations = db.extract_annotations();
154 assert!(!annotations.is_empty());
155
156 let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
157 db.diagnostics(|d| {
158 let src = d.display_source();
159 let root = db.parse_or_expand(src.file_id).unwrap();
160 // FIXME: macros...
161 let file_id = src.file_id.original_file(db);
162 let range = src.value.to_node(&root).text_range();
163 let message = d.message().to_owned();
164 actual.entry(file_id).or_default().push((range, message));
165 });
166
167 for (file_id, diags) in actual.iter_mut() {
168 diags.sort_by_key(|it| it.0.start());
169 let text = db.file_text(*file_id);
170 // For multiline spans, place them on line start
171 for (range, content) in diags {
172 if text[*range].contains('\n') {
173 *range = TextRange::new(range.start(), range.start() + TextSize::from(1));
174 *content = format!("... {}", content);
175 }
176 }
177 }
178
179 assert_eq!(annotations, actual);
180 }
143} 181}
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index 63f828707..b26ffa1ef 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -208,6 +208,42 @@ pub mod known {
208 PartialOrd, 208 PartialOrd,
209 Eq, 209 Eq,
210 PartialEq, 210 PartialEq,
211 // Safe intrinsics
212 abort,
213 size_of,
214 min_align_of,
215 needs_drop,
216 caller_location,
217 size_of_val,
218 min_align_of_val,
219 add_with_overflow,
220 sub_with_overflow,
221 mul_with_overflow,
222 wrapping_add,
223 wrapping_sub,
224 wrapping_mul,
225 saturating_add,
226 saturating_sub,
227 rotate_left,
228 rotate_right,
229 ctpop,
230 ctlz,
231 cttz,
232 bswap,
233 bitreverse,
234 discriminant_value,
235 type_id,
236 likely,
237 unlikely,
238 ptr_guaranteed_eq,
239 ptr_guaranteed_ne,
240 minnumf32,
241 minnumf64,
242 maxnumf32,
243 rustc_peek,
244 maxnumf64,
245 type_name,
246 variant_count,
211 ); 247 );
212 248
213 // self/Self cannot be used as an identifier 249 // self/Self cannot be used as an identifier
diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs
index f987636fe..f179c62b7 100644
--- a/crates/hir_ty/src/diagnostics/decl_check.rs
+++ b/crates/hir_ty/src/diagnostics/decl_check.rs
@@ -708,11 +708,23 @@ fn foo() {
708 } 708 }
709 709
710 #[test] 710 #[test]
711 fn incorrect_struct_name() { 711 fn incorrect_struct_names() {
712 check_diagnostics( 712 check_diagnostics(
713 r#" 713 r#"
714struct non_camel_case_name {} 714struct non_camel_case_name {}
715 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName` 715 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
716
717struct SCREAMING_CASE {}
718 // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
719"#,
720 );
721 }
722
723 #[test]
724 fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
725 check_diagnostics(
726 r#"
727struct AABB {}
716"#, 728"#,
717 ); 729 );
718 } 730 }
@@ -728,11 +740,23 @@ struct SomeStruct { SomeField: u8 }
728 } 740 }
729 741
730 #[test] 742 #[test]
731 fn incorrect_enum_name() { 743 fn incorrect_enum_names() {
732 check_diagnostics( 744 check_diagnostics(
733 r#" 745 r#"
734enum some_enum { Val(u8) } 746enum some_enum { Val(u8) }
735 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum` 747 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
748
749enum SOME_ENUM
750 // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
751"#,
752 );
753 }
754
755 #[test]
756 fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
757 check_diagnostics(
758 r#"
759enum AABB {}
736"#, 760"#,
737 ); 761 );
738 } 762 }
diff --git a/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs b/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs
index 3800f2a6b..324d60765 100644
--- a/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs
+++ b/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs
@@ -29,7 +29,13 @@ fn detect_case(ident: &str) -> DetectedCase {
29 29
30 if has_uppercase { 30 if has_uppercase {
31 if !has_lowercase { 31 if !has_lowercase {
32 DetectedCase::UpperSnakeCase 32 if has_underscore {
33 DetectedCase::UpperSnakeCase
34 } else {
35 // It has uppercase only and no underscores. Ex: "AABB"
36 // This is a camel cased acronym.
37 DetectedCase::UpperCamelCase
38 }
33 } else if !has_underscore { 39 } else if !has_underscore {
34 if first_lowercase { 40 if first_lowercase {
35 DetectedCase::LowerCamelCase 41 DetectedCase::LowerCamelCase
@@ -180,6 +186,7 @@ mod tests {
180 check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]); 186 check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]);
181 check(to_camel_case, "name", expect![["Name"]]); 187 check(to_camel_case, "name", expect![["Name"]]);
182 check(to_camel_case, "A", expect![[""]]); 188 check(to_camel_case, "A", expect![[""]]);
189 check(to_camel_case, "AABB", expect![[""]]);
183 } 190 }
184 191
185 #[test] 192 #[test]
diff --git a/crates/hir_ty/src/diagnostics/unsafe_check.rs b/crates/hir_ty/src/diagnostics/unsafe_check.rs
index 21a121aad..2da9688ca 100644
--- a/crates/hir_ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs
@@ -202,4 +202,22 @@ fn main() {
202"#, 202"#,
203 ); 203 );
204 } 204 }
205
206 #[test]
207 fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
208 check_diagnostics(
209 r#"
210extern "rust-intrinsic" {
211 pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
212 pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
213}
214
215fn main() {
216 let _ = bitreverse(12);
217 let _ = floorf32(12.0);
218 //^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
219}
220"#,
221 );
222 }
205} 223}
diff --git a/crates/hir_ty/src/traits.rs b/crates/hir_ty/src/traits.rs
index 14cd3a2b4..ce1174cbe 100644
--- a/crates/hir_ty/src/traits.rs
+++ b/crates/hir_ty/src/traits.rs
@@ -5,6 +5,7 @@ use base_db::CrateId;
5use chalk_ir::cast::Cast; 5use chalk_ir::cast::Cast;
6use chalk_solve::{logging_db::LoggingRustIrDatabase, Solver}; 6use chalk_solve::{logging_db::LoggingRustIrDatabase, Solver};
7use hir_def::{lang_item::LangItemTarget, TraitId}; 7use hir_def::{lang_item::LangItemTarget, TraitId};
8use stdx::panic_context;
8 9
9use crate::{db::HirDatabase, DebruijnIndex, Substs}; 10use crate::{db::HirDatabase, DebruijnIndex, Substs};
10 11
@@ -168,14 +169,23 @@ fn solve(
168 }; 169 };
169 170
170 let mut solve = || { 171 let mut solve = || {
171 if is_chalk_print() { 172 let _ctx = if is_chalk_debug() || is_chalk_print() {
172 let logging_db = LoggingRustIrDatabase::new(context); 173 Some(panic_context::enter(format!("solving {:?}", goal)))
173 let solution = solver.solve_limited(&logging_db, goal, &should_continue); 174 } else {
174 log::debug!("chalk program:\n{}", logging_db); 175 None
176 };
177 let solution = if is_chalk_print() {
178 let logging_db =
179 LoggingRustIrDatabaseLoggingOnDrop(LoggingRustIrDatabase::new(context));
180 let solution = solver.solve_limited(&logging_db.0, goal, &should_continue);
175 solution 181 solution
176 } else { 182 } else {
177 solver.solve_limited(&context, goal, &should_continue) 183 solver.solve_limited(&context, goal, &should_continue)
178 } 184 };
185
186 log::debug!("solve({:?}) => {:?}", goal, solution);
187
188 solution
179 }; 189 };
180 190
181 // don't set the TLS for Chalk unless Chalk debugging is active, to make 191 // don't set the TLS for Chalk unless Chalk debugging is active, to make
@@ -183,11 +193,17 @@ fn solve(
183 let solution = 193 let solution =
184 if is_chalk_debug() { chalk::tls::set_current_program(db, solve) } else { solve() }; 194 if is_chalk_debug() { chalk::tls::set_current_program(db, solve) } else { solve() };
185 195
186 log::debug!("solve({:?}) => {:?}", goal, solution);
187
188 solution 196 solution
189} 197}
190 198
199struct LoggingRustIrDatabaseLoggingOnDrop<'a>(LoggingRustIrDatabase<Interner, ChalkContext<'a>>);
200
201impl<'a> Drop for LoggingRustIrDatabaseLoggingOnDrop<'a> {
202 fn drop(&mut self) {
203 eprintln!("chalk program:\n{}", self.0);
204 }
205}
206
191fn is_chalk_debug() -> bool { 207fn is_chalk_debug() -> bool {
192 std::env::var("CHALK_DEBUG").is_ok() 208 std::env::var("CHALK_DEBUG").is_ok()
193} 209}
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index b9d8b8a2b..250f10f9f 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -132,7 +132,8 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
132 let import_map = db.import_map(krate.into()); 132 let import_map = db.import_map(krate.into());
133 let base = once(krate.display_name(db)?.to_string()) 133 let base = once(krate.display_name(db)?.to_string())
134 .chain(import_map.path_of(ns)?.segments.iter().map(|name| name.to_string())) 134 .chain(import_map.path_of(ns)?.segments.iter().map(|name| name.to_string()))
135 .join("/"); 135 .join("/")
136 + "/";
136 137
137 let filename = get_symbol_filename(db, &target_def); 138 let filename = get_symbol_filename(db, &target_def);
138 let fragment = match definition { 139 let fragment = match definition {
@@ -152,9 +153,16 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
152 _ => None, 153 _ => None,
153 }; 154 };
154 155
155 get_doc_url(db, &krate) 156 get_doc_url(db, &krate)?
156 .and_then(|url| url.join(&base).ok()) 157 .join(&base)
157 .and_then(|url| filename.as_deref().and_then(|f| url.join(f).ok())) 158 .ok()
159 .and_then(|mut url| {
160 if !matches!(definition, Definition::ModuleDef(ModuleDef::Module(..))) {
161 url.path_segments_mut().ok()?.pop();
162 };
163 Some(url)
164 })
165 .and_then(|url| url.join(filename.as_deref()?).ok())
158 .and_then( 166 .and_then(
159 |url| if let Some(fragment) = fragment { url.join(&fragment).ok() } else { Some(url) }, 167 |url| if let Some(fragment) = fragment { url.join(&fragment).ok() } else { Some(url) },
160 ) 168 )
@@ -522,6 +530,18 @@ pub struct Foo {
522 ); 530 );
523 } 531 }
524 532
533 #[test]
534 fn test_module() {
535 check(
536 r#"
537pub mod foo {
538 pub mod ba<|>r {}
539}
540 "#,
541 expect![[r#"https://docs.rs/test/*/test/foo/bar/index.html"#]],
542 )
543 }
544
525 // FIXME: ImportMap will return re-export paths instead of public module 545 // FIXME: ImportMap will return re-export paths instead of public module
526 // paths. The correct path to documentation will never be a re-export. 546 // paths. The correct path to documentation will never be a re-export.
527 // This problem stops us from resolving stdlib items included in the prelude 547 // This problem stops us from resolving stdlib items included in the prelude
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 6466422c5..0332c7be0 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -2128,7 +2128,7 @@ fn foo_<|>test() {}
2128 ignore: false, 2128 ignore: false,
2129 }, 2129 },
2130 }, 2130 },
2131 cfg_exprs: [], 2131 cfg: None,
2132 }, 2132 },
2133 ), 2133 ),
2134 ] 2134 ]
@@ -2166,7 +2166,7 @@ mod tests<|> {
2166 kind: TestMod { 2166 kind: TestMod {
2167 path: "tests", 2167 path: "tests",
2168 }, 2168 },
2169 cfg_exprs: [], 2169 cfg: None,
2170 }, 2170 },
2171 ), 2171 ),
2172 ] 2172 ]
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index cccea129a..49d8e4ae1 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -378,7 +378,11 @@ fn is_enum_name_similar_to_param_name(
378fn get_string_representation(expr: &ast::Expr) -> Option<String> { 378fn get_string_representation(expr: &ast::Expr) -> Option<String> {
379 match expr { 379 match expr {
380 ast::Expr::MethodCallExpr(method_call_expr) => { 380 ast::Expr::MethodCallExpr(method_call_expr) => {
381 Some(method_call_expr.name_ref()?.to_string()) 381 let name_ref = method_call_expr.name_ref()?;
382 match name_ref.text().as_str() {
383 "clone" => method_call_expr.receiver().map(|rec| rec.to_string()),
384 name_ref => Some(name_ref.to_owned()),
385 }
382 } 386 }
383 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?), 387 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
384 _ => Some(expr.to_string()), 388 _ => Some(expr.to_string()),
@@ -1208,4 +1212,27 @@ fn main() {
1208"#, 1212"#,
1209 ); 1213 );
1210 } 1214 }
1215
1216 #[test]
1217 fn hide_param_hints_for_clones() {
1218 check_with_config(
1219 InlayHintsConfig {
1220 parameter_hints: true,
1221 type_hints: false,
1222 chaining_hints: false,
1223 max_length: None,
1224 },
1225 r#"
1226fn foo(bar: i32, baz: String, qux: f32) {}
1227
1228fn main() {
1229 let bar = 3;
1230 let baz = &"baz";
1231 let fez = 1.0;
1232 foo(bar.clone(), baz.clone(), fez.clone());
1233 //^^^^^^^^^^^ qux
1234}
1235"#,
1236 );
1237 }
1211} 1238}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 752ef2f21..eb82456ad 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -15,7 +15,7 @@ use crate::{display::ToNav, FileId, NavigationTarget};
15pub struct Runnable { 15pub struct Runnable {
16 pub nav: NavigationTarget, 16 pub nav: NavigationTarget,
17 pub kind: RunnableKind, 17 pub kind: RunnableKind,
18 pub cfg_exprs: Vec<CfgExpr>, 18 pub cfg: Option<CfgExpr>,
19} 19}
20 20
21#[derive(Debug, Clone)] 21#[derive(Debug, Clone)]
@@ -168,7 +168,7 @@ fn runnable_fn(
168 }; 168 };
169 169
170 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def)); 170 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
171 let cfg_exprs = attrs.cfg().collect(); 171 let cfg = attrs.cfg();
172 172
173 let nav = if let RunnableKind::DocTest { .. } = kind { 173 let nav = if let RunnableKind::DocTest { .. } = kind {
174 NavigationTarget::from_doc_commented( 174 NavigationTarget::from_doc_commented(
@@ -179,7 +179,7 @@ fn runnable_fn(
179 } else { 179 } else {
180 NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)) 180 NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def))
181 }; 181 };
182 Some(Runnable { nav, kind, cfg_exprs }) 182 Some(Runnable { nav, kind, cfg })
183} 183}
184 184
185#[derive(Debug, Copy, Clone)] 185#[derive(Debug, Copy, Clone)]
@@ -255,9 +255,9 @@ fn runnable_mod(
255 .join("::"); 255 .join("::");
256 256
257 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module)); 257 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
258 let cfg_exprs = attrs.cfg().collect(); 258 let cfg = attrs.cfg();
259 let nav = module_def.to_nav(sema.db); 259 let nav = module_def.to_nav(sema.db);
260 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) 260 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
261} 261}
262 262
263// We could create runnables for modules with number_of_test_submodules > 0, 263// We could create runnables for modules with number_of_test_submodules > 0,
@@ -348,7 +348,7 @@ fn bench() {}
348 docs: None, 348 docs: None,
349 }, 349 },
350 kind: Bin, 350 kind: Bin,
351 cfg_exprs: [], 351 cfg: None,
352 }, 352 },
353 Runnable { 353 Runnable {
354 nav: NavigationTarget { 354 nav: NavigationTarget {
@@ -373,7 +373,7 @@ fn bench() {}
373 ignore: false, 373 ignore: false,
374 }, 374 },
375 }, 375 },
376 cfg_exprs: [], 376 cfg: None,
377 }, 377 },
378 Runnable { 378 Runnable {
379 nav: NavigationTarget { 379 nav: NavigationTarget {
@@ -398,7 +398,7 @@ fn bench() {}
398 ignore: true, 398 ignore: true,
399 }, 399 },
400 }, 400 },
401 cfg_exprs: [], 401 cfg: None,
402 }, 402 },
403 Runnable { 403 Runnable {
404 nav: NavigationTarget { 404 nav: NavigationTarget {
@@ -420,7 +420,7 @@ fn bench() {}
420 "bench", 420 "bench",
421 ), 421 ),
422 }, 422 },
423 cfg_exprs: [], 423 cfg: None,
424 }, 424 },
425 ] 425 ]
426 "#]], 426 "#]],
@@ -507,7 +507,7 @@ fn should_have_no_runnable_6() {}
507 docs: None, 507 docs: None,
508 }, 508 },
509 kind: Bin, 509 kind: Bin,
510 cfg_exprs: [], 510 cfg: None,
511 }, 511 },
512 Runnable { 512 Runnable {
513 nav: NavigationTarget { 513 nav: NavigationTarget {
@@ -527,7 +527,7 @@ fn should_have_no_runnable_6() {}
527 "should_have_runnable", 527 "should_have_runnable",
528 ), 528 ),
529 }, 529 },
530 cfg_exprs: [], 530 cfg: None,
531 }, 531 },
532 Runnable { 532 Runnable {
533 nav: NavigationTarget { 533 nav: NavigationTarget {
@@ -547,7 +547,7 @@ fn should_have_no_runnable_6() {}
547 "should_have_runnable_1", 547 "should_have_runnable_1",
548 ), 548 ),
549 }, 549 },
550 cfg_exprs: [], 550 cfg: None,
551 }, 551 },
552 Runnable { 552 Runnable {
553 nav: NavigationTarget { 553 nav: NavigationTarget {
@@ -567,7 +567,7 @@ fn should_have_no_runnable_6() {}
567 "should_have_runnable_2", 567 "should_have_runnable_2",
568 ), 568 ),
569 }, 569 },
570 cfg_exprs: [], 570 cfg: None,
571 }, 571 },
572 ] 572 ]
573 "#]], 573 "#]],
@@ -609,7 +609,7 @@ impl Data {
609 docs: None, 609 docs: None,
610 }, 610 },
611 kind: Bin, 611 kind: Bin,
612 cfg_exprs: [], 612 cfg: None,
613 }, 613 },
614 Runnable { 614 Runnable {
615 nav: NavigationTarget { 615 nav: NavigationTarget {
@@ -629,7 +629,7 @@ impl Data {
629 "Data::foo", 629 "Data::foo",
630 ), 630 ),
631 }, 631 },
632 cfg_exprs: [], 632 cfg: None,
633 }, 633 },
634 ] 634 ]
635 "#]], 635 "#]],
@@ -668,7 +668,7 @@ mod test_mod {
668 kind: TestMod { 668 kind: TestMod {
669 path: "test_mod", 669 path: "test_mod",
670 }, 670 },
671 cfg_exprs: [], 671 cfg: None,
672 }, 672 },
673 Runnable { 673 Runnable {
674 nav: NavigationTarget { 674 nav: NavigationTarget {
@@ -693,7 +693,7 @@ mod test_mod {
693 ignore: false, 693 ignore: false,
694 }, 694 },
695 }, 695 },
696 cfg_exprs: [], 696 cfg: None,
697 }, 697 },
698 ] 698 ]
699 "#]], 699 "#]],
@@ -748,7 +748,7 @@ mod root_tests {
748 kind: TestMod { 748 kind: TestMod {
749 path: "root_tests::nested_tests_0", 749 path: "root_tests::nested_tests_0",
750 }, 750 },
751 cfg_exprs: [], 751 cfg: None,
752 }, 752 },
753 Runnable { 753 Runnable {
754 nav: NavigationTarget { 754 nav: NavigationTarget {
@@ -768,7 +768,7 @@ mod root_tests {
768 kind: TestMod { 768 kind: TestMod {
769 path: "root_tests::nested_tests_0::nested_tests_1", 769 path: "root_tests::nested_tests_0::nested_tests_1",
770 }, 770 },
771 cfg_exprs: [], 771 cfg: None,
772 }, 772 },
773 Runnable { 773 Runnable {
774 nav: NavigationTarget { 774 nav: NavigationTarget {
@@ -793,7 +793,7 @@ mod root_tests {
793 ignore: false, 793 ignore: false,
794 }, 794 },
795 }, 795 },
796 cfg_exprs: [], 796 cfg: None,
797 }, 797 },
798 Runnable { 798 Runnable {
799 nav: NavigationTarget { 799 nav: NavigationTarget {
@@ -818,7 +818,7 @@ mod root_tests {
818 ignore: false, 818 ignore: false,
819 }, 819 },
820 }, 820 },
821 cfg_exprs: [], 821 cfg: None,
822 }, 822 },
823 Runnable { 823 Runnable {
824 nav: NavigationTarget { 824 nav: NavigationTarget {
@@ -838,7 +838,7 @@ mod root_tests {
838 kind: TestMod { 838 kind: TestMod {
839 path: "root_tests::nested_tests_0::nested_tests_2", 839 path: "root_tests::nested_tests_0::nested_tests_2",
840 }, 840 },
841 cfg_exprs: [], 841 cfg: None,
842 }, 842 },
843 Runnable { 843 Runnable {
844 nav: NavigationTarget { 844 nav: NavigationTarget {
@@ -863,7 +863,7 @@ mod root_tests {
863 ignore: false, 863 ignore: false,
864 }, 864 },
865 }, 865 },
866 cfg_exprs: [], 866 cfg: None,
867 }, 867 },
868 ] 868 ]
869 "#]], 869 "#]],
@@ -906,12 +906,14 @@ fn test_foo1() {}
906 ignore: false, 906 ignore: false,
907 }, 907 },
908 }, 908 },
909 cfg_exprs: [ 909 cfg: Some(
910 KeyValue { 910 Atom(
911 key: "feature", 911 KeyValue {
912 value: "foo", 912 key: "feature",
913 }, 913 value: "foo",
914 ], 914 },
915 ),
916 ),
915 }, 917 },
916 ] 918 ]
917 "#]], 919 "#]],
@@ -954,20 +956,24 @@ fn test_foo1() {}
954 ignore: false, 956 ignore: false,
955 }, 957 },
956 }, 958 },
957 cfg_exprs: [ 959 cfg: Some(
958 All( 960 All(
959 [ 961 [
960 KeyValue { 962 Atom(
961 key: "feature", 963 KeyValue {
962 value: "foo", 964 key: "feature",
963 }, 965 value: "foo",
964 KeyValue { 966 },
965 key: "feature", 967 ),
966 value: "bar", 968 Atom(
967 }, 969 KeyValue {
970 key: "feature",
971 value: "bar",
972 },
973 ),
968 ], 974 ],
969 ), 975 ),
970 ], 976 ),
971 }, 977 },
972 ] 978 ]
973 "#]], 979 "#]],
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index b35c03162..750848467 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -763,6 +763,9 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
763 if local.is_mut(db) || local.ty(db).is_mutable_reference() { 763 if local.is_mut(db) || local.ty(db).is_mutable_reference() {
764 h |= HighlightModifier::Mutable; 764 h |= HighlightModifier::Mutable;
765 } 765 }
766 if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) {
767 h |= HighlightModifier::Callable;
768 }
766 return h; 769 return h;
767 } 770 }
768 } 771 }
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index c1b817f06..e8f78ad52 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -64,6 +64,7 @@ pub enum HighlightModifier {
64 Mutable, 64 Mutable,
65 Consuming, 65 Consuming,
66 Unsafe, 66 Unsafe,
67 Callable,
67} 68}
68 69
69impl HighlightTag { 70impl HighlightTag {
@@ -122,6 +123,7 @@ impl HighlightModifier {
122 HighlightModifier::Mutable, 123 HighlightModifier::Mutable,
123 HighlightModifier::Consuming, 124 HighlightModifier::Consuming,
124 HighlightModifier::Unsafe, 125 HighlightModifier::Unsafe,
126 HighlightModifier::Callable,
125 ]; 127 ];
126 128
127 fn as_str(self) -> &'static str { 129 fn as_str(self) -> &'static str {
@@ -134,6 +136,7 @@ impl HighlightModifier {
134 HighlightModifier::Mutable => "mutable", 136 HighlightModifier::Mutable => "mutable",
135 HighlightModifier::Consuming => "consuming", 137 HighlightModifier::Consuming => "consuming",
136 HighlightModifier::Unsafe => "unsafe", 138 HighlightModifier::Unsafe => "unsafe",
139 HighlightModifier::Callable => "callable",
137 } 140 }
138 } 141 }
139 142
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 0bb0928e4..0cb84866d 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -44,6 +44,17 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
44 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span> 44 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span>
45<span class="punctuation">}</span> 45<span class="punctuation">}</span>
46 46
47<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">ops</span> <span class="punctuation">{</span>
48 <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn_once"</span><span class="attribute">]</span>
49 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">FnOnce</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
50
51 <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn_mut"</span><span class="attribute">]</span>
52 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">FnMut</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span><span class="punctuation">:</span> <span class="trait">FnOnce</span><span class="punctuation">&lt;</span><span class="type_param">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
53
54 <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn"</span><span class="attribute">]</span>
55 <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Fn</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span><span class="punctuation">:</span> <span class="trait">FnMut</span><span class="punctuation">&lt;</span><span class="type_param">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
56<span class="punctuation">}</span>
57
47 58
48<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span> 59<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span>
49 <span class="keyword">pub</span> <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span> 60 <span class="keyword">pub</span> <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span>
@@ -99,6 +110,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
99 <span class="function">foo</span><span class="operator">::</span><span class="punctuation">&lt;</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="punctuation">)</span> 110 <span class="function">foo</span><span class="operator">::</span><span class="punctuation">&lt;</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="punctuation">)</span>
100<span class="punctuation">}</span> 111<span class="punctuation">}</span>
101 112
113<span class="keyword">use</span> <span class="module">ops</span><span class="operator">::</span><span class="trait">Fn</span><span class="punctuation">;</span>
114<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">&lt;</span><span class="type_param declaration">F</span><span class="punctuation">:</span> <span class="trait">Fn</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="value_param declaration callable">f</span><span class="punctuation">:</span> <span class="type_param">F</span><span class="punctuation">)</span> <span class="punctuation">{</span>
115 <span class="value_param callable">f</span><span class="punctuation">(</span><span class="punctuation">)</span>
116<span class="punctuation">}</span>
117
102<span class="macro">macro_rules!</span> <span class="macro declaration">def_fn</span> <span class="punctuation">{</span> 118<span class="macro">macro_rules!</span> <span class="macro declaration">def_fn</span> <span class="punctuation">{</span>
103 <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">}</span> 119 <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">}</span>
104<span class="punctuation">}</span> 120<span class="punctuation">}</span>
@@ -157,6 +173,9 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
157 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 173 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
158 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 174 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
159 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span> 175 <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span>
176
177 <span class="keyword">let</span> <span class="variable declaration callable">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="punctuation">;</span>
178 <span class="keyword">let</span> <span class="variable declaration callable">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function">baz</span><span class="punctuation">;</span>
160<span class="punctuation">}</span> 179<span class="punctuation">}</span>
161 180
162<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation">&lt;</span><span class="type_param declaration">T</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span> 181<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation">&lt;</span><span class="type_param declaration">T</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 126363b8b..da20c300e 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -18,6 +18,17 @@ pub mod marker {
18 pub trait Copy {} 18 pub trait Copy {}
19} 19}
20 20
21pub mod ops {
22 #[lang = "fn_once"]
23 pub trait FnOnce<Args> {}
24
25 #[lang = "fn_mut"]
26 pub trait FnMut<Args>: FnOnce<Args> {}
27
28 #[lang = "fn"]
29 pub trait Fn<Args>: FnMut<Args> {}
30}
31
21 32
22struct Foo { 33struct Foo {
23 pub x: i32, 34 pub x: i32,
@@ -73,6 +84,11 @@ fn foo<'a, T>() -> T {
73 foo::<'a, i32>() 84 foo::<'a, i32>()
74} 85}
75 86
87use ops::Fn;
88fn baz<F: Fn() -> ()>(f: F) {
89 f()
90}
91
76macro_rules! def_fn { 92macro_rules! def_fn {
77 ($($tt:tt)*) => {$($tt)*} 93 ($($tt:tt)*) => {$($tt)*}
78} 94}
@@ -131,6 +147,9 @@ fn main() {
131 copy.quop(); 147 copy.quop();
132 copy.qux(); 148 copy.qux();
133 copy.baz(copy); 149 copy.baz(copy);
150
151 let a = |x| x;
152 let bar = Foo::baz;
134} 153}
135 154
136enum Option<T> { 155enum Option<T> {
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index ddc028148..1ab72bd91 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -1,6 +1,6 @@
1//! See `CargoTargetSpec` 1//! See `CargoTargetSpec`
2 2
3use cfg::CfgExpr; 3use cfg::{CfgAtom, CfgExpr};
4use ide::{FileId, RunnableKind, TestId}; 4use ide::{FileId, RunnableKind, TestId};
5use project_model::{self, TargetKind}; 5use project_model::{self, TargetKind};
6use vfs::AbsPathBuf; 6use vfs::AbsPathBuf;
@@ -24,7 +24,7 @@ impl CargoTargetSpec {
24 snap: &GlobalStateSnapshot, 24 snap: &GlobalStateSnapshot,
25 spec: Option<CargoTargetSpec>, 25 spec: Option<CargoTargetSpec>,
26 kind: &RunnableKind, 26 kind: &RunnableKind,
27 cfgs: &[CfgExpr], 27 cfg: &Option<CfgExpr>,
28 ) -> Result<(Vec<String>, Vec<String>)> { 28 ) -> Result<(Vec<String>, Vec<String>)> {
29 let mut args = Vec::new(); 29 let mut args = Vec::new();
30 let mut extra_args = Vec::new(); 30 let mut extra_args = Vec::new();
@@ -87,7 +87,7 @@ impl CargoTargetSpec {
87 args.push("--all-features".to_string()); 87 args.push("--all-features".to_string());
88 } else { 88 } else {
89 let mut features = Vec::new(); 89 let mut features = Vec::new();
90 for cfg in cfgs { 90 if let Some(cfg) = cfg.as_ref() {
91 required_features(cfg, &mut features); 91 required_features(cfg, &mut features);
92 } 92 }
93 for feature in &snap.config.cargo.features { 93 for feature in &snap.config.cargo.features {
@@ -160,7 +160,9 @@ impl CargoTargetSpec {
160/// Fill minimal features needed 160/// Fill minimal features needed
161fn required_features(cfg_expr: &CfgExpr, features: &mut Vec<String>) { 161fn required_features(cfg_expr: &CfgExpr, features: &mut Vec<String>) {
162 match cfg_expr { 162 match cfg_expr {
163 CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.to_string()), 163 CfgExpr::Atom(CfgAtom::KeyValue { key, value }) if key == "feature" => {
164 features.push(value.to_string())
165 }
164 CfgExpr::All(preds) => { 166 CfgExpr::All(preds) => {
165 preds.iter().for_each(|cfg| required_features(cfg, features)); 167 preds.iter().for_each(|cfg| required_features(cfg, features));
166 } 168 }
diff --git a/crates/rust-analyzer/src/dispatch.rs b/crates/rust-analyzer/src/dispatch.rs
index 9c8815e29..7a87515e9 100644
--- a/crates/rust-analyzer/src/dispatch.rs
+++ b/crates/rust-analyzer/src/dispatch.rs
@@ -34,7 +34,7 @@ impl<'a> RequestDispatcher<'a> {
34 }; 34 };
35 let world = panic::AssertUnwindSafe(&mut *self.global_state); 35 let world = panic::AssertUnwindSafe(&mut *self.global_state);
36 let response = panic::catch_unwind(move || { 36 let response = panic::catch_unwind(move || {
37 stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params)); 37 let _pctx = stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params));
38 let result = f(world.0, params); 38 let result = f(world.0, params);
39 result_to_response::<R>(id, result) 39 result_to_response::<R>(id, result)
40 }) 40 })
@@ -64,7 +64,7 @@ impl<'a> RequestDispatcher<'a> {
64 let world = self.global_state.snapshot(); 64 let world = self.global_state.snapshot();
65 65
66 move || { 66 move || {
67 let _ctx = 67 let _pctx =
68 stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params)); 68 stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params));
69 let result = f(world, params); 69 let result = f(world, params);
70 Task::Response(result_to_response::<R>(id, result)) 70 Task::Response(result_to_response::<R>(id, result))
@@ -160,7 +160,7 @@ impl<'a> NotificationDispatcher<'a> {
160 return Ok(self); 160 return Ok(self);
161 } 161 }
162 }; 162 };
163 stdx::panic_context::enter(format!("notification: {}", N::METHOD)); 163 let _pctx = stdx::panic_context::enter(format!("notification: {}", N::METHOD));
164 f(self.global_state, params)?; 164 f(self.global_state, params)?;
165 Ok(self) 165 Ok(self)
166 } 166 }
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index f2d57f986..2680e5f08 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -570,7 +570,7 @@ pub(crate) fn handle_completion(
570 let line_endings = snap.file_line_endings(position.file_id); 570 let line_endings = snap.file_line_endings(position.file_id);
571 let items: Vec<CompletionItem> = items 571 let items: Vec<CompletionItem> = items
572 .into_iter() 572 .into_iter()
573 .map(|item| to_proto::completion_item(&line_index, line_endings, item)) 573 .flat_map(|item| to_proto::completion_item(&line_index, line_endings, item))
574 .collect(); 574 .collect();
575 575
576 Ok(Some(items.into())) 576 Ok(Some(items.into()))
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs
index a6c4d6099..7df28c9dd 100644
--- a/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/semantic_tokens.rs
@@ -77,6 +77,7 @@ define_semantic_token_modifiers![
77 (CONSUMING, "consuming"), 77 (CONSUMING, "consuming"),
78 (UNSAFE, "unsafe"), 78 (UNSAFE, "unsafe"),
79 (ATTRIBUTE_MODIFIER, "attribute"), 79 (ATTRIBUTE_MODIFIER, "attribute"),
80 (CALLABLE, "callable"),
80]; 81];
81 82
82#[derive(Default)] 83#[derive(Default)]
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index aeacde0f7..0d34970bc 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -160,7 +160,13 @@ pub(crate) fn completion_item(
160 line_index: &LineIndex, 160 line_index: &LineIndex,
161 line_endings: LineEndings, 161 line_endings: LineEndings,
162 completion_item: CompletionItem, 162 completion_item: CompletionItem,
163) -> lsp_types::CompletionItem { 163) -> Vec<lsp_types::CompletionItem> {
164 fn set_score(res: &mut lsp_types::CompletionItem, label: &str) {
165 res.preselect = Some(true);
166 // HACK: sort preselect items first
167 res.sort_text = Some(format!(" {}", label));
168 }
169
164 let mut additional_text_edits = Vec::new(); 170 let mut additional_text_edits = Vec::new();
165 let mut text_edit = None; 171 let mut text_edit = None;
166 // LSP does not allow arbitrary edits in completion, so we have to do a 172 // LSP does not allow arbitrary edits in completion, so we have to do a
@@ -200,9 +206,7 @@ pub(crate) fn completion_item(
200 }; 206 };
201 207
202 if completion_item.score().is_some() { 208 if completion_item.score().is_some() {
203 res.preselect = Some(true); 209 set_score(&mut res, completion_item.label());
204 // HACK: sort preselect items first
205 res.sort_text = Some(format!(" {}", completion_item.label()));
206 } 210 }
207 211
208 if completion_item.deprecated() { 212 if completion_item.deprecated() {
@@ -217,9 +221,22 @@ pub(crate) fn completion_item(
217 }); 221 });
218 } 222 }
219 223
220 res.insert_text_format = Some(insert_text_format(completion_item.insert_text_format())); 224 let mut all_results = match completion_item.ref_match() {
225 Some(ref_match) => {
226 let mut refed = res.clone();
227 let (mutability, _score) = ref_match;
228 let label = format!("&{}{}", mutability.as_keyword_for_ref(), refed.label);
229 set_score(&mut refed, &label);
230 refed.label = label;
231 vec![res, refed]
232 }
233 None => vec![res],
234 };
221 235
222 res 236 for mut r in all_results.iter_mut() {
237 r.insert_text_format = Some(insert_text_format(completion_item.insert_text_format()));
238 }
239 all_results
223} 240}
224 241
225pub(crate) fn signature_help( 242pub(crate) fn signature_help(
@@ -408,6 +425,7 @@ fn semantic_token_type_and_modifiers(
408 HighlightModifier::Mutable => semantic_tokens::MUTABLE, 425 HighlightModifier::Mutable => semantic_tokens::MUTABLE,
409 HighlightModifier::Consuming => semantic_tokens::CONSUMING, 426 HighlightModifier::Consuming => semantic_tokens::CONSUMING,
410 HighlightModifier::Unsafe => semantic_tokens::UNSAFE, 427 HighlightModifier::Unsafe => semantic_tokens::UNSAFE,
428 HighlightModifier::Callable => semantic_tokens::CALLABLE,
411 }; 429 };
412 mods |= modifier; 430 mods |= modifier;
413 } 431 }
@@ -745,7 +763,7 @@ pub(crate) fn runnable(
745 let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone()); 763 let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
746 let target = spec.as_ref().map(|s| s.target.clone()); 764 let target = spec.as_ref().map(|s| s.target.clone());
747 let (cargo_args, executable_args) = 765 let (cargo_args, executable_args) =
748 CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg_exprs)?; 766 CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg)?;
749 let label = runnable.label(target); 767 let label = runnable.label(target);
750 let location = location_link(snap, None, runnable.nav)?; 768 let location = location_link(snap, None, runnable.nav)?;
751 769
@@ -776,6 +794,48 @@ mod tests {
776 use super::*; 794 use super::*;
777 795
778 #[test] 796 #[test]
797 fn test_completion_with_ref() {
798 let fixture = r#"
799 struct Foo;
800 fn foo(arg: &Foo) {}
801 fn main() {
802 let arg = Foo;
803 foo(<|>)
804 }"#;
805
806 let (offset, text) = test_utils::extract_offset(fixture);
807 let line_index = LineIndex::new(&text);
808 let (analysis, file_id) = Analysis::from_single_file(text);
809 let completions: Vec<(String, Option<String>)> = analysis
810 .completions(
811 &ide::CompletionConfig::default(),
812 base_db::FilePosition { file_id, offset },
813 )
814 .unwrap()
815 .unwrap()
816 .into_iter()
817 .filter(|c| c.label().ends_with("arg"))
818 .map(|c| completion_item(&line_index, LineEndings::Unix, c))
819 .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text)))
820 .collect();
821 expect_test::expect![[r#"
822 [
823 (
824 "arg",
825 None,
826 ),
827 (
828 "&arg",
829 Some(
830 " &arg",
831 ),
832 ),
833 ]
834 "#]]
835 .assert_debug_eq(&completions);
836 }
837
838 #[test]
779 fn conv_fold_line_folding_only_fixup() { 839 fn conv_fold_line_folding_only_fixup() {
780 let text = r#"mod a; 840 let text = r#"mod a;
781mod b; 841mod b;
diff --git a/crates/stdx/src/panic_context.rs b/crates/stdx/src/panic_context.rs
index fd232e0cc..8d51e20d3 100644
--- a/crates/stdx/src/panic_context.rs
+++ b/crates/stdx/src/panic_context.rs
@@ -4,7 +4,7 @@
4 4
5use std::{cell::RefCell, panic, sync::Once}; 5use std::{cell::RefCell, panic, sync::Once};
6 6
7pub fn enter(context: String) -> impl Drop { 7pub fn enter(context: String) -> PanicContext {
8 static ONCE: Once = Once::new(); 8 static ONCE: Once = Once::new();
9 ONCE.call_once(PanicContext::init); 9 ONCE.call_once(PanicContext::init);
10 10
@@ -13,7 +13,7 @@ pub fn enter(context: String) -> impl Drop {
13} 13}
14 14
15#[must_use] 15#[must_use]
16struct PanicContext { 16pub struct PanicContext {
17 _priv: (), 17 _priv: (),
18} 18}
19 19
diff --git a/docs/dev/debugging.md b/docs/dev/debugging.md
index 59a83f7d7..8c48fd5a1 100644
--- a/docs/dev/debugging.md
+++ b/docs/dev/debugging.md
@@ -53,7 +53,7 @@ To apply changes to an already running debug process, press <kbd>Ctrl+Shift+P</k
53 53
54- A list of running processes should appear. Select the `rust-analyzer` from this repo. 54- A list of running processes should appear. Select the `rust-analyzer` from this repo.
55 55
56- Navigate to `crates/rust-analyzer/src/main_loop.rs` and add a breakpoint to the `on_task` function. 56- Navigate to `crates/rust-analyzer/src/main_loop.rs` and add a breakpoint to the `on_request` function.
57 57
58- Go back to the `[Extension Development Host]` instance and hover over a Rust variable and your breakpoint should hit. 58- Go back to the `[Extension Development Host]` instance and hover over a Rust variable and your breakpoint should hit.
59 59
diff --git a/docs/user/generated_diagnostic.adoc b/docs/user/generated_diagnostic.adoc
index ec45d0c2b..34c4f98a3 100644
--- a/docs/user/generated_diagnostic.adoc
+++ b/docs/user/generated_diagnostic.adoc
@@ -5,6 +5,12 @@
5This diagnostic is triggered if `break` keyword is used outside of a loop. 5This diagnostic is triggered if `break` keyword is used outside of a loop.
6 6
7 7
8=== inactive-code
9**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L98[diagnostics.rs]
10
11This diagnostic is shown for code with inactive `#[cfg]` attributes.
12
13
8=== incorrect-ident-case 14=== incorrect-ident-case
9**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_ty/src/diagnostics.rs#L319[diagnostics.rs] 15**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_ty/src/diagnostics.rs#L319[diagnostics.rs]
10 16
@@ -81,25 +87,19 @@ This diagnostic is triggered if operation marked as `unsafe` is used outside of
81This diagnostic is triggered if created structure does not have field provided in record. 87This diagnostic is triggered if created structure does not have field provided in record.
82 88
83 89
84=== unconfigured-code
85**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L90[diagnostics.rs]
86
87This diagnostic is shown for code with inactive `#[cfg]` attributes.
88
89
90=== unresolved-extern-crate 90=== unresolved-extern-crate
91**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L35[diagnostics.rs] 91**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L43[diagnostics.rs]
92 92
93This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate. 93This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
94 94
95 95
96=== unresolved-import 96=== unresolved-import
97**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L59[diagnostics.rs] 97**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L67[diagnostics.rs]
98 98
99This diagnostic is triggered if rust-analyzer is unable to discover imported module. 99This diagnostic is triggered if rust-analyzer is unable to discover imported module.
100 100
101 101
102=== unresolved-module 102=== unresolved-module
103**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L10[diagnostics.rs] 103**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L18[diagnostics.rs]
104 104
105This diagnostic is triggered if rust-analyzer is unable to discover referred module. 105This diagnostic is triggered if rust-analyzer is unable to discover referred module.
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 7e8adcdb7..b9d907a4a 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -366,6 +366,13 @@ Cursor position or selection is signified by `┃` character.
366 366
367include::./generated_assists.adoc[] 367include::./generated_assists.adoc[]
368 368
369== Diagnostics
370
371While most errors and warnings provided by rust-analyzer come from the `cargo check` integration, there's a growing number of diagnostics implemented using rust-analyzer's own analysis.
372These diagnostics don't respect `#[allow]` or `#[deny]` attributes yet, but can be turned off using the `rust-analyzer.diagnostics.enable`, `rust-analyzer.diagnostics.enableExperimental` or `rust-analyzer.diagnostics.disabled` settings.
373
374include::./generated_diagnostic.adoc[]
375
369== Editor Features 376== Editor Features
370=== VS Code 377=== VS Code
371 378
diff --git a/editors/code/package.json b/editors/code/package.json
index 4bd3117fc..af845d7bc 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -929,6 +929,10 @@
929 { 929 {
930 "id": "consuming", 930 "id": "consuming",
931 "description": "Style for non-Copy lvalues consumed by method/function call" 931 "description": "Style for non-Copy lvalues consumed by method/function call"
932 },
933 {
934 "id": "callable",
935 "description": "Style for variables/parameters that can be used in call expressions"
932 } 936 }
933 ], 937 ],
934 "semanticTokenScopes": [ 938 "semanticTokenScopes": [
diff --git a/editors/code/rust.tmGrammar.json b/editors/code/rust.tmGrammar.json
index 3be565195..f0c5c3cf3 100644
--- a/editors/code/rust.tmGrammar.json
+++ b/editors/code/rust.tmGrammar.json
@@ -25,6 +25,9 @@
25 }, 25 },
26 "patterns": [ 26 "patterns": [
27 { 27 {
28 "include": "#block-comments"
29 },
30 {
28 "include": "#comments" 31 "include": "#comments"
29 }, 32 },
30 { 33 {
@@ -185,6 +188,9 @@
185 }, 188 },
186 "patterns": [ 189 "patterns": [
187 { 190 {
191 "include": "#block-comments"
192 },
193 {
188 "include": "#comments" 194 "include": "#comments"
189 }, 195 },
190 { 196 {
@@ -212,6 +218,9 @@
212 }, 218 },
213 "patterns": [ 219 "patterns": [
214 { 220 {
221 "include": "#block-comments"
222 },
223 {
215 "include": "#comments" 224 "include": "#comments"
216 }, 225 },
217 { 226 {
@@ -232,6 +241,9 @@
232 ] 241 ]
233 }, 242 },
234 { 243 {
244 "include": "#block-comments"
245 },
246 {
235 "include": "#comments" 247 "include": "#comments"
236 }, 248 },
237 { 249 {
@@ -277,31 +289,30 @@
277 { 289 {
278 "comment": "documentation comments", 290 "comment": "documentation comments",
279 "name": "comment.line.documentation.rust", 291 "name": "comment.line.documentation.rust",
280 "match": "^\\s*///.*", 292 "match": "^\\s*///.*"
281 "patterns": [
282 {
283 "include": "#comments"
284 }
285 ]
286 }, 293 },
287 { 294 {
288 "comment": "line comments", 295 "comment": "line comments",
289 "name": "comment.line.double-slash.rust", 296 "name": "comment.line.double-slash.rust",
290 "match": "\\s*//.*", 297 "match": "\\s*//.*"
291 "patterns": [
292 {
293 "include": "#comments"
294 }
295 ]
296 }, 298 },
297 { 299 {
300 "comment": "inferred types, wildcard patterns, ignored params",
301 "name": "comment.char.underscore.rust",
302 "match": "\\b_\\w*\\b[^!(]"
303 }
304 ]
305 },
306 "block-comments": {
307 "patterns": [
308 {
298 "comment": "block comments", 309 "comment": "block comments",
299 "name": "comment.block.rust", 310 "name": "comment.block.rust",
300 "begin": "/\\*(?!\\*)", 311 "begin": "/\\*(?!\\*)",
301 "end": "\\*/", 312 "end": "\\*/",
302 "patterns": [ 313 "patterns": [
303 { 314 {
304 "include": "#comments" 315 "include": "#block-comments"
305 } 316 }
306 ] 317 ]
307 }, 318 },
@@ -312,14 +323,9 @@
312 "end": "\\*/", 323 "end": "\\*/",
313 "patterns": [ 324 "patterns": [
314 { 325 {
315 "include": "#comments" 326 "include": "#block-comments"
316 } 327 }
317 ] 328 ]
318 },
319 {
320 "comment": "inferred types, wildcard patterns, ignored params",
321 "name": "comment.char.underscore.rust",
322 "match": "\\b_\\w*\\b"
323 } 329 }
324 ] 330 ]
325 }, 331 },
@@ -451,6 +457,9 @@
451 }, 457 },
452 "patterns": [ 458 "patterns": [
453 { 459 {
460 "include": "#block-comments"
461 },
462 {
454 "include": "#comments" 463 "include": "#comments"
455 }, 464 },
456 { 465 {
@@ -517,6 +526,9 @@
517 }, 526 },
518 "patterns": [ 527 "patterns": [
519 { 528 {
529 "include": "#block-comments"
530 },
531 {
520 "include": "#comments" 532 "include": "#comments"
521 }, 533 },
522 { 534 {
@@ -798,6 +810,9 @@
798 }, 810 },
799 "patterns": [ 811 "patterns": [
800 { 812 {
813 "include": "#block-comments"
814 },
815 {
801 "include": "#comments" 816 "include": "#comments"
802 }, 817 },
803 { 818 {