Auto merge of #134992 - Zalathar:rollup-pldy5w6, r=Zalathar
Rollup of 6 pull requests Successful merges: - #131439 (Remove allowing static_mut_refs lint) - #133292 (E0277: suggest dereferencing function arguments in more cases) - #134877 (add suggestion for wrongly ordered format parameters) - #134945 (Some small nits to the borrowck suggestions for mutating a map through index) - #134950 (bootstrap: Overhaul and simplify the `tool_check_step!` macro) - #134979 (Provide structured suggestion for `impl Default` of type where all fields have defaults) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
bc3e3015b6
24 changed files with 482 additions and 229 deletions
|
|
@ -575,7 +575,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
// ---------- place
|
||||
self.err.multipart_suggestions(
|
||||
format!(
|
||||
"to modify a `{}`, use `.get_mut()`, `.insert()` or the entry API",
|
||||
"use `.insert()` to insert a value into a `{}`, `.get_mut()` \
|
||||
to modify it, or the entry API for more flexibility",
|
||||
self.ty,
|
||||
),
|
||||
vec![
|
||||
|
|
@ -592,16 +593,17 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
(rv.span.shrink_to_hi(), ")".to_string()),
|
||||
],
|
||||
vec![
|
||||
// val.get_mut(index).map(|v| { *v = rv; });
|
||||
// if let Some(v) = val.get_mut(index) { *v = rv; }
|
||||
(val.span.shrink_to_lo(), "if let Some(val) = ".to_string()),
|
||||
(
|
||||
val.span.shrink_to_hi().with_hi(index.span.lo()),
|
||||
".get_mut(".to_string(),
|
||||
),
|
||||
(
|
||||
index.span.shrink_to_hi().with_hi(place.span.hi()),
|
||||
").map(|val| { *val".to_string(),
|
||||
") { *val".to_string(),
|
||||
),
|
||||
(rv.span.shrink_to_hi(), "; })".to_string()),
|
||||
(rv.span.shrink_to_hi(), "; }".to_string()),
|
||||
],
|
||||
vec![
|
||||
// let x = val.entry(index).or_insert(rv);
|
||||
|
|
@ -622,21 +624,22 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
self.suggested = true;
|
||||
} else if let hir::ExprKind::MethodCall(_path, receiver, _, sp) = expr.kind
|
||||
&& let hir::ExprKind::Index(val, index, _) = receiver.kind
|
||||
&& expr.span == self.assign_span
|
||||
&& receiver.span == self.assign_span
|
||||
{
|
||||
// val[index].path(args..);
|
||||
self.err.multipart_suggestion(
|
||||
format!("to modify a `{}` use `.get_mut()`", self.ty),
|
||||
vec![
|
||||
(val.span.shrink_to_lo(), "if let Some(val) = ".to_string()),
|
||||
(
|
||||
val.span.shrink_to_hi().with_hi(index.span.lo()),
|
||||
".get_mut(".to_string(),
|
||||
),
|
||||
(
|
||||
index.span.shrink_to_hi().with_hi(receiver.span.hi()),
|
||||
").map(|val| val".to_string(),
|
||||
") { val".to_string(),
|
||||
),
|
||||
(sp.shrink_to_hi(), ")".to_string()),
|
||||
(sp.shrink_to_hi(), "; }".to_string()),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -197,6 +197,8 @@ builtin_macros_format_redundant_args = redundant {$n ->
|
|||
|
||||
builtin_macros_format_remove_raw_ident = remove the `r#`
|
||||
|
||||
builtin_macros_format_reorder_format_parameter = did you mean `{$replacement}`?
|
||||
|
||||
builtin_macros_format_requires_string = requires at least a format string argument
|
||||
|
||||
builtin_macros_format_string_invalid = invalid format string: {$desc}
|
||||
|
|
|
|||
|
|
@ -618,6 +618,17 @@ pub(crate) enum InvalidFormatStringSuggestion {
|
|||
#[primary_span]
|
||||
span: Span,
|
||||
},
|
||||
#[suggestion(
|
||||
builtin_macros_format_reorder_format_parameter,
|
||||
code = "{replacement}",
|
||||
style = "verbose",
|
||||
applicability = "machine-applicable"
|
||||
)]
|
||||
ReorderFormatParameter {
|
||||
#[primary_span]
|
||||
span: Span,
|
||||
replacement: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
|
|
|
|||
|
|
@ -321,6 +321,13 @@ fn make_format_args(
|
|||
e.sugg_ = Some(errors::InvalidFormatStringSuggestion::RemoveRawIdent { span })
|
||||
}
|
||||
}
|
||||
parse::Suggestion::ReorderFormatParameter(span, replacement) => {
|
||||
let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
|
||||
e.sugg_ = Some(errors::InvalidFormatStringSuggestion::ReorderFormatParameter {
|
||||
span,
|
||||
replacement,
|
||||
});
|
||||
}
|
||||
}
|
||||
let guar = ecx.dcx().emit_err(e);
|
||||
return ExpandResult::Ready(Err(guar));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//! Primarily used to extract a backtrace from stack overflow
|
||||
|
||||
use std::alloc::{Layout, alloc};
|
||||
use std::{fmt, mem, ptr};
|
||||
use std::{fmt, mem, ptr, slice};
|
||||
|
||||
use rustc_interface::util::{DEFAULT_STACK_SIZE, STACK_SIZE};
|
||||
|
||||
|
|
@ -35,20 +35,22 @@ macro raw_errln($tokens:tt) {
|
|||
}
|
||||
|
||||
/// Signal handler installed for SIGSEGV
|
||||
// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint
|
||||
#[allow(static_mut_refs)]
|
||||
extern "C" fn print_stack_trace(_: libc::c_int) {
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Caller must ensure that this function is not re-entered.
|
||||
unsafe extern "C" fn print_stack_trace(_: libc::c_int) {
|
||||
const MAX_FRAMES: usize = 256;
|
||||
// Reserve data segment so we don't have to malloc in a signal handler, which might fail
|
||||
// in incredibly undesirable and unexpected ways due to e.g. the allocator deadlocking
|
||||
static mut STACK_TRACE: [*mut libc::c_void; MAX_FRAMES] = [ptr::null_mut(); MAX_FRAMES];
|
||||
let stack = unsafe {
|
||||
// Reserve data segment so we don't have to malloc in a signal handler, which might fail
|
||||
// in incredibly undesirable and unexpected ways due to e.g. the allocator deadlocking
|
||||
static mut STACK_TRACE: [*mut libc::c_void; MAX_FRAMES] = [ptr::null_mut(); MAX_FRAMES];
|
||||
// Collect return addresses
|
||||
let depth = libc::backtrace(STACK_TRACE.as_mut_ptr(), MAX_FRAMES as i32);
|
||||
let depth = libc::backtrace(&raw mut STACK_TRACE as _, MAX_FRAMES as i32);
|
||||
if depth == 0 {
|
||||
return;
|
||||
}
|
||||
&STACK_TRACE.as_slice()[0..(depth as _)]
|
||||
slice::from_raw_parts(&raw const STACK_TRACE as _, depth as _)
|
||||
};
|
||||
|
||||
// Just a stack trace is cryptic. Explain what we're doing.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::Diag;
|
||||
use rustc_errors::{Applicability, Diag};
|
||||
use rustc_hir as hir;
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::{declare_lint, impl_lint_pass};
|
||||
use rustc_span::Symbol;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use crate::{LateContext, LateLintPass};
|
||||
|
|
@ -149,13 +151,16 @@ impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {
|
|||
let hir_id = cx.tcx.local_def_id_to_hir_id(local);
|
||||
let hir::Node::Item(item) = cx.tcx.hir_node(hir_id) else { return };
|
||||
cx.tcx.node_span_lint(DEFAULT_OVERRIDES_DEFAULT_FIELDS, hir_id, item.span, |diag| {
|
||||
mk_lint(diag, orig_fields, fields);
|
||||
mk_lint(cx.tcx, diag, type_def_id, parent, orig_fields, fields);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn mk_lint(
|
||||
tcx: TyCtxt<'_>,
|
||||
diag: &mut Diag<'_, ()>,
|
||||
type_def_id: DefId,
|
||||
impl_def_id: DefId,
|
||||
orig_fields: FxHashMap<Symbol, &hir::FieldDef<'_>>,
|
||||
fields: &[hir::ExprField<'_>],
|
||||
) {
|
||||
|
|
@ -175,11 +180,24 @@ fn mk_lint(
|
|||
}
|
||||
}
|
||||
|
||||
diag.help(if removed_all_fields {
|
||||
"to avoid divergence in behavior between `Struct { .. }` and \
|
||||
`<Struct as Default>::default()`, derive the `Default`"
|
||||
if removed_all_fields {
|
||||
let msg = "to avoid divergence in behavior between `Struct { .. }` and \
|
||||
`<Struct as Default>::default()`, derive the `Default`";
|
||||
if let Some(hir::Node::Item(impl_)) = tcx.hir().get_if_local(impl_def_id) {
|
||||
diag.multipart_suggestion_verbose(
|
||||
msg,
|
||||
vec![
|
||||
(tcx.def_span(type_def_id).shrink_to_lo(), "#[derive(Default)] ".to_string()),
|
||||
(impl_.span, String::new()),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
diag.help(msg);
|
||||
}
|
||||
} else {
|
||||
"use the default values in the `impl` with `Struct { mandatory_field, .. }` to avoid them \
|
||||
diverging over time"
|
||||
});
|
||||
let msg = "use the default values in the `impl` with `Struct { mandatory_field, .. }` to \
|
||||
avoid them diverging over time";
|
||||
diag.help(msg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,6 +221,11 @@ pub enum Suggestion {
|
|||
/// Remove `r#` from identifier:
|
||||
/// `format!("{r#foo}")` -> `format!("{foo}")`
|
||||
RemoveRawIdent(InnerSpan),
|
||||
/// Reorder format parameter:
|
||||
/// `format!("{foo:?#}")` -> `format!("{foo:#?}")`
|
||||
/// `format!("{foo:?x}")` -> `format!("{foo:x?}")`
|
||||
/// `format!("{foo:?X}")` -> `format!("{foo:X?}")`
|
||||
ReorderFormatParameter(InnerSpan, string::String),
|
||||
}
|
||||
|
||||
/// The parser structure for interpreting the input format string. This is
|
||||
|
|
@ -731,6 +736,12 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
} else if self.consume('?') {
|
||||
spec.ty = "?";
|
||||
if let Some(&(_, maybe)) = self.cur.peek() {
|
||||
match maybe {
|
||||
'#' | 'x' | 'X' => self.suggest_format_parameter(maybe),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spec.ty = self.word();
|
||||
if !spec.ty.is_empty() {
|
||||
|
|
@ -932,6 +943,30 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn suggest_format_parameter(&mut self, c: char) {
|
||||
let replacement = match c {
|
||||
'#' => "#?",
|
||||
'x' => "x?",
|
||||
'X' => "X?",
|
||||
_ => return,
|
||||
};
|
||||
let Some(pos) = self.consume_pos(c) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let span = self.span(pos - 1, pos + 1);
|
||||
let pos = self.to_span_index(pos);
|
||||
|
||||
self.errors.insert(0, ParseError {
|
||||
description: format!("expected `}}`, found `{c}`"),
|
||||
note: None,
|
||||
label: "expected `'}'`".into(),
|
||||
span: pos.to(pos),
|
||||
secondary_label: None,
|
||||
suggestion: Suggestion::ReorderFormatParameter(span, format!("{replacement}")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the indices of all characters that have been processed and differ between the actual
|
||||
|
|
|
|||
|
|
@ -445,9 +445,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// When after several dereferencing, the reference satisfies the trait
|
||||
/// bound. This function provides dereference suggestion for this
|
||||
/// specific situation.
|
||||
/// Provide a suggestion to dereference arguments to functions and binary operators, if that
|
||||
/// would satisfy trait bounds.
|
||||
pub(super) fn suggest_dereferences(
|
||||
&self,
|
||||
obligation: &PredicateObligation<'tcx>,
|
||||
|
|
@ -461,127 +460,100 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
|
|||
&& let Some(arg_ty) = typeck_results.expr_ty_adjusted_opt(expr)
|
||||
{
|
||||
// Suggest dereferencing the argument to a function/method call if possible
|
||||
|
||||
// Get the root obligation, since the leaf obligation we have may be unhelpful (#87437)
|
||||
let mut real_trait_pred = trait_pred;
|
||||
while let Some((parent_code, parent_trait_pred)) = code.parent() {
|
||||
code = parent_code;
|
||||
if let Some(parent_trait_pred) = parent_trait_pred {
|
||||
real_trait_pred = parent_trait_pred;
|
||||
}
|
||||
}
|
||||
|
||||
// We `instantiate_bound_regions_with_erased` here because `make_subregion` does not handle
|
||||
// `ReBound`, and we don't particularly care about the regions.
|
||||
let real_ty =
|
||||
self.tcx.instantiate_bound_regions_with_erased(real_trait_pred.self_ty());
|
||||
// We `instantiate_bound_regions_with_erased` here because `make_subregion` does not handle
|
||||
// `ReBound`, and we don't particularly care about the regions.
|
||||
let real_ty = self.tcx.instantiate_bound_regions_with_erased(real_trait_pred.self_ty());
|
||||
if !self.can_eq(obligation.param_env, real_ty, arg_ty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.can_eq(obligation.param_env, real_ty, arg_ty)
|
||||
&& let ty::Ref(region, base_ty, mutbl) = *real_ty.kind()
|
||||
// Potentially, we'll want to place our dereferences under a `&`. We don't try this for
|
||||
// `&mut`, since we can't be sure users will get the side-effects they want from it.
|
||||
// If this doesn't work, we'll try removing the `&` in `suggest_remove_reference`.
|
||||
// FIXME(dianne): this misses the case where users need both to deref and remove `&`s.
|
||||
// This method could be combined with `TypeErrCtxt::suggest_remove_reference` to handle
|
||||
// that, similar to what `FnCtxt::suggest_deref_or_ref` does.
|
||||
let (is_under_ref, base_ty, span) = match expr.kind {
|
||||
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, subexpr)
|
||||
if let &ty::Ref(region, base_ty, hir::Mutability::Not) = real_ty.kind() =>
|
||||
{
|
||||
let autoderef = (self.autoderef_steps)(base_ty);
|
||||
if let Some(steps) =
|
||||
autoderef.into_iter().enumerate().find_map(|(steps, (ty, obligations))| {
|
||||
// Re-add the `&`
|
||||
let ty = Ty::new_ref(self.tcx, region, ty, mutbl);
|
||||
|
||||
// Remapping bound vars here
|
||||
let real_trait_pred_and_ty = real_trait_pred
|
||||
.map_bound(|inner_trait_pred| (inner_trait_pred, ty));
|
||||
let obligation = self.mk_trait_obligation_with_new_self_ty(
|
||||
obligation.param_env,
|
||||
real_trait_pred_and_ty,
|
||||
);
|
||||
let may_hold = obligations
|
||||
.iter()
|
||||
.chain([&obligation])
|
||||
.all(|obligation| self.predicate_may_hold(obligation))
|
||||
.then_some(steps);
|
||||
|
||||
may_hold
|
||||
})
|
||||
{
|
||||
if steps > 0 {
|
||||
// Don't care about `&mut` because `DerefMut` is used less
|
||||
// often and user will not expect that an autoderef happens.
|
||||
if let hir::Node::Expr(hir::Expr {
|
||||
kind:
|
||||
hir::ExprKind::AddrOf(
|
||||
hir::BorrowKind::Ref,
|
||||
hir::Mutability::Not,
|
||||
expr,
|
||||
),
|
||||
..
|
||||
}) = self.tcx.hir_node(*arg_hir_id)
|
||||
{
|
||||
let derefs = "*".repeat(steps);
|
||||
err.span_suggestion_verbose(
|
||||
expr.span.shrink_to_lo(),
|
||||
"consider dereferencing here",
|
||||
derefs,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if real_trait_pred != trait_pred {
|
||||
// This branch addresses #87437.
|
||||
|
||||
let span = obligation.cause.span;
|
||||
// Remapping bound vars here
|
||||
let real_trait_pred_and_base_ty = real_trait_pred
|
||||
.map_bound(|inner_trait_pred| (inner_trait_pred, base_ty));
|
||||
let obligation = self.mk_trait_obligation_with_new_self_ty(
|
||||
obligation.param_env,
|
||||
real_trait_pred_and_base_ty,
|
||||
);
|
||||
let sized_obligation = Obligation::new(
|
||||
self.tcx,
|
||||
obligation.cause.clone(),
|
||||
obligation.param_env,
|
||||
ty::TraitRef::new(
|
||||
self.tcx,
|
||||
self.tcx.require_lang_item(
|
||||
hir::LangItem::Sized,
|
||||
Some(obligation.cause.span),
|
||||
),
|
||||
[base_ty],
|
||||
),
|
||||
);
|
||||
if self.predicate_may_hold(&obligation)
|
||||
&& self.predicate_must_hold_modulo_regions(&sized_obligation)
|
||||
// Do not suggest * if it is already a reference,
|
||||
// will suggest removing the borrow instead in that case.
|
||||
&& !matches!(expr.kind, hir::ExprKind::AddrOf(..))
|
||||
{
|
||||
let call_node = self.tcx.hir_node(*call_hir_id);
|
||||
let msg = "consider dereferencing here";
|
||||
let is_receiver = matches!(
|
||||
call_node,
|
||||
Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::MethodCall(_, receiver_expr, ..),
|
||||
..
|
||||
})
|
||||
if receiver_expr.hir_id == *arg_hir_id
|
||||
);
|
||||
if is_receiver {
|
||||
err.multipart_suggestion_verbose(
|
||||
msg,
|
||||
vec![
|
||||
(span.shrink_to_lo(), "(*".to_string()),
|
||||
(span.shrink_to_hi(), ")".to_string()),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
} else {
|
||||
err.span_suggestion_verbose(
|
||||
span.shrink_to_lo(),
|
||||
msg,
|
||||
'*',
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
(Some(region), base_ty, subexpr.span)
|
||||
}
|
||||
// Don't suggest `*&mut`, etc.
|
||||
hir::ExprKind::AddrOf(..) => return false,
|
||||
_ => (None, real_ty, obligation.cause.span),
|
||||
};
|
||||
|
||||
let autoderef = (self.autoderef_steps)(base_ty);
|
||||
let mut is_boxed = base_ty.is_box();
|
||||
if let Some(steps) = autoderef.into_iter().position(|(mut ty, obligations)| {
|
||||
// Ensure one of the following for dereferencing to be valid: we're passing by
|
||||
// reference, `ty` is `Copy`, or we're moving out of a (potentially nested) `Box`.
|
||||
let can_deref = is_under_ref.is_some()
|
||||
|| self.type_is_copy_modulo_regions(obligation.param_env, ty)
|
||||
|| ty.is_numeric() // for inference vars (presumably but not provably `Copy`)
|
||||
|| is_boxed && self.type_is_sized_modulo_regions(obligation.param_env, ty);
|
||||
is_boxed &= ty.is_box();
|
||||
|
||||
// Re-add the `&` if necessary
|
||||
if let Some(region) = is_under_ref {
|
||||
ty = Ty::new_ref(self.tcx, region, ty, hir::Mutability::Not);
|
||||
}
|
||||
|
||||
// Remapping bound vars here
|
||||
let real_trait_pred_and_ty =
|
||||
real_trait_pred.map_bound(|inner_trait_pred| (inner_trait_pred, ty));
|
||||
let obligation = self.mk_trait_obligation_with_new_self_ty(
|
||||
obligation.param_env,
|
||||
real_trait_pred_and_ty,
|
||||
);
|
||||
|
||||
can_deref
|
||||
&& obligations
|
||||
.iter()
|
||||
.chain([&obligation])
|
||||
.all(|obligation| self.predicate_may_hold(obligation))
|
||||
}) && steps > 0
|
||||
{
|
||||
let derefs = "*".repeat(steps);
|
||||
let msg = "consider dereferencing here";
|
||||
let call_node = self.tcx.hir_node(*call_hir_id);
|
||||
let is_receiver = matches!(
|
||||
call_node,
|
||||
Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::MethodCall(_, receiver_expr, ..),
|
||||
..
|
||||
})
|
||||
if receiver_expr.hir_id == *arg_hir_id
|
||||
);
|
||||
if is_receiver {
|
||||
err.multipart_suggestion_verbose(
|
||||
msg,
|
||||
vec![
|
||||
(span.shrink_to_lo(), format!("({derefs}")),
|
||||
(span.shrink_to_hi(), ")".to_string()),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
} else {
|
||||
err.span_suggestion_verbose(
|
||||
span.shrink_to_lo(),
|
||||
msg,
|
||||
derefs,
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
};
|
||||
return true;
|
||||
}
|
||||
} else if let (
|
||||
ObligationCauseCode::BinOp { lhs_hir_id, rhs_hir_id: Some(rhs_hir_id), .. },
|
||||
|
|
|
|||
|
|
@ -288,8 +288,6 @@ cfg_if::cfg_if! {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint
|
||||
#[allow(static_mut_refs)]
|
||||
pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
|
||||
use core::intrinsics::atomic_store_seqcst;
|
||||
|
||||
|
|
|
|||
|
|
@ -401,12 +401,13 @@ impl Step for RustAnalyzer {
|
|||
|
||||
macro_rules! tool_check_step {
|
||||
(
|
||||
$name:ident,
|
||||
$display_name:literal,
|
||||
$path:literal,
|
||||
$($alias:literal, )*
|
||||
$source_type:path
|
||||
$(, $default:literal )?
|
||||
$name:ident {
|
||||
// The part of this path after the final '/' is also used as a display name.
|
||||
path: $path:literal
|
||||
$(, alt_path: $alt_path:literal )*
|
||||
$(, default: $default:literal )?
|
||||
$( , )?
|
||||
}
|
||||
) => {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct $name {
|
||||
|
|
@ -416,11 +417,11 @@ macro_rules! tool_check_step {
|
|||
impl Step for $name {
|
||||
type Output = ();
|
||||
const ONLY_HOSTS: bool = true;
|
||||
/// don't ever check out-of-tree tools by default, they'll fail when toolstate is broken
|
||||
const DEFAULT: bool = matches!($source_type, SourceType::InTree) $( && $default )?;
|
||||
/// Most of the tool-checks using this macro are run by default.
|
||||
const DEFAULT: bool = true $( && $default )?;
|
||||
|
||||
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
|
||||
run.paths(&[ $path, $($alias),* ])
|
||||
run.paths(&[ $path, $( $alt_path ),* ])
|
||||
}
|
||||
|
||||
fn make_run(run: RunConfig<'_>) {
|
||||
|
|
@ -428,82 +429,71 @@ macro_rules! tool_check_step {
|
|||
}
|
||||
|
||||
fn run(self, builder: &Builder<'_>) {
|
||||
let compiler = builder.compiler(builder.top_stage, builder.config.build);
|
||||
let target = self.target;
|
||||
|
||||
builder.ensure(Rustc::new(target, builder));
|
||||
|
||||
let mut cargo = prepare_tool_cargo(
|
||||
builder,
|
||||
compiler,
|
||||
Mode::ToolRustc,
|
||||
target,
|
||||
builder.kind,
|
||||
$path,
|
||||
$source_type,
|
||||
&[],
|
||||
);
|
||||
|
||||
// For ./x.py clippy, don't run with --all-targets because
|
||||
// linting tests and benchmarks can produce very noisy results
|
||||
if builder.kind != Kind::Clippy {
|
||||
cargo.arg("--all-targets");
|
||||
}
|
||||
|
||||
let _guard = builder.msg_check(&format!("{} artifacts", $display_name), target);
|
||||
run_cargo(
|
||||
builder,
|
||||
cargo,
|
||||
builder.config.free_args.clone(),
|
||||
&stamp(builder, compiler, target),
|
||||
vec![],
|
||||
true,
|
||||
false,
|
||||
);
|
||||
|
||||
/// Cargo's output path in a given stage, compiled by a particular
|
||||
/// compiler for the specified target.
|
||||
fn stamp(
|
||||
builder: &Builder<'_>,
|
||||
compiler: Compiler,
|
||||
target: TargetSelection,
|
||||
) -> PathBuf {
|
||||
builder
|
||||
.cargo_out(compiler, Mode::ToolRustc, target)
|
||||
.join(format!(".{}-check.stamp", stringify!($name).to_lowercase()))
|
||||
}
|
||||
let Self { target } = self;
|
||||
run_tool_check_step(builder, target, stringify!($name), $path);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
tool_check_step!(Rustdoc, "rustdoc", "src/tools/rustdoc", "src/librustdoc", SourceType::InTree);
|
||||
/// Used by the implementation of `Step::run` in `tool_check_step!`.
|
||||
fn run_tool_check_step(
|
||||
builder: &Builder<'_>,
|
||||
target: TargetSelection,
|
||||
step_type_name: &str,
|
||||
path: &str,
|
||||
) {
|
||||
let display_name = path.rsplit('/').next().unwrap();
|
||||
let compiler = builder.compiler(builder.top_stage, builder.config.build);
|
||||
|
||||
builder.ensure(Rustc::new(target, builder));
|
||||
|
||||
let mut cargo = prepare_tool_cargo(
|
||||
builder,
|
||||
compiler,
|
||||
Mode::ToolRustc,
|
||||
target,
|
||||
builder.kind,
|
||||
path,
|
||||
// Currently, all of the tools that use this macro/function are in-tree.
|
||||
// If support for out-of-tree tools is re-added in the future, those
|
||||
// steps should probably be marked non-default so that the default
|
||||
// checks aren't affected by toolstate being broken.
|
||||
SourceType::InTree,
|
||||
&[],
|
||||
);
|
||||
|
||||
// For ./x.py clippy, don't run with --all-targets because
|
||||
// linting tests and benchmarks can produce very noisy results
|
||||
if builder.kind != Kind::Clippy {
|
||||
cargo.arg("--all-targets");
|
||||
}
|
||||
|
||||
let stamp = builder
|
||||
.cargo_out(compiler, Mode::ToolRustc, target)
|
||||
.join(format!(".{}-check.stamp", step_type_name.to_lowercase()));
|
||||
|
||||
let _guard = builder.msg_check(format!("{display_name} artifacts"), target);
|
||||
run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false);
|
||||
}
|
||||
|
||||
tool_check_step!(Rustdoc { path: "src/tools/rustdoc", alt_path: "src/librustdoc" });
|
||||
// Clippy, miri and Rustfmt are hybrids. They are external tools, but use a git subtree instead
|
||||
// of a submodule. Since the SourceType only drives the deny-warnings
|
||||
// behavior, treat it as in-tree so that any new warnings in clippy will be
|
||||
// rejected.
|
||||
tool_check_step!(Clippy, "clippy", "src/tools/clippy", SourceType::InTree);
|
||||
tool_check_step!(Miri, "miri", "src/tools/miri", SourceType::InTree);
|
||||
tool_check_step!(CargoMiri, "cargo-miri", "src/tools/miri/cargo-miri", SourceType::InTree);
|
||||
tool_check_step!(Rls, "rls", "src/tools/rls", SourceType::InTree);
|
||||
tool_check_step!(Rustfmt, "rustfmt", "src/tools/rustfmt", SourceType::InTree);
|
||||
tool_check_step!(
|
||||
MiroptTestTools,
|
||||
"miropt-test-tools",
|
||||
"src/tools/miropt-test-tools",
|
||||
SourceType::InTree
|
||||
);
|
||||
tool_check_step!(
|
||||
TestFloatParse,
|
||||
"test-float-parse",
|
||||
"src/etc/test-float-parse",
|
||||
SourceType::InTree
|
||||
);
|
||||
tool_check_step!(Clippy { path: "src/tools/clippy" });
|
||||
tool_check_step!(Miri { path: "src/tools/miri" });
|
||||
tool_check_step!(CargoMiri { path: "src/tools/miri/cargo-miri" });
|
||||
tool_check_step!(Rls { path: "src/tools/rls" });
|
||||
tool_check_step!(Rustfmt { path: "src/tools/rustfmt" });
|
||||
tool_check_step!(MiroptTestTools { path: "src/tools/miropt-test-tools" });
|
||||
tool_check_step!(TestFloatParse { path: "src/etc/test-float-parse" });
|
||||
|
||||
tool_check_step!(Bootstrap, "bootstrap", "src/bootstrap", SourceType::InTree, false);
|
||||
tool_check_step!(Bootstrap { path: "src/bootstrap", default: false });
|
||||
// Compiletest is implicitly "checked" when it gets built in order to run tests,
|
||||
// so this is mainly for people working on compiletest to run locally.
|
||||
tool_check_step!(Compiletest, "compiletest", "src/tools/compiletest", SourceType::InTree, false);
|
||||
tool_check_step!(Compiletest { path: "src/tools/compiletest", default: false });
|
||||
|
||||
/// Cargo's output path for the standard library in a given stage, compiled
|
||||
/// by a particular compiler for the specified target.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ LL | map["peter"].clear();
|
|||
| ^^^^^^^^^^^^ cannot borrow as mutable
|
||||
|
|
||||
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `HashMap<&str, String>`
|
||||
= help: to modify a `HashMap<&str, String>`, use `.get_mut()`, `.insert()` or the entry API
|
||||
help: to modify a `HashMap<&str, String>` use `.get_mut()`
|
||||
|
|
||||
LL | if let Some(val) = map.get_mut("peter") { val.clear(); };
|
||||
| ++++++++++++++++++ ~~~~~~~~~ ~~~~~~~ +++
|
||||
|
||||
error[E0594]: cannot assign to data in an index of `HashMap<&str, String>`
|
||||
--> $DIR/index-mut-help.rs:11:5
|
||||
|
|
@ -14,12 +17,12 @@ LL | map["peter"] = "0".to_string();
|
|||
| ^^^^^^^^^^^^ cannot assign
|
||||
|
|
||||
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `HashMap<&str, String>`
|
||||
help: to modify a `HashMap<&str, String>`, use `.get_mut()`, `.insert()` or the entry API
|
||||
help: use `.insert()` to insert a value into a `HashMap<&str, String>`, `.get_mut()` to modify it, or the entry API for more flexibility
|
||||
|
|
||||
LL | map.insert("peter", "0".to_string());
|
||||
| ~~~~~~~~ ~ +
|
||||
LL | map.get_mut("peter").map(|val| { *val = "0".to_string(); });
|
||||
| ~~~~~~~~~ ~~~~~~~~~~~~~~~~~~ ++++
|
||||
LL | if let Some(val) = map.get_mut("peter") { *val = "0".to_string(); };
|
||||
| ++++++++++++++++++ ~~~~~~~~~ ~~~~~~~~ +++
|
||||
LL | let val = map.entry("peter").or_insert("0".to_string());
|
||||
| +++++++++ ~~~~~~~ ~~~~~~~~~~~~ +
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ LL | map[&0] = 1;
|
|||
| ^^^^^^^^^^^ cannot assign
|
||||
|
|
||||
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `BTreeMap<u32, u32>`
|
||||
help: to modify a `BTreeMap<u32, u32>`, use `.get_mut()`, `.insert()` or the entry API
|
||||
help: use `.insert()` to insert a value into a `BTreeMap<u32, u32>`, `.get_mut()` to modify it, or the entry API for more flexibility
|
||||
|
|
||||
LL | map.insert(&0, 1);
|
||||
| ~~~~~~~~ ~ +
|
||||
LL | map.get_mut(&0).map(|val| { *val = 1; });
|
||||
| ~~~~~~~~~ ~~~~~~~~~~~~~~~~~~ ++++
|
||||
LL | if let Some(val) = map.get_mut(&0) { *val = 1; };
|
||||
| ++++++++++++++++++ ~~~~~~~~~ ~~~~~~~~ +++
|
||||
LL | let val = map.entry(&0).or_insert(1);
|
||||
| +++++++++ ~~~~~~~ ~~~~~~~~~~~~ +
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ LL | map[&0] = 1;
|
|||
| ^^^^^^^^^^^ cannot assign
|
||||
|
|
||||
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `BTreeMap<u32, u32>`
|
||||
help: to modify a `BTreeMap<u32, u32>`, use `.get_mut()`, `.insert()` or the entry API
|
||||
help: use `.insert()` to insert a value into a `BTreeMap<u32, u32>`, `.get_mut()` to modify it, or the entry API for more flexibility
|
||||
|
|
||||
LL | map.insert(&0, 1);
|
||||
| ~~~~~~~~ ~ +
|
||||
LL | map.get_mut(&0).map(|val| { *val = 1; });
|
||||
| ~~~~~~~~~ ~~~~~~~~~~~~~~~~~~ ++++
|
||||
LL | if let Some(val) = map.get_mut(&0) { *val = 1; };
|
||||
| ++++++++++++++++++ ~~~~~~~~~ ~~~~~~~~ +++
|
||||
LL | let val = map.entry(&0).or_insert(1);
|
||||
| +++++++++ ~~~~~~~ ~~~~~~~~~~~~ +
|
||||
|
||||
|
|
|
|||
25
tests/ui/fmt/suggest-wrongly-order-format-parameter.fixed
Normal file
25
tests/ui/fmt/suggest-wrongly-order-format-parameter.fixed
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//! Regression test for https://github.com/rust-lang/rust/issues/129966
|
||||
//!
|
||||
//! Ensure we provide suggestion for wrongly ordered format parameters.
|
||||
|
||||
//@ run-rustfix
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Foo(u8, u8);
|
||||
|
||||
fn main() {
|
||||
let f = Foo(1, 2);
|
||||
|
||||
println!("{f:#?}");
|
||||
//~^ ERROR invalid format string: expected `}`, found `#`
|
||||
//~| HELP did you mean `#?`?
|
||||
|
||||
println!("{f:x?}");
|
||||
//~^ ERROR invalid format string: expected `}`, found `x`
|
||||
//~| HELP did you mean `x?`?
|
||||
|
||||
println!("{f:X?}");
|
||||
//~^ ERROR invalid format string: expected `}`, found `X`
|
||||
//~| HELP did you mean `X?`?
|
||||
}
|
||||
25
tests/ui/fmt/suggest-wrongly-order-format-parameter.rs
Normal file
25
tests/ui/fmt/suggest-wrongly-order-format-parameter.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//! Regression test for https://github.com/rust-lang/rust/issues/129966
|
||||
//!
|
||||
//! Ensure we provide suggestion for wrongly ordered format parameters.
|
||||
|
||||
//@ run-rustfix
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Foo(u8, u8);
|
||||
|
||||
fn main() {
|
||||
let f = Foo(1, 2);
|
||||
|
||||
println!("{f:?#}");
|
||||
//~^ ERROR invalid format string: expected `}`, found `#`
|
||||
//~| HELP did you mean `#?`?
|
||||
|
||||
println!("{f:?x}");
|
||||
//~^ ERROR invalid format string: expected `}`, found `x`
|
||||
//~| HELP did you mean `x?`?
|
||||
|
||||
println!("{f:?X}");
|
||||
//~^ ERROR invalid format string: expected `}`, found `X`
|
||||
//~| HELP did you mean `X?`?
|
||||
}
|
||||
35
tests/ui/fmt/suggest-wrongly-order-format-parameter.stderr
Normal file
35
tests/ui/fmt/suggest-wrongly-order-format-parameter.stderr
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
error: invalid format string: expected `}`, found `#`
|
||||
--> $DIR/suggest-wrongly-order-format-parameter.rs:14:19
|
||||
|
|
||||
LL | println!("{f:?#}");
|
||||
| ^ expected `'}'` in format string
|
||||
|
|
||||
help: did you mean `#?`?
|
||||
|
|
||||
LL | println!("{f:#?}");
|
||||
| ~~
|
||||
|
||||
error: invalid format string: expected `}`, found `x`
|
||||
--> $DIR/suggest-wrongly-order-format-parameter.rs:18:19
|
||||
|
|
||||
LL | println!("{f:?x}");
|
||||
| ^ expected `'}'` in format string
|
||||
|
|
||||
help: did you mean `x?`?
|
||||
|
|
||||
LL | println!("{f:x?}");
|
||||
| ~~
|
||||
|
||||
error: invalid format string: expected `}`, found `X`
|
||||
--> $DIR/suggest-wrongly-order-format-parameter.rs:22:19
|
||||
|
|
||||
LL | println!("{f:?X}");
|
||||
| ^ expected `'}'` in format string
|
||||
|
|
||||
help: did you mean `X?`?
|
||||
|
|
||||
LL | println!("{f:X?}");
|
||||
| ~~
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
||||
|
|
@ -5,12 +5,12 @@ LL | map[&0] = 1;
|
|||
| ^^^^^^^^^^^ cannot assign
|
||||
|
|
||||
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `HashMap<u32, u32>`
|
||||
help: to modify a `HashMap<u32, u32>`, use `.get_mut()`, `.insert()` or the entry API
|
||||
help: use `.insert()` to insert a value into a `HashMap<u32, u32>`, `.get_mut()` to modify it, or the entry API for more flexibility
|
||||
|
|
||||
LL | map.insert(&0, 1);
|
||||
| ~~~~~~~~ ~ +
|
||||
LL | map.get_mut(&0).map(|val| { *val = 1; });
|
||||
| ~~~~~~~~~ ~~~~~~~~~~~~~~~~~~ ++++
|
||||
LL | if let Some(val) = map.get_mut(&0) { *val = 1; };
|
||||
| ++++++++++++++++++ ~~~~~~~~~ ~~~~~~~~ +++
|
||||
LL | let val = map.entry(&0).or_insert(1);
|
||||
| +++++++++ ~~~~~~~ ~~~~~~~~~~~~ +
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ LL | things[src.as_str()].sort();
|
|||
| ^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
|
||||
|
|
||||
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `HashMap<String, Vec<String>>`
|
||||
= help: to modify a `HashMap<String, Vec<String>>`, use `.get_mut()`, `.insert()` or the entry API
|
||||
help: to modify a `HashMap<String, Vec<String>>` use `.get_mut()`
|
||||
|
|
||||
LL | if let Some(val) = things.get_mut(src.as_str()) { val.sort(); };
|
||||
| ++++++++++++++++++ ~~~~~~~~~ ~~~~~~~ +++
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ note: required by a bound in `bar`
|
|||
|
|
||||
LL | fn bar<T: Send>(_: T) {}
|
||||
| ^^^^ required by this bound in `bar`
|
||||
help: consider dereferencing here
|
||||
|
|
||||
LL | bar(*x);
|
||||
| +
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,10 @@ LL | | y: 0,
|
|||
LL | | }
|
||||
| |_^
|
||||
|
|
||||
= help: to avoid divergence in behavior between `Struct { .. }` and `<Struct as Default>::default()`, derive the `Default`
|
||||
help: to avoid divergence in behavior between `Struct { .. }` and `<Struct as Default>::default()`, derive the `Default`
|
||||
|
|
||||
LL ~ #[derive(Default)] struct B {
|
||||
|
|
||||
|
||||
error: `Default` impl doesn't use the declared default field values
|
||||
--> $DIR/manual-default-impl-could-be-derived.rs:43:1
|
||||
|
|
|
|||
|
|
@ -86,6 +86,10 @@ note: required by a bound in `f_send`
|
|||
|
|
||||
LL | fn f_send<T: Send>(t: T) {}
|
||||
| ^^^^ required by this bound in `f_send`
|
||||
help: consider dereferencing here
|
||||
|
|
||||
LL | f_send(*rc);
|
||||
| +
|
||||
|
||||
error: aborting due to 5 previous errors
|
||||
|
||||
|
|
|
|||
37
tests/ui/traits/suggest-dereferences/deref-argument.fixed
Normal file
37
tests/ui/traits/suggest-dereferences/deref-argument.fixed
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
//@ run-rustfix
|
||||
//! diagnostic test for #90997.
|
||||
//! test that E0277 suggests dereferences to satisfy bounds when the referent is `Copy` or boxed.
|
||||
use std::ops::Deref;
|
||||
|
||||
trait Test {
|
||||
fn test(self);
|
||||
}
|
||||
fn consume_test(x: impl Test) { x.test() }
|
||||
|
||||
impl Test for u32 {
|
||||
fn test(self) {}
|
||||
}
|
||||
struct MyRef(u32);
|
||||
impl Deref for MyRef {
|
||||
type Target = u32;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
struct NonCopy;
|
||||
impl Test for NonCopy {
|
||||
fn test(self) {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let my_ref = MyRef(0);
|
||||
consume_test(*my_ref);
|
||||
//~^ ERROR the trait bound `MyRef: Test` is not satisfied
|
||||
//~| SUGGESTION *
|
||||
|
||||
let nested_box = Box::new(Box::new(Box::new(NonCopy)));
|
||||
consume_test(***nested_box);
|
||||
//~^ ERROR the trait bound `Box<Box<Box<NonCopy>>>: Test` is not satisfied
|
||||
//~| SUGGESTION ***
|
||||
}
|
||||
37
tests/ui/traits/suggest-dereferences/deref-argument.rs
Normal file
37
tests/ui/traits/suggest-dereferences/deref-argument.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
//@ run-rustfix
|
||||
//! diagnostic test for #90997.
|
||||
//! test that E0277 suggests dereferences to satisfy bounds when the referent is `Copy` or boxed.
|
||||
use std::ops::Deref;
|
||||
|
||||
trait Test {
|
||||
fn test(self);
|
||||
}
|
||||
fn consume_test(x: impl Test) { x.test() }
|
||||
|
||||
impl Test for u32 {
|
||||
fn test(self) {}
|
||||
}
|
||||
struct MyRef(u32);
|
||||
impl Deref for MyRef {
|
||||
type Target = u32;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
struct NonCopy;
|
||||
impl Test for NonCopy {
|
||||
fn test(self) {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let my_ref = MyRef(0);
|
||||
consume_test(my_ref);
|
||||
//~^ ERROR the trait bound `MyRef: Test` is not satisfied
|
||||
//~| SUGGESTION *
|
||||
|
||||
let nested_box = Box::new(Box::new(Box::new(NonCopy)));
|
||||
consume_test(nested_box);
|
||||
//~^ ERROR the trait bound `Box<Box<Box<NonCopy>>>: Test` is not satisfied
|
||||
//~| SUGGESTION ***
|
||||
}
|
||||
39
tests/ui/traits/suggest-dereferences/deref-argument.stderr
Normal file
39
tests/ui/traits/suggest-dereferences/deref-argument.stderr
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
error[E0277]: the trait bound `MyRef: Test` is not satisfied
|
||||
--> $DIR/deref-argument.rs:29:18
|
||||
|
|
||||
LL | consume_test(my_ref);
|
||||
| ------------ ^^^^^^ the trait `Test` is not implemented for `MyRef`
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
note: required by a bound in `consume_test`
|
||||
--> $DIR/deref-argument.rs:9:25
|
||||
|
|
||||
LL | fn consume_test(x: impl Test) { x.test() }
|
||||
| ^^^^ required by this bound in `consume_test`
|
||||
help: consider dereferencing here
|
||||
|
|
||||
LL | consume_test(*my_ref);
|
||||
| +
|
||||
|
||||
error[E0277]: the trait bound `Box<Box<Box<NonCopy>>>: Test` is not satisfied
|
||||
--> $DIR/deref-argument.rs:34:18
|
||||
|
|
||||
LL | consume_test(nested_box);
|
||||
| ------------ ^^^^^^^^^^ the trait `Test` is not implemented for `Box<Box<Box<NonCopy>>>`
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
note: required by a bound in `consume_test`
|
||||
--> $DIR/deref-argument.rs:9:25
|
||||
|
|
||||
LL | fn consume_test(x: impl Test) { x.test() }
|
||||
| ^^^^ required by this bound in `consume_test`
|
||||
help: consider dereferencing here
|
||||
|
|
||||
LL | consume_test(***nested_box);
|
||||
| +++
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue