Skip to content

Tweak E0277 output when a candidate is available #132147

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

Merged
merged 8 commits into from
Nov 2, 2024
Merged
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
21 changes: 18 additions & 3 deletions compiler/rustc_middle/src/ty/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::borrow::Cow;
use std::fs::File;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::io::{Read, Write};
use std::path::PathBuf;

use rustc_errors::pluralize;
@@ -250,8 +252,8 @@ impl<'tcx> TyCtxt<'tcx> {
}

let width = self.sess.diagnostic_width();
let length_limit = width.saturating_sub(30);
if regular.len() <= width {
let length_limit = width / 2;
if regular.len() <= width * 2 / 3 {
return regular;
}
let short = self.ty_string_with_limit(ty, length_limit);
@@ -265,7 +267,20 @@ impl<'tcx> TyCtxt<'tcx> {
*path = Some(path.take().unwrap_or_else(|| {
self.output_filenames(()).temp_path_ext(&format!("long-type-{hash}.txt"), None)
}));
match std::fs::write(path.as_ref().unwrap(), &format!("{regular}\n")) {
let Ok(mut file) =
File::options().create(true).read(true).append(true).open(&path.as_ref().unwrap())
else {
return regular;
};

// Do not write the same type to the file multiple times.
let mut contents = String::new();
let _ = file.read_to_string(&mut contents);
if let Some(_) = contents.lines().find(|line| line == &regular) {
return short;
}

match write!(file, "{regular}\n") {
Ok(_) => short,
Err(_) => regular,
}
61 changes: 61 additions & 0 deletions compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs
Original file line number Diff line number Diff line change
@@ -766,6 +766,67 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
values
}

pub fn cmp_traits(
&self,
def_id1: DefId,
args1: &[ty::GenericArg<'tcx>],
def_id2: DefId,
args2: &[ty::GenericArg<'tcx>],
) -> (DiagStyledString, DiagStyledString) {
let mut values = (DiagStyledString::new(), DiagStyledString::new());

if def_id1 != def_id2 {
values.0.push_highlighted(self.tcx.def_path_str(def_id1).as_str());
values.1.push_highlighted(self.tcx.def_path_str(def_id2).as_str());
} else {
values.0.push_normal(self.tcx.item_name(def_id1).as_str());
values.1.push_normal(self.tcx.item_name(def_id2).as_str());
}

if args1.len() != args2.len() {
let (pre, post) = if args1.len() > 0 { ("<", ">") } else { ("", "") };
values.0.push_normal(format!(
"{pre}{}{post}",
args1.iter().map(|a| a.to_string()).collect::<Vec<_>>().join(", ")
));
let (pre, post) = if args2.len() > 0 { ("<", ">") } else { ("", "") };
values.1.push_normal(format!(
"{pre}{}{post}",
args2.iter().map(|a| a.to_string()).collect::<Vec<_>>().join(", ")
));
return values;
}

if args1.len() > 0 {
values.0.push_normal("<");
values.1.push_normal("<");
}
for (i, (a, b)) in std::iter::zip(args1, args2).enumerate() {
let a_str = a.to_string();
let b_str = b.to_string();
if let (Some(a), Some(b)) = (a.as_type(), b.as_type()) {
let (a, b) = self.cmp(a, b);
values.0.0.extend(a.0);
values.1.0.extend(b.0);
} else if a_str != b_str {
values.0.push_highlighted(a_str);
values.1.push_highlighted(b_str);
} else {
values.0.push_normal(a_str);
values.1.push_normal(b_str);
}
if i + 1 < args1.len() {
values.0.push_normal(", ");
values.1.push_normal(", ");
}
}
if args1.len() > 0 {
values.0.push_normal(">");
values.1.push_normal(">");
}
values
}

/// Compares two given types, eliding parts that are the same between them and highlighting
/// relevant differences, and return two representation of those types for highlighted printing.
pub fn cmp(&self, t1: Ty<'tcx>, t2: Ty<'tcx>) -> (DiagStyledString, DiagStyledString) {
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::unord::UnordSet;
use rustc_errors::codes::*;
use rustc_errors::{
Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, StringPart, Suggestions, pluralize,
struct_span_code_err,
Applicability, Diag, ErrorGuaranteed, Level, MultiSpan, StashKey, StringPart, Suggestions,
pluralize, struct_span_code_err,
};
use rustc_hir::def::Namespace;
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
@@ -328,6 +328,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
}
} else if let Some(custom_explanation) = safe_transmute_explanation {
err.span_label(span, custom_explanation);
} else if explanation.len() > self.tcx.sess.diagnostic_width() {
// Really long types don't look good as span labels, instead move it
// to a `help`.
err.span_label(span, "unsatisfied trait bound");
err.help(explanation);
} else {
err.span_label(span, explanation);
}
@@ -1832,21 +1837,81 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
if impl_trait_ref.references_error() {
return false;
}
let self_ty = impl_trait_ref.self_ty().to_string();
err.highlighted_help(vec![
StringPart::normal(format!(
"the trait `{}` ",
impl_trait_ref.print_trait_sugared()
)),
StringPart::highlighted("is"),

if let [child, ..] = &err.children[..]
&& child.level == Level::Help
&& let Some(line) = child.messages.get(0)
&& let Some(line) = line.0.as_str()
&& line.starts_with("the trait")
&& line.contains("is not implemented for")
{
// HACK(estebank): we remove the pre-existing
// "the trait `X` is not implemented for" note, which only happens if there
// was a custom label. We do this because we want that note to always be the
// first, and making this logic run earlier will get tricky. For now, we
// instead keep the logic the same and modify the already constructed error
// to avoid the wording duplication.
err.children.remove(0);
}

let traits = self.cmp_traits(
obligation_trait_ref.def_id,
&obligation_trait_ref.args[1..],
impl_trait_ref.def_id,
&impl_trait_ref.args[1..],
);
let traits_content = (traits.0.content(), traits.1.content());
let types = self.cmp(obligation_trait_ref.self_ty(), impl_trait_ref.self_ty());
let types_content = (types.0.content(), types.1.content());
let mut msg = vec![StringPart::normal("the trait `")];
if traits_content.0 == traits_content.1 {
msg.push(StringPart::normal(
impl_trait_ref.print_trait_sugared().to_string(),
));
} else {
msg.extend(traits.0.0);
}
msg.extend([
StringPart::normal("` "),
StringPart::highlighted("is not"),
StringPart::normal(" implemented for `"),
if let [TypeError::Sorts(_)] = &terrs[..] {
StringPart::normal(self_ty)
} else {
StringPart::highlighted(self_ty)
},
StringPart::normal("`"),
]);
if types_content.0 == types_content.1 {
let ty =
self.tcx.short_ty_string(obligation_trait_ref.self_ty(), &mut None);
msg.push(StringPart::normal(ty));
} else {
msg.extend(types.0.0);
}
msg.push(StringPart::normal("`"));
if types_content.0 == types_content.1 {
msg.push(StringPart::normal("\nbut trait `"));
msg.extend(traits.1.0);
msg.extend([
StringPart::normal("` "),
StringPart::highlighted("is"),
StringPart::normal(" implemented for it"),
]);
} else if traits_content.0 == traits_content.1 {
msg.extend([
StringPart::normal("\nbut it "),
StringPart::highlighted("is"),
StringPart::normal(" implemented for `"),
]);
msg.extend(types.1.0);
msg.push(StringPart::normal("`"));
} else {
msg.push(StringPart::normal("\nbut trait `"));
msg.extend(traits.1.0);
msg.extend([
StringPart::normal("` "),
StringPart::highlighted("is"),
StringPart::normal(" implemented for `"),
]);
msg.extend(types.1.0);
msg.push(StringPart::normal("`"));
}
err.highlighted_help(msg);

if let [TypeError::Sorts(exp_found)] = &terrs[..] {
let exp_found = self.resolve_vars_if_possible(*exp_found);
@@ -2475,12 +2540,16 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
&& self.tcx.trait_impls_of(trait_def_id).is_empty()
&& !self.tcx.trait_is_auto(trait_def_id)
&& !self.tcx.trait_is_alias(trait_def_id)
&& trait_predicate.polarity() == ty::PredicatePolarity::Positive
{
err.span_help(
self.tcx.def_span(trait_def_id),
crate::fluent_generated::trait_selection_trait_has_no_impls,
);
} else if !suggested && !unsatisfied_const {
} else if !suggested
&& !unsatisfied_const
&& trait_predicate.polarity() == ty::PredicatePolarity::Positive
{
// Can't show anything else useful, try to find similar impls.
let impl_candidates = self.find_similar_impl_candidates(trait_predicate);
if !self.report_similar_impl_candidates(
Original file line number Diff line number Diff line change
@@ -3563,17 +3563,34 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
)]);
}
ObligationCauseCode::OpaqueReturnType(expr_info) => {
if let Some((expr_ty, hir_id)) = expr_info {
let expr_ty = self.tcx.short_ty_string(expr_ty, long_ty_file);
let expr = self.infcx.tcx.hir().expect_expr(hir_id);
err.span_label(
expr.span,
with_forced_trimmed_paths!(format!(
"return type was inferred to be `{expr_ty}` here",
)),
);
suggest_remove_deref(err, &expr);
}
let (expr_ty, expr) = if let Some((expr_ty, hir_id)) = expr_info {
let expr_ty = tcx.short_ty_string(expr_ty, long_ty_file);
let expr = tcx.hir().expect_expr(hir_id);
(expr_ty, expr)
} else if let Some(body_id) = tcx.hir_node_by_def_id(body_id).body_id()
&& let body = tcx.hir().body(body_id)
&& let hir::ExprKind::Block(block, _) = body.value.kind
&& let Some(expr) = block.expr
&& let Some(expr_ty) = self
.typeck_results
.as_ref()
.and_then(|typeck| typeck.node_type_opt(expr.hir_id))
&& let Some(pred) = predicate.as_clause()
&& let ty::ClauseKind::Trait(pred) = pred.kind().skip_binder()
&& self.can_eq(param_env, pred.self_ty(), expr_ty)
{
let expr_ty = tcx.short_ty_string(expr_ty, long_ty_file);
(expr_ty, expr)
} else {
return;
};
err.span_label(
expr.span,
with_forced_trimmed_paths!(format!(
"return type was inferred to be `{expr_ty}` here",
)),
);
suggest_remove_deref(err, &expr);
}
}
}
@@ -3680,6 +3697,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
err: &mut Diag<'_>,
trait_pred: ty::PolyTraitPredicate<'tcx>,
) {
if trait_pred.polarity() == ty::PredicatePolarity::Negative {
return;
}
let Some(diagnostic_name) = self.tcx.get_diagnostic_name(trait_pred.def_id()) else {
return;
};
3 changes: 2 additions & 1 deletion tests/ui/async-await/async-closures/not-clone-closure.stderr
Original file line number Diff line number Diff line change
@@ -2,8 +2,9 @@ error[E0277]: the trait bound `NotClonableUpvar: Clone` is not satisfied in `{as
--> $DIR/not-clone-closure.rs:32:15
|
LL | not_clone.clone();
| ^^^^^ within `{async closure@$DIR/not-clone-closure.rs:29:21: 29:34}`, the trait `Clone` is not implemented for `NotClonableUpvar`
| ^^^^^ unsatisfied trait bound
|
= help: within `{async closure@$DIR/not-clone-closure.rs:29:21: 29:34}`, the trait `Clone` is not implemented for `NotClonableUpvar`
note: required because it's used within this closure
--> $DIR/not-clone-closure.rs:29:21
|
3 changes: 3 additions & 0 deletions tests/ui/async-await/async-error-span.stderr
Original file line number Diff line number Diff line change
@@ -3,6 +3,9 @@ error[E0277]: `()` is not a future
|
LL | fn get_future() -> impl Future<Output = ()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^ `()` is not a future
LL |
LL | panic!()
| -------- return type was inferred to be `_` here
|
= help: the trait `Future` is not implemented for `()`

1 change: 1 addition & 0 deletions tests/ui/async-await/coroutine-not-future.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//@ edition:2018
//@compile-flags: --diagnostic-width=300
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]

use std::future::Future;
Loading