Skip to main content

pedant_core/resolution/rust/
warning.rs

1//! Non-fatal project-layout diagnostics discovered while sources are
2//! snapshotted.
3
4use std::collections::BTreeMap;
5use std::fmt;
6use std::sync::Arc;
7
8use super::snapshot::RustResolutionUnit;
9
10/// A project layout that preserves Tier 1 resolution but prevents sound
11/// semantic promotion.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum RustResolutionWarning {
14    /// One physical Rust source is instantiated in more than one resolution
15    /// unit, while rust-analyzer exposes only one semantic interpretation for
16    /// that file.
17    SharedSourceUnits {
18        /// The normalized repository-relative source path.
19        path: Arc<str>,
20        /// Stable keys of every resolution unit that instantiates the source.
21        units: Box<[Arc<str>]>,
22    },
23}
24
25impl RustResolutionWarning {
26    /// The normalized repository-relative source this warning concerns.
27    pub fn path(&self) -> &str {
28        match self {
29            Self::SharedSourceUnits { path, .. } => path,
30        }
31    }
32
33    /// Stable keys of every resolution unit that instantiates the source.
34    pub fn unit_keys(&self) -> &[Arc<str>] {
35        match self {
36            Self::SharedSourceUnits { units, .. } => units,
37        }
38    }
39}
40
41impl fmt::Display for RustResolutionWarning {
42    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match self {
44            Self::SharedSourceUnits { path, units } => {
45                write!(
46                    formatter,
47                    "{path} is instantiated by multiple resolution units ("
48                )?;
49                write_unit_keys(formatter, units)?;
50                formatter.write_str(
51                    "), but rust-analyzer cannot distinguish their semantic contexts for one \
52                     physical source; give each unit its own physical source, or, when the units \
53                     share a package library, declare the module only there and reference it \
54                     through the library crate",
55                )
56            }
57        }
58    }
59}
60
61/// Stable report key of one resolution unit.
62pub(in crate::resolution::rust) fn unit_key(unit: &RustResolutionUnit) -> Arc<str> {
63    Arc::from(format!(
64        "{}#{}#{}",
65        unit.manifest_path(),
66        unit.kind().token(),
67        unit.name()
68    ))
69}
70
71/// Find every physical source instantiated in more than one unit.
72pub(super) fn shared_sources(units: &[RustResolutionUnit]) -> Box<[RustResolutionWarning]> {
73    let mut owners: BTreeMap<Arc<str>, Vec<Arc<str>>> = BTreeMap::new();
74    for unit in units {
75        let key = unit_key(unit);
76        for path in unit.sources() {
77            owners
78                .entry(Arc::clone(path))
79                .or_default()
80                .push(Arc::clone(&key));
81        }
82    }
83    owners
84        .into_iter()
85        .filter_map(|(path, mut unit_keys)| {
86            unit_keys.sort();
87            unit_keys.dedup();
88            match unit_keys.len() > 1 {
89                true => Some(RustResolutionWarning::SharedSourceUnits {
90                    path,
91                    units: unit_keys.into_boxed_slice(),
92                }),
93                false => None,
94            }
95        })
96        .collect()
97}
98
99fn write_unit_keys(formatter: &mut fmt::Formatter<'_>, units: &[Arc<str>]) -> fmt::Result {
100    for (index, unit) in units.iter().enumerate() {
101        match index {
102            0 => formatter.write_str(unit)?,
103            _ => write!(formatter, ", {unit}")?,
104        }
105    }
106    Ok(())
107}