Rollup merge of #110514 - compiler-errors:remove-find_map_relevant_impl, r=b-naber
Remove `find_map_relevant_impl` Fixes #108895
This commit is contained in:
commit
d60c64a0c5
6 changed files with 168 additions and 156 deletions
|
@ -139,11 +139,38 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||||
treat_projections: TreatProjections,
|
treat_projections: TreatProjections,
|
||||||
mut f: impl FnMut(DefId),
|
mut f: impl FnMut(DefId),
|
||||||
) {
|
) {
|
||||||
let _: Option<()> =
|
// FIXME: This depends on the set of all impls for the trait. That is
|
||||||
self.find_map_relevant_impl(trait_def_id, self_ty, treat_projections, |did| {
|
// unfortunate wrt. incremental compilation.
|
||||||
f(did);
|
//
|
||||||
None
|
// If we want to be faster, we could have separate queries for
|
||||||
});
|
// blanket and non-blanket impls, and compare them separately.
|
||||||
|
let impls = self.trait_impls_of(trait_def_id);
|
||||||
|
|
||||||
|
for &impl_def_id in impls.blanket_impls.iter() {
|
||||||
|
f(impl_def_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that we're using `TreatParams::ForLookup` to query `non_blanket_impls` while using
|
||||||
|
// `TreatParams::AsCandidateKey` while actually adding them.
|
||||||
|
let treat_params = match treat_projections {
|
||||||
|
TreatProjections::NextSolverLookup => TreatParams::NextSolverLookup,
|
||||||
|
TreatProjections::ForLookup => TreatParams::ForLookup,
|
||||||
|
};
|
||||||
|
// This way, when searching for some impl for `T: Trait`, we do not look at any impls
|
||||||
|
// whose outer level is not a parameter or projection. Especially for things like
|
||||||
|
// `T: Clone` this is incredibly useful as we would otherwise look at all the impls
|
||||||
|
// of `Clone` for `Option<T>`, `Vec<T>`, `ConcreteType` and so on.
|
||||||
|
if let Some(simp) = fast_reject::simplify_type(self, self_ty, treat_params) {
|
||||||
|
if let Some(impls) = impls.non_blanket_impls.get(&simp) {
|
||||||
|
for &impl_def_id in impls {
|
||||||
|
f(impl_def_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for &impl_def_id in impls.non_blanket_impls.values().flatten() {
|
||||||
|
f(impl_def_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `trait_def_id` MUST BE the `DefId` of a trait.
|
/// `trait_def_id` MUST BE the `DefId` of a trait.
|
||||||
|
@ -162,59 +189,6 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||||
[].iter().copied()
|
[].iter().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies function to every impl that could possibly match the self type `self_ty` and returns
|
|
||||||
/// the first non-none value.
|
|
||||||
///
|
|
||||||
/// `trait_def_id` MUST BE the `DefId` of a trait.
|
|
||||||
pub fn find_map_relevant_impl<T>(
|
|
||||||
self,
|
|
||||||
trait_def_id: DefId,
|
|
||||||
self_ty: Ty<'tcx>,
|
|
||||||
treat_projections: TreatProjections,
|
|
||||||
mut f: impl FnMut(DefId) -> Option<T>,
|
|
||||||
) -> Option<T> {
|
|
||||||
// FIXME: This depends on the set of all impls for the trait. That is
|
|
||||||
// unfortunate wrt. incremental compilation.
|
|
||||||
//
|
|
||||||
// If we want to be faster, we could have separate queries for
|
|
||||||
// blanket and non-blanket impls, and compare them separately.
|
|
||||||
let impls = self.trait_impls_of(trait_def_id);
|
|
||||||
|
|
||||||
for &impl_def_id in impls.blanket_impls.iter() {
|
|
||||||
if let result @ Some(_) = f(impl_def_id) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that we're using `TreatParams::ForLookup` to query `non_blanket_impls` while using
|
|
||||||
// `TreatParams::AsCandidateKey` while actually adding them.
|
|
||||||
let treat_params = match treat_projections {
|
|
||||||
TreatProjections::NextSolverLookup => TreatParams::NextSolverLookup,
|
|
||||||
TreatProjections::ForLookup => TreatParams::ForLookup,
|
|
||||||
};
|
|
||||||
// This way, when searching for some impl for `T: Trait`, we do not look at any impls
|
|
||||||
// whose outer level is not a parameter or projection. Especially for things like
|
|
||||||
// `T: Clone` this is incredibly useful as we would otherwise look at all the impls
|
|
||||||
// of `Clone` for `Option<T>`, `Vec<T>`, `ConcreteType` and so on.
|
|
||||||
if let Some(simp) = fast_reject::simplify_type(self, self_ty, treat_params) {
|
|
||||||
if let Some(impls) = impls.non_blanket_impls.get(&simp) {
|
|
||||||
for &impl_def_id in impls {
|
|
||||||
if let result @ Some(_) = f(impl_def_id) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for &impl_def_id in impls.non_blanket_impls.values().flatten() {
|
|
||||||
if let result @ Some(_) = f(impl_def_id) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an iterator containing all impls for `trait_def_id`.
|
/// Returns an iterator containing all impls for `trait_def_id`.
|
||||||
///
|
///
|
||||||
/// `trait_def_id` MUST BE the `DefId` of a trait.
|
/// `trait_def_id` MUST BE the `DefId` of a trait.
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags;
|
use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags;
|
||||||
use crate::mir;
|
use crate::mir;
|
||||||
use crate::ty::fast_reject::TreatProjections;
|
|
||||||
use crate::ty::layout::IntegerExt;
|
use crate::ty::layout::IntegerExt;
|
||||||
use crate::ty::{
|
use crate::ty::{
|
||||||
self, FallibleTypeFolder, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
|
self, FallibleTypeFolder, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
|
||||||
|
@ -359,21 +358,29 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||||
self.ensure().coherent_trait(drop_trait);
|
self.ensure().coherent_trait(drop_trait);
|
||||||
|
|
||||||
let ty = self.type_of(adt_did).subst_identity();
|
let ty = self.type_of(adt_did).subst_identity();
|
||||||
let (did, constness) = self.find_map_relevant_impl(
|
let mut dtor_candidate = None;
|
||||||
drop_trait,
|
self.for_each_relevant_impl(drop_trait, ty, |impl_did| {
|
||||||
ty,
|
let Some(item_id) = self.associated_item_def_ids(impl_did).first() else {
|
||||||
// FIXME: This could also be some other mode, like "unexpected"
|
self.sess.delay_span_bug(self.def_span(impl_did), "Drop impl without drop function");
|
||||||
TreatProjections::ForLookup,
|
return;
|
||||||
|impl_did| {
|
};
|
||||||
if let Some(item_id) = self.associated_item_def_ids(impl_did).first() {
|
|
||||||
if validate(self, impl_did).is_ok() {
|
|
||||||
return Some((*item_id, self.constness(impl_did)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
|
if validate(self, impl_did).is_err() {
|
||||||
|
// Already `ErrorGuaranteed`, no need to delay a span bug here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((old_item_id, _)) = dtor_candidate {
|
||||||
|
self.sess
|
||||||
|
.struct_span_err(self.def_span(item_id), "multiple drop impls found")
|
||||||
|
.span_note(self.def_span(old_item_id), "other impl here")
|
||||||
|
.delay_as_bug();
|
||||||
|
}
|
||||||
|
|
||||||
|
dtor_candidate = Some((*item_id, self.constness(impl_did)));
|
||||||
|
});
|
||||||
|
|
||||||
|
let (did, constness) = dtor_candidate?;
|
||||||
Some(ty::Destructor { did, constness })
|
Some(ty::Destructor { did, constness })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -645,12 +645,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
// FIXME: Handling opaques here is kinda sus. Especially because we
|
// FIXME: Handling opaques here is kinda sus. Especially because we
|
||||||
// simplify them to PlaceholderSimplifiedType.
|
// simplify them to PlaceholderSimplifiedType.
|
||||||
| ty::Alias(ty::Opaque, _) => {
|
| ty::Alias(ty::Opaque, _) => {
|
||||||
if let Some(def_id) = self.tcx().find_map_relevant_impl(
|
let mut disqualifying_impl = None;
|
||||||
|
self.tcx().for_each_relevant_impl_treating_projections(
|
||||||
goal.predicate.def_id(),
|
goal.predicate.def_id(),
|
||||||
goal.predicate.self_ty(),
|
goal.predicate.self_ty(),
|
||||||
TreatProjections::NextSolverLookup,
|
TreatProjections::NextSolverLookup,
|
||||||
Some,
|
|impl_def_id| {
|
||||||
) {
|
disqualifying_impl = Some(impl_def_id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if let Some(def_id) = disqualifying_impl {
|
||||||
debug!(?def_id, ?goal, "disqualified auto-trait implementation");
|
debug!(?def_id, ?goal, "disqualified auto-trait implementation");
|
||||||
// No need to actually consider the candidate here,
|
// No need to actually consider the candidate here,
|
||||||
// since we do that in `consider_impl_candidate`.
|
// since we do that in `consider_impl_candidate`.
|
||||||
|
|
|
@ -32,7 +32,6 @@ use rustc_infer::infer::{InferOk, TypeTrace};
|
||||||
use rustc_middle::traits::select::OverflowError;
|
use rustc_middle::traits::select::OverflowError;
|
||||||
use rustc_middle::ty::abstract_const::NotConstEvaluatable;
|
use rustc_middle::ty::abstract_const::NotConstEvaluatable;
|
||||||
use rustc_middle::ty::error::{ExpectedFound, TypeError};
|
use rustc_middle::ty::error::{ExpectedFound, TypeError};
|
||||||
use rustc_middle::ty::fast_reject::TreatProjections;
|
|
||||||
use rustc_middle::ty::fold::{TypeFolder, TypeSuperFoldable};
|
use rustc_middle::ty::fold::{TypeFolder, TypeSuperFoldable};
|
||||||
use rustc_middle::ty::print::{with_forced_trimmed_paths, FmtPrinter, Print};
|
use rustc_middle::ty::print::{with_forced_trimmed_paths, FmtPrinter, Print};
|
||||||
use rustc_middle::ty::{
|
use rustc_middle::ty::{
|
||||||
|
@ -1836,30 +1835,34 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
});
|
});
|
||||||
let mut diag = struct_span_err!(self.tcx.sess, obligation.cause.span, E0271, "{msg}");
|
let mut diag = struct_span_err!(self.tcx.sess, obligation.cause.span, E0271, "{msg}");
|
||||||
|
|
||||||
let secondary_span = match predicate.kind().skip_binder() {
|
let secondary_span = (|| {
|
||||||
ty::PredicateKind::Clause(ty::Clause::Projection(proj)) => self
|
let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) =
|
||||||
.tcx
|
predicate.kind().skip_binder()
|
||||||
.opt_associated_item(proj.projection_ty.def_id)
|
else {
|
||||||
.and_then(|trait_assoc_item| {
|
return None;
|
||||||
self.tcx
|
};
|
||||||
.trait_of_item(proj.projection_ty.def_id)
|
|
||||||
.map(|id| (trait_assoc_item, id))
|
let trait_assoc_item = self.tcx.opt_associated_item(proj.projection_ty.def_id)?;
|
||||||
})
|
|
||||||
.and_then(|(trait_assoc_item, id)| {
|
|
||||||
let trait_assoc_ident = trait_assoc_item.ident(self.tcx);
|
let trait_assoc_ident = trait_assoc_item.ident(self.tcx);
|
||||||
self.tcx.find_map_relevant_impl(
|
|
||||||
id,
|
let mut associated_items = vec![];
|
||||||
|
self.tcx.for_each_relevant_impl(
|
||||||
|
self.tcx.trait_of_item(proj.projection_ty.def_id)?,
|
||||||
proj.projection_ty.self_ty(),
|
proj.projection_ty.self_ty(),
|
||||||
TreatProjections::ForLookup,
|
|impl_def_id| {
|
||||||
|did| {
|
associated_items.extend(
|
||||||
self.tcx
|
self.tcx
|
||||||
.associated_items(did)
|
.associated_items(impl_def_id)
|
||||||
.in_definition_order()
|
.in_definition_order()
|
||||||
.find(|assoc| assoc.ident(self.tcx) == trait_assoc_ident)
|
.find(|assoc| assoc.ident(self.tcx) == trait_assoc_ident),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
})
|
|
||||||
.and_then(|item| match self.tcx.hir().get_if_local(item.def_id) {
|
let [associated_item]: &[ty::AssocItem] = &associated_items[..] else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
match self.tcx.hir().get_if_local(associated_item.def_id) {
|
||||||
Some(
|
Some(
|
||||||
hir::Node::TraitItem(hir::TraitItem {
|
hir::Node::TraitItem(hir::TraitItem {
|
||||||
kind: hir::TraitItemKind::Type(_, Some(ty)),
|
kind: hir::TraitItemKind::Type(_, Some(ty)),
|
||||||
|
@ -1884,9 +1887,9 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
)),
|
)),
|
||||||
)),
|
)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}
|
||||||
_ => None,
|
})();
|
||||||
};
|
|
||||||
self.note_type_err(
|
self.note_type_err(
|
||||||
&mut diag,
|
&mut diag,
|
||||||
&obligation.cause,
|
&obligation.cause,
|
||||||
|
@ -2228,14 +2231,18 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
err: &mut Diagnostic,
|
err: &mut Diagnostic,
|
||||||
trait_ref: &ty::PolyTraitRef<'tcx>,
|
trait_ref: &ty::PolyTraitRef<'tcx>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let get_trait_impl = |trait_def_id| {
|
let get_trait_impls = |trait_def_id| {
|
||||||
self.tcx.find_map_relevant_impl(
|
let mut trait_impls = vec![];
|
||||||
|
self.tcx.for_each_relevant_impl(
|
||||||
trait_def_id,
|
trait_def_id,
|
||||||
trait_ref.skip_binder().self_ty(),
|
trait_ref.skip_binder().self_ty(),
|
||||||
TreatProjections::ForLookup,
|
|impl_def_id| {
|
||||||
Some,
|
trait_impls.push(impl_def_id);
|
||||||
)
|
},
|
||||||
|
);
|
||||||
|
trait_impls
|
||||||
};
|
};
|
||||||
|
|
||||||
let required_trait_path = self.tcx.def_path_str(trait_ref.def_id());
|
let required_trait_path = self.tcx.def_path_str(trait_ref.def_id());
|
||||||
let traits_with_same_path: std::collections::BTreeSet<_> = self
|
let traits_with_same_path: std::collections::BTreeSet<_> = self
|
||||||
.tcx
|
.tcx
|
||||||
|
@ -2245,9 +2252,16 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
.collect();
|
.collect();
|
||||||
let mut suggested = false;
|
let mut suggested = false;
|
||||||
for trait_with_same_path in traits_with_same_path {
|
for trait_with_same_path in traits_with_same_path {
|
||||||
if let Some(impl_def_id) = get_trait_impl(trait_with_same_path) {
|
let trait_impls = get_trait_impls(trait_with_same_path);
|
||||||
let impl_span = self.tcx.def_span(impl_def_id);
|
if trait_impls.is_empty() {
|
||||||
err.span_help(impl_span, "trait impl with same name found");
|
continue;
|
||||||
|
}
|
||||||
|
let impl_spans: Vec<_> =
|
||||||
|
trait_impls.iter().map(|impl_def_id| self.tcx.def_span(*impl_def_id)).collect();
|
||||||
|
err.span_help(
|
||||||
|
impl_spans,
|
||||||
|
format!("trait impl{} with same name found", pluralize!(trait_impls.len())),
|
||||||
|
);
|
||||||
let trait_crate = self.tcx.crate_name(trait_with_same_path.krate);
|
let trait_crate = self.tcx.crate_name(trait_with_same_path.krate);
|
||||||
let crate_msg = format!(
|
let crate_msg = format!(
|
||||||
"perhaps two different versions of crate `{}` are being used?",
|
"perhaps two different versions of crate `{}` are being used?",
|
||||||
|
@ -2256,7 +2270,6 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
err.note(&crate_msg);
|
err.note(&crate_msg);
|
||||||
suggested = true;
|
suggested = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
suggested
|
suggested
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use hir::LangItem;
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_infer::traits::ObligationCause;
|
use rustc_infer::traits::ObligationCause;
|
||||||
use rustc_infer::traits::{Obligation, SelectionError, TraitObligation};
|
use rustc_infer::traits::{Obligation, SelectionError, TraitObligation};
|
||||||
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams, TreatProjections};
|
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
|
||||||
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
|
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
|
||||||
|
|
||||||
use crate::traits;
|
use crate::traits;
|
||||||
|
@ -875,12 +875,24 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
ty::Adt(..) => {
|
ty::Adt(..) => {
|
||||||
// Find a custom `impl Drop` impl, if it exists
|
let mut relevant_impl = None;
|
||||||
let relevant_impl = self.tcx().find_map_relevant_impl(
|
self.tcx().for_each_relevant_impl(
|
||||||
self.tcx().require_lang_item(LangItem::Drop, None),
|
self.tcx().require_lang_item(LangItem::Drop, None),
|
||||||
obligation.predicate.skip_binder().trait_ref.self_ty(),
|
obligation.predicate.skip_binder().trait_ref.self_ty(),
|
||||||
TreatProjections::ForLookup,
|
|impl_def_id| {
|
||||||
Some,
|
if let Some(old_impl_def_id) = relevant_impl {
|
||||||
|
self.tcx()
|
||||||
|
.sess
|
||||||
|
.struct_span_err(
|
||||||
|
self.tcx().def_span(impl_def_id),
|
||||||
|
"multiple drop impls found",
|
||||||
|
)
|
||||||
|
.span_note(self.tcx().def_span(old_impl_def_id), "other impl here")
|
||||||
|
.delay_as_bug();
|
||||||
|
}
|
||||||
|
|
||||||
|
relevant_impl = Some(impl_def_id);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(impl_def_id) = relevant_impl {
|
if let Some(impl_def_id) = relevant_impl {
|
||||||
|
|
|
@ -13,7 +13,7 @@ use rustc_hir::def::Namespace::*;
|
||||||
use rustc_hir::def::{DefKind, Namespace, PerNS};
|
use rustc_hir::def::{DefKind, Namespace, PerNS};
|
||||||
use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
|
use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
|
||||||
use rustc_hir::Mutability;
|
use rustc_hir::Mutability;
|
||||||
use rustc_middle::ty::{fast_reject::TreatProjections, Ty, TyCtxt};
|
use rustc_middle::ty::{Ty, TyCtxt};
|
||||||
use rustc_middle::{bug, ty};
|
use rustc_middle::{bug, ty};
|
||||||
use rustc_resolve::rustdoc::{has_primitive_or_keyword_docs, prepare_to_doc_link_resolution};
|
use rustc_resolve::rustdoc::{has_primitive_or_keyword_docs, prepare_to_doc_link_resolution};
|
||||||
use rustc_resolve::rustdoc::{strip_generics_from_path, MalformedGenerics};
|
use rustc_resolve::rustdoc::{strip_generics_from_path, MalformedGenerics};
|
||||||
|
@ -772,11 +772,10 @@ fn trait_impls_for<'a>(
|
||||||
module: DefId,
|
module: DefId,
|
||||||
) -> FxHashSet<(DefId, DefId)> {
|
) -> FxHashSet<(DefId, DefId)> {
|
||||||
let tcx = cx.tcx;
|
let tcx = cx.tcx;
|
||||||
let iter = tcx.doc_link_traits_in_scope(module).iter().flat_map(|&trait_| {
|
let mut impls = FxHashSet::default();
|
||||||
trace!("considering explicit impl for trait {:?}", trait_);
|
|
||||||
|
|
||||||
// Look at each trait implementation to see if it's an impl for `did`
|
for &trait_ in tcx.doc_link_traits_in_scope(module) {
|
||||||
tcx.find_map_relevant_impl(trait_, ty, TreatProjections::ForLookup, |impl_| {
|
tcx.for_each_relevant_impl(trait_, ty, |impl_| {
|
||||||
let trait_ref = tcx.impl_trait_ref(impl_).expect("this is not an inherent impl");
|
let trait_ref = tcx.impl_trait_ref(impl_).expect("this is not an inherent impl");
|
||||||
// Check if these are the same type.
|
// Check if these are the same type.
|
||||||
let impl_type = trait_ref.skip_binder().self_ty();
|
let impl_type = trait_ref.skip_binder().self_ty();
|
||||||
|
@ -800,10 +799,13 @@ fn trait_impls_for<'a>(
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if saw_impl { Some((impl_, trait_)) } else { None }
|
if saw_impl {
|
||||||
})
|
impls.insert((impl_, trait_));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
iter.collect()
|
}
|
||||||
|
|
||||||
|
impls
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check for resolve collisions between a trait and its derive.
|
/// Check for resolve collisions between a trait and its derive.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue