use crate::astconv::AstConv;
use crate::check::coercion::CoerceMany;
+use crate::check::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error};
use crate::check::gather_locals::Declaration;
use crate::check::method::MethodCallee;
use crate::check::Expectation::*;
potentially_plural_count, struct_span_err, BreakableCtxt, Diverges, Expectation, FnCtxt,
LocalTy, Needs, TupleArgumentsFlag,
};
+use crate::structured_errors::StructuredDiagnostic;
use rustc_ast as ast;
use rustc_data_structures::sync::Lrc;
use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{ExprKind, Node, QPath};
+use rustc_infer::infer::error_reporting::{FailureCode, ObligationCauseExt};
+use rustc_infer::infer::InferOk;
+use rustc_infer::infer::TypeTrace;
use rustc_middle::ty::adjustment::AllowTwoPhase;
+use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::fold::TypeFoldable;
use rustc_middle::ty::{self, Ty};
use rustc_session::Session;
use rustc_span::{self, Span};
use rustc_trait_selection::traits::{self, ObligationCauseCode, StatementAsExpression};
-use crate::structured_errors::StructuredDiagnostic;
use std::iter;
use std::slice;
-struct FnArgsAsTuple<'hir> {
- first: &'hir hir::Expr<'hir>,
- last: &'hir hir::Expr<'hir>,
+enum TupleMatchFound {
+ None,
+ Single,
+ /// Beginning and end Span
+ Multiple(Span, Span),
}
-
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub(in super::super) fn check_casts(&self) {
let mut deferred_cast_checks = self.deferred_cast_checks.borrow_mut();
sp,
expr,
&err_inputs,
- vec![],
+ None,
args_no_rcvr,
false,
tuple_arguments,
// Types (as defined in the *signature* of the target function)
formal_input_tys: &[Ty<'tcx>],
// More specific expected types, after unifying with caller output types
- expected_input_tys: Vec<Ty<'tcx>>,
+ expected_input_tys: Option<Vec<Ty<'tcx>>>,
// The expressions for each provided argument
provided_args: &'tcx [hir::Expr<'tcx>],
// Whether the function is variadic, for example when imported from C
fn_def_id: Option<DefId>,
) {
let tcx = self.tcx;
- // Grab the argument types, supplying fresh type variables
- // if the wrong number of arguments were supplied
- let supplied_arg_count =
- if tuple_arguments == DontTupleArguments { provided_args.len() } else { 1 };
+
+ // Conceptually, we've got some number of expected inputs, and some number of provided aguments
+ // and we can form a grid of whether each argument could satisfy a given input:
+ // in1 | in2 | in3 | ...
+ // arg1 ? | | |
+ // arg2 | ? | |
+ // arg3 | | ? |
+ // ...
+ // Initially, we just check the diagonal, because in the case of correct code
+ // these are the only checks that matter
+ // However, in the unhappy path, we'll fill in this whole grid to attempt to provide
+ // better error messages about invalid method calls.
// All the input types from the fn signature must outlive the call
// so as to validate implied bounds.
self.register_wf_obligation(fn_input_ty.into(), arg_expr.span, traits::MiscObligation);
}
- let expected_arg_count = formal_input_tys.len();
-
- // expected_count, arg_count, error_code, sugg_unit, sugg_tuple_wrap_args
- let mut arg_count_error: Option<(usize, usize, &str, bool, Option<FnArgsAsTuple<'_>>)> =
- None;
+ let mut err_code = "E0061";
// If the arguments should be wrapped in a tuple (ex: closures), unwrap them here
let (formal_input_tys, expected_input_tys) = if tuple_arguments == TupleArguments {
ty::Tuple(arg_types) => {
// Argument length differs
if arg_types.len() != provided_args.len() {
- arg_count_error =
- Some((arg_types.len(), provided_args.len(), "E0057", false, None));
+ err_code = "E0057";
}
- let expected_input_tys = match expected_input_tys.get(0) {
- Some(&ty) => match ty.kind() {
- ty::Tuple(tys) => tys.iter().collect(),
- _ => vec![],
+ let expected_input_tys = match expected_input_tys {
+ Some(expected_input_tys) => match expected_input_tys.get(0) {
+ Some(ty) => match ty.kind() {
+ ty::Tuple(tys) => Some(tys.iter().collect()),
+ _ => None,
+ },
+ None => None,
},
- None => vec![],
+ None => None,
};
(arg_types.iter().collect(), expected_input_tys)
}
for the function trait is neither a tuple nor unit"
)
.emit();
- (self.err_args(provided_args.len()), vec![])
+ (self.err_args(provided_args.len()), None)
}
}
- } else if expected_arg_count == supplied_arg_count {
- (formal_input_tys.to_vec(), expected_input_tys)
- } else if c_variadic {
- if supplied_arg_count >= expected_arg_count {
- (formal_input_tys.to_vec(), expected_input_tys)
- } else {
- arg_count_error =
- Some((expected_arg_count, supplied_arg_count, "E0060", false, None));
- (self.err_args(supplied_arg_count), vec![])
- }
} else {
- // is the missing argument of type `()`?
- let sugg_unit = if expected_input_tys.len() == 1 && supplied_arg_count == 0 {
- self.resolve_vars_if_possible(expected_input_tys[0]).is_unit()
- } else if formal_input_tys.len() == 1 && supplied_arg_count == 0 {
- self.resolve_vars_if_possible(formal_input_tys[0]).is_unit()
- } else {
- false
- };
-
- // are we passing elements of a tuple without the tuple parentheses?
- let expected_input_tys = if expected_input_tys.is_empty() {
- // In most cases we can use expected_input_tys, but some callers won't have the type
- // information, in which case we fall back to the types from the input expressions.
- formal_input_tys
- } else {
- &*expected_input_tys
- };
-
- let sugg_tuple_wrap_args = self.suggested_tuple_wrap(expected_input_tys, provided_args);
-
- arg_count_error = Some((
- expected_arg_count,
- supplied_arg_count,
- "E0061",
- sugg_unit,
- sugg_tuple_wrap_args,
- ));
- (self.err_args(supplied_arg_count), vec![])
+ (formal_input_tys.to_vec(), expected_input_tys)
};
- debug!(
- "check_argument_types: formal_input_tys={:?}",
- formal_input_tys.iter().map(|t| self.ty_to_string(*t)).collect::<Vec<String>>()
- );
-
- // If there is no expectation, expect formal_input_tys.
- let expected_input_tys = if !expected_input_tys.is_empty() {
+ // If there are no external expectations at the call site, just use the types from the function defn
+ let expected_input_tys = if let Some(expected_input_tys) = expected_input_tys {
+ assert_eq!(expected_input_tys.len(), formal_input_tys.len());
expected_input_tys
} else {
formal_input_tys.clone()
};
- assert_eq!(expected_input_tys.len(), formal_input_tys.len());
-
- let provided_arg_count: usize = provided_args.len();
+ let minimum_input_count = expected_input_tys.len();
+ let provided_arg_count = provided_args.len();
- // Keep track of the fully coerced argument types
+ // We'll also want to keep track of the fully coerced argument types, for an awkward hack near the end
let mut final_arg_types: Vec<Option<(Ty<'_>, Ty<'_>)>> = vec![None; provided_arg_count];
// We introduce a helper function to demand that a given argument satisfy a given input
debug!("checking argument {}: {:?} = {:?}", idx, provided_arg, formal_input_ty);
- // The special-cased logic below has three functions:
- // 1. Provide as good of an expected type as possible.
+ // We're on the happy path here, so we'll do a more involved check and write back types
+ // To check compatibility, we'll do 3 things:
+ // 1. Unify the provided argument with the expected type
let expectation = Expectation::rvalue_hint(self, expected_input_ty);
let checked_ty = self.check_expr_with_expectation(provided_arg, expectation);
final_arg_types[idx] = Some((checked_ty, coerced_ty));
// Cause selection errors caused by resolving a single argument to point at the
- // argument and not the call. This is otherwise redundant with the `demand_coerce`
- // call immediately after, but it lets us customize the span pointed to in the
+ // argument and not the call. This lets us customize the span pointed to in the
// fulfillment error to be more accurate.
let coerced_ty =
self.resolve_vars_with_obligations_and_mutate_fulfillment(coerced_ty, |errors| {
);
});
+ // Make sure we store the resolved type
final_arg_types[idx] = Some((checked_ty, coerced_ty));
- // We're processing function arguments so we definitely want to use
- // two-phase borrows.
- self.demand_coerce(&provided_arg, checked_ty, coerced_ty, None, AllowTwoPhase::Yes);
+ let coerce_error = self
+ .try_coerce(provided_arg, checked_ty, coerced_ty, AllowTwoPhase::Yes, None)
+ .err();
+
+ if coerce_error.is_some() {
+ return Compatibility::Incompatible(coerce_error);
+ }
+
+ // 3. Check if the formal type is a supertype of the checked one
+ // and register any such obligations for future type checks
+ let supertype_error = self
+ .at(&self.misc(provided_arg.span), self.param_env)
+ .sup(formal_input_ty, coerced_ty);
+ let subtyping_error = match supertype_error {
+ Ok(InferOk { obligations, value: () }) => {
+ self.register_predicates(obligations);
+ None
+ }
+ Err(err) => Some(err),
+ };
+
+ // If neither check failed, the types are compatible
+ match subtyping_error {
+ None => Compatibility::Compatible,
+ Some(_) => Compatibility::Incompatible(subtyping_error),
+ }
+ };
+
+ // A "softer" version of the helper above, which checks types without persisting them,
+ // and treats error types differently
+ // This will allow us to "probe" for other argument orders that would likely have been correct
+ let check_compatible = |arg_idx, input_idx| {
+ let formal_input_ty: Ty<'tcx> = formal_input_tys[input_idx];
+ let expected_input_ty: Ty<'tcx> = expected_input_tys[input_idx];
+
+ // If either is an error type, we defy the usual convention and consider them to *not* be
+ // coercible. This prevents our error message heuristic from trying to pass errors into
+ // every argument.
+ if formal_input_ty.references_error() || expected_input_ty.references_error() {
+ return Compatibility::Incompatible(None);
+ }
+
+ let provided_arg: &hir::Expr<'tcx> = &provided_args[arg_idx];
+ let expectation = Expectation::rvalue_hint(self, expected_input_ty);
+ // FIXME: check that this is safe; I don't believe this commits any of the obligations, but I can't be sure.
+ //
+ // I had another method of "soft" type checking before,
+ // but it was failing to find the type of some expressions (like "")
+ // so I prodded this method and made it pub(super) so I could call it, and it seems to work well.
+ let checked_ty = self.check_expr_kind(provided_arg, expectation);
+
+ let coerced_ty = expectation.only_has_type(self).unwrap_or(formal_input_ty);
+ let can_coerce = self.can_coerce(checked_ty, coerced_ty);
+
+ if !can_coerce {
+ return Compatibility::Incompatible(None);
+ }
+
+ let subtyping_result = self
+ .at(&self.misc(provided_arg.span), self.param_env)
+ .sup(formal_input_ty, coerced_ty);
- // 3. Relate the expected type and the formal one,
- // if the expected type was used for the coercion.
- self.demand_suptype(provided_arg.span, formal_input_ty, coerced_ty);
+ // Same as above: if either the coerce type or the checked type is an error type,
+ // consider them *not* compatible.
+ let coercible =
+ !coerced_ty.references_error() && !checked_ty.references_error() && can_coerce;
+
+ match (coercible, &subtyping_result) {
+ (true, Ok(_)) => Compatibility::Compatible,
+ _ => Compatibility::Incompatible(subtyping_result.err()),
+ }
};
- let minimum_input_count = formal_input_tys.len();
+ // To start, we only care "along the diagonal", where we expect every
+ // provided arg to be in the right spot
+ let mut compatibility = vec![Compatibility::Incompatible(None); provided_args.len()];
+
+ // Keep track of whether we *could possibly* be satisfied, i.e. whether we're on the happy path
+ // if the wrong number of arguments were supplied, we CAN'T be satisfied,
+ // and if we're c_variadic, the supplied arguments must be >= the minimum count from the function
+ // otherwise, they need to be identical, because rust doesn't currently support variadic functions
+ let mut call_appears_satisfied = if c_variadic {
+ provided_arg_count >= minimum_input_count
+ } else {
+ provided_arg_count == minimum_input_count
+ };
// Check the arguments.
// We do this in a pretty awful way: first we type-check any arguments
})
}
+ // Check each argument, to satisfy the input it was provided for
+ // Visually, we're traveling down the diagonal of the compatibility matrix
for (idx, arg) in provided_args.iter().enumerate() {
// Warn only for the first loop (the "no closures" one).
// Closure arguments themselves can't be diverging, but
continue;
}
- demand_compatible(idx, &mut final_arg_types);
+ let compatible = demand_compatible(idx, &mut final_arg_types);
+ let is_compatible = matches!(compatible, Compatibility::Compatible);
+ compatibility[idx] = compatible;
+
+ if !is_compatible {
+ call_appears_satisfied = false;
+ }
}
}
- // If there was an error in parameter count, emit that here
- if let Some((expected_count, arg_count, err_code, sugg_unit, sugg_tuple_wrap_args)) =
- arg_count_error
- {
- let (span, start_span, args, ctor_of) = match &call_expr.kind {
+ // Logic here is a bit hairy
+ 'errors: {
+ // If something above didn't typecheck, we've fallen off the happy path
+ // and we should make some effort to provide better error messages
+ if call_appears_satisfied {
+ break 'errors;
+ }
+
+ // The algorithm here is inspired by levenshtein distance and longest common subsequence.
+ // We'll try to detect 4 different types of mistakes:
+ // - An extra parameter has been provided that doesn't satisfy *any* of the other inputs
+ // - An input is missing, which isn't satisfied by *any* of the other arguments
+ // - Some number of arguments have been provided in the wrong order
+ // - A type is straight up invalid
+
+ // First, let's find the errors
+ let mut compatibility: Vec<_> = compatibility.into_iter().map(Some).collect();
+ let (mut errors, matched_inputs) =
+ ArgMatrix::new(minimum_input_count, provided_arg_count, |i, j| {
+ if i == j { compatibility[i].take().unwrap() } else { check_compatible(i, j) }
+ })
+ .find_errors();
+
+ // Okay, so here's where it gets complicated in regards to what errors
+ // we emit and how.
+ // There are 3 different "types" of errors we might encounter.
+ // 1) Missing/extra/swapped arguments
+ // 2) Valid but incorrect arguments
+ // 3) Invalid arguments
+ // - Currently I think this only comes up with `CyclicTy`
+ //
+ // We first need to go through, remove those from (3) and emit those
+ // as their own error, particularly since they're error code and
+ // message is special. From what I can tell, we *must* emit these
+ // here (vs somewhere prior to this function) since the arguments
+ // become invalid *because* of how they get used in the function.
+ // It is what it is.
+
+ let found_errors = !errors.is_empty();
+
+ errors.drain_filter(|error| {
+ let Error::Invalid(input_idx, Compatibility::Incompatible(error)) = error else { return false };
+ let expected_ty = expected_input_tys[*input_idx];
+ let provided_ty = final_arg_types[*input_idx].map(|ty| ty.0).unwrap();
+ let cause = &self.misc(provided_args[*input_idx].span);
+ let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
+ if let Some(e) = error {
+ if !matches!(trace.cause.as_failure_code(e), FailureCode::Error0308(_)) {
+ self.report_and_explain_type_error(trace, e).emit();
+ return true;
+ }
+ }
+ false
+ });
+
+ // We're done if we found errors, but we already emitted them.
+ // I don't think we *should* be able to enter this bit of code
+ // (`!call_appears_satisfied`) without *also* finding errors, but we
+ // don't want to accidentally not emit an error if there is some
+ // logic bug in the `ArgMatrix` code.
+ if found_errors && errors.is_empty() {
+ break 'errors;
+ }
+
+ // Next, let's construct the error
+ let (error_span, full_call_span, ctor_of) = match &call_expr.kind {
hir::ExprKind::Call(
hir::Expr {
span,
)),
..
},
- args,
- ) => (*span, *span, &args[..], Some(of)),
- hir::ExprKind::Call(hir::Expr { span, .. }, args) => {
- (*span, *span, &args[..], None)
+ _,
+ ) => (call_span, *span, Some(of)),
+ hir::ExprKind::Call(hir::Expr { span, .. }, _) => (call_span, *span, None),
+ hir::ExprKind::MethodCall(path_segment, _, span) => {
+ let ident_span = path_segment.ident.span;
+ let ident_span = if let Some(args) = path_segment.args {
+ ident_span.with_hi(args.span_ext.hi())
+ } else {
+ ident_span
+ };
+ (
+ *span, ident_span, None, // methods are never ctors
+ )
}
- hir::ExprKind::MethodCall(path_segment, args, _) => (
- path_segment.ident.span,
- // `sp` doesn't point at the whole `foo.bar()`, only at `bar`.
- path_segment
- .args
- .and_then(|args| args.args.iter().last())
- // Account for `foo.bar::<T>()`.
- .map(|arg| {
- // Skip the closing `>`.
- tcx.sess
- .source_map()
- .next_point(tcx.sess.source_map().next_point(arg.span()))
- })
- .unwrap_or(path_segment.ident.span),
- &args[1..], // Skip the receiver.
- None, // methods are never ctors
- ),
k => span_bug!(call_span, "checking argument types on a non-call: `{:?}`", k),
};
- let arg_spans = if provided_args.is_empty() {
- // foo()
- // ^^^-- supplied 0 arguments
- // |
- // expected 2 arguments
- vec![tcx.sess.source_map().next_point(start_span).with_hi(call_span.hi())]
- } else {
- // foo(1, 2, 3)
- // ^^^ - - - supplied 3 arguments
- // |
- // expected 2 arguments
- args.iter().map(|arg| arg.span).collect::<Vec<Span>>()
- };
+ let args_span = error_span.trim_start(full_call_span).unwrap_or(error_span);
let call_name = match ctor_of {
Some(CtorOf::Struct) => "struct",
Some(CtorOf::Variant) => "enum variant",
None => "function",
};
- let mut err = tcx.sess.struct_span_err_with_code(
- span,
- &format!(
- "this {} takes {}{} but {} {} supplied",
+ if c_variadic && provided_arg_count < minimum_input_count {
+ err_code = "E0060";
+ }
+
+ // Next special case: The case where we expect a single tuple and
+ // wrapping all the args in parentheses (or adding a comma to
+ // already existing parentheses) will result in a tuple that
+ // satisfies the call.
+ // This isn't super ideal code, because we copy code from elsewhere
+ // and somewhat duplicate this. We also delegate to the general type
+ // mismatch suggestions for the single arg case.
+ let sugg_tuple_wrap_args =
+ self.suggested_tuple_wrap(&expected_input_tys, provided_args);
+ match sugg_tuple_wrap_args {
+ TupleMatchFound::None => {}
+ TupleMatchFound::Single => {
+ let expected_ty = expected_input_tys[0];
+ let provided_ty = final_arg_types[0].map(|ty| ty.0).unwrap();
+ let cause = &self.misc(provided_args[0].span);
+ let compatibility = demand_compatible(0, &mut final_arg_types);
+ let type_error = match compatibility {
+ Compatibility::Incompatible(Some(error)) => error,
+ _ => TypeError::Mismatch,
+ };
+ let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
+ let mut err = self.report_and_explain_type_error(trace, &type_error);
+ self.emit_coerce_suggestions(
+ &mut err,
+ &provided_args[0],
+ final_arg_types[0].map(|ty| ty.0).unwrap(),
+ final_arg_types[0].map(|ty| ty.1).unwrap(),
+ None,
+ None,
+ );
+ err.span_label(
+ full_call_span,
+ format!("arguments to this {} are incorrect", call_name),
+ );
+ // Call out where the function is defined
+ if let Some(def_id) = fn_def_id && let Some(def_span) = tcx.def_ident_span(def_id) {
+ let mut spans: MultiSpan = def_span.into();
+
+ let params = tcx
+ .hir()
+ .get_if_local(def_id)
+ .and_then(|node| node.body_id())
+ .into_iter()
+ .map(|id| tcx.hir().body(id).params)
+ .flatten();
+
+ for param in params {
+ spans.push_span_label(param.span, String::new());
+ }
+
+ let def_kind = tcx.def_kind(def_id);
+ err.span_note(spans, &format!("{} defined here", def_kind.descr(def_id)));
+ }
+ err.emit();
+ break 'errors;
+ }
+ TupleMatchFound::Multiple(start, end) => {
+ let mut err = tcx.sess.struct_span_err_with_code(
+ full_call_span,
+ &format!(
+ "this {} takes {}{} but {} {} supplied",
+ call_name,
+ if c_variadic { "at least " } else { "" },
+ potentially_plural_count(minimum_input_count, "argument"),
+ potentially_plural_count(provided_arg_count, "argument"),
+ if provided_arg_count == 1 { "was" } else { "were" }
+ ),
+ DiagnosticId::Error(err_code.to_owned()),
+ );
+ // Call out where the function is defined
+ if let Some(def_id) = fn_def_id && let Some(def_span) = tcx.def_ident_span(def_id) {
+ let mut spans: MultiSpan = def_span.into();
+
+ let params = tcx
+ .hir()
+ .get_if_local(def_id)
+ .and_then(|node| node.body_id())
+ .into_iter()
+ .map(|id| tcx.hir().body(id).params)
+ .flatten();
+
+ for param in params {
+ spans.push_span_label(param.span, String::new());
+ }
+
+ let def_kind = tcx.def_kind(def_id);
+ err.span_note(spans, &format!("{} defined here", def_kind.descr(def_id)));
+ }
+ err.multipart_suggestion(
+ "use parentheses to construct a tuple",
+ vec![(start, '('.to_string()), (end, ')'.to_string())],
+ Applicability::MachineApplicable,
+ );
+ err.emit();
+ break 'errors;
+ }
+ }
+
+ // Okay, now that we've emitted the special errors separately, we
+ // are only left missing/extra/swapped and mismatched arguments, both
+ // can be collated pretty easily if needed.
+
+ // Next special case: if there is only one "Incompatible" error, just emit that
+ if errors.len() == 1 {
+ if let Some(Error::Invalid(input_idx, Compatibility::Incompatible(Some(error)))) =
+ errors.iter().next()
+ {
+ let expected_ty = expected_input_tys[*input_idx];
+ let provided_ty = final_arg_types[*input_idx].map(|ty| ty.0).unwrap();
+ let cause = &self.misc(provided_args[*input_idx].span);
+ let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
+ let mut err = self.report_and_explain_type_error(trace, error);
+ self.emit_coerce_suggestions(
+ &mut err,
+ &provided_args[*input_idx],
+ final_arg_types[*input_idx].map(|ty| ty.0).unwrap(),
+ final_arg_types[*input_idx].map(|ty| ty.1).unwrap(),
+ None,
+ None,
+ );
+ err.span_label(
+ full_call_span,
+ format!("arguments to this {} are incorrect", call_name),
+ );
+ // Call out where the function is defined
+ if let Some(def_id) = fn_def_id && let Some(def_span) = tcx.def_ident_span(def_id) {
+ let mut spans: MultiSpan = def_span.into();
+
+ let params = tcx
+ .hir()
+ .get_if_local(def_id)
+ .and_then(|node| node.body_id())
+ .into_iter()
+ .map(|id| tcx.hir().body(id).params)
+ .flatten();
+
+ for param in params {
+ spans.push_span_label(param.span, String::new());
+ }
+
+ let def_kind = tcx.def_kind(def_id);
+ err.span_note(spans, &format!("{} defined here", def_kind.descr(def_id)));
+ }
+ err.emit();
+ break 'errors;
+ }
+ }
+
+ let mut err = if minimum_input_count == provided_arg_count {
+ struct_span_err!(
+ tcx.sess,
+ full_call_span,
+ E0308,
+ "arguments to this {} are incorrect",
call_name,
- if c_variadic { "at least " } else { "" },
- potentially_plural_count(expected_count, "argument"),
- potentially_plural_count(arg_count, "argument"),
- if arg_count == 1 { "was" } else { "were" }
- ),
- DiagnosticId::Error(err_code.to_owned()),
- );
- let label = format!("supplied {}", potentially_plural_count(arg_count, "argument"));
- for (i, span) in arg_spans.into_iter().enumerate() {
- err.span_label(
- span,
- if arg_count == 0 || i + 1 == arg_count { &label } else { "" },
- );
+ )
+ } else {
+ tcx.sess.struct_span_err_with_code(
+ full_call_span,
+ &format!(
+ "this {} takes {}{} but {} {} supplied",
+ call_name,
+ if c_variadic { "at least " } else { "" },
+ potentially_plural_count(minimum_input_count, "argument"),
+ potentially_plural_count(provided_arg_count, "argument"),
+ if provided_arg_count == 1 { "was" } else { "were" }
+ ),
+ DiagnosticId::Error(err_code.to_owned()),
+ )
+ };
+
+ // As we encounter issues, keep track of what we want to provide for the suggestion
+ let mut labels = vec![];
+ // If there is a single error, we give a specific suggestion; otherwise, we change to
+ // "did you mean" with the suggested function call
+ enum SuggestionText {
+ None,
+ Provide(bool),
+ Remove(bool),
+ Swap,
+ Reorder,
+ DidYouMean,
}
+ let mut suggestion_text = SuggestionText::None;
+
+ let mut errors = errors.into_iter().peekable();
+ while let Some(error) = errors.next() {
+ match error {
+ Error::Invalid(input_idx, compatibility) => {
+ let expected_ty = expected_input_tys[input_idx];
+ if let Compatibility::Incompatible(error) = &compatibility {
+ let provided_ty = final_arg_types[input_idx].map(|ty| ty.0).unwrap();
+ let cause = &self.misc(provided_args[input_idx].span);
+ let trace = TypeTrace::types(cause, true, expected_ty, provided_ty);
+ if let Some(e) = error {
+ self.note_type_err(
+ &mut err,
+ &trace.cause,
+ None,
+ Some(trace.values),
+ e,
+ false,
+ true,
+ );
+ }
+ }
+
+ self.emit_coerce_suggestions(
+ &mut err,
+ &provided_args[input_idx],
+ final_arg_types[input_idx].map(|ty| ty.0).unwrap(),
+ final_arg_types[input_idx].map(|ty| ty.1).unwrap(),
+ None,
+ None,
+ );
+ }
+ Error::Extra(arg_idx) => {
+ let arg_type = if let Some((_, ty)) = final_arg_types[arg_idx] {
+ if ty.references_error() || ty.has_infer_types() {
+ "".into()
+ } else {
+ format!(" of type `{}`", ty)
+ }
+ } else {
+ "".into()
+ };
+ labels.push((
+ provided_args[arg_idx].span,
+ format!("argument{} unexpected", arg_type),
+ ));
+ suggestion_text = match suggestion_text {
+ SuggestionText::None => SuggestionText::Remove(false),
+ SuggestionText::Remove(_) => SuggestionText::Remove(true),
+ _ => SuggestionText::DidYouMean,
+ };
+ }
+ Error::Missing(input_idx) => {
+ // If there are multiple missing arguments adjacent to each other,
+ // then we can provide a single error.
+
+ let mut missing_idxs = vec![input_idx];
+ while let Some(e) = errors.next_if(|e| matches!(e, Error::Missing(input_idx) if *input_idx == (missing_idxs.last().unwrap() + 1))) {
+ match e {
+ Error::Missing(input_idx) => missing_idxs.push(input_idx),
+ _ => unreachable!(),
+ }
+ }
+
+ // NOTE: Because we might be re-arranging arguments, might have extra
+ // arguments, etc. it's hard to *really* know where we should provide
+ // this error label, so as a heuristic, we point to the provided arg, or
+ // to the call if the missing inputs pass the provided args.
+ match &missing_idxs[..] {
+ &[input_idx] => {
+ let expected_ty = expected_input_tys[input_idx];
+ let input_ty = self.resolve_vars_if_possible(expected_ty);
+ let span = if input_idx < provided_arg_count {
+ let arg_span = provided_args[input_idx].span;
+ Span::new(arg_span.lo(), arg_span.hi(), arg_span.ctxt(), None)
+ } else {
+ args_span
+ };
+ let arg_type =
+ if input_ty.references_error() || input_ty.has_infer_types() {
+ "".into()
+ } else {
+ format!(" of type `{}`", input_ty)
+ };
+ labels.push((span, format!("an argument{} is missing", arg_type)));
+ suggestion_text = match suggestion_text {
+ SuggestionText::None => SuggestionText::Provide(false),
+ SuggestionText::Provide(_) => SuggestionText::Provide(true),
+ _ => SuggestionText::DidYouMean,
+ };
+ }
+ &[first_idx, second_idx] => {
+ let first_input_ty =
+ self.resolve_vars_if_possible(expected_input_tys[first_idx]);
+ let second_input_ty =
+ self.resolve_vars_if_possible(expected_input_tys[second_idx]);
+
+ let span = if second_idx < provided_arg_count {
+ let first_arg_span = provided_args[first_idx].span;
+ let second_arg_span = provided_args[second_idx].span;
+ Span::new(
+ first_arg_span.lo(),
+ second_arg_span.hi(),
+ first_arg_span.ctxt(),
+ None,
+ )
+ } else {
+ args_span
+ };
+ let any_unnameable = false
+ || first_input_ty.references_error()
+ || first_input_ty.has_infer_types()
+ || second_input_ty.references_error()
+ || second_input_ty.has_infer_types();
+ let arg_type = if any_unnameable {
+ "".into()
+ } else {
+ format!(
+ " of type `{}` and `{}`",
+ first_input_ty, second_input_ty
+ )
+ };
+ labels
+ .push((span, format!("two arguments{} are missing", arg_type)));
+ suggestion_text = match suggestion_text {
+ SuggestionText::None | SuggestionText::Provide(_) => {
+ SuggestionText::Provide(true)
+ }
+ _ => SuggestionText::DidYouMean,
+ };
+ }
+ &[first_idx, second_idx, third_idx] => {
+ let first_input_ty =
+ self.resolve_vars_if_possible(expected_input_tys[first_idx]);
+ let second_input_ty =
+ self.resolve_vars_if_possible(expected_input_tys[second_idx]);
+ let third_input_ty =
+ self.resolve_vars_if_possible(expected_input_tys[second_idx]);
+ let span = if third_idx < provided_arg_count {
+ let first_arg_span = provided_args[first_idx].span;
+ let third_arg_span = provided_args[third_idx].span;
+ Span::new(
+ first_arg_span.lo(),
+ third_arg_span.hi(),
+ first_arg_span.ctxt(),
+ None,
+ )
+ } else {
+ args_span
+ };
+ let any_unnameable = false
+ || first_input_ty.references_error()
+ || first_input_ty.has_infer_types()
+ || second_input_ty.references_error()
+ || second_input_ty.has_infer_types()
+ || third_input_ty.references_error()
+ || third_input_ty.has_infer_types();
+ let arg_type = if any_unnameable {
+ "".into()
+ } else {
+ format!(
+ " of type `{}`, `{}`, and `{}`",
+ first_input_ty, second_input_ty, third_input_ty
+ )
+ };
+ labels.push((
+ span,
+ format!("three arguments{} are missing", arg_type),
+ ));
+ suggestion_text = match suggestion_text {
+ SuggestionText::None | SuggestionText::Provide(_) => {
+ SuggestionText::Provide(true)
+ }
+ _ => SuggestionText::DidYouMean,
+ };
+ }
+ missing_idxs => {
+ let first_idx = *missing_idxs.first().unwrap();
+ let second_idx = *missing_idxs.last().unwrap();
+ // NOTE: Because we might be re-arranging arguments, might have extra arguments, etc.
+ // It's hard to *really* know where we should provide this error label, so this is a
+ // decent heuristic
+ let span = if first_idx < provided_arg_count {
+ let first_arg_span = provided_args[first_idx].span;
+ let second_arg_span = provided_args[second_idx].span;
+ Span::new(
+ first_arg_span.lo(),
+ second_arg_span.hi(),
+ first_arg_span.ctxt(),
+ None,
+ )
+ } else {
+ // Otherwise just label the whole function
+ args_span
+ };
+ labels.push((span, format!("multiple arguments are missing")));
+ suggestion_text = match suggestion_text {
+ SuggestionText::None | SuggestionText::Provide(_) => {
+ SuggestionText::Provide(true)
+ }
+ _ => SuggestionText::DidYouMean,
+ };
+ }
+ }
+ }
+ Error::Swap(input_idx, other_input_idx, arg_idx, other_arg_idx) => {
+ let first_span = provided_args[arg_idx].span;
+ let second_span = provided_args[other_arg_idx].span;
+
+ let first_expected_ty =
+ self.resolve_vars_if_possible(expected_input_tys[input_idx]);
+ let first_provided_ty = if let Some((ty, _)) = final_arg_types[arg_idx] {
+ format!(",found `{}`", ty)
+ } else {
+ "".into()
+ };
+ labels.push((
+ first_span,
+ format!("expected `{}`{}", first_expected_ty, first_provided_ty),
+ ));
+ let other_expected_ty =
+ self.resolve_vars_if_possible(expected_input_tys[other_input_idx]);
+ let other_provided_ty =
+ if let Some((ty, _)) = final_arg_types[other_arg_idx] {
+ format!(",found `{}`", ty)
+ } else {
+ "".into()
+ };
+ labels.push((
+ second_span,
+ format!("expected `{}`{}", other_expected_ty, other_provided_ty),
+ ));
+ suggestion_text = match suggestion_text {
+ SuggestionText::None => SuggestionText::Swap,
+ _ => SuggestionText::DidYouMean,
+ };
+ }
+ Error::Permutation(args) => {
+ for (dst_arg, dest_input) in args {
+ let expected_ty =
+ self.resolve_vars_if_possible(expected_input_tys[dest_input]);
+ let provided_ty = if let Some((ty, _)) = final_arg_types[dst_arg] {
+ format!(",found `{}`", ty)
+ } else {
+ "".into()
+ };
+ labels.push((
+ provided_args[dst_arg].span,
+ format!("expected `{}`{}", expected_ty, provided_ty),
+ ));
+ }
+
+ suggestion_text = match suggestion_text {
+ SuggestionText::None => SuggestionText::Reorder,
+ _ => SuggestionText::DidYouMean,
+ };
+ }
+ }
+ }
+
+ // If we have less than 5 things to say, it would be useful to call out exactly what's wrong
+ if labels.len() <= 5 {
+ for (span, label) in labels {
+ err.span_label(span, label);
+ }
+ }
+
+ // Call out where the function is defined
if let Some(def_id) = fn_def_id && let Some(def_span) = tcx.def_ident_span(def_id) {
let mut spans: MultiSpan = def_span.into();
let def_kind = tcx.def_kind(def_id);
err.span_note(spans, &format!("{} defined here", def_kind.descr(def_id)));
}
- if sugg_unit {
- let sugg_span = tcx.sess.source_map().end_point(call_expr.span);
- // remove closing `)` from the span
- let sugg_span = sugg_span.shrink_to_lo();
- err.span_suggestion(
- sugg_span,
- "expected the unit value `()`; create it with empty parentheses",
- String::from("()"),
- Applicability::MachineApplicable,
- );
- } else if let Some(FnArgsAsTuple { first, last }) = sugg_tuple_wrap_args {
- err.multipart_suggestion(
- "use parentheses to construct a tuple",
- vec![
- (first.span.shrink_to_lo(), '('.to_string()),
- (last.span.shrink_to_hi(), ')'.to_string()),
- ],
- Applicability::MachineApplicable,
+
+ // And add a suggestion block for all of the parameters
+ let suggestion_text = match suggestion_text {
+ SuggestionText::None => None,
+ SuggestionText::Provide(plural) => {
+ Some(format!("provide the argument{}", if plural { "s" } else { "" }))
+ }
+ SuggestionText::Remove(plural) => {
+ Some(format!("remove the extra argument{}", if plural { "s" } else { "" }))
+ }
+ SuggestionText::Swap => Some("swap these arguments".to_string()),
+ SuggestionText::Reorder => Some("reorder these arguments".to_string()),
+ SuggestionText::DidYouMean => Some("did you mean".to_string()),
+ };
+ if let Some(suggestion_text) = suggestion_text {
+ let source_map = self.sess().source_map();
+ let mut suggestion = format!(
+ "{}(",
+ source_map.span_to_snippet(full_call_span).unwrap_or_else(|_| String::new())
);
- } else {
- err.span_label(
- span,
- format!(
- "expected {}{}",
- if c_variadic { "at least " } else { "" },
- potentially_plural_count(expected_count, "argument")
- ),
+ for (idx, arg) in matched_inputs.iter().enumerate() {
+ let suggestion_text = if let Some(arg) = arg {
+ let arg_span = provided_args[*arg].span;
+ let arg_text = source_map.span_to_snippet(arg_span).unwrap();
+ arg_text
+ } else {
+ // Propose a placeholder of the correct type
+ let expected_ty = expected_input_tys[idx];
+ let input_ty = self.resolve_vars_if_possible(expected_ty);
+ if input_ty.is_unit() {
+ "()".to_string()
+ } else {
+ format!("{{{}}}", input_ty)
+ }
+ };
+ suggestion += &suggestion_text;
+ if idx < minimum_input_count - 1 {
+ suggestion += ", ";
+ }
+ }
+ suggestion += ")";
+ err.span_suggestion_verbose(
+ error_span,
+ &suggestion_text,
+ suggestion,
+ Applicability::HasPlaceholders,
);
}
err.emit();
for arg in provided_args.iter().skip(minimum_input_count) {
let arg_ty = self.check_expr(&arg);
+ // If the function is c-style variadic, we skipped a bunch of arguments
+ // so we need to check those, and write out the types
+ // Ideally this would be folded into the above, for uniform style
+ // but c-variadic is already a corner case
if c_variadic {
- // We also need to make sure we at least write the ty of the other
- // arguments which we skipped above, either because they were additional
- // c_variadic args, or because we had an argument count mismatch.
fn variadic_error<'tcx>(sess: &Session, span: Span, ty: Ty<'tcx>, cast_ty: &str) {
use crate::structured_errors::MissingCastForVariadicArg;
&self,
expected_input_tys: &[Ty<'tcx>],
provided_args: &'tcx [hir::Expr<'tcx>],
- ) -> Option<FnArgsAsTuple<'_>> {
- let [expected_arg_type] = expected_input_tys[..] else { return None };
-
+ ) -> TupleMatchFound {
+ // Only handle the case where we expect only one tuple arg
+ let [expected_arg_type] = expected_input_tys[..] else { return TupleMatchFound::None };
let &ty::Tuple(expected_types) = self.resolve_vars_if_possible(expected_arg_type).kind()
- else { return None };
+ else { return TupleMatchFound::None };
+
+ // First check that there are the same number of types.
+ if expected_types.len() != provided_args.len() {
+ return TupleMatchFound::None;
+ }
let supplied_types: Vec<_> = provided_args.iter().map(|arg| self.check_expr(arg)).collect();
let all_match = iter::zip(expected_types, supplied_types)
.all(|(expected, supplied)| self.can_eq(self.param_env, expected, supplied).is_ok());
- if all_match {
- match provided_args {
- [] => None,
- [_] => unreachable!(
- "shouldn't reach here - need count mismatch between 1-tuple and 1-argument"
- ),
- [first, .., last] => Some(FnArgsAsTuple { first, last }),
+ if !all_match {
+ return TupleMatchFound::None;
+ }
+ match provided_args {
+ [] => TupleMatchFound::None,
+ [_] => TupleMatchFound::Single,
+ [first, .., last] => {
+ TupleMatchFound::Multiple(first.span.shrink_to_lo(), last.span.shrink_to_hi())
}
- } else {
- None
}
}
/// A common error is to add an extra semicolon:
///
- /// ```
+ /// ```compile_fail,E0308
/// fn foo() -> usize {
/// 22;
/// }