rustc_hir_analysis/coherence/
inherent_impls_overlap.rs

1use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry};
2use rustc_errors::codes::*;
3use rustc_errors::struct_span_code_err;
4use rustc_hir as hir;
5use rustc_hir::def::DefKind;
6use rustc_hir::def_id::DefId;
7use rustc_index::IndexVec;
8use rustc_middle::traits::specialization_graph::OverlapMode;
9use rustc_middle::ty::{self, TyCtxt};
10use rustc_span::{ErrorGuaranteed, Symbol};
11use rustc_trait_selection::traits::{self, SkipLeakCheck};
12use smallvec::SmallVec;
13use tracing::debug;
14
15pub(crate) fn crate_inherent_impls_overlap_check(
16    tcx: TyCtxt<'_>,
17    (): (),
18) -> Result<(), ErrorGuaranteed> {
19    let mut inherent_overlap_checker = InherentOverlapChecker { tcx };
20    let mut res = Ok(());
21    for id in tcx.hir().items() {
22        res = res.and(inherent_overlap_checker.check_item(id));
23    }
24    res
25}
26
27struct InherentOverlapChecker<'tcx> {
28    tcx: TyCtxt<'tcx>,
29}
30
31rustc_index::newtype_index! {
32    #[orderable]
33    pub struct RegionId {}
34}
35
36impl<'tcx> InherentOverlapChecker<'tcx> {
37    /// Checks whether any associated items in impls 1 and 2 share the same identifier and
38    /// namespace.
39    fn impls_have_common_items(
40        &self,
41        impl_items1: &ty::AssocItems,
42        impl_items2: &ty::AssocItems,
43    ) -> bool {
44        let mut impl_items1 = &impl_items1;
45        let mut impl_items2 = &impl_items2;
46
47        // Performance optimization: iterate over the smaller list
48        if impl_items1.len() > impl_items2.len() {
49            std::mem::swap(&mut impl_items1, &mut impl_items2);
50        }
51
52        for &item1 in impl_items1.in_definition_order() {
53            let collision = impl_items2
54                .filter_by_name_unhygienic(item1.name)
55                .any(|&item2| self.compare_hygienically(item1, item2));
56
57            if collision {
58                return true;
59            }
60        }
61
62        false
63    }
64
65    fn compare_hygienically(&self, item1: ty::AssocItem, item2: ty::AssocItem) -> bool {
66        // Symbols and namespace match, compare hygienically.
67        item1.kind.namespace() == item2.kind.namespace()
68            && item1.ident(self.tcx).normalize_to_macros_2_0()
69                == item2.ident(self.tcx).normalize_to_macros_2_0()
70    }
71
72    fn check_for_duplicate_items_in_impl(&self, impl_: DefId) -> Result<(), ErrorGuaranteed> {
73        let impl_items = self.tcx.associated_items(impl_);
74
75        let mut seen_items = FxIndexMap::default();
76        let mut res = Ok(());
77        for impl_item in impl_items.in_definition_order() {
78            let span = self.tcx.def_span(impl_item.def_id);
79            let ident = impl_item.ident(self.tcx);
80
81            let norm_ident = ident.normalize_to_macros_2_0();
82            match seen_items.entry(norm_ident) {
83                IndexEntry::Occupied(entry) => {
84                    let former = entry.get();
85                    res = Err(struct_span_code_err!(
86                        self.tcx.dcx(),
87                        span,
88                        E0592,
89                        "duplicate definitions with name `{}`",
90                        ident,
91                    )
92                    .with_span_label(span, format!("duplicate definitions for `{ident}`"))
93                    .with_span_label(*former, format!("other definition for `{ident}`"))
94                    .emit());
95                }
96                IndexEntry::Vacant(entry) => {
97                    entry.insert(span);
98                }
99            }
100        }
101        res
102    }
103
104    fn check_for_common_items_in_impls(
105        &self,
106        impl1: DefId,
107        impl2: DefId,
108        overlap: traits::OverlapResult<'_>,
109    ) -> Result<(), ErrorGuaranteed> {
110        let impl_items1 = self.tcx.associated_items(impl1);
111        let impl_items2 = self.tcx.associated_items(impl2);
112
113        let mut res = Ok(());
114        for &item1 in impl_items1.in_definition_order() {
115            let collision = impl_items2
116                .filter_by_name_unhygienic(item1.name)
117                .find(|&&item2| self.compare_hygienically(item1, item2));
118
119            if let Some(item2) = collision {
120                let name = item1.ident(self.tcx).normalize_to_macros_2_0();
121                let mut err = struct_span_code_err!(
122                    self.tcx.dcx(),
123                    self.tcx.def_span(item1.def_id),
124                    E0592,
125                    "duplicate definitions with name `{}`",
126                    name
127                );
128                err.span_label(
129                    self.tcx.def_span(item1.def_id),
130                    format!("duplicate definitions for `{name}`"),
131                );
132                err.span_label(
133                    self.tcx.def_span(item2.def_id),
134                    format!("other definition for `{name}`"),
135                );
136
137                for cause in &overlap.intercrate_ambiguity_causes {
138                    cause.add_intercrate_ambiguity_hint(&mut err);
139                }
140
141                if overlap.involves_placeholder {
142                    traits::add_placeholder_note(&mut err);
143                }
144
145                res = Err(err.emit());
146            }
147        }
148        res
149    }
150
151    fn check_for_overlapping_inherent_impls(
152        &self,
153        overlap_mode: OverlapMode,
154        impl1_def_id: DefId,
155        impl2_def_id: DefId,
156    ) -> Result<(), ErrorGuaranteed> {
157        let maybe_overlap = traits::overlapping_impls(
158            self.tcx,
159            impl1_def_id,
160            impl2_def_id,
161            // We go ahead and just skip the leak check for
162            // inherent impls without warning.
163            SkipLeakCheck::Yes,
164            overlap_mode,
165        );
166
167        if let Some(overlap) = maybe_overlap {
168            self.check_for_common_items_in_impls(impl1_def_id, impl2_def_id, overlap)
169        } else {
170            Ok(())
171        }
172    }
173
174    fn check_item(&mut self, id: hir::ItemId) -> Result<(), ErrorGuaranteed> {
175        let def_kind = self.tcx.def_kind(id.owner_id);
176        if !matches!(def_kind, DefKind::Enum | DefKind::Struct | DefKind::Trait | DefKind::Union) {
177            return Ok(());
178        }
179
180        let impls = self.tcx.inherent_impls(id.owner_id);
181        let overlap_mode = OverlapMode::get(self.tcx, id.owner_id.to_def_id());
182
183        let impls_items = impls
184            .iter()
185            .map(|impl_def_id| (impl_def_id, self.tcx.associated_items(*impl_def_id)))
186            .collect::<SmallVec<[_; 8]>>();
187
188        // Perform a O(n^2) algorithm for small n,
189        // otherwise switch to an allocating algorithm with
190        // faster asymptotic runtime.
191        const ALLOCATING_ALGO_THRESHOLD: usize = 500;
192        let mut res = Ok(());
193        if impls.len() < ALLOCATING_ALGO_THRESHOLD {
194            for (i, &(&impl1_def_id, impl_items1)) in impls_items.iter().enumerate() {
195                res = res.and(self.check_for_duplicate_items_in_impl(impl1_def_id));
196
197                for &(&impl2_def_id, impl_items2) in &impls_items[(i + 1)..] {
198                    if self.impls_have_common_items(impl_items1, impl_items2) {
199                        res = res.and(self.check_for_overlapping_inherent_impls(
200                            overlap_mode,
201                            impl1_def_id,
202                            impl2_def_id,
203                        ));
204                    }
205                }
206            }
207        } else {
208            // Build a set of connected regions of impl blocks.
209            // Two impl blocks are regarded as connected if they share
210            // an item with the same unhygienic identifier.
211            // After we have assembled the connected regions,
212            // run the O(n^2) algorithm on each connected region.
213            // This is advantageous to running the algorithm over the
214            // entire graph when there are many connected regions.
215
216            struct ConnectedRegion {
217                idents: SmallVec<[Symbol; 8]>,
218                impl_blocks: FxIndexSet<usize>,
219            }
220            let mut connected_regions: IndexVec<RegionId, _> = Default::default();
221            // Reverse map from the Symbol to the connected region id.
222            let mut connected_region_ids = FxIndexMap::default();
223
224            for (i, &(&_impl_def_id, impl_items)) in impls_items.iter().enumerate() {
225                if impl_items.len() == 0 {
226                    continue;
227                }
228                // First obtain a list of existing connected region ids
229                let mut idents_to_add = SmallVec::<[Symbol; 8]>::new();
230                let mut ids = impl_items
231                    .in_definition_order()
232                    .filter_map(|item| {
233                        let entry = connected_region_ids.entry(item.name);
234                        if let IndexEntry::Occupied(e) = &entry {
235                            Some(*e.get())
236                        } else {
237                            idents_to_add.push(item.name);
238                            None
239                        }
240                    })
241                    .collect::<SmallVec<[RegionId; 8]>>();
242                // Sort the id list so that the algorithm is deterministic
243                ids.sort_unstable();
244                ids.dedup();
245                let ids = ids;
246                match &ids[..] {
247                    // Create a new connected region
248                    [] => {
249                        let id_to_set = connected_regions.next_index();
250                        // Update the connected region ids
251                        for ident in &idents_to_add {
252                            connected_region_ids.insert(*ident, id_to_set);
253                        }
254                        connected_regions.insert(
255                            id_to_set,
256                            ConnectedRegion {
257                                idents: idents_to_add,
258                                impl_blocks: std::iter::once(i).collect(),
259                            },
260                        );
261                    }
262                    // Take the only id inside the list
263                    &[id_to_set] => {
264                        let region = connected_regions[id_to_set].as_mut().unwrap();
265                        region.impl_blocks.insert(i);
266                        region.idents.extend_from_slice(&idents_to_add);
267                        // Update the connected region ids
268                        for ident in &idents_to_add {
269                            connected_region_ids.insert(*ident, id_to_set);
270                        }
271                    }
272                    // We have multiple connected regions to merge.
273                    // In the worst case this might add impl blocks
274                    // one by one and can thus be O(n^2) in the size
275                    // of the resulting final connected region, but
276                    // this is no issue as the final step to check
277                    // for overlaps runs in O(n^2) as well.
278                    &[id_to_set, ..] => {
279                        let mut region = connected_regions.remove(id_to_set).unwrap();
280                        region.impl_blocks.insert(i);
281                        region.idents.extend_from_slice(&idents_to_add);
282                        // Update the connected region ids
283                        for ident in &idents_to_add {
284                            connected_region_ids.insert(*ident, id_to_set);
285                        }
286
287                        // Remove other regions from ids.
288                        for &id in ids.iter() {
289                            if id == id_to_set {
290                                continue;
291                            }
292                            let r = connected_regions.remove(id).unwrap();
293                            for ident in r.idents.iter() {
294                                connected_region_ids.insert(*ident, id_to_set);
295                            }
296                            region.idents.extend_from_slice(&r.idents);
297                            region.impl_blocks.extend(r.impl_blocks);
298                        }
299
300                        connected_regions.insert(id_to_set, region);
301                    }
302                }
303            }
304
305            debug!(
306                "churning through {} components (sum={}, avg={}, var={}, max={})",
307                connected_regions.len(),
308                impls.len(),
309                impls.len() / connected_regions.len(),
310                {
311                    let avg = impls.len() / connected_regions.len();
312                    let s = connected_regions
313                        .iter()
314                        .flatten()
315                        .map(|r| r.impl_blocks.len() as isize - avg as isize)
316                        .map(|v| v.unsigned_abs())
317                        .sum::<usize>();
318                    s / connected_regions.len()
319                },
320                connected_regions.iter().flatten().map(|r| r.impl_blocks.len()).max().unwrap()
321            );
322            // List of connected regions is built. Now, run the overlap check
323            // for each pair of impl blocks in the same connected region.
324            for region in connected_regions.into_iter().flatten() {
325                let impl_blocks = region.impl_blocks.into_iter().collect::<SmallVec<[usize; 8]>>();
326                for (i, &impl1_items_idx) in impl_blocks.iter().enumerate() {
327                    let &(&impl1_def_id, impl_items1) = &impls_items[impl1_items_idx];
328                    res = res.and(self.check_for_duplicate_items_in_impl(impl1_def_id));
329
330                    for &impl2_items_idx in impl_blocks[(i + 1)..].iter() {
331                        let &(&impl2_def_id, impl_items2) = &impls_items[impl2_items_idx];
332                        if self.impls_have_common_items(impl_items1, impl_items2) {
333                            res = res.and(self.check_for_overlapping_inherent_impls(
334                                overlap_mode,
335                                impl1_def_id,
336                                impl2_def_id,
337                            ));
338                        }
339                    }
340                }
341            }
342        }
343        res
344    }
345}