Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,28 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
};

let mut err = struct_span_code_err!(self.dcx(), span, E0277, "{}", err_msg);

let trait_def_id = main_trait_predicate.def_id();
if self.tcx.is_diagnostic_item(sym::From, trait_def_id)
|| self.tcx.is_diagnostic_item(sym::TryFrom, trait_def_id)
{
let found_ty = leaf_trait_predicate.skip_binder().trait_ref.args.type_at(1);
let ty = main_trait_predicate.skip_binder().self_ty();
if let Some(cast_ty) = self.find_explicit_cast_type(
obligation.param_env,
found_ty,
ty,
) {
let found_ty_str = self.tcx.short_string(found_ty, &mut long_ty_file);
let cast_ty_str = self.tcx.short_string(cast_ty, &mut long_ty_file);
err.help(
format!(
"consider casting the `{found_ty_str}` value to `{cast_ty_str}`",
),
);
}
}

*err.long_ty_path() = long_ty_file;

let mut suggested = false;
Expand Down Expand Up @@ -2930,6 +2952,69 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
})
}

/// If `found_ty` is a reference that can be explicitly cast to another reference type for which
/// a `From` / `TryFrom` impl exists for `self_ty`, return that type.
fn find_explicit_cast_type(
&self,
param_env: ty::ParamEnv<'tcx>,
found_ty: Ty<'tcx>,
self_ty: Ty<'tcx>,
) -> Option<Ty<'tcx>> {
let ty::Ref(region, inner_ty, mutbl) = *found_ty.kind() else {
return None;
};

let mut derefs = (self.autoderef_steps)(inner_ty).into_iter();
derefs.next(); // skip the first one, which is inner_ty itself
let deref_target = derefs.into_iter().next()?.0;

let cast_ty = Ty::new_ref(self.tcx, region, deref_target, mutbl);

let Some(from_def_id) = self.tcx.get_diagnostic_item(sym::From) else {
return None;
};
let Some(try_from_def_id) = self.tcx.get_diagnostic_item(sym::TryFrom) else {
return None;
};

if self.has_impl_for_type(
param_env,
ty::TraitRef::new(
self.tcx,
from_def_id,
self.tcx.mk_args(&[self_ty.into(), cast_ty.into()]),
),
) {
Some(cast_ty)
} else if self.has_impl_for_type(
param_env,
ty::TraitRef::new(
self.tcx,
try_from_def_id,
self.tcx.mk_args(&[self_ty.into(), cast_ty.into()]),
),
) {
Some(cast_ty)
} else {
None
}
}

fn has_impl_for_type(
&self,
param_env: ty::ParamEnv<'tcx>,
trait_ref: ty::TraitRef<'tcx>,
) -> bool {
let obligation = Obligation::new(
self.tcx,
ObligationCause::dummy(),
param_env,
ty::TraitPredicate { trait_ref, polarity: ty::PredicatePolarity::Positive },
);

self.predicate_must_hold_modulo_regions(&obligation)
}

