aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-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
40 files changed, 1545 insertions, 294 deletions
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