1pub(crate) mod flycheck_to_proto;
3
4use std::mem;
5
6use ide::FileId;
7use ide_db::{FxHashMap, base_db::DbPanicContext};
8use itertools::Itertools;
9use rustc_hash::FxHashSet;
10use smallvec::SmallVec;
11use stdx::iter_eq_by;
12use triomphe::Arc;
13
14use crate::{
15 flycheck::PackageSpecifier, global_state::GlobalStateSnapshot, lsp, lsp_ext,
16 main_loop::DiagnosticsTaskKind,
17};
18
19pub(crate) type CheckFixes =
20 Arc<Vec<FxHashMap<Option<PackageSpecifier>, FxHashMap<FileId, Vec<Fix>>>>>;
21
22#[derive(Debug, Default, Clone)]
23pub struct DiagnosticsMapConfig {
24 pub remap_prefix: FxHashMap<String, String>,
25 pub warnings_as_info: Vec<String>,
26 pub warnings_as_hint: Vec<String>,
27 pub check_ignore: FxHashSet<String>,
28}
29
30pub(crate) type DiagnosticsGeneration = usize;
31
32#[derive(Debug, Clone, Default)]
33pub(crate) struct WorkspaceFlycheckDiagnostic {
34 pub(crate) per_package: FxHashMap<Option<PackageSpecifier>, PackageFlycheckDiagnostic>,
35}
36
37#[derive(Debug, Clone)]
38pub(crate) struct PackageFlycheckDiagnostic {
39 generation: DiagnosticsGeneration,
40 per_file: FxHashMap<FileId, Vec<lsp_types::Diagnostic>>,
41}
42
43#[derive(Debug, Default, Clone)]
44pub(crate) struct DiagnosticCollection {
45 pub(crate) native_syntax:
47 FxHashMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>,
48 pub(crate) native_semantic:
49 FxHashMap<FileId, (DiagnosticsGeneration, Vec<lsp_types::Diagnostic>)>,
50 pub(crate) check: Vec<WorkspaceFlycheckDiagnostic>,
51 pub(crate) check_fixes: CheckFixes,
52 changes: FxHashSet<FileId>,
53 generation: DiagnosticsGeneration,
58}
59
60#[derive(Debug, Clone)]
61pub(crate) struct Fix {
62 pub(crate) ranges: SmallVec<[lsp_types::Range; 1]>,
64 pub(crate) action: lsp_ext::CodeAction,
65}
66
67impl DiagnosticCollection {
68 pub(crate) fn clear_check(&mut self, flycheck_id: usize) {
69 let Some(check) = self.check.get_mut(flycheck_id) else {
70 return;
71 };
72 self.changes.extend(check.per_package.drain().flat_map(|(_, v)| v.per_file.into_keys()));
73 if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) {
74 fixes.clear();
75 }
76 }
77
78 pub(crate) fn clear_check_all(&mut self) {
79 Arc::make_mut(&mut self.check_fixes).clear();
80 self.changes.extend(
81 self.check
82 .iter_mut()
83 .flat_map(|it| it.per_package.drain().flat_map(|(_, v)| v.per_file.into_keys())),
84 )
85 }
86
87 pub(crate) fn clear_check_for_package(
88 &mut self,
89 flycheck_id: usize,
90 package_id: PackageSpecifier,
91 ) {
92 let Some(check) = self.check.get_mut(flycheck_id) else {
93 return;
94 };
95 let package_id = Some(package_id);
96 if let Some(checks) = check.per_package.remove(&package_id) {
97 self.changes.extend(checks.per_file.into_keys());
98 }
99 if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) {
100 fixes.remove(&package_id);
101 }
102 }
103
104 pub(crate) fn clear_check_older_than(
105 &mut self,
106 flycheck_id: usize,
107 generation: DiagnosticsGeneration,
108 ) {
109 if let Some(flycheck) = self.check.get_mut(flycheck_id) {
110 let mut packages = vec![];
111 self.changes.extend(
112 flycheck
113 .per_package
114 .extract_if(|_, v| v.generation < generation)
115 .inspect(|(package_id, _)| packages.push(package_id.clone()))
116 .flat_map(|(_, v)| v.per_file.into_keys()),
117 );
118 if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) {
119 for package in packages {
120 fixes.remove(&package);
121 }
122 }
123 }
124 }
125
126 pub(crate) fn clear_check_older_than_for_package(
127 &mut self,
128 flycheck_id: usize,
129 package_id: PackageSpecifier,
130 generation: DiagnosticsGeneration,
131 ) {
132 let Some(check) = self.check.get_mut(flycheck_id) else {
133 return;
134 };
135 let package_id = Some(package_id);
136 let Some((_, checks)) = check
137 .per_package
138 .extract_if(|k, v| *k == package_id && v.generation < generation)
139 .next()
140 else {
141 return;
142 };
143 self.changes.extend(checks.per_file.into_keys());
144 if let Some(fixes) = Arc::make_mut(&mut self.check_fixes).get_mut(flycheck_id) {
145 fixes.remove(&package_id);
146 }
147 }
148
149 pub(crate) fn clear_native_for(&mut self, file_id: FileId) {
150 self.native_syntax.remove(&file_id);
151 self.native_semantic.remove(&file_id);
152 self.changes.insert(file_id);
153 }
154
155 pub(crate) fn add_check_diagnostic(
156 &mut self,
157 flycheck_id: usize,
158 generation: DiagnosticsGeneration,
159 package_id: &Option<PackageSpecifier>,
160 file_id: FileId,
161 diagnostic: lsp_types::Diagnostic,
162 fix: Option<Box<Fix>>,
163 ) {
164 if self.check.len() <= flycheck_id {
165 self.check.resize_with(flycheck_id + 1, WorkspaceFlycheckDiagnostic::default);
166 }
167
168 let check = &mut self.check[flycheck_id];
169 let package = check.per_package.entry(package_id.clone()).or_insert_with(|| {
170 PackageFlycheckDiagnostic { generation, per_file: FxHashMap::default() }
171 });
172 if package.generation > generation {
174 return;
175 }
176 package.generation = generation;
177 let diagnostics = package.per_file.entry(file_id).or_default();
178 for existing_diagnostic in diagnostics.iter() {
179 if are_diagnostics_equal(existing_diagnostic, &diagnostic) {
180 return;
181 }
182 }
183
184 if let Some(fix) = fix {
185 let check_fixes = Arc::make_mut(&mut self.check_fixes);
186 if check_fixes.len() <= flycheck_id {
187 check_fixes.resize_with(flycheck_id + 1, Default::default);
188 }
189 check_fixes[flycheck_id]
190 .entry(package_id.clone())
191 .or_default()
192 .entry(file_id)
193 .or_default()
194 .push(*fix);
195 }
196 diagnostics.push(diagnostic);
197 self.changes.insert(file_id);
198 }
199
200 pub(crate) fn set_native_diagnostics(&mut self, kind: DiagnosticsTaskKind) {
201 let (generation, diagnostics, target) = match kind {
202 DiagnosticsTaskKind::Syntax(generation, diagnostics) => {
203 (generation, diagnostics, &mut self.native_syntax)
204 }
205 DiagnosticsTaskKind::Semantic(generation, diagnostics) => {
206 (generation, diagnostics, &mut self.native_semantic)
207 }
208 };
209
210 for (file_id, mut diagnostics) in diagnostics {
211 diagnostics.sort_by_key(|it| (it.range.start, it.range.end));
212
213 if let Some((old_gen, existing_diagnostics)) = target.get_mut(&file_id) {
214 if existing_diagnostics.len() == diagnostics.len()
215 && iter_eq_by(&diagnostics, &*existing_diagnostics, |new, existing| {
216 are_diagnostics_equal(new, existing)
217 })
218 {
219 continue;
221 }
222 if *old_gen < generation || generation == 0 {
223 target.insert(file_id, (generation, diagnostics));
224 } else {
225 existing_diagnostics.extend(diagnostics);
226 existing_diagnostics.sort_by_key(|it| (it.range.start, it.range.end))
229 }
230 } else {
231 target.insert(file_id, (generation, diagnostics));
232 }
233 self.changes.insert(file_id);
234 }
235 }
236
237 pub(crate) fn diagnostics_for(
238 &self,
239 file_id: FileId,
240 ) -> impl Iterator<Item = &lsp_types::Diagnostic> {
241 let native_syntax = self.native_syntax.get(&file_id).into_iter().flat_map(|(_, d)| d);
242 let native_semantic = self.native_semantic.get(&file_id).into_iter().flat_map(|(_, d)| d);
243 let check = self
244 .check
245 .iter()
246 .flat_map(|it| it.per_package.values())
247 .filter_map(move |it| it.per_file.get(&file_id))
248 .flatten();
249 native_syntax.chain(native_semantic).chain(check)
250 }
251
252 pub(crate) fn take_changes(&mut self) -> Option<FxHashSet<FileId>> {
253 if self.changes.is_empty() {
254 return None;
255 }
256 Some(mem::take(&mut self.changes))
257 }
258
259 pub(crate) fn next_generation(&mut self) -> usize {
260 self.generation += 1;
261 self.generation
262 }
263}
264
265fn are_diagnostics_equal(left: &lsp_types::Diagnostic, right: &lsp_types::Diagnostic) -> bool {
266 left.source == right.source
267 && left.severity == right.severity
268 && left.range == right.range
269 && left.message == right.message
270}
271
272pub(crate) enum NativeDiagnosticsFetchKind {
273 Syntax,
274 Semantic,
275}
276
277pub(crate) fn fetch_native_diagnostics(
278 snapshot: &GlobalStateSnapshot,
279 subscriptions: std::sync::Arc<[FileId]>,
280 slice: std::ops::Range<usize>,
281 kind: NativeDiagnosticsFetchKind,
282) -> Vec<(FileId, Vec<lsp_types::Diagnostic>)> {
283 let _p = tracing::info_span!("fetch_native_diagnostics").entered();
284 let _ctx = DbPanicContext::enter("fetch_native_diagnostics".to_owned());
285
286 let mut odd_ones = Vec::new();
289 let mut diagnostics = subscriptions[slice]
290 .iter()
291 .copied()
292 .map(|file_id| {
293 let diagnostics = (|| {
294 let line_index = snapshot.file_line_index(file_id).ok()?;
295 let source_root = snapshot.analysis.source_root_id(file_id).ok()?;
296
297 let config = &snapshot.config.diagnostics(Some(source_root));
298 let diagnostics = match kind {
299 NativeDiagnosticsFetchKind::Syntax => {
300 snapshot.analysis.syntax_diagnostics(config, file_id).ok()?
301 }
302
303 NativeDiagnosticsFetchKind::Semantic if config.enabled => snapshot
304 .analysis
305 .semantic_diagnostics(config, ide::AssistResolveStrategy::None, file_id)
306 .ok()?,
307 NativeDiagnosticsFetchKind::Semantic => return None,
308 };
309 Some(
310 diagnostics
311 .into_iter()
312 .filter_map(|d| {
313 if d.range.file_id == file_id {
314 Some(convert_diagnostic(&line_index, d))
315 } else {
316 odd_ones.push(d);
317 None
318 }
319 })
320 .collect::<Vec<_>>(),
321 )
322 })()
323 .unwrap_or_default();
324
325 (file_id, diagnostics)
326 })
327 .collect::<Vec<_>>();
328
329 for (file_id, group) in odd_ones
331 .into_iter()
332 .sorted_by_key(|it| it.range.file_id)
333 .chunk_by(|it| it.range.file_id)
334 .into_iter()
335 {
336 if !subscriptions.contains(&file_id) {
337 continue;
338 }
339 let Some((_, diagnostics)) = diagnostics.iter_mut().find(|&&mut (id, _)| id == file_id)
340 else {
341 continue;
342 };
343 let Some(line_index) = snapshot.file_line_index(file_id).ok() else {
344 break;
345 };
346 for diagnostic in group {
347 diagnostics.push(convert_diagnostic(&line_index, diagnostic));
348 }
349 }
350 diagnostics
351}
352
353pub(crate) fn convert_diagnostic(
354 line_index: &crate::line_index::LineIndex,
355 d: ide::Diagnostic,
356) -> lsp_types::Diagnostic {
357 lsp_types::Diagnostic {
358 range: lsp::to_proto::range(line_index, d.range.range),
359 severity: Some(lsp::to_proto::diagnostic_severity(d.severity)),
360 code: Some(lsp_types::NumberOrString::String(d.code.as_str().to_owned())),
361 code_description: Some(lsp_types::CodeDescription {
362 href: lsp_types::Url::parse(&d.code.url()).unwrap(),
363 }),
364 source: Some("rust-analyzer".to_owned()),
365 message: d.message,
366 related_information: None,
367 tags: d.unused.then(|| vec![lsp_types::DiagnosticTag::UNNECESSARY]),
368 data: None,
369 }
370}