Rework trait expansion to happen once explicitly
This commit is contained in:
parent
8361aef0d7
commit
824a867e82
26 changed files with 576 additions and 657 deletions
|
@ -4,13 +4,12 @@ use rustc_errors::struct_span_code_err;
|
|||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_lint_defs::builtin::UNUSED_ASSOCIATED_TYPE_BOUNDS;
|
||||
use rustc_middle::span_bug;
|
||||
use rustc_middle::ty::fold::BottomUpFolder;
|
||||
use rustc_middle::ty::{
|
||||
self, DynKind, ExistentialPredicateStableCmpExt as _, Ty, TyCtxt, TypeFoldable,
|
||||
TypeVisitableExt, Upcast,
|
||||
};
|
||||
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span};
|
||||
use rustc_span::{ErrorGuaranteed, Span};
|
||||
use rustc_trait_selection::error_reporting::traits::report_dyn_incompatibility;
|
||||
use rustc_trait_selection::traits::{self, hir_ty_lowering_dyn_compatibility_violations};
|
||||
use rustc_type_ir::elaborate::ClauseWithSupertraitSpan;
|
||||
|
@ -30,16 +29,16 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
&self,
|
||||
span: Span,
|
||||
hir_id: hir::HirId,
|
||||
hir_trait_bounds: &[hir::PolyTraitRef<'tcx>],
|
||||
hir_bounds: &[hir::PolyTraitRef<'tcx>],
|
||||
lifetime: &hir::Lifetime,
|
||||
representation: DynKind,
|
||||
) -> Ty<'tcx> {
|
||||
let tcx = self.tcx();
|
||||
let dummy_self = tcx.types.trait_object_dummy_self;
|
||||
|
||||
let mut bounds = Bounds::default();
|
||||
let mut user_written_bounds = Bounds::default();
|
||||
let mut potential_assoc_types = Vec::new();
|
||||
let dummy_self = self.tcx().types.trait_object_dummy_self;
|
||||
for trait_bound in hir_trait_bounds.iter().rev() {
|
||||
for trait_bound in hir_bounds.iter() {
|
||||
if let hir::BoundPolarity::Maybe(_) = trait_bound.modifiers.polarity {
|
||||
continue;
|
||||
}
|
||||
|
@ -53,92 +52,67 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
hir::BoundConstness::Never,
|
||||
hir::BoundPolarity::Positive,
|
||||
dummy_self,
|
||||
&mut bounds,
|
||||
&mut user_written_bounds,
|
||||
PredicateFilter::SelfOnly,
|
||||
) {
|
||||
potential_assoc_types.extend(cur_potential_assoc_types);
|
||||
}
|
||||
}
|
||||
|
||||
let mut trait_bounds = vec![];
|
||||
let mut projection_bounds = vec![];
|
||||
for (pred, span) in bounds.clauses() {
|
||||
let bound_pred = pred.kind();
|
||||
match bound_pred.skip_binder() {
|
||||
ty::ClauseKind::Trait(trait_pred) => {
|
||||
assert_eq!(trait_pred.polarity, ty::PredicatePolarity::Positive);
|
||||
trait_bounds.push((bound_pred.rebind(trait_pred.trait_ref), span));
|
||||
}
|
||||
ty::ClauseKind::Projection(proj) => {
|
||||
projection_bounds.push((bound_pred.rebind(proj), span));
|
||||
}
|
||||
ty::ClauseKind::TypeOutlives(_) => {
|
||||
// Do nothing, we deal with regions separately
|
||||
}
|
||||
ty::ClauseKind::RegionOutlives(_)
|
||||
| ty::ClauseKind::ConstArgHasType(..)
|
||||
| ty::ClauseKind::WellFormed(_)
|
||||
| ty::ClauseKind::ConstEvaluatable(_)
|
||||
| ty::ClauseKind::HostEffect(..) => {
|
||||
span_bug!(span, "did not expect {pred} clause in object bounds");
|
||||
}
|
||||
}
|
||||
let (trait_bounds, mut projection_bounds) =
|
||||
traits::expand_trait_aliases(tcx, user_written_bounds.clauses());
|
||||
let (regular_traits, mut auto_traits): (Vec<_>, Vec<_>) = trait_bounds
|
||||
.into_iter()
|
||||
.partition(|(trait_ref, _)| !tcx.trait_is_auto(trait_ref.def_id()));
|
||||
|
||||
// We don't support empty trait objects.
|
||||
if regular_traits.is_empty() && auto_traits.is_empty() {
|
||||
let guar =
|
||||
self.report_trait_object_with_no_traits_error(span, user_written_bounds.clauses());
|
||||
return Ty::new_error(tcx, guar);
|
||||
}
|
||||
|
||||
// Expand trait aliases recursively and check that only one regular (non-auto) trait
|
||||
// is used and no 'maybe' bounds are used.
|
||||
let expanded_traits =
|
||||
traits::expand_trait_aliases(tcx, trait_bounds.iter().map(|&(a, b)| (a, b)));
|
||||
|
||||
let (mut auto_traits, regular_traits): (Vec<_>, Vec<_>) =
|
||||
expanded_traits.partition(|i| tcx.trait_is_auto(i.trait_ref().def_id()));
|
||||
|
||||
// We don't support >1 principal
|
||||
if regular_traits.len() > 1 {
|
||||
let guar = self.report_trait_object_addition_traits_error(®ular_traits);
|
||||
return Ty::new_error(tcx, guar);
|
||||
}
|
||||
// We don't support empty trait objects.
|
||||
if regular_traits.is_empty() && auto_traits.is_empty() {
|
||||
let guar = self.report_trait_object_with_no_traits_error(span, &trait_bounds);
|
||||
return Ty::new_error(tcx, guar);
|
||||
}
|
||||
// Don't create a dyn trait if we have errors in the principal.
|
||||
if let Err(guar) = trait_bounds.error_reported() {
|
||||
if let Err(guar) = regular_traits.error_reported() {
|
||||
return Ty::new_error(tcx, guar);
|
||||
}
|
||||
|
||||
// Check that there are no gross dyn-compatibility violations;
|
||||
// most importantly, that the supertraits don't contain `Self`,
|
||||
// to avoid ICEs.
|
||||
for item in ®ular_traits {
|
||||
let violations =
|
||||
hir_ty_lowering_dyn_compatibility_violations(tcx, item.trait_ref().def_id());
|
||||
if !violations.is_empty() {
|
||||
let reported = report_dyn_incompatibility(
|
||||
tcx,
|
||||
span,
|
||||
Some(hir_id),
|
||||
item.trait_ref().def_id(),
|
||||
&violations,
|
||||
)
|
||||
.emit();
|
||||
return Ty::new_error(tcx, reported);
|
||||
for (clause, span) in user_written_bounds.clauses() {
|
||||
if let Some(trait_pred) = clause.as_trait_clause() {
|
||||
let violations =
|
||||
hir_ty_lowering_dyn_compatibility_violations(tcx, trait_pred.def_id());
|
||||
if !violations.is_empty() {
|
||||
let reported = report_dyn_incompatibility(
|
||||
tcx,
|
||||
span,
|
||||
Some(hir_id),
|
||||
trait_pred.def_id(),
|
||||
&violations,
|
||||
)
|
||||
.emit();
|
||||
return Ty::new_error(tcx, reported);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let principal_trait = regular_traits.into_iter().next();
|
||||
|
||||
let mut needed_associated_types = FxIndexSet::default();
|
||||
|
||||
let principal_span = regular_traits.first().map_or(DUMMY_SP, |info| info.bottom().1);
|
||||
let regular_traits_refs_spans = trait_bounds
|
||||
.into_iter()
|
||||
.filter(|(trait_ref, _)| !tcx.trait_is_auto(trait_ref.def_id()));
|
||||
|
||||
for (base_trait_ref, original_span) in regular_traits_refs_spans {
|
||||
let base_pred: ty::Predicate<'tcx> = base_trait_ref.upcast(tcx);
|
||||
if let Some((principal_trait, spans)) = &principal_trait {
|
||||
let pred: ty::Predicate<'tcx> = (*principal_trait).upcast(tcx);
|
||||
for ClauseWithSupertraitSpan { pred, supertrait_span } in
|
||||
traits::elaborate(tcx, [ClauseWithSupertraitSpan::new(base_pred, original_span)])
|
||||
.filter_only_self()
|
||||
traits::elaborate(tcx, [ClauseWithSupertraitSpan::new(
|
||||
pred,
|
||||
*spans.last().unwrap(),
|
||||
)])
|
||||
.filter_only_self()
|
||||
{
|
||||
debug!("observing object predicate `{pred:?}`");
|
||||
|
||||
|
@ -179,7 +153,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
// }
|
||||
// ```
|
||||
//
|
||||
// Here, the user could theoretically write `dyn MyTrait<Output = X>`,
|
||||
// Here, the user could theoretically write `dyn MyTrait<MyOutput = X>`,
|
||||
// but actually supporting that would "expand" to an infinitely-long type
|
||||
// `fix $ τ → dyn MyTrait<MyOutput = X, Output = <τ as MyTrait>::MyOutput`.
|
||||
//
|
||||
|
@ -188,12 +162,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
// the discussion in #56288 for alternatives.
|
||||
if !references_self {
|
||||
// Include projections defined on supertraits.
|
||||
projection_bounds.push((pred, original_span));
|
||||
projection_bounds.push((pred, supertrait_span));
|
||||
}
|
||||
|
||||
self.check_elaborated_projection_mentions_input_lifetimes(
|
||||
pred,
|
||||
original_span,
|
||||
*spans.first().unwrap(),
|
||||
supertrait_span,
|
||||
);
|
||||
}
|
||||
|
@ -202,11 +176,13 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
}
|
||||
}
|
||||
|
||||
// `dyn Trait<Assoc = Foo>` desugars to (not Rust syntax) `dyn Trait where <Self as Trait>::Assoc = Foo`.
|
||||
// So every `Projection` clause is an `Assoc = Foo` bound. `associated_types` contains all associated
|
||||
// types's `DefId`, so the following loop removes all the `DefIds` of the associated types that have a
|
||||
// corresponding `Projection` clause
|
||||
for (projection_bound, span) in &projection_bounds {
|
||||
// `dyn Trait<Assoc = Foo>` desugars to (not Rust syntax) `dyn Trait where
|
||||
// <Self as Trait>::Assoc = Foo`. So every `Projection` clause is an
|
||||
// `Assoc = Foo` bound. `needed_associated_types` contains all associated
|
||||
// types that we expect to be provided by the user, so the following loop
|
||||
// removes all the associated types that have a corresponding `Projection`
|
||||
// clause, either from expanding trait aliases or written by the user.
|
||||
for &(projection_bound, span) in &projection_bounds {
|
||||
let def_id = projection_bound.item_def_id();
|
||||
let trait_ref = tcx.anonymize_bound_vars(
|
||||
projection_bound.map_bound(|p| p.projection_term.trait_ref(tcx)),
|
||||
|
@ -216,17 +192,17 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
tcx.emit_node_span_lint(
|
||||
UNUSED_ASSOCIATED_TYPE_BOUNDS,
|
||||
hir_id,
|
||||
*span,
|
||||
crate::errors::UnusedAssociatedTypeBounds { span: *span },
|
||||
span,
|
||||
crate::errors::UnusedAssociatedTypeBounds { span },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(guar) = self.check_for_required_assoc_tys(
|
||||
principal_span,
|
||||
principal_trait.as_ref().map_or(smallvec![], |(_, spans)| spans.clone()),
|
||||
needed_associated_types,
|
||||
potential_assoc_types,
|
||||
hir_trait_bounds,
|
||||
hir_bounds,
|
||||
) {
|
||||
return Ty::new_error(tcx, guar);
|
||||
}
|
||||
|
@ -236,32 +212,36 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
// We remove duplicates by inserting into a `FxHashSet` to avoid re-ordering
|
||||
// the bounds
|
||||
let mut duplicates = FxHashSet::default();
|
||||
auto_traits.retain(|i| duplicates.insert(i.trait_ref().def_id()));
|
||||
debug!(?regular_traits);
|
||||
auto_traits.retain(|(trait_pred, _)| duplicates.insert(trait_pred.def_id()));
|
||||
|
||||
debug!(?principal_trait);
|
||||
debug!(?auto_traits);
|
||||
|
||||
// Erase the `dummy_self` (`trait_object_dummy_self`) used above.
|
||||
let existential_trait_refs = regular_traits.iter().map(|i| {
|
||||
i.trait_ref().map_bound(|trait_ref: ty::TraitRef<'tcx>| {
|
||||
let principal_trait_ref = principal_trait.map(|(trait_pred, spans)| {
|
||||
trait_pred.map_bound(|trait_pred| {
|
||||
let trait_ref = trait_pred.trait_ref;
|
||||
assert_eq!(trait_pred.polarity, ty::PredicatePolarity::Positive);
|
||||
assert_eq!(trait_ref.self_ty(), dummy_self);
|
||||
|
||||
let span = *spans.first().unwrap();
|
||||
|
||||
// Verify that `dummy_self` did not leak inside default type parameters. This
|
||||
// could not be done at path creation, since we need to see through trait aliases.
|
||||
let mut missing_type_params = vec![];
|
||||
let mut references_self = false;
|
||||
let generics = tcx.generics_of(trait_ref.def_id);
|
||||
let args: Vec<_> = trait_ref
|
||||
.args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(1) // Remove `Self` for `ExistentialPredicate`.
|
||||
// Skip `Self`
|
||||
.skip(1)
|
||||
.map(|(index, arg)| {
|
||||
if arg == dummy_self.into() {
|
||||
let param = &generics.own_params[index];
|
||||
missing_type_params.push(param.name);
|
||||
Ty::new_misc_error(tcx).into()
|
||||
} else if arg.walk().any(|arg| arg == dummy_self.into()) {
|
||||
references_self = true;
|
||||
let guar = self.dcx().span_delayed_bug(
|
||||
span,
|
||||
"trait object trait bounds reference `Self`",
|
||||
|
@ -273,8 +253,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let span = i.bottom().1;
|
||||
let empty_generic_args = hir_trait_bounds.iter().any(|hir_bound| {
|
||||
let empty_generic_args = hir_bounds.iter().any(|hir_bound| {
|
||||
hir_bound.trait_ref.path.res == Res::Def(DefKind::Trait, trait_ref.def_id)
|
||||
&& hir_bound.span.contains(span)
|
||||
});
|
||||
|
@ -285,26 +264,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
empty_generic_args,
|
||||
);
|
||||
|
||||
if references_self {
|
||||
let def_id = i.bottom().0.def_id();
|
||||
struct_span_code_err!(
|
||||
self.dcx(),
|
||||
i.bottom().1,
|
||||
E0038,
|
||||
"the {} `{}` cannot be made into an object",
|
||||
tcx.def_descr(def_id),
|
||||
tcx.item_name(def_id),
|
||||
)
|
||||
.with_note(
|
||||
rustc_middle::traits::DynCompatibilityViolation::SupertraitSelf(
|
||||
smallvec![],
|
||||
)
|
||||
.error_msg(),
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
|
||||
ty::ExistentialTraitRef::new(tcx, trait_ref.def_id, args)
|
||||
ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::new(
|
||||
tcx,
|
||||
trait_ref.def_id,
|
||||
args,
|
||||
))
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -327,21 +291,24 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
b.projection_term = replace_dummy_self_with_error(tcx, b.projection_term, guar);
|
||||
}
|
||||
|
||||
ty::ExistentialProjection::erase_self_ty(tcx, b)
|
||||
ty::ExistentialPredicate::Projection(ty::ExistentialProjection::erase_self_ty(
|
||||
tcx, b,
|
||||
))
|
||||
})
|
||||
});
|
||||
|
||||
let regular_trait_predicates = existential_trait_refs
|
||||
.map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait));
|
||||
let auto_trait_predicates = auto_traits.into_iter().map(|trait_ref| {
|
||||
ty::Binder::dummy(ty::ExistentialPredicate::AutoTrait(trait_ref.trait_ref().def_id()))
|
||||
let auto_trait_predicates = auto_traits.into_iter().map(|(trait_pred, _)| {
|
||||
assert_eq!(trait_pred.polarity(), ty::PredicatePolarity::Positive);
|
||||
assert_eq!(trait_pred.self_ty().skip_binder(), dummy_self);
|
||||
|
||||
ty::Binder::dummy(ty::ExistentialPredicate::AutoTrait(trait_pred.def_id()))
|
||||
});
|
||||
|
||||
// N.b. principal, projections, auto traits
|
||||
// FIXME: This is actually wrong with multiple principals in regards to symbol mangling
|
||||
let mut v = regular_trait_predicates
|
||||
.chain(
|
||||
existential_projections.map(|x| x.map_bound(ty::ExistentialPredicate::Projection)),
|
||||
)
|
||||
let mut v = principal_trait_ref
|
||||
.into_iter()
|
||||
.chain(existential_projections)
|
||||
.chain(auto_trait_predicates)
|
||||
.collect::<SmallVec<[_; 8]>>();
|
||||
v.sort_by(|a, b| a.skip_binder().stable_cmp(tcx, &b.skip_binder()));
|
||||
|
|
|
@ -11,7 +11,7 @@ use rustc_hir::def_id::DefId;
|
|||
use rustc_middle::bug;
|
||||
use rustc_middle::ty::print::{PrintPolyTraitRefExt as _, PrintTraitRefExt as _};
|
||||
use rustc_middle::ty::{
|
||||
self, AdtDef, Binder, GenericParamDefKind, TraitRef, Ty, TyCtxt, TypeVisitableExt,
|
||||
self, AdtDef, GenericParamDefKind, Ty, TyCtxt, TypeVisitableExt,
|
||||
suggest_constraining_type_param,
|
||||
};
|
||||
use rustc_session::parse::feature_err;
|
||||
|
@ -19,8 +19,9 @@ use rustc_span::edit_distance::find_best_match_for_name;
|
|||
use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol, kw, sym};
|
||||
use rustc_trait_selection::error_reporting::traits::report_dyn_incompatibility;
|
||||
use rustc_trait_selection::traits::{
|
||||
FulfillmentError, TraitAliasExpansionInfo, dyn_compatibility_violations_for_assoc_item,
|
||||
FulfillmentError, dyn_compatibility_violations_for_assoc_item,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::errors::{
|
||||
self, AssocItemConstraintsNotAllowedHere, ManualImplementation, MissingTypeParams,
|
||||
|
@ -720,7 +721,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
/// emit a generic note suggesting using a `where` clause to constraint instead.
|
||||
pub(crate) fn check_for_required_assoc_tys(
|
||||
&self,
|
||||
principal_span: Span,
|
||||
spans: SmallVec<[Span; 1]>,
|
||||
missing_assoc_types: FxIndexSet<(DefId, ty::PolyTraitRef<'tcx>)>,
|
||||
potential_assoc_types: Vec<usize>,
|
||||
trait_bounds: &[hir::PolyTraitRef<'_>],
|
||||
|
@ -729,6 +730,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let principal_span = *spans.first().unwrap();
|
||||
|
||||
let tcx = self.tcx();
|
||||
// FIXME: This logic needs some more care w.r.t handling of conflicts
|
||||
let missing_assoc_types: Vec<_> = missing_assoc_types
|
||||
|
@ -1124,29 +1127,36 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
|
||||
pub fn report_trait_object_addition_traits_error(
|
||||
&self,
|
||||
regular_traits: &Vec<TraitAliasExpansionInfo<'_>>,
|
||||
regular_traits: &Vec<(ty::PolyTraitPredicate<'tcx>, SmallVec<[Span; 1]>)>,
|
||||
) -> ErrorGuaranteed {
|
||||
let first_trait = ®ular_traits[0];
|
||||
let additional_trait = ®ular_traits[1];
|
||||
// we use the last span to point at the traits themselves,
|
||||
// and all other preceding spans are trait alias expansions.
|
||||
let (&first_span, first_alias_spans) = regular_traits[0].1.split_last().unwrap();
|
||||
let (&second_span, second_alias_spans) = regular_traits[1].1.split_last().unwrap();
|
||||
let mut err = struct_span_code_err!(
|
||||
self.dcx(),
|
||||
additional_trait.bottom().1,
|
||||
*regular_traits[1].1.first().unwrap(),
|
||||
E0225,
|
||||
"only auto traits can be used as additional traits in a trait object"
|
||||
);
|
||||
additional_trait.label_with_exp_info(
|
||||
&mut err,
|
||||
"additional non-auto trait",
|
||||
"additional use",
|
||||
);
|
||||
first_trait.label_with_exp_info(&mut err, "first non-auto trait", "first use");
|
||||
err.span_label(first_span, "first non-auto trait");
|
||||
for &alias_span in first_alias_spans {
|
||||
err.span_label(alias_span, "first non-auto trait comes from this alias");
|
||||
}
|
||||
err.span_label(second_span, "additional non-auto trait");
|
||||
for &alias_span in second_alias_spans {
|
||||
err.span_label(alias_span, "second non-auto trait comes from this alias");
|
||||
}
|
||||
err.help(format!(
|
||||
"consider creating a new trait with all of these as supertraits and using that \
|
||||
trait here instead: `trait NewTrait: {} {{}}`",
|
||||
regular_traits
|
||||
.iter()
|
||||
// FIXME: This should `print_sugared`, but also needs to integrate projection bounds...
|
||||
.map(|t| t.trait_ref().print_only_trait_path().to_string())
|
||||
.map(|(pred, _)| pred
|
||||
.map_bound(|pred| pred.trait_ref)
|
||||
.print_only_trait_path()
|
||||
.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" + "),
|
||||
));
|
||||
|
@ -1161,14 +1171,14 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||
pub fn report_trait_object_with_no_traits_error(
|
||||
&self,
|
||||
span: Span,
|
||||
trait_bounds: &Vec<(Binder<'tcx, TraitRef<'tcx>>, Span)>,
|
||||
user_written_clauses: impl IntoIterator<Item = (ty::Clause<'tcx>, Span)>,
|
||||
) -> ErrorGuaranteed {
|
||||
let tcx = self.tcx();
|
||||
let trait_alias_span = trait_bounds
|
||||
.iter()
|
||||
.map(|&(trait_ref, _)| trait_ref.def_id())
|
||||
.find(|&trait_ref| tcx.is_trait_alias(trait_ref))
|
||||
.map(|trait_ref| tcx.def_span(trait_ref));
|
||||
let trait_alias_span = user_written_clauses
|
||||
.into_iter()
|
||||
.filter_map(|(clause, _)| clause.as_trait_clause())
|
||||
.find(|trait_ref| tcx.is_trait_alias(trait_ref.def_id()))
|
||||
.map(|trait_ref| tcx.def_span(trait_ref.def_id()));
|
||||
|
||||
self.dcx().emit_err(TraitObjectDeclaredWithNoTraits { span, trait_alias_span })
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue