X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=compiler%2Frustc_typeck%2Fsrc%2Fcheck%2Ffn_ctxt%2Fchecks.rs;h=d7a817f67e0cd1f94791d5f8dbb82c9c910bbd6d;hb=7907385999b4a83d37ed31d334f3ed9ca02983a1;hp=9c702610888807edf93db00027866e1abda7f622;hpb=080d5452e1bb6e18e12a073d4d0283fd9b6dac0b;p=rust.git diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs index 9c702610888..d7a817f67e0 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs @@ -1,5 +1,6 @@ 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::*; @@ -8,6 +9,7 @@ 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; @@ -16,7 +18,11 @@ 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; @@ -24,15 +30,15 @@ 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(); @@ -67,7 +73,7 @@ pub(in super::super) fn check_method_argument_types( sp, expr, &err_inputs, - vec![], + None, args_no_rcvr, false, tuple_arguments, @@ -108,7 +114,7 @@ pub(in super::super) fn check_argument_types( // 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>, + expected_input_tys: Option>>, // The expressions for each provided argument provided_args: &'tcx [hir::Expr<'tcx>], // Whether the function is variadic, for example when imported from C @@ -119,10 +125,18 @@ pub(in super::super) fn check_argument_types( fn_def_id: Option, ) { 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. @@ -130,11 +144,7 @@ pub(in super::super) fn check_argument_types( 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>)> = - 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 { @@ -144,15 +154,17 @@ pub(in super::super) fn check_argument_types( 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) } @@ -167,67 +179,25 @@ pub(in super::super) fn check_argument_types( 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::>() - ); - - // 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, Ty<'_>)>> = vec![None; provided_arg_count]; // We introduce a helper function to demand that a given argument satisfy a given input @@ -240,8 +210,9 @@ pub(in super::super) fn check_argument_types( 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); @@ -255,8 +226,7 @@ pub(in super::super) fn check_argument_types( 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| { @@ -270,18 +240,95 @@ pub(in super::super) fn check_argument_types( ); }); + // 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 @@ -305,6 +352,8 @@ pub(in super::super) fn check_argument_types( }) } + // 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 @@ -327,15 +376,82 @@ pub(in super::super) fn check_argument_types( 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, @@ -346,67 +462,484 @@ pub(in super::super) fn check_argument_types( )), .. }, - 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::()`. - .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::>() - }; + 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(); @@ -425,33 +958,52 @@ pub(in super::super) fn check_argument_types( 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(); @@ -460,10 +1012,11 @@ pub(in super::super) fn check_argument_types( 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; @@ -498,27 +1051,31 @@ fn suggested_tuple_wrap( &self, expected_input_tys: &[Ty<'tcx>], provided_args: &'tcx [hir::Expr<'tcx>], - ) -> Option> { - 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 } } @@ -910,7 +1467,7 @@ pub(in super::super) fn check_block_with_expected( /// A common error is to add an extra semicolon: /// - /// ``` + /// ```compile_fail,E0308 /// fn foo() -> usize { /// 22; /// }