]> git.proxmox.com Git - rustc.git/blobdiff - compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs
New upstream version 1.66.0+dfsg1
[rustc.git] / compiler / rustc_hir_analysis / src / coherence / inherent_impls_overlap.rs
diff --git a/compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs b/compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs
new file mode 100644 (file)
index 0000000..972769e
--- /dev/null
@@ -0,0 +1,335 @@
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::struct_span_err;
+use rustc_hir as hir;
+use rustc_hir::def::DefKind;
+use rustc_hir::def_id::DefId;
+use rustc_index::vec::IndexVec;
+use rustc_middle::traits::specialization_graph::OverlapMode;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_span::Symbol;
+use rustc_trait_selection::traits::{self, SkipLeakCheck};
+use smallvec::SmallVec;
+use std::collections::hash_map::Entry;
+
+pub fn crate_inherent_impls_overlap_check(tcx: TyCtxt<'_>, (): ()) {
+    let mut inherent_overlap_checker = InherentOverlapChecker { tcx };
+    for id in tcx.hir().items() {
+        inherent_overlap_checker.check_item(id);
+    }
+}
+
+struct InherentOverlapChecker<'tcx> {
+    tcx: TyCtxt<'tcx>,
+}
+
+impl<'tcx> InherentOverlapChecker<'tcx> {
+    /// Checks whether any associated items in impls 1 and 2 share the same identifier and
+    /// namespace.
+    fn impls_have_common_items(
+        &self,
+        impl_items1: &ty::AssocItems<'_>,
+        impl_items2: &ty::AssocItems<'_>,
+    ) -> bool {
+        let mut impl_items1 = &impl_items1;
+        let mut impl_items2 = &impl_items2;
+
+        // Performance optimization: iterate over the smaller list
+        if impl_items1.len() > impl_items2.len() {
+            std::mem::swap(&mut impl_items1, &mut impl_items2);
+        }
+
+        for item1 in impl_items1.in_definition_order() {
+            let collision = impl_items2
+                .filter_by_name_unhygienic(item1.name)
+                .any(|item2| self.compare_hygienically(item1, item2));
+
+            if collision {
+                return true;
+            }
+        }
+
+        false
+    }
+
+    fn compare_hygienically(&self, item1: &ty::AssocItem, item2: &ty::AssocItem) -> bool {
+        // Symbols and namespace match, compare hygienically.
+        item1.kind.namespace() == item2.kind.namespace()
+            && item1.ident(self.tcx).normalize_to_macros_2_0()
+                == item2.ident(self.tcx).normalize_to_macros_2_0()
+    }
+
+    fn check_for_duplicate_items_in_impl(&self, impl_: DefId) {
+        let impl_items = self.tcx.associated_items(impl_);
+
+        let mut seen_items = FxHashMap::default();
+        for impl_item in impl_items.in_definition_order() {
+            let span = self.tcx.def_span(impl_item.def_id);
+            let ident = impl_item.ident(self.tcx);
+
+            let norm_ident = ident.normalize_to_macros_2_0();
+            match seen_items.entry(norm_ident) {
+                Entry::Occupied(entry) => {
+                    let former = entry.get();
+                    let mut err = struct_span_err!(
+                        self.tcx.sess,
+                        span,
+                        E0592,
+                        "duplicate definitions with name `{}`",
+                        ident,
+                    );
+                    err.span_label(span, format!("duplicate definitions for `{}`", ident));
+                    err.span_label(*former, format!("other definition for `{}`", ident));
+
+                    err.emit();
+                }
+                Entry::Vacant(entry) => {
+                    entry.insert(span);
+                }
+            }
+        }
+    }
+
+    fn check_for_common_items_in_impls(
+        &self,
+        impl1: DefId,
+        impl2: DefId,
+        overlap: traits::OverlapResult<'_>,
+    ) {
+        let impl_items1 = self.tcx.associated_items(impl1);
+        let impl_items2 = self.tcx.associated_items(impl2);
+
+        for item1 in impl_items1.in_definition_order() {
+            let collision = impl_items2
+                .filter_by_name_unhygienic(item1.name)
+                .find(|item2| self.compare_hygienically(item1, item2));
+
+            if let Some(item2) = collision {
+                let name = item1.ident(self.tcx).normalize_to_macros_2_0();
+                let mut err = struct_span_err!(
+                    self.tcx.sess,
+                    self.tcx.def_span(item1.def_id),
+                    E0592,
+                    "duplicate definitions with name `{}`",
+                    name
+                );
+                err.span_label(
+                    self.tcx.def_span(item1.def_id),
+                    format!("duplicate definitions for `{}`", name),
+                );
+                err.span_label(
+                    self.tcx.def_span(item2.def_id),
+                    format!("other definition for `{}`", name),
+                );
+
+                for cause in &overlap.intercrate_ambiguity_causes {
+                    cause.add_intercrate_ambiguity_hint(&mut err);
+                }
+
+                if overlap.involves_placeholder {
+                    traits::add_placeholder_note(&mut err);
+                }
+
+                err.emit();
+            }
+        }
+    }
+
+    fn check_for_overlapping_inherent_impls(
+        &self,
+        overlap_mode: OverlapMode,
+        impl1_def_id: DefId,
+        impl2_def_id: DefId,
+    ) {
+        traits::overlapping_impls(
+            self.tcx,
+            impl1_def_id,
+            impl2_def_id,
+            // We go ahead and just skip the leak check for
+            // inherent impls without warning.
+            SkipLeakCheck::Yes,
+            overlap_mode,
+        )
+        .map_or(true, |overlap| {
+            self.check_for_common_items_in_impls(impl1_def_id, impl2_def_id, overlap);
+            false
+        });
+    }
+
+    fn check_item(&mut self, id: hir::ItemId) {
+        let def_kind = self.tcx.def_kind(id.owner_id);
+        if !matches!(def_kind, DefKind::Enum | DefKind::Struct | DefKind::Trait | DefKind::Union) {
+            return;
+        }
+
+        let impls = self.tcx.inherent_impls(id.owner_id);
+
+        let overlap_mode = OverlapMode::get(self.tcx, id.owner_id.to_def_id());
+
+        let impls_items = impls
+            .iter()
+            .map(|impl_def_id| (impl_def_id, self.tcx.associated_items(*impl_def_id)))
+            .collect::<SmallVec<[_; 8]>>();
+
+        // Perform a O(n^2) algorithm for small n,
+        // otherwise switch to an allocating algorithm with
+        // faster asymptotic runtime.
+        const ALLOCATING_ALGO_THRESHOLD: usize = 500;
+        if impls.len() < ALLOCATING_ALGO_THRESHOLD {
+            for (i, &(&impl1_def_id, impl_items1)) in impls_items.iter().enumerate() {
+                self.check_for_duplicate_items_in_impl(impl1_def_id);
+
+                for &(&impl2_def_id, impl_items2) in &impls_items[(i + 1)..] {
+                    if self.impls_have_common_items(impl_items1, impl_items2) {
+                        self.check_for_overlapping_inherent_impls(
+                            overlap_mode,
+                            impl1_def_id,
+                            impl2_def_id,
+                        );
+                    }
+                }
+            }
+        } else {
+            // Build a set of connected regions of impl blocks.
+            // Two impl blocks are regarded as connected if they share
+            // an item with the same unhygienic identifier.
+            // After we have assembled the connected regions,
+            // run the O(n^2) algorithm on each connected region.
+            // This is advantageous to running the algorithm over the
+            // entire graph when there are many connected regions.
+
+            rustc_index::newtype_index! {
+                pub struct RegionId {
+                    ENCODABLE = custom
+                }
+            }
+            struct ConnectedRegion {
+                idents: SmallVec<[Symbol; 8]>,
+                impl_blocks: FxHashSet<usize>,
+            }
+            let mut connected_regions: IndexVec<RegionId, _> = Default::default();
+            // Reverse map from the Symbol to the connected region id.
+            let mut connected_region_ids = FxHashMap::default();
+
+            for (i, &(&_impl_def_id, impl_items)) in impls_items.iter().enumerate() {
+                if impl_items.len() == 0 {
+                    continue;
+                }
+                // First obtain a list of existing connected region ids
+                let mut idents_to_add = SmallVec::<[Symbol; 8]>::new();
+                let mut ids = impl_items
+                    .in_definition_order()
+                    .filter_map(|item| {
+                        let entry = connected_region_ids.entry(item.name);
+                        if let Entry::Occupied(e) = &entry {
+                            Some(*e.get())
+                        } else {
+                            idents_to_add.push(item.name);
+                            None
+                        }
+                    })
+                    .collect::<SmallVec<[RegionId; 8]>>();
+                // Sort the id list so that the algorithm is deterministic
+                ids.sort_unstable();
+                ids.dedup();
+                let ids = ids;
+                match &ids[..] {
+                    // Create a new connected region
+                    [] => {
+                        let id_to_set = connected_regions.next_index();
+                        // Update the connected region ids
+                        for ident in &idents_to_add {
+                            connected_region_ids.insert(*ident, id_to_set);
+                        }
+                        connected_regions.insert(
+                            id_to_set,
+                            ConnectedRegion {
+                                idents: idents_to_add,
+                                impl_blocks: std::iter::once(i).collect(),
+                            },
+                        );
+                    }
+                    // Take the only id inside the list
+                    &[id_to_set] => {
+                        let region = connected_regions[id_to_set].as_mut().unwrap();
+                        region.impl_blocks.insert(i);
+                        region.idents.extend_from_slice(&idents_to_add);
+                        // Update the connected region ids
+                        for ident in &idents_to_add {
+                            connected_region_ids.insert(*ident, id_to_set);
+                        }
+                    }
+                    // We have multiple connected regions to merge.
+                    // In the worst case this might add impl blocks
+                    // one by one and can thus be O(n^2) in the size
+                    // of the resulting final connected region, but
+                    // this is no issue as the final step to check
+                    // for overlaps runs in O(n^2) as well.
+                    &[id_to_set, ..] => {
+                        let mut region = connected_regions.remove(id_to_set).unwrap();
+                        region.impl_blocks.insert(i);
+                        region.idents.extend_from_slice(&idents_to_add);
+                        // Update the connected region ids
+                        for ident in &idents_to_add {
+                            connected_region_ids.insert(*ident, id_to_set);
+                        }
+
+                        // Remove other regions from ids.
+                        for &id in ids.iter() {
+                            if id == id_to_set {
+                                continue;
+                            }
+                            let r = connected_regions.remove(id).unwrap();
+                            for ident in r.idents.iter() {
+                                connected_region_ids.insert(*ident, id_to_set);
+                            }
+                            region.idents.extend_from_slice(&r.idents);
+                            region.impl_blocks.extend(r.impl_blocks);
+                        }
+
+                        connected_regions.insert(id_to_set, region);
+                    }
+                }
+            }
+
+            debug!(
+                "churning through {} components (sum={}, avg={}, var={}, max={})",
+                connected_regions.len(),
+                impls.len(),
+                impls.len() / connected_regions.len(),
+                {
+                    let avg = impls.len() / connected_regions.len();
+                    let s = connected_regions
+                        .iter()
+                        .flatten()
+                        .map(|r| r.impl_blocks.len() as isize - avg as isize)
+                        .map(|v| v.abs() as usize)
+                        .sum::<usize>();
+                    s / connected_regions.len()
+                },
+                connected_regions.iter().flatten().map(|r| r.impl_blocks.len()).max().unwrap()
+            );
+            // List of connected regions is built. Now, run the overlap check
+            // for each pair of impl blocks in the same connected region.
+            for region in connected_regions.into_iter().flatten() {
+                let mut impl_blocks =
+                    region.impl_blocks.into_iter().collect::<SmallVec<[usize; 8]>>();
+                impl_blocks.sort_unstable();
+                for (i, &impl1_items_idx) in impl_blocks.iter().enumerate() {
+                    let &(&impl1_def_id, impl_items1) = &impls_items[impl1_items_idx];
+                    self.check_for_duplicate_items_in_impl(impl1_def_id);
+
+                    for &impl2_items_idx in impl_blocks[(i + 1)..].iter() {
+                        let &(&impl2_def_id, impl_items2) = &impls_items[impl2_items_idx];
+                        if self.impls_have_common_items(impl_items1, impl_items2) {
+                            self.check_for_overlapping_inherent_impls(
+                                overlap_mode,
+                                impl1_def_id,
+                                impl2_def_id,
+                            );
+                        }
+                    }
+                }
+            }
+        }
+    }
+}