diff options
Diffstat (limited to 'crates')
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 @@ | |||
1 | use itertools::Itertools; | 1 | use itertools::Itertools; |
2 | use stdx::format_to; | 2 | use stdx::format_to; |
3 | use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner}; | 3 | use syntax::ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner}; |
4 | 4 | ||
5 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 5 | use 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] |
18 | mbe = { path = "../mbe" } | 18 | mbe = { path = "../mbe" } |
19 | syntax = { path = "../syntax" } | 19 | syntax = { path = "../syntax" } |
20 | expect-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 | ||
5 | use std::slice::Iter as SliceIter; | 5 | use std::{fmt, slice::Iter as SliceIter}; |
6 | 6 | ||
7 | use tt::SmolStr; | 7 | use tt::SmolStr; |
8 | 8 | ||
9 | /// A simple configuration value passed in from the outside. | ||
10 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] | ||
11 | pub 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 | |||
21 | impl 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 | |||
43 | impl 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)] |
10 | pub enum CfgExpr { | 53 | pub 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 | ||
61 | impl From<CfgAtom> for CfgExpr { | ||
62 | fn from(atom: CfgAtom) -> Self { | ||
63 | CfgExpr::Atom(atom) | ||
64 | } | ||
65 | } | ||
66 | |||
19 | impl CfgExpr { | 67 | impl 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)] | ||
86 | mod 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 | |||
9 | use std::fmt; | ||
10 | |||
11 | use rustc_hash::FxHashSet; | ||
12 | |||
13 | use crate::{CfgAtom, CfgDiff, CfgExpr, CfgOptions, InactiveReason}; | ||
14 | |||
15 | /// A `#[cfg]` directive in Disjunctive Normal Form (DNF). | ||
16 | pub struct DnfExpr { | ||
17 | conjunctions: Vec<Conjunction>, | ||
18 | } | ||
19 | |||
20 | struct Conjunction { | ||
21 | literals: Vec<Literal>, | ||
22 | } | ||
23 | |||
24 | struct Literal { | ||
25 | negate: bool, | ||
26 | var: Option<CfgAtom>, // None = Invalid | ||
27 | } | ||
28 | |||
29 | impl 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 | |||
125 | impl 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 | |||
145 | impl 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 | |||
165 | impl 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 | |||
185 | impl 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 | |||
200 | impl 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 | |||
219 | struct Builder { | ||
220 | expr: DnfExpr, | ||
221 | } | ||
222 | |||
223 | impl 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 | |||
255 | fn 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. | ||
268 | fn 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 | |||
300 | fn 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 | ||
3 | mod cfg_expr; | 3 | mod cfg_expr; |
4 | mod dnf; | ||
5 | #[cfg(test)] | ||
6 | mod tests; | ||
7 | |||
8 | use std::fmt; | ||
4 | 9 | ||
5 | use rustc_hash::FxHashSet; | 10 | use rustc_hash::FxHashSet; |
6 | use tt::SmolStr; | 11 | use tt::SmolStr; |
7 | 12 | ||
8 | pub use cfg_expr::CfgExpr; | 13 | pub use cfg_expr::{CfgAtom, CfgExpr}; |
14 | pub 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)] |
21 | pub struct CfgOptions { | 27 | pub struct CfgOptions { |
22 | atoms: FxHashSet<SmolStr>, | 28 | enabled: FxHashSet<CfgAtom>, |
23 | key_values: FxHashSet<(SmolStr, SmolStr)>, | ||
24 | } | 29 | } |
25 | 30 | ||
26 | impl CfgOptions { | 31 | impl 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 | |||
61 | pub 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 | |||
67 | impl 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 | |||
74 | impl 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 | |||
112 | pub struct InactiveReason { | ||
113 | enabled: Vec<CfgAtom>, | ||
114 | disabled: Vec<CfgAtom>, | ||
115 | } | ||
116 | |||
117 | impl 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 @@ | |||
1 | use expect_test::{expect, Expect}; | ||
2 | use mbe::ast_to_token_tree; | ||
3 | use syntax::{ast, AstNode}; | ||
4 | |||
5 | use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr}; | ||
6 | |||
7 | fn 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 | |||
17 | fn 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 | |||
28 | fn 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] | ||
41 | fn 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] | ||
54 | fn 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] | ||
86 | fn 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] | ||
98 | fn 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] | ||
115 | fn 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] | ||
124 | fn 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] | ||
134 | fn 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] | ||
153 | fn 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] | ||
164 | fn 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 | ||
3 | use std::fmt; | 3 | use std::fmt; |
4 | 4 | ||
5 | use hir::Documentation; | 5 | use hir::{Documentation, Mutability}; |
6 | use syntax::TextRange; | 6 | use syntax::TextRange; |
7 | use text_edit::TextEdit; | 7 | use 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)] | ||
247 | pub(crate) struct Builder { | 257 | pub(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 | ||
263 | impl Builder { | 274 | impl 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 | ||
355 | impl<'a> Into<CompletionItem> for Builder { | 374 | impl<'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 | ||
4 | use hir::{HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; | 4 | use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type}; |
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use syntax::{ast::NameOwner, display::*}; | 6 | use syntax::{ast::NameOwner, display::*}; |
7 | use test_utils::mark; | 7 | use 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 | ||
345 | pub(crate) fn compute_score( | 355 | fn 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 | } |
376 | fn 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 | |||
390 | fn 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 | ||
377 | enum Params { | 395 | enum 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#" | ||
616 | mod m { | ||
617 | pub enum Spam { Foo, Bar(i32) } | ||
618 | } | ||
619 | fn 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. |
3 | mod lower; | 3 | mod lower; |
4 | mod diagnostics; | ||
5 | #[cfg(test)] | ||
6 | mod tests; | ||
4 | pub mod scope; | 7 | pub mod scope; |
5 | 8 | ||
6 | use std::{mem, ops::Index, sync::Arc}; | 9 | use std::{mem, ops::Index, sync::Arc}; |
@@ -10,7 +13,10 @@ use base_db::CrateId; | |||
10 | use cfg::CfgOptions; | 13 | use cfg::CfgOptions; |
11 | use drop_bomb::DropBomb; | 14 | use drop_bomb::DropBomb; |
12 | use either::Either; | 15 | use either::Either; |
13 | use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId}; | 16 | use hir_expand::{ |
17 | ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, HirFileId, InFile, | ||
18 | MacroDefId, | ||
19 | }; | ||
14 | use rustc_hash::FxHashMap; | 20 | use rustc_hash::FxHashMap; |
15 | use syntax::{ast, AstNode, AstPtr}; | 21 | use syntax::{ast, AstNode, AstPtr}; |
16 | use test_utils::mark; | 22 | use 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)] | ||
324 | mod 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 | " | ||
352 | macro_rules! n_nuple { | ||
353 | ($e:tt) => (); | ||
354 | ($($rest:tt)*) => {{ | ||
355 | (n_nuple!($($rest)*)None,) | ||
356 | }}; | ||
357 | } | ||
358 | fn 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 | |||
3 | use hir_expand::diagnostics::DiagnosticSink; | ||
4 | |||
5 | use crate::diagnostics::InactiveCode; | ||
6 | |||
7 | #[derive(Debug, Eq, PartialEq)] | ||
8 | pub enum BodyDiagnostic { | ||
9 | InactiveCode(InactiveCode), | ||
10 | } | ||
11 | |||
12 | impl 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 | }; |
21 | use test_utils::mark; | 21 | use 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 | ||
40 | use super::{ExprSource, PatSource}; | 41 | use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource}; |
41 | 42 | ||
42 | pub(crate) struct LowerCtx { | 43 | pub(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 | ||
877 | impl From<ast::BinOp> for BinaryOp { | 909 | impl 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 @@ | |||
1 | use base_db::{fixture::WithFixture, SourceDatabase}; | ||
2 | use test_utils::mark; | ||
3 | |||
4 | use crate::{test_db::TestDB, ModuleDefId}; | ||
5 | |||
6 | use super::*; | ||
7 | |||
8 | fn 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 | |||
23 | fn check_diagnostics(ra_fixture: &str) { | ||
24 | let db: TestDB = TestDB::with_files(ra_fixture); | ||
25 | db.check_diagnostics(); | ||
26 | } | ||
27 | |||
28 | #[test] | ||
29 | fn your_stack_belongs_to_me() { | ||
30 | mark::check!(your_stack_belongs_to_me); | ||
31 | lower( | ||
32 | " | ||
33 | macro_rules! n_nuple { | ||
34 | ($e:tt) => (); | ||
35 | ($($rest:tt)*) => {{ | ||
36 | (n_nuple!($($rest)*)None,) | ||
37 | }}; | ||
38 | } | ||
39 | fn main() { n_nuple!(1,2,3); } | ||
40 | ", | ||
41 | ); | ||
42 | } | ||
43 | |||
44 | #[test] | ||
45 | fn cfg_diagnostics() { | ||
46 | check_diagnostics( | ||
47 | r" | ||
48 | fn 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 | ||
3 | use std::any::Any; | 3 | use std::any::Any; |
4 | use stdx::format_to; | ||
4 | 5 | ||
5 | use hir_expand::diagnostics::{Diagnostic, DiagnosticCode}; | 6 | use cfg::{CfgExpr, CfgOptions, DnfExpr}; |
7 | use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink}; | ||
8 | use hir_expand::{HirFileId, InFile}; | ||
6 | use syntax::{ast, AstPtr, SyntaxNodePtr}; | 9 | use syntax::{ast, AstPtr, SyntaxNodePtr}; |
7 | 10 | ||
8 | use hir_expand::{HirFileId, InFile}; | 11 | use crate::{db::DefDatabase, DefWithBodyId}; |
12 | |||
13 | pub 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)] |
94 | pub struct InactiveCode { | 102 | pub 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 | ||
99 | impl Diagnostic for InactiveCode { | 109 | impl 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 @@ | |||
3 | use std::{collections::hash_map::Entry, mem, sync::Arc}; | 3 | use std::{collections::hash_map::Entry, mem, sync::Arc}; |
4 | 4 | ||
5 | use arena::map::ArenaMap; | 5 | use arena::map::ArenaMap; |
6 | use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId}; | 6 | use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, name::known, HirFileId}; |
7 | use smallvec::SmallVec; | 7 | use smallvec::SmallVec; |
8 | use syntax::{ | 8 | use 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. | ||
719 | fn 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 | ||
285 | mod diagnostics { | 285 | mod 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 @@ | |||
6 | use std::iter; | 6 | use std::iter; |
7 | 7 | ||
8 | use base_db::{CrateId, FileId, ProcMacroId}; | 8 | use base_db::{CrateId, FileId, ProcMacroId}; |
9 | use cfg::CfgOptions; | 9 | use cfg::{CfgExpr, CfgOptions}; |
10 | use hir_expand::InFile; | 10 | use hir_expand::InFile; |
11 | use hir_expand::{ | 11 | use 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 @@ | |||
1 | use base_db::fixture::WithFixture; | 1 | use base_db::fixture::WithFixture; |
2 | use base_db::FileId; | ||
3 | use base_db::SourceDatabaseExt; | ||
4 | use hir_expand::db::AstDatabase; | ||
5 | use rustc_hash::FxHashMap; | ||
6 | use syntax::TextRange; | ||
7 | use syntax::TextSize; | ||
8 | 2 | ||
9 | use crate::test_db::TestDB; | 3 | use crate::test_db::TestDB; |
10 | 4 | ||
11 | fn check_diagnostics(ra_fixture: &str) { | 5 | fn 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] | ||
102 | fn 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; | |||
12 | use hir_expand::diagnostics::DiagnosticSinkBuilder; | 12 | use hir_expand::diagnostics::DiagnosticSinkBuilder; |
13 | use rustc_hash::FxHashMap; | 13 | use rustc_hash::FxHashMap; |
14 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
15 | use syntax::TextRange; | 15 | use syntax::{TextRange, TextSize}; |
16 | use test_utils::extract_annotations; | 16 | use test_utils::extract_annotations; |
17 | 17 | ||
18 | use crate::db::DefDatabase; | 18 | use 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#" |
714 | struct non_camel_case_name {} | 714 | struct 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 | |||
717 | struct 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#" | ||
727 | struct 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#" |
734 | enum some_enum { Val(u8) } | 746 | enum 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 | |||
749 | enum 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#" | ||
759 | enum 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#" | ||
210 | extern "rust-intrinsic" { | ||
211 | pub fn bitreverse(x: u32) -> u32; // Safe intrinsic | ||
212 | pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic | ||
213 | } | ||
214 | |||
215 | fn 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; | |||
5 | use chalk_ir::cast::Cast; | 5 | use chalk_ir::cast::Cast; |
6 | use chalk_solve::{logging_db::LoggingRustIrDatabase, Solver}; | 6 | use chalk_solve::{logging_db::LoggingRustIrDatabase, Solver}; |
7 | use hir_def::{lang_item::LangItemTarget, TraitId}; | 7 | use hir_def::{lang_item::LangItemTarget, TraitId}; |
8 | use stdx::panic_context; | ||
8 | 9 | ||
9 | use crate::{db::HirDatabase, DebruijnIndex, Substs}; | 10 | use 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 | ||
199 | struct LoggingRustIrDatabaseLoggingOnDrop<'a>(LoggingRustIrDatabase<Interner, ChalkContext<'a>>); | ||
200 | |||
201 | impl<'a> Drop for LoggingRustIrDatabaseLoggingOnDrop<'a> { | ||
202 | fn drop(&mut self) { | ||
203 | eprintln!("chalk program:\n{}", self.0); | ||
204 | } | ||
205 | } | ||
206 | |||
191 | fn is_chalk_debug() -> bool { | 207 | fn 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#" | ||
537 | pub 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( | |||
378 | fn get_string_representation(expr: &ast::Expr) -> Option<String> { | 378 | fn 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#" | ||
1226 | fn foo(bar: i32, baz: String, qux: f32) {} | ||
1227 | |||
1228 | fn 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}; | |||
15 | pub struct Runnable { | 15 | pub 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 | ||
69 | impl HighlightTag { | 70 | impl 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"><</span><span class="type_param declaration">Args</span><span class="punctuation">></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"><</span><span class="type_param declaration">Args</span><span class="punctuation">></span><span class="punctuation">:</span> <span class="trait">FnOnce</span><span class="punctuation"><</span><span class="type_param">Args</span><span class="punctuation">></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"><</span><span class="type_param declaration">Args</span><span class="punctuation">></span><span class="punctuation">:</span> <span class="trait">FnMut</span><span class="punctuation"><</span><span class="type_param">Args</span><span class="punctuation">></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"><</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">></span><span class="punctuation">(</span><span class="punctuation">)</span> | 110 | <span class="function">foo</span><span class="operator">::</span><span class="punctuation"><</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">></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"><</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">-></span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">></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">></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">></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"><</span><span class="type_param declaration">T</span><span class="punctuation">></span> <span class="punctuation">{</span> | 181 | <span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation"><</span><span class="type_param declaration">T</span><span class="punctuation">></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 | ||
21 | pub 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 | ||
22 | struct Foo { | 33 | struct 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 | ||
87 | use ops::Fn; | ||
88 | fn baz<F: Fn() -> ()>(f: F) { | ||
89 | f() | ||
90 | } | ||
91 | |||
76 | macro_rules! def_fn { | 92 | macro_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 | ||
136 | enum Option<T> { | 155 | enum 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 | ||
3 | use cfg::CfgExpr; | 3 | use cfg::{CfgAtom, CfgExpr}; |
4 | use ide::{FileId, RunnableKind, TestId}; | 4 | use ide::{FileId, RunnableKind, TestId}; |
5 | use project_model::{self, TargetKind}; | 5 | use project_model::{self, TargetKind}; |
6 | use vfs::AbsPathBuf; | 6 | use 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 |
161 | fn required_features(cfg_expr: &CfgExpr, features: &mut Vec<String>) { | 161 | fn 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 | ||
225 | pub(crate) fn signature_help( | 242 | pub(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; |
781 | mod b; | 841 | mod 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 | ||
5 | use std::{cell::RefCell, panic, sync::Once}; | 5 | use std::{cell::RefCell, panic, sync::Once}; |
6 | 6 | ||
7 | pub fn enter(context: String) -> impl Drop { | 7 | pub 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] |
16 | struct PanicContext { | 16 | pub struct PanicContext { |
17 | _priv: (), | 17 | _priv: (), |
18 | } | 18 | } |
19 | 19 | ||