use crate::check::report_unexpected_variant_res;
use crate::check::BreakableCtxt;
use crate::check::Diverges;
+use crate::check::DynamicCoerceMany;
use crate::check::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation};
use crate::check::FnCtxt;
use crate::check::Needs;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_middle::ty;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
+use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::Ty;
use rustc_middle::ty::TypeFoldable;
use rustc_middle::ty::{AdtKind, Visibility};
+use rustc_span::edition::LATEST_STABLE_EDITION;
use rustc_span::hygiene::DesugaringKind;
use rustc_span::lev_distance::find_best_match_for_name;
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_trait_selection::traits::{self, ObligationCauseCode};
-use std::fmt::Display;
-
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn check_expr_eq_type(&self, expr: &'tcx hir::Expr<'tcx>, expected: Ty<'tcx>) {
let ty = self.check_expr_with_hint(expr, expected);
// Warn for non-block expressions with diverging children.
match expr.kind {
- ExprKind::Block(..) | ExprKind::Loop(..) | ExprKind::Match(..) => {}
+ ExprKind::Block(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) => {}
// If `expr` is a result of desugaring the try block and is an ok-wrapped
// diverging expression (e.g. it arose from desugaring of `try { return }`),
// we skip issuing a warning because it is autogenerated code.
}
}
ExprKind::Ret(ref expr_opt) => self.check_expr_return(expr_opt.as_deref(), expr),
- ExprKind::Loop(ref body, _, source) => {
+ ExprKind::Loop(ref body, _, source, _) => {
self.check_expr_loop(body, source, expected, expr)
}
ExprKind::Match(ref discrim, ref arms, match_src) => {
self.check_expr_eq_type(&e, ty);
ty
}
+ ExprKind::If(ref cond, ref then_expr, ref opt_else_expr) => self.check_then_else(
+ &cond,
+ then_expr,
+ opt_else_expr.as_ref().map(|e| &**e),
+ expr.span,
+ expected,
+ ),
ExprKind::DropTemps(ref e) => self.check_expr_with_expectation(e, expected),
ExprKind::Array(ref args) => self.check_expr_array(args, expected, expr),
ExprKind::ConstBlock(ref anon_const) => self.to_const(anon_const).ty,
if self.ret_coercion.is_none() {
self.tcx.sess.emit_err(ReturnStmtOutsideOfFnBody { span: expr.span });
} else if let Some(ref e) = expr_opt {
- if self.ret_coercion_span.borrow().is_none() {
- *self.ret_coercion_span.borrow_mut() = Some(e.span);
+ if self.ret_coercion_span.get().is_none() {
+ self.ret_coercion_span.set(Some(e.span));
}
self.check_return_expr(e);
} else {
let mut coercion = self.ret_coercion.as_ref().unwrap().borrow_mut();
- if self.ret_coercion_span.borrow().is_none() {
- *self.ret_coercion_span.borrow_mut() = Some(expr.span);
+ if self.ret_coercion_span.get().is_none() {
+ self.ret_coercion_span.set(Some(expr.span));
}
let cause = self.cause(expr.span, ObligationCauseCode::ReturnNoExpression);
if let Some((fn_decl, _)) = self.get_fn_decl(expr.hir_id) {
err.emit();
}
+ // A generic function for checking the 'then' and 'else' clauses in an 'if'
+ // or 'if-else' expression.
+ fn check_then_else(
+ &self,
+ cond_expr: &'tcx hir::Expr<'tcx>,
+ then_expr: &'tcx hir::Expr<'tcx>,
+ opt_else_expr: Option<&'tcx hir::Expr<'tcx>>,
+ sp: Span,
+ orig_expected: Expectation<'tcx>,
+ ) -> Ty<'tcx> {
+ let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {});
+
+ self.warn_if_unreachable(cond_expr.hir_id, then_expr.span, "block in `if` expression");
+
+ let cond_diverges = self.diverges.get();
+ self.diverges.set(Diverges::Maybe);
+
+ let expected = orig_expected.adjust_for_branches(self);
+ let then_ty = self.check_expr_with_expectation(then_expr, expected);
+ let then_diverges = self.diverges.get();
+ self.diverges.set(Diverges::Maybe);
+
+ // We've already taken the expected type's preferences
+ // into account when typing the `then` branch. To figure
+ // out the initial shot at a LUB, we thus only consider
+ // `expected` if it represents a *hard* constraint
+ // (`only_has_type`); otherwise, we just go with a
+ // fresh type variable.
+ let coerce_to_ty = expected.coercion_target_type(self, sp);
+ let mut coerce: DynamicCoerceMany<'_> = CoerceMany::new(coerce_to_ty);
+
+ coerce.coerce(self, &self.misc(sp), then_expr, then_ty);
+
+ if let Some(else_expr) = opt_else_expr {
+ let else_ty = self.check_expr_with_expectation(else_expr, expected);
+ let else_diverges = self.diverges.get();
+
+ let opt_suggest_box_span =
+ self.opt_suggest_box_span(else_expr.span, else_ty, orig_expected);
+ let if_cause =
+ self.if_cause(sp, then_expr, else_expr, then_ty, else_ty, opt_suggest_box_span);
+
+ coerce.coerce(self, &if_cause, else_expr, else_ty);
+
+ // We won't diverge unless both branches do (or the condition does).
+ self.diverges.set(cond_diverges | then_diverges & else_diverges);
+ } else {
+ self.if_fallback_coercion(sp, then_expr, &mut coerce, |hir_id, span| {
+ self.maybe_get_coercion_reason_if(hir_id, span)
+ });
+
+ // If the condition is false we can't diverge.
+ self.diverges.set(cond_diverges);
+ }
+
+ let result_ty = coerce.complete(self);
+ if cond_ty.references_error() { self.tcx.ty_error() } else { result_ty }
+ }
+
/// Type check assignment expression `expr` of form `lhs = rhs`.
- /// The expected type is `()` and is passsed to the function for the purposes of diagnostics.
+ /// The expected type is `()` and is passed to the function for the purposes of diagnostics.
fn check_expr_assign(
&self,
expr: &'tcx hir::Expr<'tcx>,
};
if !lhs.is_syntactic_place_expr() {
// Do not suggest `if let x = y` as `==` is way more likely to be the intention.
- if let hir::Node::Expr(hir::Expr {
- kind:
- ExprKind::Match(
- _,
- _,
- hir::MatchSource::IfDesugar { .. } | hir::MatchSource::WhileDesugar,
- ),
- ..
- }) = self.tcx.hir().get(
- self.tcx.hir().get_parent_node(self.tcx.hir().get_parent_node(expr.hir_id)),
- ) {
+ let mut span_err = || {
// Likely `if let` intended.
err.span_suggestion_verbose(
expr.span.shrink_to_lo(),
"let ".to_string(),
applicability,
);
+ };
+ if let hir::Node::Expr(hir::Expr {
+ kind: ExprKind::Match(_, _, hir::MatchSource::WhileDesugar),
+ ..
+ }) = self.tcx.hir().get(
+ self.tcx.hir().get_parent_node(self.tcx.hir().get_parent_node(expr.hir_id)),
+ ) {
+ span_err();
+ } else if let hir::Node::Expr(hir::Expr { kind: ExprKind::If { .. }, .. }) =
+ self.tcx.hir().get(self.tcx.hir().get_parent_node(expr.hir_id))
+ {
+ span_err();
}
}
if eq {
Ok(method)
}
Err(error) => {
- if segment.ident.name != kw::Invalid {
+ if segment.ident.name != kw::Empty {
self.report_extended_method_error(segment, span, args, rcvr_t, error);
}
Err(())
fields,
base_expr.is_none(),
);
- if let &Some(ref base_expr) = base_expr {
+ if let Some(base_expr) = base_expr {
// If check_expr_struct_fields hit an error, do not attempt to populate
// the fields with the base_expr. This could cause us to hit errors later
// when certain fields are assumed to exist that in fact do not.
// re-link the regions that EIfEO can erase.
self.demand_eqtype(span, adt_ty_hint, adt_ty);
- let (substs, adt_kind, kind_name) = match &adt_ty.kind() {
- &ty::Adt(adt, substs) => (substs, adt.adt_kind(), adt.variant_descr()),
+ let (substs, adt_kind, kind_name) = match adt_ty.kind() {
+ ty::Adt(adt, substs) => (substs, adt.adt_kind(), adt.variant_descr()),
_ => span_bug!(span, "non-ADT passed to check_expr_struct_fields"),
};
ty,
);
match variant.ctor_kind {
- CtorKind::Fn => {
- err.span_label(variant.ident.span, format!("`{adt}` defined here", adt = ty));
- err.span_label(field.ident.span, "field does not exist");
- err.span_label(
- ty_span,
- format!(
- "`{adt}` is a tuple {kind_name}, \
- use the appropriate syntax: `{adt}(/* fields */)`",
- adt = ty,
- kind_name = kind_name
- ),
- );
- }
+ CtorKind::Fn => match ty.kind() {
+ ty::Adt(adt, ..) if adt.is_enum() => {
+ err.span_label(
+ variant.ident.span,
+ format!(
+ "`{adt}::{variant}` defined here",
+ adt = ty,
+ variant = variant.ident,
+ ),
+ );
+ err.span_label(field.ident.span, "field does not exist");
+ err.span_label(
+ ty_span,
+ format!(
+ "`{adt}::{variant}` is a tuple {kind_name}, \
+ use the appropriate syntax: `{adt}::{variant}(/* fields */)`",
+ adt = ty,
+ variant = variant.ident,
+ kind_name = kind_name
+ ),
+ );
+ }
+ _ => {
+ err.span_label(variant.ident.span, format!("`{adt}` defined here", adt = ty));
+ err.span_label(field.ident.span, "field does not exist");
+ err.span_label(
+ ty_span,
+ format!(
+ "`{adt}` is a tuple {kind_name}, \
+ use the appropriate syntax: `{adt}(/* fields */)`",
+ adt = ty,
+ kind_name = kind_name
+ ),
+ );
+ }
+ },
_ => {
// prevent all specified fields from being suggested
let skip_fields = skip_fields.iter().map(|ref x| x.ident.name);
base: &'tcx hir::Expr<'tcx>,
field: Ident,
) -> Ty<'tcx> {
+ debug!("check_field(expr: {:?}, base: {:?}, field: {:?})", expr, base, field);
let expr_t = self.check_expr(base);
let expr_t = self.structurally_resolved_type(base.span, expr_t);
let mut private_candidate = None;
let mut autoderef = self.autoderef(expr.span, expr_t);
while let Some((base_t, _)) = autoderef.next() {
+ debug!("base_t: {:?}", base_t);
match base_t.kind() {
ty::Adt(base_def, substs) if !base_def.is_enum() => {
debug!("struct named {:?}", base_t);
return field_ty;
}
- if field.name == kw::Invalid {
+ if field.name == kw::Empty {
} else if self.method_exists(field, expr_t, expr.hir_id, true) {
self.ban_take_value_of_method(expr, expr_t, field);
} else if !expr_t.is_primitive_ty() {
"ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, expr_ty={:?}",
field, base, expr, expr_t
);
- let mut err = self.no_such_field_err(field.span, field, expr_t);
+ let mut err = self.no_such_field_err(field, expr_t);
match *expr_t.peel_refs().kind() {
ty::Array(_, len) => {
if field.name == kw::Await {
// We know by construction that `<expr>.await` is either on Rust 2015
// or results in `ExprKind::Await`. Suggest switching the edition to 2018.
- err.note("to `.await` a `Future`, switch to Rust 2018");
- err.help("set `edition = \"2018\"` in `Cargo.toml`");
+ err.note("to `.await` a `Future`, switch to Rust 2018 or later");
+ err.help(&format!("set `edition = \"{}\"` in `Cargo.toml`", LATEST_STABLE_EDITION));
err.note("for more on editions, read https://doc.rust-lang.org/edition-guide");
}
}
}
- fn no_such_field_err<T: Display>(
+ fn no_such_field_err(
&self,
- span: Span,
- field: T,
- expr_t: &ty::TyS<'_>,
+ field: Ident,
+ expr_t: &'tcx ty::TyS<'tcx>,
) -> DiagnosticBuilder<'_> {
- type_error_struct!(
+ let span = field.span;
+ debug!("no_such_field_err(span: {:?}, field: {:?}, expr_t: {:?})", span, field, expr_t);
+
+ let mut err = type_error_struct!(
self.tcx().sess,
- span,
+ field.span,
expr_t,
E0609,
"no field `{}` on type `{}`",
field,
expr_t
- )
+ );
+
+ // try to add a suggestion in case the field is a nested field of a field of the Adt
+ if let Some((fields, substs)) = self.get_field_candidates(span, &expr_t) {
+ for candidate_field in fields.iter() {
+ if let Some(field_path) =
+ self.check_for_nested_field(span, field, candidate_field, substs, vec![])
+ {
+ let field_path_str = field_path
+ .iter()
+ .map(|id| id.name.to_ident_string())
+ .collect::<Vec<String>>()
+ .join(".");
+ debug!("field_path_str: {:?}", field_path_str);
+
+ err.span_suggestion_verbose(
+ field.span.shrink_to_lo(),
+ "one of the expressions' fields has a field of the same name",
+ format!("{}.", field_path_str),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+ err
+ }
+
+ fn get_field_candidates(
+ &self,
+ span: Span,
+ base_t: Ty<'tcx>,
+ ) -> Option<(&Vec<ty::FieldDef>, SubstsRef<'tcx>)> {
+ debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_t);
+
+ let mut autoderef = self.autoderef(span, base_t);
+ while let Some((base_t, _)) = autoderef.next() {
+ match base_t.kind() {
+ ty::Adt(base_def, substs) if !base_def.is_enum() => {
+ let fields = &base_def.non_enum_variant().fields;
+ // For compile-time reasons put a limit on number of fields we search
+ if fields.len() > 100 {
+ return None;
+ }
+ return Some((fields, substs));
+ }
+ _ => {}
+ }
+ }
+ None
+ }
+
+ /// This method is called after we have encountered a missing field error to recursively
+ /// search for the field
+ fn check_for_nested_field(
+ &self,
+ span: Span,
+ target_field: Ident,
+ candidate_field: &ty::FieldDef,
+ subst: SubstsRef<'tcx>,
+ mut field_path: Vec<Ident>,
+ ) -> Option<Vec<Ident>> {
+ debug!(
+ "check_for_nested_field(span: {:?}, candidate_field: {:?}, field_path: {:?}",
+ span, candidate_field, field_path
+ );
+
+ if candidate_field.ident == target_field {
+ Some(field_path)
+ } else if field_path.len() > 3 {
+ // For compile-time reasons and to avoid infinite recursion we only check for fields
+ // up to a depth of three
+ None
+ } else {
+ // recursively search fields of `candidate_field` if it's a ty::Adt
+
+ field_path.push(candidate_field.ident.normalize_to_macros_2_0());
+ let field_ty = candidate_field.ty(self.tcx, subst);
+ if let Some((nested_fields, subst)) = self.get_field_candidates(span, &field_ty) {
+ for field in nested_fields.iter() {
+ let ident = field.ident.normalize_to_macros_2_0();
+ if ident == target_field {
+ return Some(field_path);
+ } else {
+ let field_path = field_path.clone();
+ if let Some(path) = self.check_for_nested_field(
+ span,
+ target_field,
+ field,
+ subst,
+ field_path,
+ ) {
+ return Some(path);
+ }
+ }
+ }
+ }
+ None
+ }
}
fn check_expr_index(