aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r--crates/ra_ide/src/completion/complete_snippet.rs8
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs10
-rw-r--r--crates/ra_ide/src/completion/presentation.rs31
-rw-r--r--crates/ra_ide/src/diagnostics.rs182
-rw-r--r--crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs171
-rw-r--r--crates/ra_ide/src/display/short_label.rs12
-rw-r--r--crates/ra_ide/src/folding_ranges.rs23
-rw-r--r--crates/ra_ide/src/goto_definition.rs36
-rw-r--r--crates/ra_ide/src/hover.rs88
-rw-r--r--crates/ra_ide/src/lib.rs10
-rw-r--r--crates/ra_ide/src/references.rs4
-rw-r--r--crates/ra_ide/src/references/rename.rs2
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs150
-rw-r--r--crates/ra_ide/src/syntax_highlighting/injection.rs5
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs71
15 files changed, 575 insertions, 228 deletions
diff --git a/crates/ra_ide/src/completion/complete_snippet.rs b/crates/ra_ide/src/completion/complete_snippet.rs
index 28d8f7876..4368e4eec 100644
--- a/crates/ra_ide/src/completion/complete_snippet.rs
+++ b/crates/ra_ide/src/completion/complete_snippet.rs
@@ -36,7 +36,7 @@ pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionConte
36 snippet( 36 snippet(
37 ctx, 37 ctx,
38 cap, 38 cap,
39 "Test module", 39 "tmod (Test module)",
40 "\ 40 "\
41#[cfg(test)] 41#[cfg(test)]
42mod tests { 42mod tests {
@@ -54,7 +54,7 @@ mod tests {
54 snippet( 54 snippet(
55 ctx, 55 ctx,
56 cap, 56 cap,
57 "Test function", 57 "tfn (Test function)",
58 "\ 58 "\
59#[test] 59#[test]
60fn ${1:feature}() { 60fn ${1:feature}() {
@@ -106,10 +106,10 @@ mod tests {
106} 106}
107"#, 107"#,
108 expect![[r#" 108 expect![[r#"
109 sn Test function
110 sn Test module
111 sn macro_rules 109 sn macro_rules
112 sn pub(crate) 110 sn pub(crate)
111 sn tfn (Test function)
112 sn tmod (Test module)
113 "#]], 113 "#]],
114 ) 114 )
115 } 115 }
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 6b03b30bb..4aa761148 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -27,7 +27,7 @@ pub(crate) struct CompletionContext<'a> {
27 pub(super) scope: SemanticsScope<'a>, 27 pub(super) scope: SemanticsScope<'a>,
28 pub(super) db: &'a RootDatabase, 28 pub(super) db: &'a RootDatabase,
29 pub(super) config: &'a CompletionConfig, 29 pub(super) config: &'a CompletionConfig,
30 pub(super) offset: TextSize, 30 pub(super) position: FilePosition,
31 /// The token before the cursor, in the original file. 31 /// The token before the cursor, in the original file.
32 pub(super) original_token: SyntaxToken, 32 pub(super) original_token: SyntaxToken,
33 /// The token before the cursor, in the macro-expanded file. 33 /// The token before the cursor, in the macro-expanded file.
@@ -117,7 +117,7 @@ impl<'a> CompletionContext<'a> {
117 config, 117 config,
118 original_token, 118 original_token,
119 token, 119 token,
120 offset: position.offset, 120 position,
121 krate, 121 krate,
122 expected_type: None, 122 expected_type: None,
123 name_ref_syntax: None, 123 name_ref_syntax: None,
@@ -209,7 +209,7 @@ impl<'a> CompletionContext<'a> {
209 mark::hit!(completes_if_prefix_is_keyword); 209 mark::hit!(completes_if_prefix_is_keyword);
210 self.original_token.text_range() 210 self.original_token.text_range()
211 } else { 211 } else {
212 TextRange::empty(self.offset) 212 TextRange::empty(self.position.offset)
213 } 213 }
214 } 214 }
215 215
@@ -379,8 +379,8 @@ impl<'a> CompletionContext<'a> {
379 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); 379 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
380 self.has_type_args = segment.generic_arg_list().is_some(); 380 self.has_type_args = segment.generic_arg_list().is_some();
381 381
382 #[allow(deprecated)] 382 let hygiene = hir::Hygiene::new(self.db, self.position.file_id.into());
383 if let Some(path) = hir::Path::from_ast(path.clone()) { 383 if let Some(path) = hir::Path::from_src(path.clone(), &hygiene) {
384 if let Some(path_prefix) = path.qualifier() { 384 if let Some(path_prefix) = path.qualifier() {
385 self.path_prefix = Some(path_prefix); 385 self.path_prefix = Some(path_prefix);
386 return; 386 return;
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index 9a94ff476..59f1b1424 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -2,8 +2,8 @@
2//! It also handles scoring (sorting) completions. 2//! It also handles scoring (sorting) completions.
3 3
4use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; 4use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type};
5use itertools::Itertools;
5use ra_syntax::ast::NameOwner; 6use ra_syntax::ast::NameOwner;
6use stdx::SepBy;
7use test_utils::mark; 7use test_utils::mark;
8 8
9use crate::{ 9use crate::{
@@ -289,16 +289,16 @@ impl Completions {
289 .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db))); 289 .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db)));
290 let variant_kind = variant.kind(ctx.db); 290 let variant_kind = variant.kind(ctx.db);
291 let detail = match variant_kind { 291 let detail = match variant_kind {
292 StructKind::Tuple | StructKind::Unit => detail_types 292 StructKind::Tuple | StructKind::Unit => format!(
293 .map(|(_, t)| t.display(ctx.db).to_string()) 293 "({})",
294 .sep_by(", ") 294 detail_types.map(|(_, t)| t.display(ctx.db).to_string()).format(", ")
295 .surround_with("(", ")") 295 ),
296 .to_string(), 296 StructKind::Record => format!(
297 StructKind::Record => detail_types 297 "{{ {} }}",
298 .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string())) 298 detail_types
299 .sep_by(", ") 299 .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string()))
300 .surround_with("{ ", " }") 300 .format(", ")
301 .to_string(), 301 ),
302 }; 302 };
303 let mut res = CompletionItem::new( 303 let mut res = CompletionItem::new(
304 CompletionKind::Reference, 304 CompletionKind::Reference,
@@ -412,11 +412,10 @@ impl Builder {
412 self = self.trigger_call_info(); 412 self = self.trigger_call_info();
413 let snippet = match (ctx.config.add_call_argument_snippets, params) { 413 let snippet = match (ctx.config.add_call_argument_snippets, params) {
414 (true, Params::Named(params)) => { 414 (true, Params::Named(params)) => {
415 let function_params_snippet = params 415 let function_params_snippet =
416 .iter() 416 params.iter().enumerate().format_with(", ", |(index, param_name), f| {
417 .enumerate() 417 f(&format_args!("${{{}:{}}}", index + 1, param_name))
418 .map(|(index, param_name)| format!("${{{}:{}}}", index + 1, param_name)) 418 });
419 .sep_by(", ");
420 format!("{}({})$0", name, function_params_snippet) 419 format!("{}({})$0", name, function_params_snippet)
421 } 420 }
422 _ => { 421 _ => {
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index dd8a7ffd9..1046d7ab3 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -6,22 +6,21 @@
6 6
7use std::cell::RefCell; 7use std::cell::RefCell;
8 8
9use hir::{ 9use hir::{diagnostics::DiagnosticSinkBuilder, Semantics};
10 diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSinkBuilder},
11 HasSource, HirDisplay, Semantics, VariantDef,
12};
13use itertools::Itertools; 10use itertools::Itertools;
14use ra_db::SourceDatabase; 11use ra_db::SourceDatabase;
15use ra_ide_db::RootDatabase; 12use ra_ide_db::RootDatabase;
16use ra_prof::profile; 13use ra_prof::profile;
17use ra_syntax::{ 14use ra_syntax::{
18 algo, 15 ast::{self, AstNode},
19 ast::{self, edit::IndentLevel, make, AstNode},
20 SyntaxNode, TextRange, T, 16 SyntaxNode, TextRange, T,
21}; 17};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 18use ra_text_edit::{TextEdit, TextEditBuilder};
23 19
24use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceFileEdit}; 20use crate::{Diagnostic, FileId, Fix, SourceFileEdit};
21
22mod diagnostics_with_fix;
23use diagnostics_with_fix::DiagnosticWithFix;
25 24
26#[derive(Debug, Copy, Clone)] 25#[derive(Debug, Copy, Clone)]
27pub enum Severity { 26pub enum Severity {
@@ -54,71 +53,16 @@ pub(crate) fn diagnostics(
54 let res = RefCell::new(res); 53 let res = RefCell::new(res);
55 let mut sink = DiagnosticSinkBuilder::new() 54 let mut sink = DiagnosticSinkBuilder::new()
56 .on::<hir::diagnostics::UnresolvedModule, _>(|d| { 55 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
57 let original_file = d.source().file_id.original_file(db); 56 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
58 let fix = Fix::new(
59 "Create module",
60 FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }
61 .into(),
62 );
63 res.borrow_mut().push(Diagnostic {
64 range: sema.diagnostics_range(d).range,
65 message: d.message(),
66 severity: Severity::Error,
67 fix: Some(fix),
68 })
69 }) 57 })
70 .on::<hir::diagnostics::MissingFields, _>(|d| { 58 .on::<hir::diagnostics::MissingFields, _>(|d| {
71 // Note that although we could add a diagnostics to 59 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
72 // fill the missing tuple field, e.g :
73 // `struct A(usize);`
74 // `let a = A { 0: () }`
75 // but it is uncommon usage and it should not be encouraged.
76 let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
77 None
78 } else {
79 let mut field_list = d.ast(db);
80 for f in d.missed_fields.iter() {
81 let field =
82 make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
83 field_list = field_list.append_field(&field);
84 }
85
86 let edit = {
87 let mut builder = TextEditBuilder::default();
88 algo::diff(&d.ast(db).syntax(), &field_list.syntax())
89 .into_text_edit(&mut builder);
90 builder.finish()
91 };
92 Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into()))
93 };
94
95 res.borrow_mut().push(Diagnostic {
96 range: sema.diagnostics_range(d).range,
97 message: d.message(),
98 severity: Severity::Error,
99 fix,
100 })
101 }) 60 })
102 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { 61 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
103 let node = d.ast(db); 62 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
104 let replacement = format!("Ok({})", node.syntax());
105 let edit = TextEdit::replace(node.syntax().text_range(), replacement);
106 let source_change = SourceFileEdit { file_id, edit }.into();
107 let fix = Fix::new("Wrap with ok", source_change);
108 res.borrow_mut().push(Diagnostic {
109 range: sema.diagnostics_range(d).range,
110 message: d.message(),
111 severity: Severity::Error,
112 fix: Some(fix),
113 })
114 }) 63 })
115 .on::<hir::diagnostics::NoSuchField, _>(|d| { 64 .on::<hir::diagnostics::NoSuchField, _>(|d| {
116 res.borrow_mut().push(Diagnostic { 65 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
117 range: sema.diagnostics_range(d).range,
118 message: d.message(),
119 severity: Severity::Error,
120 fix: missing_struct_field_fix(&sema, file_id, d),
121 })
122 }) 66 })
123 // Only collect experimental diagnostics when they're enabled. 67 // Only collect experimental diagnostics when they're enabled.
124 .filter(|diag| !diag.is_experimental() || enable_experimental) 68 .filter(|diag| !diag.is_experimental() || enable_experimental)
@@ -126,7 +70,7 @@ pub(crate) fn diagnostics(
126 .build(|d| { 70 .build(|d| {
127 res.borrow_mut().push(Diagnostic { 71 res.borrow_mut().push(Diagnostic {
128 message: d.message(), 72 message: d.message(),
129 range: sema.diagnostics_range(d).range, 73 range: sema.diagnostics_display_range(d).range,
130 severity: Severity::Error, 74 severity: Severity::Error,
131 fix: None, 75 fix: None,
132 }) 76 })
@@ -139,77 +83,12 @@ pub(crate) fn diagnostics(
139 res.into_inner() 83 res.into_inner()
140} 84}
141 85
142fn missing_struct_field_fix( 86fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
143 sema: &Semantics<RootDatabase>, 87 Diagnostic {
144 usage_file_id: FileId, 88 range: sema.diagnostics_display_range(d).range,
145 d: &hir::diagnostics::NoSuchField, 89 message: d.message(),
146) -> Option<Fix> { 90 severity: Severity::Error,
147 let record_expr = sema.ast(d); 91 fix: d.fix(&sema),
148
149 let record_lit = ast::RecordExpr::cast(record_expr.syntax().parent()?.parent()?)?;
150 let def_id = sema.resolve_variant(record_lit)?;
151 let module;
152 let def_file_id;
153 let record_fields = match VariantDef::from(def_id) {
154 VariantDef::Struct(s) => {
155 module = s.module(sema.db);
156 let source = s.source(sema.db);
157 def_file_id = source.file_id;
158 let fields = source.value.field_list()?;
159 record_field_list(fields)?
160 }
161 VariantDef::Union(u) => {
162 module = u.module(sema.db);
163 let source = u.source(sema.db);
164 def_file_id = source.file_id;
165 source.value.record_field_list()?
166 }
167 VariantDef::EnumVariant(e) => {
168 module = e.module(sema.db);
169 let source = e.source(sema.db);
170 def_file_id = source.file_id;
171 let fields = source.value.field_list()?;
172 record_field_list(fields)?
173 }
174 };
175 let def_file_id = def_file_id.original_file(sema.db);
176
177 let new_field_type = sema.type_of_expr(&record_expr.expr()?)?;
178 if new_field_type.is_unknown() {
179 return None;
180 }
181 let new_field = make::record_field_def(
182 record_expr.field_name()?,
183 make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
184 );
185
186 let last_field = record_fields.fields().last()?;
187 let last_field_syntax = last_field.syntax();
188 let indent = IndentLevel::from_node(last_field_syntax);
189
190 let mut new_field = new_field.to_string();
191 if usage_file_id != def_file_id {
192 new_field = format!("pub(crate) {}", new_field);
193 }
194 new_field = format!("\n{}{}", indent, new_field);
195
196 let needs_comma = !last_field_syntax.to_string().ends_with(',');
197 if needs_comma {
198 new_field = format!(",{}", new_field);
199 }
200
201 let source_change = SourceFileEdit {
202 file_id: def_file_id,
203 edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
204 };
205 let fix = Fix::new("Create field", source_change.into());
206 return Some(fix);
207
208 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
209 match field_def_list {
210 ast::FieldList::RecordFieldList(it) => Some(it),
211 ast::FieldList::TupleFieldList(_) => None,
212 }
213 } 92 }
214} 93}
215 94
@@ -220,24 +99,25 @@ fn check_unnecessary_braces_in_use_statement(
220) -> Option<()> { 99) -> Option<()> {
221 let use_tree_list = ast::UseTreeList::cast(node.clone())?; 100 let use_tree_list = ast::UseTreeList::cast(node.clone())?;
222 if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { 101 if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
223 let range = use_tree_list.syntax().text_range(); 102 let use_range = use_tree_list.syntax().text_range();
224 let edit = 103 let edit =
225 text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) 104 text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree)
226 .unwrap_or_else(|| { 105 .unwrap_or_else(|| {
227 let to_replace = single_use_tree.syntax().text().to_string(); 106 let to_replace = single_use_tree.syntax().text().to_string();
228 let mut edit_builder = TextEditBuilder::default(); 107 let mut edit_builder = TextEditBuilder::default();
229 edit_builder.delete(range); 108 edit_builder.delete(use_range);
230 edit_builder.insert(range.start(), to_replace); 109 edit_builder.insert(use_range.start(), to_replace);
231 edit_builder.finish() 110 edit_builder.finish()
232 }); 111 });
233 112
234 acc.push(Diagnostic { 113 acc.push(Diagnostic {
235 range, 114 range: use_range,
236 message: "Unnecessary braces in use statement".to_string(), 115 message: "Unnecessary braces in use statement".to_string(),
237 severity: Severity::WeakWarning, 116 severity: Severity::WeakWarning,
238 fix: Some(Fix::new( 117 fix: Some(Fix::new(
239 "Remove unnecessary braces", 118 "Remove unnecessary braces",
240 SourceFileEdit { file_id, edit }.into(), 119 SourceFileEdit { file_id, edit }.into(),
120 use_range,
241 )), 121 )),
242 }); 122 });
243 } 123 }
@@ -252,8 +132,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
252 if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { 132 if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] {
253 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); 133 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
254 let end = use_tree_list_node.text_range().end(); 134 let end = use_tree_list_node.text_range().end();
255 let range = TextRange::new(start, end); 135 return Some(TextEdit::delete(TextRange::new(start, end)));
256 return Some(TextEdit::delete(range));
257 } 136 }
258 None 137 None
259} 138}
@@ -276,13 +155,15 @@ fn check_struct_shorthand_initialization(
276 edit_builder.insert(record_field.syntax().text_range().start(), field_name); 155 edit_builder.insert(record_field.syntax().text_range().start(), field_name);
277 let edit = edit_builder.finish(); 156 let edit = edit_builder.finish();
278 157
158 let field_range = record_field.syntax().text_range();
279 acc.push(Diagnostic { 159 acc.push(Diagnostic {
280 range: record_field.syntax().text_range(), 160 range: field_range,
281 message: "Shorthand struct initialization".to_string(), 161 message: "Shorthand struct initialization".to_string(),
282 severity: Severity::WeakWarning, 162 severity: Severity::WeakWarning,
283 fix: Some(Fix::new( 163 fix: Some(Fix::new(
284 "Use struct shorthand initialization", 164 "Use struct shorthand initialization",
285 SourceFileEdit { file_id, edit }.into(), 165 SourceFileEdit { file_id, edit }.into(),
166 field_range,
286 )), 167 )),
287 }); 168 });
288 } 169 }
@@ -302,7 +183,7 @@ mod tests {
302 /// Takes a multi-file input fixture with annotated cursor positions, 183 /// Takes a multi-file input fixture with annotated cursor positions,
303 /// and checks that: 184 /// and checks that:
304 /// * a diagnostic is produced 185 /// * a diagnostic is produced
305 /// * this diagnostic touches the input cursor position 186 /// * this diagnostic fix trigger range touches the input cursor position
306 /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied 187 /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
307 fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { 188 fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
308 let after = trim_indent(ra_fixture_after); 189 let after = trim_indent(ra_fixture_after);
@@ -320,10 +201,10 @@ mod tests {
320 201
321 assert_eq_text!(&after, &actual); 202 assert_eq_text!(&after, &actual);
322 assert!( 203 assert!(
323 diagnostic.range.start() <= file_position.offset 204 fix.fix_trigger_range.start() <= file_position.offset
324 && diagnostic.range.end() >= file_position.offset, 205 && fix.fix_trigger_range.end() >= file_position.offset,
325 "diagnostic range {:?} does not touch cursor position {:?}", 206 "diagnostic fix range {:?} does not touch cursor position {:?}",
326 diagnostic.range, 207 fix.fix_trigger_range,
327 file_position.offset 208 file_position.offset
328 ); 209 );
329 } 210 }
@@ -640,6 +521,7 @@ fn test_fn() {
640 ], 521 ],
641 is_snippet: false, 522 is_snippet: false,
642 }, 523 },
524 fix_trigger_range: 0..8,
643 }, 525 },
644 ), 526 ),
645 }, 527 },
diff --git a/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs
new file mode 100644
index 000000000..f7c73773f
--- /dev/null
+++ b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs
@@ -0,0 +1,171 @@
1//! Provides a way to attach fixes to the diagnostics.
2//! The same module also has all curret custom fixes for the diagnostics implemented.
3use crate::Fix;
4use ast::{edit::IndentLevel, make};
5use hir::{
6 db::AstDatabase,
7 diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule},
8 HasSource, HirDisplay, Semantics, VariantDef,
9};
10use ra_db::FileId;
11use ra_ide_db::{
12 source_change::{FileSystemEdit, SourceFileEdit},
13 RootDatabase,
14};
15use ra_syntax::{algo, ast, AstNode};
16use ra_text_edit::{TextEdit, TextEditBuilder};
17
18/// A [Diagnostic] that potentially has a fix available.
19///
20/// [Diagnostic]: hir::diagnostics::Diagnostic
21pub trait DiagnosticWithFix: Diagnostic {
22 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>;
23}
24
25impl DiagnosticWithFix for UnresolvedModule {
26 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
27 let root = sema.db.parse_or_expand(self.file)?;
28 let unresolved_module = self.decl.to_node(&root);
29 Some(Fix::new(
30 "Create module",
31 FileSystemEdit::CreateFile {
32 anchor: self.file.original_file(sema.db),
33 dst: self.candidate.clone(),
34 }
35 .into(),
36 unresolved_module.syntax().text_range(),
37 ))
38 }
39}
40
41impl DiagnosticWithFix for NoSuchField {
42 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
43 let root = sema.db.parse_or_expand(self.file)?;
44 missing_record_expr_field_fix(
45 &sema,
46 self.file.original_file(sema.db),
47 &self.field.to_node(&root),
48 )
49 }
50}
51
52impl DiagnosticWithFix for MissingFields {
53 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
54 // Note that although we could add a diagnostics to
55 // fill the missing tuple field, e.g :
56 // `struct A(usize);`
57 // `let a = A { 0: () }`
58 // but it is uncommon usage and it should not be encouraged.
59 if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
60 return None;
61 }
62
63 let root = sema.db.parse_or_expand(self.file)?;
64 let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?;
65 let mut new_field_list = old_field_list.clone();
66 for f in self.missed_fields.iter() {
67 let field =
68 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
69 new_field_list = new_field_list.append_field(&field);
70 }
71
72 let edit = {
73 let mut builder = TextEditBuilder::default();
74 algo::diff(&old_field_list.syntax(), &new_field_list.syntax())
75 .into_text_edit(&mut builder);
76 builder.finish()
77 };
78 Some(Fix::new(
79 "Fill struct fields",
80 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(),
81 sema.original_range(&old_field_list.syntax()).range,
82 ))
83 }
84}
85
86impl DiagnosticWithFix for MissingOkInTailExpr {
87 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
88 let root = sema.db.parse_or_expand(self.file)?;
89 let tail_expr = self.expr.to_node(&root);
90 let tail_expr_range = tail_expr.syntax().text_range();
91 let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax()));
92 let source_change =
93 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
94 Some(Fix::new("Wrap with ok", source_change, tail_expr_range))
95 }
96}
97
98fn missing_record_expr_field_fix(
99 sema: &Semantics<RootDatabase>,
100 usage_file_id: FileId,
101 record_expr_field: &ast::RecordExprField,
102) -> Option<Fix> {
103 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
104 let def_id = sema.resolve_variant(record_lit)?;
105 let module;
106 let def_file_id;
107 let record_fields = match VariantDef::from(def_id) {
108 VariantDef::Struct(s) => {
109 module = s.module(sema.db);
110 let source = s.source(sema.db);
111 def_file_id = source.file_id;
112 let fields = source.value.field_list()?;
113 record_field_list(fields)?
114 }
115 VariantDef::Union(u) => {
116 module = u.module(sema.db);
117 let source = u.source(sema.db);
118 def_file_id = source.file_id;
119 source.value.record_field_list()?
120 }
121 VariantDef::EnumVariant(e) => {
122 module = e.module(sema.db);
123 let source = e.source(sema.db);
124 def_file_id = source.file_id;
125 let fields = source.value.field_list()?;
126 record_field_list(fields)?
127 }
128 };
129 let def_file_id = def_file_id.original_file(sema.db);
130
131 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
132 if new_field_type.is_unknown() {
133 return None;
134 }
135 let new_field = make::record_field(
136 record_expr_field.field_name()?,
137 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
138 );
139
140 let last_field = record_fields.fields().last()?;
141 let last_field_syntax = last_field.syntax();
142 let indent = IndentLevel::from_node(last_field_syntax);
143
144 let mut new_field = new_field.to_string();
145 if usage_file_id != def_file_id {
146 new_field = format!("pub(crate) {}", new_field);
147 }
148 new_field = format!("\n{}{}", indent, new_field);
149
150 let needs_comma = !last_field_syntax.to_string().ends_with(',');
151 if needs_comma {
152 new_field = format!(",{}", new_field);
153 }
154
155 let source_change = SourceFileEdit {
156 file_id: def_file_id,
157 edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
158 };
159 return Some(Fix::new(
160 "Create field",
161 source_change.into(),
162 record_expr_field.syntax().text_range(),
163 ));
164
165 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
166 match field_def_list {
167 ast::FieldList::RecordFieldList(it) => Some(it),
168 ast::FieldList::TupleFieldList(_) => None,
169 }
170 }
171}
diff --git a/crates/ra_ide/src/display/short_label.rs b/crates/ra_ide/src/display/short_label.rs
index 0fdf8e9a5..010c34705 100644
--- a/crates/ra_ide/src/display/short_label.rs
+++ b/crates/ra_ide/src/display/short_label.rs
@@ -47,6 +47,12 @@ impl ShortLabel for ast::Module {
47 } 47 }
48} 48}
49 49
50impl ShortLabel for ast::SourceFile {
51 fn short_label(&self) -> Option<String> {
52 None
53 }
54}
55
50impl ShortLabel for ast::TypeAlias { 56impl ShortLabel for ast::TypeAlias {
51 fn short_label(&self) -> Option<String> { 57 fn short_label(&self) -> Option<String> {
52 short_label_from_node(self, "type ") 58 short_label_from_node(self, "type ")
@@ -55,7 +61,11 @@ impl ShortLabel for ast::TypeAlias {
55 61
56impl ShortLabel for ast::Const { 62impl ShortLabel for ast::Const {
57 fn short_label(&self) -> Option<String> { 63 fn short_label(&self) -> Option<String> {
58 short_label_from_ty(self, self.ty(), "const ") 64 let mut new_buf = short_label_from_ty(self, self.ty(), "const ")?;
65 if let Some(expr) = self.body() {
66 format_to!(new_buf, " = {}", expr.syntax());
67 }
68 Some(new_buf)
59 } 69 }
60} 70}
61 71
diff --git a/crates/ra_ide/src/folding_ranges.rs b/crates/ra_ide/src/folding_ranges.rs
index 903c34996..0fbc9babd 100644
--- a/crates/ra_ide/src/folding_ranges.rs
+++ b/crates/ra_ide/src/folding_ranges.rs
@@ -85,7 +85,8 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
85 COMMENT => Some(FoldKind::Comment), 85 COMMENT => Some(FoldKind::Comment),
86 USE => Some(FoldKind::Imports), 86 USE => Some(FoldKind::Imports),
87 ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList), 87 ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList),
88 RECORD_FIELD_LIST 88 ASSOC_ITEM_LIST
89 | RECORD_FIELD_LIST
89 | RECORD_PAT_FIELD_LIST 90 | RECORD_PAT_FIELD_LIST
90 | RECORD_EXPR_FIELD_LIST 91 | RECORD_EXPR_FIELD_LIST
91 | ITEM_LIST 92 | ITEM_LIST
@@ -337,6 +338,26 @@ fn main() <fold block>{
337 } 338 }
338 339
339 #[test] 340 #[test]
341 fn test_folds_structs() {
342 check(
343 r#"
344struct Foo <fold block>{
345}</fold>
346"#,
347 );
348 }
349
350 #[test]
351 fn test_folds_traits() {
352 check(
353 r#"
354trait Foo <fold block>{
355}</fold>
356"#,
357 );
358 }
359
360 #[test]
340 fn test_folds_macros() { 361 fn test_folds_macros() {
341 check( 362 check(
342 r#" 363 r#"
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
index 4e3f428fa..45389fd23 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -1,6 +1,6 @@
1use hir::Semantics; 1use hir::Semantics;
2use ra_ide_db::{ 2use ra_ide_db::{
3 defs::{classify_name, classify_name_ref, NameClass}, 3 defs::{classify_name, classify_name_ref},
4 symbol_index, RootDatabase, 4 symbol_index, RootDatabase,
5}; 5};
6use ra_syntax::{ 6use ra_syntax::{
@@ -40,10 +40,7 @@ pub(crate) fn goto_definition(
40 reference_definition(&sema, &name_ref).to_vec() 40 reference_definition(&sema, &name_ref).to_vec()
41 }, 41 },
42 ast::Name(name) => { 42 ast::Name(name) => {
43 let def = match classify_name(&sema, &name)? { 43 let def = classify_name(&sema, &name)?.definition(sema.db);
44 NameClass::Definition(def) | NameClass::ConstReference(def) => def,
45 NameClass::FieldShorthand { local: _, field } => field,
46 };
47 let nav = def.try_to_nav(sema.db)?; 44 let nav = def.try_to_nav(sema.db)?;
48 vec![nav] 45 vec![nav]
49 }, 46 },
@@ -86,8 +83,7 @@ pub(crate) fn reference_definition(
86) -> ReferenceResult { 83) -> ReferenceResult {
87 let name_kind = classify_name_ref(sema, name_ref); 84 let name_kind = classify_name_ref(sema, name_ref);
88 if let Some(def) = name_kind { 85 if let Some(def) = name_kind {
89 let def = def.definition(); 86 let def = def.definition(sema.db);
90
91 return match def.try_to_nav(sema.db) { 87 return match def.try_to_nav(sema.db) {
92 Some(nav) => ReferenceResult::Exact(nav), 88 Some(nav) => ReferenceResult::Exact(nav),
93 None => ReferenceResult::Approximate(Vec::new()), 89 None => ReferenceResult::Approximate(Vec::new()),
@@ -134,6 +130,32 @@ mod tests {
134 } 130 }
135 131
136 #[test] 132 #[test]
133 fn goto_def_for_extern_crate() {
134 check(
135 r#"
136 //- /main.rs
137 extern crate std<|>;
138 //- /std/lib.rs
139 // empty
140 //^ file
141 "#,
142 )
143 }
144
145 #[test]
146 fn goto_def_for_renamed_extern_crate() {
147 check(
148 r#"
149 //- /main.rs
150 extern crate std as abc<|>;
151 //- /std/lib.rs
152 // empty
153 //^ file
154 "#,
155 )
156 }
157
158 #[test]
137 fn goto_def_in_items() { 159 fn goto_def_in_items() {
138 check( 160 check(
139 r#" 161 r#"
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index aa48cb412..f66f62bfb 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -85,8 +85,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
85 let node = token.parent(); 85 let node = token.parent();
86 let definition = match_ast! { 86 let definition = match_ast! {
87 match node { 87 match node {
88 ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition()), 88 ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)),
89 ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition()), 89 ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)),
90 _ => None, 90 _ => None,
91 } 91 }
92 }; 92 };
@@ -304,7 +304,10 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
304 let docs = Documentation::from_ast(&it).map(Into::into); 304 let docs = Documentation::from_ast(&it).map(Into::into);
305 hover_markup(docs, it.short_label(), mod_path) 305 hover_markup(docs, it.short_label(), mod_path)
306 } 306 }
307 _ => None, 307 ModuleSource::SourceFile(it) => {
308 let docs = Documentation::from_ast(&it).map(Into::into);
309 hover_markup(docs, it.short_label(), mod_path)
310 }
308 }, 311 },
309 ModuleDef::Function(it) => from_def_source(db, it, mod_path), 312 ModuleDef::Function(it) => from_def_source(db, it, mod_path),
310 ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path), 313 ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path),
@@ -509,6 +512,37 @@ fn main() { }
509 } 512 }
510 513
511 #[test] 514 #[test]
515 fn hover_shows_fn_doc() {
516 check(
517 r#"
518/// # Example
519/// ```
520/// # use std::path::Path;
521/// #
522/// foo(Path::new("hello, world!"))
523/// ```
524pub fn foo<|>(_: &Path) {}
525
526fn main() { }
527"#,
528 expect![[r#"
529 *foo*
530 ```rust
531 pub fn foo(_: &Path)
532 ```
533 ___
534
535 # Example
536 ```
537 # use std::path::Path;
538 #
539 foo(Path::new("hello, world!"))
540 ```
541 "#]],
542 );
543 }
544
545 #[test]
512 fn hover_shows_struct_field_info() { 546 fn hover_shows_struct_field_info() {
513 // Hovering over the field when instantiating 547 // Hovering over the field when instantiating
514 check( 548 check(
@@ -556,16 +590,16 @@ fn main() {
556 #[test] 590 #[test]
557 fn hover_const_static() { 591 fn hover_const_static() {
558 check( 592 check(
559 r#"const foo<|>: u32 = 0;"#, 593 r#"const foo<|>: u32 = 123;"#,
560 expect![[r#" 594 expect![[r#"
561 *foo* 595 *foo*
562 ```rust 596 ```rust
563 const foo: u32 597 const foo: u32 = 123
564 ``` 598 ```
565 "#]], 599 "#]],
566 ); 600 );
567 check( 601 check(
568 r#"static foo<|>: u32 = 0;"#, 602 r#"static foo<|>: u32 = 456;"#,
569 expect![[r#" 603 expect![[r#"
570 *foo* 604 *foo*
571 ```rust 605 ```rust
@@ -800,7 +834,7 @@ fn main() {
800 expect![[r#" 834 expect![[r#"
801 *C* 835 *C*
802 ```rust 836 ```rust
803 const C: u32 837 const C: u32 = 1
804 ``` 838 ```
805 "#]], 839 "#]],
806 ) 840 )
@@ -1107,6 +1141,46 @@ fn bar() { fo<|>o(); }
1107 } 1141 }
1108 1142
1109 #[test] 1143 #[test]
1144 fn test_hover_extern_crate() {
1145 check(
1146 r#"
1147//- /main.rs
1148extern crate st<|>d;
1149//- /std/lib.rs
1150//! Standard library for this test
1151//!
1152//! Printed?
1153//! abc123
1154 "#,
1155 expect![[r#"
1156 *std*
1157 Standard library for this test
1158
1159 Printed?
1160 abc123
1161 "#]],
1162 );
1163 check(
1164 r#"
1165//- /main.rs
1166extern crate std as ab<|>c;
1167//- /std/lib.rs
1168//! Standard library for this test
1169//!
1170//! Printed?
1171//! abc123
1172 "#,
1173 expect![[r#"
1174 *abc*
1175 Standard library for this test
1176
1177 Printed?
1178 abc123
1179 "#]],
1180 );
1181 }
1182
1183 #[test]
1110 fn test_hover_mod_with_same_name_as_function() { 1184 fn test_hover_mod_with_same_name_as_function() {
1111 check( 1185 check(
1112 r#" 1186 r#"
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 0fede0d87..89fcb6f17 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -112,13 +112,19 @@ pub struct Diagnostic {
112pub struct Fix { 112pub struct Fix {
113 pub label: String, 113 pub label: String,
114 pub source_change: SourceChange, 114 pub source_change: SourceChange,
115 /// Allows to trigger the fix only when the caret is in the range given
116 pub fix_trigger_range: TextRange,
115} 117}
116 118
117impl Fix { 119impl Fix {
118 pub fn new(label: impl Into<String>, source_change: SourceChange) -> Self { 120 pub fn new(
121 label: impl Into<String>,
122 source_change: SourceChange,
123 fix_trigger_range: TextRange,
124 ) -> Self {
119 let label = label.into(); 125 let label = label.into();
120 assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.')); 126 assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
121 Self { label, source_change } 127 Self { label, source_change, fix_trigger_range }
122 } 128 }
123} 129}
124 130
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
index cf456630a..453985de3 100644
--- a/crates/ra_ide/src/references.rs
+++ b/crates/ra_ide/src/references.rs
@@ -130,13 +130,13 @@ fn find_name(
130 opt_name: Option<ast::Name>, 130 opt_name: Option<ast::Name>,
131) -> Option<RangeInfo<Definition>> { 131) -> Option<RangeInfo<Definition>> {
132 if let Some(name) = opt_name { 132 if let Some(name) = opt_name {
133 let def = classify_name(sema, &name)?.definition(); 133 let def = classify_name(sema, &name)?.definition(sema.db);
134 let range = name.syntax().text_range(); 134 let range = name.syntax().text_range();
135 return Some(RangeInfo::new(range, def)); 135 return Some(RangeInfo::new(range, def));
136 } 136 }
137 let name_ref = 137 let name_ref =
138 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; 138 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?;
139 let def = classify_name_ref(sema, &name_ref)?.definition(); 139 let def = classify_name_ref(sema, &name_ref)?.definition(sema.db);
140 let range = name_ref.syntax().text_range(); 140 let range = name_ref.syntax().text_range();
141 Some(RangeInfo::new(range, def)) 141 Some(RangeInfo::new(range, def))
142} 142}
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 3cd6c815b..c8d80fcf7 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -157,7 +157,7 @@ fn rename_to_self(
157 } 157 }
158 let first_param = params.params().next()?; 158 let first_param = params.params().next()?;
159 let mutable = match first_param.ty() { 159 let mutable = match first_param.ty() {
160 Some(ast::Type::ReferenceType(rt)) => rt.mut_token().is_some(), 160 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
161 _ => return None, // not renaming other types 161 _ => return None, // not renaming other types
162 }; 162 };
163 163
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 32f34ef10..c10e15db8 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -4,7 +4,7 @@ mod injection;
4#[cfg(test)] 4#[cfg(test)]
5mod tests; 5mod tests;
6 6
7use hir::{Name, Semantics}; 7use hir::{Name, Semantics, VariantDef};
8use ra_ide_db::{ 8use ra_ide_db::{
9 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, 9 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
10 RootDatabase, 10 RootDatabase,
@@ -455,6 +455,18 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
455 Some(TextRange::new(range_start, range_end)) 455 Some(TextRange::new(range_start, range_end))
456} 456}
457 457
458fn is_possibly_unsafe(name_ref: &ast::NameRef) -> bool {
459 name_ref
460 .syntax()
461 .parent()
462 .and_then(|parent| {
463 ast::FieldExpr::cast(parent.clone())
464 .map(|_| true)
465 .or_else(|| ast::RecordPatField::cast(parent).map(|_| true))
466 })
467 .unwrap_or(false)
468}
469
458fn highlight_element( 470fn highlight_element(
459 sema: &Semantics<RootDatabase>, 471 sema: &Semantics<RootDatabase>,
460 bindings_shadow_count: &mut FxHashMap<Name, u32>, 472 bindings_shadow_count: &mut FxHashMap<Name, u32>,
@@ -483,11 +495,21 @@ fn highlight_element(
483 }; 495 };
484 496
485 match name_kind { 497 match name_kind {
498 Some(NameClass::ExternCrate(_)) => HighlightTag::Module.into(),
486 Some(NameClass::Definition(def)) => { 499 Some(NameClass::Definition(def)) => {
487 highlight_name(db, def) | HighlightModifier::Definition 500 highlight_name(sema, db, def, None, false) | HighlightModifier::Definition
501 }
502 Some(NameClass::ConstReference(def)) => highlight_name(sema, db, def, None, false),
503 Some(NameClass::FieldShorthand { field, .. }) => {
504 let mut h = HighlightTag::Field.into();
505 if let Definition::Field(field) = field {
506 if let VariantDef::Union(_) = field.parent_def(db) {
507 h |= HighlightModifier::Unsafe;
508 }
509 }
510
511 h
488 } 512 }
489 Some(NameClass::ConstReference(def)) => highlight_name(db, def),
490 Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(),
491 None => highlight_name_by_syntax(name) | HighlightModifier::Definition, 513 None => highlight_name_by_syntax(name) | HighlightModifier::Definition,
492 } 514 }
493 } 515 }
@@ -498,8 +520,10 @@ fn highlight_element(
498 } 520 }
499 NAME_REF => { 521 NAME_REF => {
500 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); 522 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
523 let possibly_unsafe = is_possibly_unsafe(&name_ref);
501 match classify_name_ref(sema, &name_ref) { 524 match classify_name_ref(sema, &name_ref) {
502 Some(name_kind) => match name_kind { 525 Some(name_kind) => match name_kind {
526 NameRefClass::ExternCrate(_) => HighlightTag::Module.into(),
503 NameRefClass::Definition(def) => { 527 NameRefClass::Definition(def) => {
504 if let Definition::Local(local) = &def { 528 if let Definition::Local(local) = &def {
505 if let Some(name) = local.name(db) { 529 if let Some(name) = local.name(db) {
@@ -508,11 +532,13 @@ fn highlight_element(
508 binding_hash = Some(calc_binding_hash(&name, *shadow_count)) 532 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
509 } 533 }
510 }; 534 };
511 highlight_name(db, def) 535 highlight_name(sema, db, def, Some(name_ref), possibly_unsafe)
512 } 536 }
513 NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), 537 NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(),
514 }, 538 },
515 None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref), 539 None if syntactic_name_ref_highlighting => {
540 highlight_name_ref_by_syntax(name_ref, sema)
541 }
516 None => HighlightTag::UnresolvedReference.into(), 542 None => HighlightTag::UnresolvedReference.into(),
517 } 543 }
518 } 544 }
@@ -540,13 +566,24 @@ fn highlight_element(
540 } 566 }
541 } 567 }
542 p if p.is_punct() => match p { 568 p if p.is_punct() => match p {
543 T![::] | T![->] | T![=>] | T![&] | T![..] | T![=] | T![@] => { 569 T![&] => {
544 HighlightTag::Operator.into() 570 let h = HighlightTag::Operator.into();
571 let is_unsafe = element
572 .parent()
573 .and_then(ast::RefExpr::cast)
574 .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr))
575 .unwrap_or(false);
576 if is_unsafe {
577 h | HighlightModifier::Unsafe
578 } else {
579 h
580 }
545 } 581 }
582 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] => HighlightTag::Operator.into(),
546 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { 583 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
547 HighlightTag::Macro.into() 584 HighlightTag::Macro.into()
548 } 585 }
549 T![*] if element.parent().and_then(ast::PointerType::cast).is_some() => { 586 T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => {
550 HighlightTag::Keyword.into() 587 HighlightTag::Keyword.into()
551 } 588 }
552 T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { 589 T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
@@ -623,6 +660,18 @@ fn highlight_element(
623 HighlightTag::SelfKeyword.into() 660 HighlightTag::SelfKeyword.into()
624 } 661 }
625 } 662 }
663 T![ref] => element
664 .parent()
665 .and_then(ast::IdentPat::cast)
666 .and_then(|ident_pat| {
667 if sema.is_unsafe_ident_pat(&ident_pat) {
668 Some(HighlightModifier::Unsafe)
669 } else {
670 None
671 }
672 })
673 .map(|modifier| h | modifier)
674 .unwrap_or(h),
626 _ => h, 675 _ => h,
627 } 676 }
628 } 677 }
@@ -652,16 +701,40 @@ fn is_child_of_impl(element: &SyntaxElement) -> bool {
652 } 701 }
653} 702}
654 703
655fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { 704fn highlight_name(
705 sema: &Semantics<RootDatabase>,
706 db: &RootDatabase,
707 def: Definition,
708 name_ref: Option<ast::NameRef>,
709 possibly_unsafe: bool,
710) -> Highlight {
656 match def { 711 match def {
657 Definition::Macro(_) => HighlightTag::Macro, 712 Definition::Macro(_) => HighlightTag::Macro,
658 Definition::Field(_) => HighlightTag::Field, 713 Definition::Field(field) => {
714 let mut h = HighlightTag::Field.into();
715 if possibly_unsafe {
716 if let VariantDef::Union(_) = field.parent_def(db) {
717 h |= HighlightModifier::Unsafe;
718 }
719 }
720
721 return h;
722 }
659 Definition::ModuleDef(def) => match def { 723 Definition::ModuleDef(def) => match def {
660 hir::ModuleDef::Module(_) => HighlightTag::Module, 724 hir::ModuleDef::Module(_) => HighlightTag::Module,
661 hir::ModuleDef::Function(func) => { 725 hir::ModuleDef::Function(func) => {
662 let mut h = HighlightTag::Function.into(); 726 let mut h = HighlightTag::Function.into();
663 if func.is_unsafe(db) { 727 if func.is_unsafe(db) {
664 h |= HighlightModifier::Unsafe; 728 h |= HighlightModifier::Unsafe;
729 } else {
730 let is_unsafe = name_ref
731 .and_then(|name_ref| name_ref.syntax().parent())
732 .and_then(ast::MethodCallExpr::cast)
733 .map(|method_call_expr| sema.is_unsafe_method_call(method_call_expr))
734 .unwrap_or(false);
735 if is_unsafe {
736 h |= HighlightModifier::Unsafe;
737 }
665 } 738 }
666 return h; 739 return h;
667 } 740 }
@@ -677,6 +750,7 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
677 let mut h = Highlight::new(HighlightTag::Static); 750 let mut h = Highlight::new(HighlightTag::Static);
678 if s.is_mut(db) { 751 if s.is_mut(db) {
679 h |= HighlightModifier::Mutable; 752 h |= HighlightModifier::Mutable;
753 h |= HighlightModifier::Unsafe;
680 } 754 }
681 return h; 755 return h;
682 } 756 }
@@ -724,7 +798,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
724 tag.into() 798 tag.into()
725} 799}
726 800
727fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight { 801fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
728 let default = HighlightTag::UnresolvedReference; 802 let default = HighlightTag::UnresolvedReference;
729 803
730 let parent = match name.syntax().parent() { 804 let parent = match name.syntax().parent() {
@@ -732,9 +806,36 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight {
732 _ => return default.into(), 806 _ => return default.into(),
733 }; 807 };
734 808
735 let tag = match parent.kind() { 809 match parent.kind() {
736 METHOD_CALL_EXPR => HighlightTag::Function, 810 METHOD_CALL_EXPR => {
737 FIELD_EXPR => HighlightTag::Field, 811 let mut h = Highlight::new(HighlightTag::Function);
812 let is_unsafe = ast::MethodCallExpr::cast(parent)
813 .map(|method_call_expr| sema.is_unsafe_method_call(method_call_expr))
814 .unwrap_or(false);
815 if is_unsafe {
816 h |= HighlightModifier::Unsafe;
817 }
818
819 h
820 }
821 FIELD_EXPR => {
822 let h = HighlightTag::Field;
823 let is_union = ast::FieldExpr::cast(parent)
824 .and_then(|field_expr| {
825 let field = sema.resolve_field(&field_expr)?;
826 Some(if let VariantDef::Union(_) = field.parent_def(sema.db) {
827 true
828 } else {
829 false
830 })
831 })
832 .unwrap_or(false);
833 if is_union {
834 h | HighlightModifier::Unsafe
835 } else {
836 h.into()
837 }
838 }
738 PATH_SEGMENT => { 839 PATH_SEGMENT => {
739 let path = match parent.parent().and_then(ast::Path::cast) { 840 let path = match parent.parent().and_then(ast::Path::cast) {
740 Some(it) => it, 841 Some(it) => it,
@@ -758,18 +859,15 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight {
758 }; 859 };
759 860
760 match parent.kind() { 861 match parent.kind() {
761 CALL_EXPR => HighlightTag::Function, 862 CALL_EXPR => HighlightTag::Function.into(),
762 _ => { 863 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
763 if name.text().chars().next().unwrap_or_default().is_uppercase() { 864 HighlightTag::Struct.into()
764 HighlightTag::Struct 865 } else {
765 } else { 866 HighlightTag::Constant
766 HighlightTag::Constant
767 }
768 } 867 }
868 .into(),
769 } 869 }
770 } 870 }
771 _ => default, 871 _ => default.into(),
772 }; 872 }
773
774 tag.into()
775} 873}
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs
index 8665b480f..6046643ef 100644
--- a/crates/ra_ide/src/syntax_highlighting/injection.rs
+++ b/crates/ra_ide/src/syntax_highlighting/injection.rs
@@ -4,8 +4,8 @@ use std::{collections::BTreeMap, convert::TryFrom};
4 4
5use ast::{HasQuotes, HasStringValue}; 5use ast::{HasQuotes, HasStringValue};
6use hir::Semantics; 6use hir::Semantics;
7use itertools::Itertools;
7use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; 8use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
8use stdx::SepBy;
9 9
10use crate::{ 10use crate::{
11 call_info::ActiveParameter, Analysis, Highlight, HighlightModifier, HighlightTag, 11 call_info::ActiveParameter, Analysis, Highlight, HighlightModifier, HighlightTag,
@@ -129,8 +129,7 @@ pub(super) fn extract_doc_comments(
129 129
130 line[pos..].to_owned() 130 line[pos..].to_owned()
131 }) 131 })
132 .sep_by("\n") 132 .join("\n");
133 .to_string();
134 133
135 if doctest.is_empty() { 134 if doctest.is_empty() {
136 return None; 135 return None;
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index 87a6e2523..a8087635a 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -9,6 +9,9 @@ use crate::{mock_analysis::single_file, FileRange, TextRange};
9fn test_highlighting() { 9fn test_highlighting() {
10 check_highlighting( 10 check_highlighting(
11 r#" 11 r#"
12use inner::{self as inner_mod};
13mod inner {}
14
12#[derive(Clone, Debug)] 15#[derive(Clone, Debug)]
13struct Foo { 16struct Foo {
14 pub x: i32, 17 pub x: i32,
@@ -272,19 +275,64 @@ fn test_unsafe_highlighting() {
272 r#" 275 r#"
273unsafe fn unsafe_fn() {} 276unsafe fn unsafe_fn() {}
274 277
278union Union {
279 a: u32,
280 b: f32,
281}
282
275struct HasUnsafeFn; 283struct HasUnsafeFn;
276 284
277impl HasUnsafeFn { 285impl HasUnsafeFn {
278 unsafe fn unsafe_method(&self) {} 286 unsafe fn unsafe_method(&self) {}
279} 287}
280 288
289struct TypeForStaticMut {
290 a: u8
291}
292
293static mut global_mut: TypeForStaticMut = TypeForStaticMut { a: 0 };
294
295#[repr(packed)]
296struct Packed {
297 a: u16,
298}
299
300trait DoTheAutoref {
301 fn calls_autoref(&self);
302}
303
304impl DoTheAutoref for u16 {
305 fn calls_autoref(&self) {}
306}
307
281fn main() { 308fn main() {
282 let x = &5 as *const usize; 309 let x = &5 as *const _ as *const usize;
310 let u = Union { b: 0 };
283 unsafe { 311 unsafe {
312 // unsafe fn and method calls
284 unsafe_fn(); 313 unsafe_fn();
314 let b = u.b;
315 match u {
316 Union { b: 0 } => (),
317 Union { a } => (),
318 }
285 HasUnsafeFn.unsafe_method(); 319 HasUnsafeFn.unsafe_method();
286 let y = *(x); 320
287 let z = -x; 321 // unsafe deref
322 let y = *x;
323
324 // unsafe access to a static mut
325 let a = global_mut.a;
326
327 // unsafe ref of packed fields
328 let packed = Packed { a: 0 };
329 let a = &packed.a;
330 let ref a = packed.a;
331 let Packed { ref a } = packed;
332 let Packed { a: ref _a } = packed;
333
334 // unsafe auto ref of packed field
335 packed.a.calls_autoref();
288 } 336 }
289} 337}
290"# 338"#
@@ -370,6 +418,23 @@ macro_rules! noop {
370 ); 418 );
371} 419}
372 420
421#[test]
422fn test_extern_crate() {
423 check_highlighting(
424 r#"
425 //- /main.rs
426 extern crate std;
427 extern crate alloc as abc;
428 //- /std/lib.rs
429 pub struct S;
430 //- /alloc/lib.rs
431 pub struct A
432 "#,
433 expect_file!["crates/ra_ide/test_data/highlight_extern_crate.html"],
434 false,
435 );
436}
437
373/// Highlights the code given by the `ra_fixture` argument, renders the 438/// Highlights the code given by the `ra_fixture` argument, renders the
374/// result as HTML, and compares it with the HTML file given as `snapshot`. 439/// result as HTML, and compares it with the HTML file given as `snapshot`.
375/// Note that the `snapshot` file is overwritten by the rendered HTML. 440/// Note that the `snapshot` file is overwritten by the rendered HTML.