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