Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor dyn-compatibility error and suggestions #133372

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_feature/src/removed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ declare_features! (
/// then removed. But there was no utility storing it separately, so now
/// it's in this list.
(removed, no_stack_check, "1.0.0", None, None),
/// Allows making `dyn Trait` well-formed even if `Trait` is not dyn-compatible (object safe).
/// Allows making `dyn Trait` well-formed even if `Trait` is not dyn compatible (object safe).
/// Renamed to `dyn_compatible_for_dispatch`.
(removed, object_safe_for_dispatch, "1.83.0", Some(43561),
Some("renamed to `dyn_compatible_for_dispatch`")),
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ declare_features! (
(unstable, doc_notable_trait, "1.52.0", Some(45040)),
/// Allows using the `may_dangle` attribute (RFC 1327).
(unstable, dropck_eyepatch, "1.10.0", Some(34761)),
/// Allows making `dyn Trait` well-formed even if `Trait` is not dyn-compatible[^1].
/// Allows making `dyn Trait` well-formed even if `Trait` is not dyn compatible[^1].
/// In that case, `dyn Trait: Trait` does not hold. Moreover, coercions and
/// casts in safe Rust to `dyn Trait` for such a `Trait` is also forbidden.
///
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/coherence/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ fn check_object_overlap<'tcx>(
// check for overlap with the automatic `impl Trait for dyn Trait`
if let ty::Dynamic(data, ..) = trait_ref.self_ty().kind() {
// This is something like `impl Trait1 for Trait2`. Illegal if
// Trait1 is a supertrait of Trait2 or Trait2 is not dyn-compatible.
// Trait1 is a supertrait of Trait2 or Trait2 is not dyn compatible.

let component_def_ids = data.iter().flat_map(|predicate| {
match predicate.skip_binder() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
// most importantly, that the supertraits don't contain `Self`,
// to avoid ICEs.
for item in &regular_traits {
let violations =
hir_ty_lowering_dyn_compatibility_violations(tcx, item.trait_ref().def_id());
let item_def_id = item.trait_ref().def_id();
let violations = hir_ty_lowering_dyn_compatibility_violations(tcx, item_def_id);
if !violations.is_empty() {
let reported = report_dyn_incompatibility(
tcx,
span,
Some(hir_id),
item.trait_ref().def_id(),
&violations,
)
.emit();
let reported =
report_dyn_incompatibility(tcx, span, Some(hir_id), item_def_id, &violations)
.emit();
return Ty::new_error(tcx, reported);
}
}
Expand Down Expand Up @@ -287,7 +282,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
self.dcx(),
i.bottom().1,
E0038,
"the {} `{}` cannot be made into an object",
"the {} `{}` is not dyn compatible",
tcx.def_descr(def_id),
tcx.item_name(def_id),
)
Expand Down
193 changes: 115 additions & 78 deletions compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,33 +432,18 @@ pub fn report_dyn_incompatibility<'tcx>(
hir::Node::Item(item) => Some(item.ident.span),
_ => None,
});

let mut err = struct_span_code_err!(
tcx.dcx(),
span,
E0038,
"the trait `{}` cannot be made into an object",
"the trait `{}` is not dyn compatible",
trait_str
);
err.span_label(span, format!("`{trait_str}` cannot be made into an object"));

if let Some(hir_id) = hir_id
&& let hir::Node::Ty(ty) = tcx.hir_node(hir_id)
&& let hir::TyKind::TraitObject([trait_ref, ..], ..) = ty.kind
{
let mut hir_id = hir_id;
while let hir::Node::Ty(ty) = tcx.parent_hir_node(hir_id) {
hir_id = ty.hir_id;
}
if tcx.parent_hir_node(hir_id).fn_sig().is_some() {
// Do not suggest `impl Trait` when dealing with things like super-traits.
err.span_suggestion_verbose(
ty.span.until(trait_ref.span),
"consider using an opaque type instead",
"impl ",
Applicability::MaybeIncorrect,
);
}
}
err.span_label(span, format!("`{trait_str}` is not dyn compatible"));

attempt_dyn_to_impl_suggestion(tcx, hir_id, &mut err);

let mut reported_violations = FxIndexSet::default();
let mut multi_span = vec![];
let mut messages = vec![];
Expand All @@ -473,7 +458,7 @@ pub fn report_dyn_incompatibility<'tcx>(
if reported_violations.insert(violation.clone()) {
let spans = violation.spans();
let msg = if trait_span.is_none() || spans.is_empty() {
format!("the trait cannot be made into an object because {}", violation.error_msg())
format!("the trait is not dyn compatible because {}", violation.error_msg())
} else {
format!("...because {}", violation.error_msg())
};
Expand All @@ -490,24 +475,20 @@ pub fn report_dyn_incompatibility<'tcx>(
let has_multi_span = !multi_span.is_empty();
let mut note_span = MultiSpan::from_spans(multi_span.clone());
if let (Some(trait_span), true) = (trait_span, has_multi_span) {
note_span.push_span_label(trait_span, "this trait cannot be made into an object...");
note_span.push_span_label(trait_span, "this trait is not dyn compatible...");
}
for (span, msg) in iter::zip(multi_span, messages) {
note_span.push_span_label(span, msg);
}
// FIXME(dyn_compat_renaming): Update the URL.
err.span_note(
note_span,
"for a trait to be \"dyn-compatible\" it needs to allow building a vtable to allow the call \
to be resolvable dynamically; for more information visit \
<https://doc.rust-lang.org/reference/items/traits.html#object-safety>",
"for a trait to be dyn compatible it needs to allow building a vtable\n\
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>",
);

// Only provide the help if its a local trait, otherwise it's not actionable.
if trait_span.is_some() {
let mut reported_violations: Vec<_> = reported_violations.into_iter().collect();
reported_violations.sort();

let mut potential_solutions: Vec<_> =
reported_violations.into_iter().map(|violation| violation.solution()).collect();
potential_solutions.sort();
Expand All @@ -518,68 +499,124 @@ pub fn report_dyn_incompatibility<'tcx>(
}
}

attempt_dyn_to_enum_suggestion(tcx, trait_def_id, &*trait_str, &mut err);

err
}

/// Attempt to suggest converting the `dyn Trait` argument to an enumeration
/// over the types that implement `Trait`.
fn attempt_dyn_to_enum_suggestion(
tcx: TyCtxt<'_>,
trait_def_id: DefId,
trait_str: &str,
err: &mut Diag<'_>,
) {
let impls_of = tcx.trait_impls_of(trait_def_id);
let impls = if impls_of.blanket_impls().is_empty() {
impls_of
.non_blanket_impls()
.values()
.flatten()
.filter(|def_id| {
!matches!(tcx.type_of(*def_id).instantiate_identity().kind(), ty::Dynamic(..))
})
.collect::<Vec<_>>()
} else {
vec![]

// Don't suggest converting to an enum if there are any blanket impls.
if !impls_of.blanket_impls().is_empty() {
return;
}

let concrete_impls = {
let mut concrete_impls = Vec::new();
for impl_id in impls_of.non_blanket_impls().values().flatten() {
// Don't suggest converting to enum if there are any non-lifetime generics.
if has_non_lifetime_generics(tcx, *impl_id) {
return;
}

let impl_type = tcx.type_of(*impl_id).instantiate_identity();

// Don't suggest converting to enum if there are any
// `impl Trait for dyn OtherTrait`
if let ty::Dynamic(..) = impl_type.kind() {
return;
}

concrete_impls.push(impl_type);
}
concrete_impls
};
let externally_visible = if !impls.is_empty()
&& let Some(def_id) = trait_def_id.as_local()
if concrete_impls.is_empty() {
return;
}

const MAX_IMPLS_TO_SUGGEST_CONVERTING_TO_ENUM: usize = 9;
if concrete_impls.len() > MAX_IMPLS_TO_SUGGEST_CONVERTING_TO_ENUM {
return;
}

let externally_visible = if let Some(def_id) = trait_def_id.as_local() {
// We may be executing this during typeck, which would result in cycle
// if we used effective_visibilities query, which looks into opaque types
// (and therefore calls typeck).
&& tcx.resolutions(()).effective_visibilities.is_exported(def_id)
{
true
tcx.resolutions(()).effective_visibilities.is_exported(def_id)
} else {
false
};
match &impls[..] {
[] => {}
_ if impls.len() > 9 => {}
[only] if externally_visible => {
err.help(with_no_trimmed_paths!(format!(
"only type `{}` is seen to implement the trait in this crate, consider using it \
directly instead",
tcx.type_of(*only).instantiate_identity(),
)));
}
[only] => {
err.help(with_no_trimmed_paths!(format!(
"only type `{}` implements the trait, consider using it directly instead",
tcx.type_of(*only).instantiate_identity(),
)));
}
impls => {
let types = impls
.iter()
.map(|t| {
with_no_trimmed_paths!(format!(" {}", tcx.type_of(*t).instantiate_identity(),))
})
.collect::<Vec<_>>();
err.help(format!(
"the following types implement the trait, consider defining an enum where each \
variant holds one of these types, implementing `{}` for this new enum and using \
it instead:\n{}",
trait_str,
types.join("\n"),
));
}

if let [only_impl] = &concrete_impls[..] {
let within = if externally_visible { " within this crate" } else { "" };
err.help(with_no_trimmed_paths!(format!(
"only type `{only_impl}` implements `{trait_str}`{within}. \
Consider using it directly instead."
)));
} else {
let types = concrete_impls
.iter()
.map(|t| with_no_trimmed_paths!(format!(" {}", t)))
.collect::<Vec<String>>()
.join("\n");

err.help(format!(
"the following types implement `{trait_str}`:\n\
{types}\n\
Consider defining an enum where each variant holds one of these types,\n\
implementing `{trait_str}` for this new enum and using it instead.",
));
}

if externally_visible {
err.note(format!(
"`{trait_str}` can be implemented in other crates; if you want to support your users \
"`{trait_str}` may be implemented in other crates; if you want to support your users \
passing their own types here, you can't refer to a specific type",
));
}
}

err
fn has_non_lifetime_generics(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
tcx.generics_of(def_id).own_params.iter().any(|param| match param.kind {
ty::GenericParamDefKind::Type { .. } | ty::GenericParamDefKind::Const { .. } => true,
ty::GenericParamDefKind::Lifetime => false,
})
}

/// Attempt to suggest that a `dyn Trait` argument or return type be converted
/// to use `impl Trait`.
fn attempt_dyn_to_impl_suggestion(tcx: TyCtxt<'_>, hir_id: Option<hir::HirId>, err: &mut Diag<'_>) {
let Some(hir_id) = hir_id else { return };
let hir::Node::Ty(ty) = tcx.hir_node(hir_id) else { return };
let hir::TyKind::TraitObject([trait_ref, ..], ..) = ty.kind else { return };

// Only suggest converting `dyn` to `impl` if we're in a function signature.
// This ensures that we don't suggest converting e.g.
// `type Alias = Box<dyn DynIncompatibleTrait>;` to
// `type Alias = Box<impl DynIncompatibleTrait>;`
let Some((_id, first_non_type_parent_node)) =
tcx.hir().parent_iter(hir_id).find(|(_id, node)| !matches!(node, hir::Node::Ty(_)))
else {
return;
};
if first_non_type_parent_node.fn_sig().is_none() {
return;
}

err.span_suggestion_verbose(
ty.span.until(trait_ref.span),
"consider using an opaque type instead",
"impl ",
Applicability::MaybeIncorrect,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ fn dyn_compatibility_violations(
) -> &'_ [DynCompatibilityViolation] {
debug_assert!(tcx.generics_of(trait_def_id).has_self);
debug!("dyn_compatibility_violations: {:?}", trait_def_id);

tcx.arena.alloc_from_iter(
tcx.supertrait_def_ids(trait_def_id)
.flat_map(|def_id| dyn_compatibility_violations_for_trait(tcx, def_id)),
Expand Down
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/invalid_const_in_lifetime_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ fn f<'a>(arg : Box<dyn X<Y<1> = &'a ()>>) {}
//~| ERROR associated type takes 0 generic arguments but 1 generic argument
//~| ERROR associated type takes 1 lifetime argument but 0 lifetime arguments
//~| ERROR associated type takes 0 generic arguments but 1 generic argument
//~| ERROR trait `X` cannot be made into an object
//~| ERROR trait `X` is not dyn compatible
9 changes: 5 additions & 4 deletions tests/rustdoc-ui/invalid_const_in_lifetime_position.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,18 @@ LL | type Y<'a>;
| ^
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error[E0038]: the trait `X` cannot be made into an object
error[E0038]: the trait `X` is not dyn compatible
--> $DIR/invalid_const_in_lifetime_position.rs:4:20
|
LL | fn f<'a>(arg : Box<dyn X<Y<1> = &'a ()>>) {}
| ^^^^^^^^^^^^^^^^^^^^ `X` cannot be made into an object
| ^^^^^^^^^^^^^^^^^^^^ `X` is not dyn compatible
|
note: for a trait to be "dyn-compatible" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
note: for a trait to be dyn compatible it needs to allow building a vtable
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> $DIR/invalid_const_in_lifetime_position.rs:2:10
|
LL | trait X {
| - this trait cannot be made into an object...
| - this trait is not dyn compatible...
LL | type Y<'a>;
| ^ ...because it contains the generic associated type `Y`
= help: consider moving `Y` to another trait
Expand Down
4 changes: 2 additions & 2 deletions tests/rustdoc-ui/issues/issue-105742.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::ops::Index;
pub fn next<'a, T>(s: &'a mut dyn SVec<Item = T, Output = T>) {
//~^ expected 1 lifetime argument
//~| expected 1 generic argument
//~| the trait `SVec` cannot be made into an object
//~| `SVec` cannot be made into an object
//~| the trait `SVec` is not dyn compatible
//~| `SVec` is not dyn compatible
//~| missing generics for associated type `SVec::Item`
//~| missing generics for associated type `SVec::Item`
let _ = s;
Expand Down
9 changes: 5 additions & 4 deletions tests/rustdoc-ui/issues/issue-105742.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -294,19 +294,20 @@ help: add missing generic argument
LL | Output = <Self as SVec>::Item> as SVec>::Item<T>,
| +++

error[E0038]: the trait `SVec` cannot be made into an object
error[E0038]: the trait `SVec` is not dyn compatible
--> $DIR/issue-105742.rs:4:31
|
LL | pub fn next<'a, T>(s: &'a mut dyn SVec<Item = T, Output = T>) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SVec` cannot be made into an object
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SVec` is not dyn compatible
|
note: for a trait to be "dyn-compatible" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
note: for a trait to be dyn compatible it needs to allow building a vtable
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> $DIR/issue-105742.rs:14:17
|
LL | pub trait SVec: Index<
| ____________----__^
| | |
| | this trait cannot be made into an object...
| | this trait is not dyn compatible...
LL | | <Self as SVec>::Item,
LL | |
LL | |
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/associated-consts/associated-const-in-trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ trait Trait {
}

impl dyn Trait {
//~^ ERROR the trait `Trait` cannot be made into an object [E0038]
//~^ ERROR the trait `Trait` is not dyn compatible [E0038]
const fn n() -> usize { Self::N }
//~^ ERROR the trait `Trait` cannot be made into an object [E0038]
//~^ ERROR the trait `Trait` is not dyn compatible [E0038]
}

fn main() {}
Loading
Loading