fn add_tuple_trait_message(
&self,
obligation_cause_code: &ObligationCauseCode<'tcx>,
Expand Down
50 changes: 50 additions & 0 deletions tests/ui/traits/explicit-reference-cast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// compile-fail

use std::convert::TryFrom;
use std::path::{Path, PathBuf};

pub struct ToolA(PathBuf);
//~^ HELP the trait `From<&PathBuf>` is not implemented for `ToolA`

impl From<&Path> for ToolA {
//~^ HELP the following other types implement trait `From<T>`
fn from(p: &Path) -> ToolA {
ToolA(p.to_path_buf())
}
}

// Add a different From<T> impl to ensure we suggest the correct cast
impl From<&str> for ToolA {
fn from(s: &str) -> ToolA {
ToolA(PathBuf::from(s))
}
}

pub struct ToolB(PathBuf);
//~^ HELP the trait `From<&PathBuf>` is not implemented for `ToolB`
//~| HELP the trait `From<&PathBuf>` is not implemented for `ToolB`

impl TryFrom<&Path> for ToolB {
//~^ HELP the trait `TryFrom<&PathBuf>` is not implemented for `ToolB`
//~| HELP the trait `TryFrom<&PathBuf>` is not implemented for `ToolB`
type Error = ();

fn try_from(p: &Path) -> Result<ToolB, ()> {
Ok(ToolB(p.to_path_buf()))
}
}

fn main() {
let path = PathBuf::new();

let _ = ToolA::from(&path);
//~^ ERROR the trait bound `ToolA: From<&PathBuf>` is not satisfied
//~| HELP consider casting the `&PathBuf` value to `&Path`
let _ = ToolB::try_from(&path);
//~^ ERROR the trait bound `ToolB: TryFrom<&PathBuf>` is not satisfied
//~| ERROR the trait bound `ToolB: From<&PathBuf>` is not satisfied
//~| HELP consider casting the `&PathBuf` value to `&Path`
//~| HELP consider casting the `&PathBuf` value to `&Path`
//~| HELP for that trait implementation, expected `Path`, found `PathBuf`
//~| HELP for that trait implementation, expected `Path`, found `PathBuf`
}
68 changes: 68 additions & 0 deletions tests/ui/traits/explicit-reference-cast.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
error[E0277]: the trait bound `ToolA: From<&PathBuf>` is not satisfied
--> $DIR/explicit-reference-cast.rs:40:13
|
LL | let _ = ToolA::from(&path);
| ^^^^^ unsatisfied trait bound
|
= help: consider casting the `&PathBuf` value to `&Path`
help: the trait `From<&PathBuf>` is not implemented for `ToolA`
--> $DIR/explicit-reference-cast.rs:6:1
|
LL | pub struct ToolA(PathBuf);
| ^^^^^^^^^^^^^^^^
help: the following other types implement trait `From<T>`
--> $DIR/explicit-reference-cast.rs:9:1
|
LL | impl From<&Path> for ToolA {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ `ToolA` implements `From<&Path>`
...
LL | impl From<&str> for ToolA {
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `ToolA` implements `From<&str>`

error[E0277]: the trait bound `ToolB: TryFrom<&PathBuf>` is not satisfied
--> $DIR/explicit-reference-cast.rs:43:13
|
LL | let _ = ToolB::try_from(&path);
| ^^^^^ unsatisfied trait bound
|
= help: consider casting the `&PathBuf` value to `&Path`
help: the trait `From<&PathBuf>` is not implemented for `ToolB`
--> $DIR/explicit-reference-cast.rs:23:1
|
LL | pub struct ToolB(PathBuf);
| ^^^^^^^^^^^^^^^^
help: the trait `TryFrom<&PathBuf>` is not implemented for `ToolB`
but trait `TryFrom<&Path>` is implemented for it
--> $DIR/explicit-reference-cast.rs:27:1
|
LL | impl TryFrom<&Path> for ToolB {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: for that trait implementation, expected `Path`, found `PathBuf`
= note: required for `&PathBuf` to implement `Into<ToolB>`
= note: required for `ToolB` to implement `TryFrom<&PathBuf>`

error[E0277]: the trait bound `ToolB: From<&PathBuf>` is not satisfied
--> $DIR/explicit-reference-cast.rs:43:13
|
LL | let _ = ToolB::try_from(&path);
| ^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound
|
= help: consider casting the `&PathBuf` value to `&Path`
help: the trait `From<&PathBuf>` is not implemented for `ToolB`
--> $DIR/explicit-reference-cast.rs:23:1
|
LL | pub struct ToolB(PathBuf);
| ^^^^^^^^^^^^^^^^
help: the trait `TryFrom<&PathBuf>` is not implemented for `ToolB`
but trait `TryFrom<&Path>` is implemented for it
--> $DIR/explicit-reference-cast.rs:27:1
|
LL | impl TryFrom<&Path> for ToolB {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: for that trait implementation, expected `Path`, found `PathBuf`
= note: required for `&PathBuf` to implement `Into<ToolB>`
= note: required for `ToolB` to implement `TryFrom<&PathBuf>`

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0277`.
Loading