blob: 7b552bb7077432ae6152cb12946bd56ef71a2d08 [file] [log] [blame]
//! Type checking expressions.
//!
//! See [`rustc_hir_analysis::check`] for more context on type checking in general.
use crate::cast;
use crate::coercion::CoerceMany;
use crate::coercion::DynamicCoerceMany;
use crate::errors::ReturnLikeStatementKind;
use crate::errors::TypeMismatchFruTypo;
use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive};
use crate::errors::{
FieldMultiplySpecifiedInInitializer, FunctionalRecordUpdateOnNonStruct, HelpUseLatestEdition,
YieldExprOutsideOfCoroutine,
};
use crate::fatally_break_rust;
use crate::method::SelfSource;
use crate::type_error_struct;
use crate::CoroutineTypes;
use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation};
use crate::{
report_unexpected_variant_res, BreakableCtxt, Diverges, FnCtxt, Needs,
TupleArgumentsFlag::DontTupleArguments,
};
use rustc_ast as ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_data_structures::unord::UnordMap;
use rustc_errors::{
codes::*, pluralize, struct_span_code_err, Applicability, Diag, ErrorGuaranteed, StashKey,
Subdiagnostic,
};
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::Visitor;
use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, HirId, QPath};
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer as _;
use rustc_infer::infer;
use rustc_infer::infer::type_variable::TypeVariableOrigin;
use rustc_infer::infer::DefineOpaqueTypes;
use rustc_infer::infer::InferOk;
use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::ObligationCause;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
use rustc_middle::ty::error::{ExpectedFound, TypeError::Sorts};
use rustc_middle::ty::GenericArgsRef;
use rustc_middle::ty::{self, AdtKind, Ty, TypeVisitableExt};
use rustc_session::errors::ExprParenthesesNeeded;
use rustc_session::parse::feature_err;
use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::hygiene::DesugaringKind;
use rustc_span::source_map::Spanned;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::Span;
use rustc_target::abi::{FieldIdx, FIRST_VARIANT};
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt as _;
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
use rustc_trait_selection::traits::ObligationCtxt;
use rustc_trait_selection::traits::{self, ObligationCauseCode};
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub fn check_expr_has_type_or_error(
&self,
expr: &'tcx hir::Expr<'tcx>,
expected_ty: Ty<'tcx>,
extend_err: impl FnOnce(&mut Diag<'_>),
) -> Ty<'tcx> {
let mut ty = self.check_expr_with_expectation(expr, ExpectHasType(expected_ty));
// While we don't allow *arbitrary* coercions here, we *do* allow
// coercions from ! to `expected`.
if ty.is_never() {
if let Some(_) = self.typeck_results.borrow().adjustments().get(expr.hir_id) {
let reported = self.dcx().span_delayed_bug(
expr.span,
"expression with never type wound up being adjusted",
);
return Ty::new_error(self.tcx(), reported);
}
let adj_ty =
self.next_ty_var(TypeVariableOrigin { param_def_id: None, span: expr.span });
self.apply_adjustments(
expr,
vec![Adjustment { kind: Adjust::NeverToAny, target: adj_ty }],
);
ty = adj_ty;
}
if let Some(mut err) = self.demand_suptype_diag(expr.span, expected_ty, ty) {
let _ = self.emit_type_mismatch_suggestions(
&mut err,
expr.peel_drop_temps(),
ty,
expected_ty,
None,
None,
);
extend_err(&mut err);
err.emit();
}
ty
}
pub(super) fn check_expr_coercible_to_type(
&self,
expr: &'tcx hir::Expr<'tcx>,
expected: Ty<'tcx>,
expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
) -> Ty<'tcx> {
let ty = self.check_expr_with_hint(expr, expected);
// checks don't need two phase
self.demand_coerce(expr, ty, expected, expected_ty_expr, AllowTwoPhase::No)
}
pub(super) fn check_expr_with_hint(
&self,
expr: &'tcx hir::Expr<'tcx>,
expected: Ty<'tcx>,
) -> Ty<'tcx> {
self.check_expr_with_expectation(expr, ExpectHasType(expected))
}
fn check_expr_with_expectation_and_needs(
&self,
expr: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
needs: Needs,
) -> Ty<'tcx> {
let ty = self.check_expr_with_expectation(expr, expected);
// If the expression is used in a place whether mutable place is required
// e.g. LHS of assignment, perform the conversion.
if let Needs::MutPlace = needs {
self.convert_place_derefs_to_mutable(expr);
}
ty
}
pub(super) fn check_expr(&self, expr: &'tcx hir::Expr<'tcx>) -> Ty<'tcx> {
self.check_expr_with_expectation(expr, NoExpectation)
}
pub(super) fn check_expr_with_needs(
&self,
expr: &'tcx hir::Expr<'tcx>,
needs: Needs,
) -> Ty<'tcx> {
self.check_expr_with_expectation_and_needs(expr, NoExpectation, needs)
}
/// Invariant:
/// If an expression has any sub-expressions that result in a type error,
/// inspecting that expression's type with `ty.references_error()` will return
/// true. Likewise, if an expression is known to diverge, inspecting its
/// type with `ty::type_is_bot` will return true (n.b.: since Rust is
/// strict, _|_ can appear in the type of an expression that does not,
/// itself, diverge: for example, fn() -> _|_.)
/// Note that inspecting a type's structure *directly* may expose the fact
/// that there are actually multiple representations for `Error`, so avoid
/// that when err needs to be handled differently.
#[instrument(skip(self, expr), level = "debug")]
pub(super) fn check_expr_with_expectation(
&self,
expr: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
self.check_expr_with_expectation_and_args(expr, expected, &[], None)
}
/// Same as `check_expr_with_expectation`, but allows us to pass in the arguments of a
/// `ExprKind::Call` when evaluating its callee when it is an `ExprKind::Path`.
pub(super) fn check_expr_with_expectation_and_args(
&self,
expr: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
args: &'tcx [hir::Expr<'tcx>],
call: Option<&'tcx hir::Expr<'tcx>>,
) -> Ty<'tcx> {
if self.tcx().sess.verbose_internals() {
// make this code only run with -Zverbose-internals because it is probably slow
if let Ok(lint_str) = self.tcx.sess.source_map().span_to_snippet(expr.span) {
if !lint_str.contains('\n') {
debug!("expr text: {lint_str}");
} else {
let mut lines = lint_str.lines();
if let Some(line0) = lines.next() {
let remaining_lines = lines.count();
debug!("expr text: {line0}");
debug!("expr text: ...(and {remaining_lines} more lines)");
}
}
}
}
// True if `expr` is a `Try::from_ok(())` that is a result of desugaring a try block
// without the final expr (e.g. `try { return; }`). We don't want to generate an
// unreachable_code lint for it since warnings for autogenerated code are confusing.
let is_try_block_generated_unit_expr = match expr.kind {
ExprKind::Call(_, [arg]) => {
expr.span.is_desugaring(DesugaringKind::TryBlock)
&& arg.span.is_desugaring(DesugaringKind::TryBlock)
}
_ => false,
};
// Warn for expressions after diverging siblings.
if !is_try_block_generated_unit_expr {
self.warn_if_unreachable(expr.hir_id, expr.span, "expression");
}
// Whether a past expression diverges doesn't affect typechecking of this expression, so we
// reset `diverges` while checking `expr`.
let old_diverges = self.diverges.replace(Diverges::Maybe);
if self.is_whole_body.replace(false) {
// If this expression is the whole body and the function diverges because of its
// arguments, we check this here to ensure the body is considered to diverge.
self.diverges.set(self.function_diverges_because_of_empty_arguments.get())
};
let ty = ensure_sufficient_stack(|| match &expr.kind {
hir::ExprKind::Path(
qpath @ (hir::QPath::Resolved(..) | hir::QPath::TypeRelative(..)),
) => self.check_expr_path(qpath, expr, args, call),
_ => self.check_expr_kind(expr, expected),
});
let ty = self.resolve_vars_if_possible(ty);
// Warn for non-block expressions with diverging children.
match expr.kind {
ExprKind::Block(..)
| ExprKind::If(..)
| ExprKind::Let(..)
| 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::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {}
ExprKind::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"),
ExprKind::MethodCall(segment, ..) => {
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")
}
_ => self.warn_if_unreachable(expr.hir_id, expr.span, "expression"),
}
// Any expression that produces a value of type `!` must have diverged
if ty.is_never() {
self.diverges.set(self.diverges.get() | Diverges::always(expr.span));
}
// Record the type, which applies it effects.
// We need to do this after the warning above, so that
// we don't warn for the diverging expression itself.
self.write_ty(expr.hir_id, ty);
// Combine the diverging and has_error flags.
self.diverges.set(self.diverges.get() | old_diverges);
debug!("type of {} is...", self.tcx.hir().node_to_string(expr.hir_id));
debug!("... {:?}, expected is {:?}", ty, expected);
ty
}
#[instrument(skip(self, expr), level = "debug")]
fn check_expr_kind(
&self,
expr: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
trace!("expr={:#?}", expr);
let tcx = self.tcx;
match expr.kind {
ExprKind::Lit(ref lit) => self.check_lit(lit, expected),
ExprKind::Binary(op, lhs, rhs) => self.check_binop(expr, op, lhs, rhs, expected),
ExprKind::Assign(lhs, rhs, span) => {
self.check_expr_assign(expr, expected, lhs, rhs, span)
}
ExprKind::AssignOp(op, lhs, rhs) => {
self.check_binop_assign(expr, op, lhs, rhs, expected)
}
ExprKind::Unary(unop, oprnd) => self.check_expr_unary(unop, oprnd, expected, expr),
ExprKind::AddrOf(kind, mutbl, oprnd) => {
self.check_expr_addr_of(kind, mutbl, oprnd, expected, expr)
}
ExprKind::Path(QPath::LangItem(lang_item, _)) => {
self.check_lang_item_path(lang_item, expr)
}
ExprKind::Path(ref qpath) => self.check_expr_path(qpath, expr, &[], None),
ExprKind::InlineAsm(asm) => {
// We defer some asm checks as we may not have resolved the input and output types yet (they may still be infer vars).
self.deferred_asm_checks.borrow_mut().push((asm, expr.hir_id));
self.check_expr_asm(asm)
}
ExprKind::OffsetOf(container, fields) => self.check_offset_of(container, fields, expr),
ExprKind::Break(destination, ref expr_opt) => {
self.check_expr_break(destination, expr_opt.as_deref(), expr)
}
ExprKind::Continue(destination) => {
if destination.target_id.is_ok() {
tcx.types.never
} else {
// There was an error; make type-check fail.
Ty::new_misc_error(tcx)
}
}
ExprKind::Ret(ref expr_opt) => self.check_expr_return(expr_opt.as_deref(), expr),
ExprKind::Become(call) => self.check_expr_become(call, expr),
ExprKind::Let(let_expr) => self.check_expr_let(let_expr, expr.hir_id),
ExprKind::Loop(body, _, source, _) => {
self.check_expr_loop(body, source, expected, expr)
}
ExprKind::Match(discrim, arms, match_src) => {
self.check_match(expr, discrim, arms, expected, match_src)
}
ExprKind::Closure(closure) => self.check_expr_closure(closure, expr.span, expected),
ExprKind::Block(body, _) => self.check_block_with_expected(body, expected),
ExprKind::Call(callee, args) => self.check_call(expr, callee, args, expected),
ExprKind::MethodCall(segment, receiver, args, _) => {
self.check_method_call(expr, segment, receiver, args, expected)
}
ExprKind::Cast(e, t) => self.check_expr_cast(e, t, expr),
ExprKind::Type(e, t) => {
let ascribed_ty = self.lower_ty_saving_user_provided_ty(t);
let ty = self.check_expr_with_hint(e, ascribed_ty);
self.demand_eqtype(e.span, ascribed_ty, ty);
ascribed_ty
}
ExprKind::If(cond, then_expr, opt_else_expr) => {
self.check_then_else(cond, then_expr, opt_else_expr, expr.span, expected)
}
ExprKind::DropTemps(e) => self.check_expr_with_expectation(e, expected),
ExprKind::Array(args) => self.check_expr_array(args, expected, expr),
ExprKind::ConstBlock(ref block) => self.check_expr_const_block(block, expected),
ExprKind::Repeat(element, ref count) => {
self.check_expr_repeat(element, count, expected, expr)
}
ExprKind::Tup(elts) => self.check_expr_tuple(elts, expected, expr),
ExprKind::Struct(qpath, fields, ref base_expr) => {
self.check_expr_struct(expr, expected, qpath, fields, base_expr)
}
ExprKind::Field(base, field) => self.check_field(expr, base, field, expected),
ExprKind::Index(base, idx, brackets_span) => {
self.check_expr_index(base, idx, expr, brackets_span)
}
ExprKind::Yield(value, _) => self.check_expr_yield(value, expr),
hir::ExprKind::Err(guar) => Ty::new_error(tcx, guar),
}
}
fn check_expr_unary(
&self,
unop: hir::UnOp,
oprnd: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
let tcx = self.tcx;
let expected_inner = match unop {
hir::UnOp::Not | hir::UnOp::Neg => expected,
hir::UnOp::Deref => NoExpectation,
};
let mut oprnd_t = self.check_expr_with_expectation(oprnd, expected_inner);
if !oprnd_t.references_error() {
oprnd_t = self.structurally_resolve_type(expr.span, oprnd_t);
match unop {
hir::UnOp::Deref => {
if let Some(ty) = self.lookup_derefing(expr, oprnd, oprnd_t) {
oprnd_t = ty;
} else {
let mut err = type_error_struct!(
self.dcx(),
expr.span,
oprnd_t,
E0614,
"type `{oprnd_t}` cannot be dereferenced",
);
let sp = tcx.sess.source_map().start_point(expr.span).with_parent(None);
if let Some(sp) =
tcx.sess.psess.ambiguous_block_expr_parse.borrow().get(&sp)
{
err.subdiagnostic(self.dcx(), ExprParenthesesNeeded::surrounding(*sp));
}
oprnd_t = Ty::new_error(tcx, err.emit());
}
}
hir::UnOp::Not => {
let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner);
// If it's builtin, we can reuse the type, this helps inference.
if !(oprnd_t.is_integral() || *oprnd_t.kind() == ty::Bool) {
oprnd_t = result;
}
}
hir::UnOp::Neg => {
let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner);
// If it's builtin, we can reuse the type, this helps inference.
if !oprnd_t.is_numeric() {
oprnd_t = result;
}
}
}
}
oprnd_t
}
fn check_expr_addr_of(
&self,
kind: hir::BorrowKind,
mutbl: hir::Mutability,
oprnd: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
let hint = expected.only_has_type(self).map_or(NoExpectation, |ty| {
match ty.kind() {
ty::Ref(_, ty, _) | ty::RawPtr(ty, _) => {
if oprnd.is_syntactic_place_expr() {
// Places may legitimately have unsized types.
// For example, dereferences of a fat pointer and
// the last field of a struct can be unsized.
ExpectHasType(*ty)
} else {
Expectation::rvalue_hint(self, *ty)
}
}
_ => NoExpectation,
}
});
let ty =
self.check_expr_with_expectation_and_needs(oprnd, hint, Needs::maybe_mut_place(mutbl));
match kind {
_ if ty.references_error() => Ty::new_misc_error(self.tcx),
hir::BorrowKind::Raw => {
self.check_named_place_expr(oprnd);
Ty::new_ptr(self.tcx, ty, mutbl)
}
hir::BorrowKind::Ref => {
// Note: at this point, we cannot say what the best lifetime
// is to use for resulting pointer. We want to use the
// shortest lifetime possible so as to avoid spurious borrowck
// errors. Moreover, the longest lifetime will depend on the
// precise details of the value whose address is being taken
// (and how long it is valid), which we don't know yet until
// type inference is complete.
//
// Therefore, here we simply generate a region variable. The
// region inferencer will then select a suitable value.
// Finally, borrowck will infer the value of the region again,
// this time with enough precision to check that the value
// whose address was taken can actually be made to live as long
// as it needs to live.
let region = self.next_region_var(infer::AddrOfRegion(expr.span));
Ty::new_ref(self.tcx, region, ty, mutbl)
}
}
}
/// Does this expression refer to a place that either:
/// * Is based on a local or static.
/// * Contains a dereference
/// Note that the adjustments for the children of `expr` should already
/// have been resolved.
fn check_named_place_expr(&self, oprnd: &'tcx hir::Expr<'tcx>) {
let is_named = oprnd.is_place_expr(|base| {
// Allow raw borrows if there are any deref adjustments.
//
// const VAL: (i32,) = (0,);
// const REF: &(i32,) = &(0,);
//
// &raw const VAL.0; // ERROR
// &raw const REF.0; // OK, same as &raw const (*REF).0;
//
// This is maybe too permissive, since it allows
// `let u = &raw const Box::new((1,)).0`, which creates an
// immediately dangling raw pointer.
self.typeck_results
.borrow()
.adjustments()
.get(base.hir_id)
.is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
});
if !is_named {
self.dcx().emit_err(AddressOfTemporaryTaken { span: oprnd.span });
}
}
fn check_lang_item_path(
&self,
lang_item: hir::LangItem,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
self.resolve_lang_item_path(lang_item, expr.span, expr.hir_id).1
}
pub(crate) fn check_expr_path(
&self,
qpath: &'tcx hir::QPath<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
args: &'tcx [hir::Expr<'tcx>],
call: Option<&'tcx hir::Expr<'tcx>>,
) -> Ty<'tcx> {
let tcx = self.tcx;
let (res, opt_ty, segs) =
self.resolve_ty_and_res_fully_qualified_call(qpath, expr.hir_id, expr.span, Some(args));
let ty = match res {
Res::Err => {
self.suggest_assoc_method_call(segs);
let e =
self.dcx().span_delayed_bug(qpath.span(), "`Res::Err` but no error emitted");
self.set_tainted_by_errors(e);
Ty::new_error(tcx, e)
}
Res::Def(DefKind::Variant, _) => {
let e = report_unexpected_variant_res(tcx, res, qpath, expr.span, E0533, "value");
Ty::new_error(tcx, e)
}
_ => {
self.instantiate_value_path(
segs,
opt_ty,
res,
call.map_or(expr.span, |e| e.span),
expr.span,
expr.hir_id,
)
.0
}
};
if let ty::FnDef(did, _) = *ty.kind() {
let fn_sig = ty.fn_sig(tcx);
if tcx.is_intrinsic(did, sym::transmute) {
let Some(from) = fn_sig.inputs().skip_binder().get(0) else {
span_bug!(
tcx.def_span(did),
"intrinsic fn `transmute` defined with no parameters"
);
};
let to = fn_sig.output().skip_binder();
// We defer the transmute to the end of typeck, once all inference vars have
// been resolved or we errored. This is important as we can only check transmute
// on concrete types, but the output type may not be known yet (it would only
// be known if explicitly specified via turbofish).
self.deferred_transmute_checks.borrow_mut().push((*from, to, expr.hir_id));
}
if !tcx.features().unsized_fn_params {
// We want to remove some Sized bounds from std functions,
// but don't want to expose the removal to stable Rust.
// i.e., we don't want to allow
//
// ```rust
// drop as fn(str);
// ```
//
// to work in stable even if the Sized bound on `drop` is relaxed.
for i in 0..fn_sig.inputs().skip_binder().len() {
// We just want to check sizedness, so instead of introducing
// placeholder lifetimes with probing, we just replace higher lifetimes
// with fresh vars.
let span = args.get(i).map(|a| a.span).unwrap_or(expr.span);
let input = self.instantiate_binder_with_fresh_vars(
span,
infer::BoundRegionConversionTime::FnCall,
fn_sig.input(i),
);
self.require_type_is_sized_deferred(
input,
span,
traits::SizedArgumentType(None),
);
}
}
// Here we want to prevent struct constructors from returning unsized types.
// There were two cases this happened: fn pointer coercion in stable
// and usual function call in presence of unsized_locals.
// Also, as we just want to check sizedness, instead of introducing
// placeholder lifetimes with probing, we just replace higher lifetimes
// with fresh vars.
let output = self.instantiate_binder_with_fresh_vars(
expr.span,
infer::BoundRegionConversionTime::FnCall,
fn_sig.output(),
);
self.require_type_is_sized_deferred(
output,
call.map_or(expr.span, |e| e.span),
traits::SizedCallReturnType,
);
}
// We always require that the type provided as the value for
// a type parameter outlives the moment of instantiation.
let args = self.typeck_results.borrow().node_args(expr.hir_id);
self.add_wf_bounds(args, expr);
ty
}
fn check_expr_break(
&self,
destination: hir::Destination,
expr_opt: Option<&'tcx hir::Expr<'tcx>>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
let tcx = self.tcx;
if let Ok(target_id) = destination.target_id {
let (e_ty, cause);
if let Some(e) = expr_opt {
// If this is a break with a value, we need to type-check
// the expression. Get an expected type from the loop context.
let opt_coerce_to = {
// We should release `enclosing_breakables` before the `check_expr_with_hint`
// below, so can't move this block of code to the enclosing scope and share
// `ctxt` with the second `enclosing_breakables` borrow below.
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
match enclosing_breakables.opt_find_breakable(target_id) {
Some(ctxt) => ctxt.coerce.as_ref().map(|coerce| coerce.expected_ty()),
None => {
// Avoid ICE when `break` is inside a closure (#65383).
return Ty::new_error_with_message(
tcx,
expr.span,
"break was outside loop, but no error was emitted",
);
}
}
};
// If the loop context is not a `loop { }`, then break with
// a value is illegal, and `opt_coerce_to` will be `None`.
// Set expectation to error in that case and set tainted
// by error (#114529)
let coerce_to = opt_coerce_to.unwrap_or_else(|| {
let guar = tcx.dcx().span_delayed_bug(
expr.span,
"illegal break with value found but no error reported",
);
self.set_tainted_by_errors(guar);
Ty::new_error(tcx, guar)
});
// Recurse without `enclosing_breakables` borrowed.
e_ty = self.check_expr_with_hint(e, coerce_to);
cause = self.misc(e.span);
} else {
// Otherwise, this is a break *without* a value. That's
// always legal, and is equivalent to `break ()`.
e_ty = tcx.types.unit;
cause = self.misc(expr.span);
}
// Now that we have type-checked `expr_opt`, borrow
// the `enclosing_loops` field and let's coerce the
// type of `expr_opt` into what is expected.
let mut enclosing_breakables = self.enclosing_breakables.borrow_mut();
let Some(ctxt) = enclosing_breakables.opt_find_breakable(target_id) else {
// Avoid ICE when `break` is inside a closure (#65383).
return Ty::new_error_with_message(
tcx,
expr.span,
"break was outside loop, but no error was emitted",
);
};
if let Some(ref mut coerce) = ctxt.coerce {
if let Some(e) = expr_opt {
coerce.coerce(self, &cause, e, e_ty);
} else {
assert!(e_ty.is_unit());
let ty = coerce.expected_ty();
coerce.coerce_forced_unit(
self,
&cause,
|mut err| {
self.suggest_missing_semicolon(&mut err, expr, e_ty, false);
self.suggest_mismatched_types_on_tail(
&mut err, expr, ty, e_ty, target_id,
);
let error = Some(Sorts(ExpectedFound { expected: ty, found: e_ty }));
self.annotate_loop_expected_due_to_inference(err, expr, error);
if let Some(val) =
self.err_ctxt().ty_kind_suggestion(self.param_env, ty)
{
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
"give the `break` a value of the expected type",
format!(" {val}"),
Applicability::HasPlaceholders,
);
}
},
false,
);
}
} else {
// If `ctxt.coerce` is `None`, we can just ignore
// the type of the expression. This is because
// either this was a break *without* a value, in
// which case it is always a legal type (`()`), or
// else an error would have been flagged by the
// `loops` pass for using break with an expression
// where you are not supposed to.
assert!(expr_opt.is_none() || self.dcx().has_errors().is_some());
}
// If we encountered a `break`, then (no surprise) it may be possible to break from the
// loop... unless the value being returned from the loop diverges itself, e.g.
// `break return 5` or `break loop {}`.
ctxt.may_break |= !self.diverges.get().is_always();
// the type of a `break` is always `!`, since it diverges
tcx.types.never
} else {
// Otherwise, we failed to find the enclosing loop;
// this can only happen if the `break` was not
// inside a loop at all, which is caught by the
// loop-checking pass.
let err = Ty::new_error_with_message(
self.tcx,
expr.span,
"break was outside loop, but no error was emitted",
);
// We still need to assign a type to the inner expression to
// prevent the ICE in #43162.
if let Some(e) = expr_opt {
self.check_expr_with_hint(e, err);
// ... except when we try to 'break rust;'.
// ICE this expression in particular (see #43162).
if let ExprKind::Path(QPath::Resolved(_, path)) = e.kind {
if let [segment] = path.segments
&& segment.ident.name == sym::rust
{
fatally_break_rust(self.tcx, expr.span);
}
}
}
// There was an error; make type-check fail.
err
}
}
fn check_expr_return(
&self,
expr_opt: Option<&'tcx hir::Expr<'tcx>>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
if self.ret_coercion.is_none() {
self.emit_return_outside_of_fn_body(expr, ReturnLikeStatementKind::Return);
if let Some(e) = expr_opt {
// We still have to type-check `e` (issue #86188), but calling
// `check_return_expr` only works inside fn bodies.
self.check_expr(e);
}
} else if let Some(e) = expr_opt {
if self.ret_coercion_span.get().is_none() {
self.ret_coercion_span.set(Some(e.span));
}
self.check_return_expr(e, true);
} else {
let mut coercion = self.ret_coercion.as_ref().unwrap().borrow_mut();
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) {
coercion.coerce_forced_unit(
self,
&cause,
|db| {
let span = fn_decl.output.span();
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
db.span_label(
span,
format!("expected `{snippet}` because of this return type"),
);
}
},
true,
);
} else {
coercion.coerce_forced_unit(self, &cause, |_| (), true);
}
}
self.tcx.types.never
}
fn check_expr_become(
&self,
call: &'tcx hir::Expr<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
match &self.ret_coercion {
Some(ret_coercion) => {
let ret_ty = ret_coercion.borrow().expected_ty();
let call_expr_ty = self.check_expr_with_hint(call, ret_ty);
// N.B. don't coerce here, as tail calls can't support most/all coercions
// FIXME(explicit_tail_calls): add a diagnostic note that `become` doesn't allow coercions
self.demand_suptype(expr.span, ret_ty, call_expr_ty);
}
None => {
self.emit_return_outside_of_fn_body(expr, ReturnLikeStatementKind::Become);
// Fallback to simply type checking `call` without hint/demanding the right types.
// Best effort to highlight more errors.
self.check_expr(call);
}
}
self.tcx.types.never
}
/// Check an expression that _is being returned_.
/// For example, this is called with `return_expr: $expr` when `return $expr`
/// is encountered.
///
/// Note that this function must only be called in function bodies.
///
/// `explicit_return` is `true` if we're checking an explicit `return expr`,
/// and `false` if we're checking a trailing expression.
pub(super) fn check_return_expr(
&self,
return_expr: &'tcx hir::Expr<'tcx>,
explicit_return: bool,
) {
let ret_coercion = self.ret_coercion.as_ref().unwrap_or_else(|| {
span_bug!(return_expr.span, "check_return_expr called outside fn body")
});
let ret_ty = ret_coercion.borrow().expected_ty();
let return_expr_ty = self.check_expr_with_hint(return_expr, ret_ty);
let mut span = return_expr.span;
// Use the span of the trailing expression for our cause,
// not the span of the entire function
if !explicit_return
&& let ExprKind::Block(body, _) = return_expr.kind
&& let Some(last_expr) = body.expr
{
span = last_expr.span;
}
ret_coercion.borrow_mut().coerce(
self,
&self.cause(span, ObligationCauseCode::ReturnValue(return_expr.hir_id)),
return_expr,
return_expr_ty,
);
if let Some(fn_sig) = self.body_fn_sig()
&& fn_sig.output().has_opaque_types()
{
// Point any obligations that were registered due to opaque type
// inference at the return expression.
self.select_obligations_where_possible(|errors| {
self.point_at_return_for_opaque_ty_error(
errors,
span,
return_expr_ty,
return_expr.span,
);
});
}
}
/// Emit an error because `return` or `become` is used outside of a function body.
///
/// `expr` is the `return` (`become`) "statement", `kind` is the kind of the statement
/// either `Return` or `Become`.
fn emit_return_outside_of_fn_body(&self, expr: &hir::Expr<'_>, kind: ReturnLikeStatementKind) {
let mut err = ReturnStmtOutsideOfFnBody {
span: expr.span,
encl_body_span: None,
encl_fn_span: None,
statement_kind: kind,
};
let encl_item_id = self.tcx.hir().get_parent_item(expr.hir_id);
if let hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn(..), span: encl_fn_span, ..
})
| hir::Node::TraitItem(hir::TraitItem {
kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)),
span: encl_fn_span,
..
})
| hir::Node::ImplItem(hir::ImplItem {
kind: hir::ImplItemKind::Fn(..),
span: encl_fn_span,
..
}) = self.tcx.hir_node_by_def_id(encl_item_id.def_id)
{
// We are inside a function body, so reporting "return statement
// outside of function body" needs an explanation.
let encl_body_owner_id = self.tcx.hir().enclosing_body_owner(expr.hir_id);
// If this didn't hold, we would not have to report an error in
// the first place.
assert_ne!(encl_item_id.def_id, encl_body_owner_id);
let encl_body_id = self.tcx.hir().body_owned_by(encl_body_owner_id);
let encl_body = self.tcx.hir().body(encl_body_id);
err.encl_body_span = Some(encl_body.value.span);
err.encl_fn_span = Some(*encl_fn_span);
}
self.dcx().emit_err(err);
}
fn point_at_return_for_opaque_ty_error(
&self,
errors: &mut Vec<traits::FulfillmentError<'tcx>>,
span: Span,
return_expr_ty: Ty<'tcx>,
return_span: Span,
) {
// Don't point at the whole block if it's empty
if span == return_span {
return;
}
for err in errors {
let cause = &mut err.obligation.cause;
if let ObligationCauseCode::OpaqueReturnType(None) = cause.code() {
let new_cause = ObligationCause::new(
cause.span,
cause.body_id,
ObligationCauseCode::OpaqueReturnType(Some((return_expr_ty, span))),
);
*cause = new_cause;
}
}
}
pub(crate) fn check_lhs_assignable(
&self,
lhs: &'tcx hir::Expr<'tcx>,
code: ErrCode,
op_span: Span,
adjust_err: impl FnOnce(&mut Diag<'_>),
) {
if lhs.is_syntactic_place_expr() {
return;
}
let mut err = self.dcx().struct_span_err(op_span, "invalid left-hand side of assignment");
err.code(code);
err.span_label(lhs.span, "cannot assign to this expression");
self.comes_from_while_condition(lhs.hir_id, |expr| {
err.span_suggestion_verbose(
expr.span.shrink_to_lo(),
"you might have meant to use pattern destructuring",
"let ",
Applicability::MachineApplicable,
);
});
self.check_for_missing_semi(lhs, &mut err);
adjust_err(&mut err);
err.emit();
}
/// Check if the expression that could not be assigned to was a typoed expression that
pub fn check_for_missing_semi(&self, expr: &'tcx hir::Expr<'tcx>, err: &mut Diag<'_>) -> bool {
if let hir::ExprKind::Binary(binop, lhs, rhs) = expr.kind
&& let hir::BinOpKind::Mul = binop.node
&& self.tcx.sess.source_map().is_multiline(lhs.span.between(rhs.span))
&& rhs.is_syntactic_place_expr()
{
// v missing semicolon here
// foo()
// *bar = baz;
// (#80446).
err.span_suggestion_verbose(
lhs.span.shrink_to_hi(),
"you might have meant to write a semicolon here",
";",
Applicability::MachineApplicable,
);
return true;
}
false
}
// Check if an expression `original_expr_id` comes from the condition of a while loop,
/// as opposed from the body of a while loop, which we can naively check by iterating
/// parents until we find a loop...
pub(super) fn comes_from_while_condition(
&self,
original_expr_id: HirId,
then: impl FnOnce(&hir::Expr<'_>),
) {
let mut parent = self.tcx.parent_hir_id(original_expr_id);
loop {
let node = self.tcx.hir_node(parent);
match node {
hir::Node::Expr(hir::Expr {
kind:
hir::ExprKind::Loop(
hir::Block {
expr:
Some(hir::Expr {
kind:
hir::ExprKind::Match(expr, ..) | hir::ExprKind::If(expr, ..),
..
}),
..
},
_,
hir::LoopSource::While,
_,
),
..
}) => {
// Check if our original expression is a child of the condition of a while loop.
// If it is, then we have a situation like `while Some(0) = value.get(0) {`,
// where `while let` was more likely intended.
if self.tcx.hir().parent_id_iter(original_expr_id).any(|id| id == expr.hir_id) {
then(expr);
}
break;
}
hir::Node::Item(_)
| hir::Node::ImplItem(_)
| hir::Node::TraitItem(_)
| hir::Node::Crate(_) => break,
_ => {
parent = self.tcx.parent_hir_id(parent);
}
}
}
}
// 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` or `while` 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 tail_defines_return_position_impl_trait =
self.return_position_impl_trait_from_match_expectation(orig_expected);
let if_cause = self.if_cause(
sp,
cond_expr.span,
then_expr,
else_expr,
then_ty,
else_ty,
tail_defines_return_position_impl_trait,
);
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, cond_expr, then_expr, &mut coerce);
// If the condition is false we can't diverge.
self.diverges.set(cond_diverges);
}
let result_ty = coerce.complete(self);
if let Err(guar) = cond_ty.error_reported() {
Ty::new_error(self.tcx, guar)
} else {
result_ty
}
}
/// Type check assignment expression `expr` of form `lhs = rhs`.
/// 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>,
expected: Expectation<'tcx>,
lhs: &'tcx hir::Expr<'tcx>,
rhs: &'tcx hir::Expr<'tcx>,
span: Span,
) -> Ty<'tcx> {
let expected_ty = expected.coercion_target_type(self, expr.span);
if expected_ty == self.tcx.types.bool {
// The expected type is `bool` but this will result in `()` so we can reasonably
// say that the user intended to write `lhs == rhs` instead of `lhs = rhs`.
// The likely cause of this is `if foo = bar { .. }`.
let actual_ty = self.tcx.types.unit;
let mut err = self.demand_suptype_diag(expr.span, expected_ty, actual_ty).unwrap();
let lhs_ty = self.check_expr(lhs);
let rhs_ty = self.check_expr(rhs);
let refs_can_coerce = |lhs: Ty<'tcx>, rhs: Ty<'tcx>| {
let lhs = Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, lhs.peel_refs());
let rhs = Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, rhs.peel_refs());
self.can_coerce(rhs, lhs)
};
let (applicability, eq) = if self.can_coerce(rhs_ty, lhs_ty) {
(Applicability::MachineApplicable, true)
} else if refs_can_coerce(rhs_ty, lhs_ty) {
// The lhs and rhs are likely missing some references in either side. Subsequent
// suggestions will show up.
(Applicability::MaybeIncorrect, true)
} else if let ExprKind::Binary(
Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. },
_,
rhs_expr,
) = lhs.kind
{
// if x == 1 && y == 2 { .. }
// +
let actual_lhs_ty = self.check_expr(rhs_expr);
(
Applicability::MaybeIncorrect,
self.can_coerce(rhs_ty, actual_lhs_ty)
|| refs_can_coerce(rhs_ty, actual_lhs_ty),
)
} else if let ExprKind::Binary(
Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. },
lhs_expr,
_,
) = rhs.kind
{
// if x == 1 && y == 2 { .. }
// +
let actual_rhs_ty = self.check_expr(lhs_expr);
(
Applicability::MaybeIncorrect,
self.can_coerce(actual_rhs_ty, lhs_ty)
|| refs_can_coerce(actual_rhs_ty, lhs_ty),
)
} else {
(Applicability::MaybeIncorrect, false)
};
if !lhs.is_syntactic_place_expr()
&& lhs.is_approximately_pattern()
&& !matches!(lhs.kind, hir::ExprKind::Lit(_))
{
// 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::If { .. }, .. }) =
self.tcx.parent_hir_node(expr.hir_id)
{
err.span_suggestion_verbose(
expr.span.shrink_to_lo(),
"you might have meant to use pattern matching",
"let ",
applicability,
);
};
}
if eq {
err.span_suggestion_verbose(
span.shrink_to_hi(),
"you might have meant to compare for equality",
'=',
applicability,
);
}
// If the assignment expression itself is ill-formed, don't
// bother emitting another error
let reported = err.emit_unless(lhs_ty.references_error() || rhs_ty.references_error());
return Ty::new_error(self.tcx, reported);
}
let lhs_ty = self.check_expr_with_needs(lhs, Needs::MutPlace);
let suggest_deref_binop = |err: &mut Diag<'_>, rhs_ty: Ty<'tcx>| {
if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
// Can only assign if the type is sized, so if `DerefMut` yields a type that is
// unsized, do not suggest dereferencing it.
let lhs_deref_ty_is_sized = self
.infcx
.type_implements_trait(
self.tcx.require_lang_item(LangItem::Sized, None),
[lhs_deref_ty],
self.param_env,
)
.may_apply();
if lhs_deref_ty_is_sized && self.can_coerce(rhs_ty, lhs_deref_ty) {
err.span_suggestion_verbose(
lhs.span.shrink_to_lo(),
"consider dereferencing here to assign to the mutably borrowed value",
"*",
Applicability::MachineApplicable,
);
}
}
};
// This is (basically) inlined `check_expr_coercible_to_type`, but we want
// to suggest an additional fixup here in `suggest_deref_binop`.
let rhs_ty = self.check_expr_with_hint(rhs, lhs_ty);
if let (_, Some(mut diag)) =
self.demand_coerce_diag(rhs, rhs_ty, lhs_ty, Some(lhs), AllowTwoPhase::No)
{
suggest_deref_binop(&mut diag, rhs_ty);
diag.emit();
}
self.check_lhs_assignable(lhs, E0070, span, |err| {
if let Some(rhs_ty) = self.typeck_results.borrow().expr_ty_opt(rhs) {
suggest_deref_binop(err, rhs_ty);
}
});
self.require_type_is_sized(lhs_ty, lhs.span, traits::AssignmentLhsSized);
if let Err(guar) = (lhs_ty, rhs_ty).error_reported() {
Ty::new_error(self.tcx, guar)
} else {
self.tcx.types.unit
}
}
pub(super) fn check_expr_let(
&self,
let_expr: &'tcx hir::LetExpr<'tcx>,
hir_id: HirId,
) -> Ty<'tcx> {
// for let statements, this is done in check_stmt
let init = let_expr.init;
self.warn_if_unreachable(init.hir_id, init.span, "block in `let` expression");
// otherwise check exactly as a let statement
self.check_decl((let_expr, hir_id).into());
// but return a bool, for this is a boolean expression
if let Some(error_guaranteed) = let_expr.is_recovered {
self.set_tainted_by_errors(error_guaranteed);
Ty::new_error(self.tcx, error_guaranteed)
} else {
self.tcx.types.bool
}
}
fn check_expr_loop(
&self,
body: &'tcx hir::Block<'tcx>,
source: hir::LoopSource,
expected: Expectation<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
let coerce = match source {
// you can only use break with a value from a normal `loop { }`
hir::LoopSource::Loop => {
let coerce_to = expected.coercion_target_type(self, body.span);
Some(CoerceMany::new(coerce_to))
}
hir::LoopSource::While | hir::LoopSource::ForLoop => None,
};
let ctxt = BreakableCtxt {
coerce,
may_break: false, // Will get updated if/when we find a `break`.
};
let (ctxt, ()) = self.with_breakable_ctxt(expr.hir_id, ctxt, || {
self.check_block_no_value(body);
});
if ctxt.may_break {
// No way to know whether it's diverging because
// of a `break` or an outer `break` or `return`.
self.diverges.set(Diverges::Maybe);
}
// If we permit break with a value, then result type is
// the LUB of the breaks (possibly ! if none); else, it
// is nil. This makes sense because infinite loops
// (which would have type !) are only possible iff we
// permit break with a value.
if ctxt.coerce.is_none() && !ctxt.may_break {
self.dcx().span_bug(body.span, "no coercion, but loop may not break");
}
ctxt.coerce.map(|c| c.complete(self)).unwrap_or_else(|| self.tcx.types.unit)
}
/// Checks a method call.
fn check_method_call(
&self,
expr: &'tcx hir::Expr<'tcx>,
segment: &'tcx hir::PathSegment<'tcx>,
rcvr: &'tcx hir::Expr<'tcx>,
args: &'tcx [hir::Expr<'tcx>],
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
let rcvr_t = self.check_expr(rcvr);
// no need to check for bot/err -- callee does that
let rcvr_t = self.structurally_resolve_type(rcvr.span, rcvr_t);
let span = segment.ident.span;
let method = match self.lookup_method(rcvr_t, segment, span, expr, rcvr, args) {
Ok(method) => {
// We could add a "consider `foo::<params>`" suggestion here, but I wasn't able to
// trigger this codepath causing `structurally_resolve_type` to emit an error.
self.write_method_call_and_enforce_effects(expr.hir_id, expr.span, method);
Ok(method)
}
Err(error) => {
if segment.ident.name != kw::Empty {
if let Some(err) = self.report_method_error(
span,
rcvr_t,
segment.ident,
SelfSource::MethodCall(rcvr),
error,
Some(args),
expected,
false,
) {
err.emit();
}
}
Err(())
}
};
// Call the generic checker.
self.check_method_argument_types(span, expr, method, args, DontTupleArguments, expected)
}
fn check_expr_cast(
&self,
e: &'tcx hir::Expr<'tcx>,
t: &'tcx hir::Ty<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
// Find the type of `e`. Supply hints based on the type we are casting to,
// if appropriate.
let t_cast = self.lower_ty_saving_user_provided_ty(t);
let t_cast = self.resolve_vars_if_possible(t_cast);
let t_expr = self.check_expr_with_expectation(e, ExpectCastableToType(t_cast));
let t_expr = self.resolve_vars_if_possible(t_expr);
// Eagerly check for some obvious errors.
if let Err(guar) = (t_expr, t_cast).error_reported() {
Ty::new_error(self.tcx, guar)
} else {
// Defer other checks until we're done type checking.
let mut deferred_cast_checks = self.deferred_cast_checks.borrow_mut();
match cast::CastCheck::new(self, e, t_expr, t_cast, t.span, expr.span) {
Ok(cast_check) => {
debug!(
"check_expr_cast: deferring cast from {:?} to {:?}: {:?}",
t_cast, t_expr, cast_check,
);
deferred_cast_checks.push(cast_check);
t_cast
}
Err(guar) => Ty::new_error(self.tcx, guar),
}
}
}
fn check_expr_array(
&self,
args: &'tcx [hir::Expr<'tcx>],
expected: Expectation<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
let element_ty = if !args.is_empty() {
let coerce_to = expected
.to_option(self)
.and_then(|uty| match *uty.kind() {
ty::Array(ty, _) | ty::Slice(ty) => Some(ty),
_ => None,
})
.unwrap_or_else(|| {
self.next_ty_var(TypeVariableOrigin { param_def_id: None, span: expr.span })
});
let mut coerce = CoerceMany::with_coercion_sites(coerce_to, args);
assert_eq!(self.diverges.get(), Diverges::Maybe);
for e in args {
let e_ty = self.check_expr_with_hint(e, coerce_to);
let cause = self.misc(e.span);
coerce.coerce(self, &cause, e, e_ty);
}
coerce.complete(self)
} else {
self.next_ty_var(TypeVariableOrigin { param_def_id: None, span: expr.span })
};
let array_len = args.len() as u64;
self.suggest_array_len(expr, array_len);
Ty::new_array(self.tcx, element_ty, array_len)
}
fn suggest_array_len(&self, expr: &'tcx hir::Expr<'tcx>, array_len: u64) {
let parent_node = self.tcx.hir().parent_iter(expr.hir_id).find(|(_, node)| {
!matches!(node, hir::Node::Expr(hir::Expr { kind: hir::ExprKind::AddrOf(..), .. }))
});
let Some((
_,
hir::Node::LetStmt(hir::LetStmt { ty: Some(ty), .. })
| hir::Node::Item(hir::Item { kind: hir::ItemKind::Const(ty, _, _), .. }),
)) = parent_node
else {
return;
};
if let hir::TyKind::Array(_, length) = ty.peel_refs().kind
&& let hir::ArrayLen::Body(&hir::AnonConst { hir_id, .. }) = length
{
let span = self.tcx.hir().span(hir_id);
self.dcx().try_steal_modify_and_emit_err(
span,
StashKey::UnderscoreForArrayLengths,
|err| {
err.span_suggestion(
span,
"consider specifying the array length",
array_len,
Applicability::MaybeIncorrect,
);
},
);
}
}
fn check_expr_const_block(
&self,
block: &'tcx hir::ConstBlock,
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
let body = self.tcx.hir().body(block.body);
// Create a new function context.
let def_id = block.def_id;
let fcx = FnCtxt::new(self, self.param_env, def_id);
crate::GatherLocalsVisitor::new(&fcx).visit_body(body);
let ty = fcx.check_expr_with_expectation(body.value, expected);
fcx.require_type_is_sized(ty, body.value.span, traits::ConstSized);
fcx.write_ty(block.hir_id, ty);
ty
}
fn check_expr_repeat(
&self,
element: &'tcx hir::Expr<'tcx>,
count: &'tcx hir::ArrayLen<'tcx>,
expected: Expectation<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
let tcx = self.tcx;
let count = self.lower_array_length(count);
if let Some(count) = count.try_eval_target_usize(tcx, self.param_env) {
self.suggest_array_len(expr, count);
}
let uty = match expected {
ExpectHasType(uty) => match *uty.kind() {
ty::Array(ty, _) | ty::Slice(ty) => Some(ty),
_ => None,
},
_ => None,
};
let (element_ty, t) = match uty {
Some(uty) => {
self.check_expr_coercible_to_type(element, uty, None);
(uty, uty)
}
None => {
let ty =
self.next_ty_var(TypeVariableOrigin { param_def_id: None, span: element.span });
let element_ty = self.check_expr_has_type_or_error(element, ty, |_| {});
(element_ty, ty)
}
};
if let Err(guar) = element_ty.error_reported() {
return Ty::new_error(tcx, guar);
}
self.check_repeat_element_needs_copy_bound(element, count, element_ty);
let ty = Ty::new_array_with_const_len(tcx, t, count);
self.register_wf_obligation(ty.into(), expr.span, traits::WellFormed(None));
ty
}
fn check_repeat_element_needs_copy_bound(
&self,
element: &hir::Expr<'_>,
count: ty::Const<'tcx>,
element_ty: Ty<'tcx>,
) {
let tcx = self.tcx;
// Actual constants as the repeat element get inserted repeatedly instead of getting copied via Copy.
match &element.kind {
hir::ExprKind::ConstBlock(..) => return,
hir::ExprKind::Path(qpath) => {
let res = self.typeck_results.borrow().qpath_res(qpath, element.hir_id);
if let Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::AnonConst, _) = res
{
return;
}
}
_ => {}
}
// If someone calls a const fn or constructs a const value, they can extract that
// out into a separate constant (or a const block in the future), so we check that
// to tell them that in the diagnostic. Does not affect typeck.
let is_constable = match element.kind {
hir::ExprKind::Call(func, _args) => match *self.node_ty(func.hir_id).kind() {
ty::FnDef(def_id, _) if tcx.is_const_fn(def_id) => traits::IsConstable::Fn,
_ => traits::IsConstable::No,
},
hir::ExprKind::Path(qpath) => {
match self.typeck_results.borrow().qpath_res(&qpath, element.hir_id) {
Res::Def(DefKind::Ctor(_, CtorKind::Const), _) => traits::IsConstable::Ctor,
_ => traits::IsConstable::No,
}
}
_ => traits::IsConstable::No,
};
// If the length is 0, we don't create any elements, so we don't copy any. If the length is 1, we
// don't copy that one element, we move it. Only check for Copy if the length is larger.
if count.try_eval_target_usize(tcx, self.param_env).map_or(true, |len| len > 1) {
let lang_item = self.tcx.require_lang_item(LangItem::Copy, None);
let code = traits::ObligationCauseCode::RepeatElementCopy {
is_constable,
elt_type: element_ty,
elt_span: element.span,
elt_stmt_span: self
.tcx
.hir()
.parent_iter(element.hir_id)
.find_map(|(_, node)| match node {
hir::Node::Item(it) => Some(it.span),
hir::Node::Stmt(stmt) => Some(stmt.span),
_ => None,
})
.expect("array repeat expressions must be inside an item or statement"),
};
self.require_type_meets(element_ty, element.span, code, lang_item);
}
}
fn check_expr_tuple(
&self,
elts: &'tcx [hir::Expr<'tcx>],
expected: Expectation<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
let flds = expected.only_has_type(self).and_then(|ty| {
let ty = self.resolve_vars_with_obligations(ty);
match ty.kind() {
ty::Tuple(flds) => Some(&flds[..]),
_ => None,
}
});
let elt_ts_iter = elts.iter().enumerate().map(|(i, e)| match flds {
Some(fs) if i < fs.len() => {
let ety = fs[i];
self.check_expr_coercible_to_type(e, ety, None);
ety
}
_ => self.check_expr_with_expectation(e, NoExpectation),
});
let tuple = Ty::new_tup_from_iter(self.tcx, elt_ts_iter);
if let Err(guar) = tuple.error_reported() {
Ty::new_error(self.tcx, guar)
} else {
self.require_type_is_sized(tuple, expr.span, traits::TupleInitializerSized);
tuple
}
}
fn check_expr_struct(
&self,
expr: &hir::Expr<'_>,
expected: Expectation<'tcx>,
qpath: &QPath<'tcx>,
fields: &'tcx [hir::ExprField<'tcx>],
base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>,
) -> Ty<'tcx> {
// Find the relevant variant
let (variant, adt_ty) = match self.check_struct_path(qpath, expr.hir_id) {
Ok(data) => data,
Err(guar) => {
self.check_struct_fields_on_error(fields, base_expr);
return Ty::new_error(self.tcx, guar);
}
};
// Prohibit struct expressions when non-exhaustive flag is set.
let adt = adt_ty.ty_adt_def().expect("`check_struct_path` returned non-ADT type");
if !adt.did().is_local() && variant.is_field_list_non_exhaustive() {
self.dcx()
.emit_err(StructExprNonExhaustive { span: expr.span, what: adt.variant_descr() });
}
self.check_expr_struct_fields(
adt_ty,
expected,
expr,
qpath.span(),
variant,
fields,
base_expr,
);
self.require_type_is_sized(adt_ty, expr.span, traits::StructInitializerSized);
adt_ty
}
fn check_expr_struct_fields(
&self,
adt_ty: Ty<'tcx>,
expected: Expectation<'tcx>,
expr: &hir::Expr<'_>,
span: Span,
variant: &'tcx ty::VariantDef,
hir_fields: &'tcx [hir::ExprField<'tcx>],
base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>,
) {
let tcx = self.tcx;
let expected_inputs =
self.expected_inputs_for_expected_output(span, expected, adt_ty, &[adt_ty]);
let adt_ty_hint = if let Some(expected_inputs) = expected_inputs {
expected_inputs.get(0).cloned().unwrap_or(adt_ty)
} else {
adt_ty
};
// re-link the regions that EIfEO can erase.
self.demand_eqtype(span, adt_ty_hint, adt_ty);
let ty::Adt(adt, args) = adt_ty.kind() else {
span_bug!(span, "non-ADT passed to check_expr_struct_fields");
};
let adt_kind = adt.adt_kind();
let mut remaining_fields = variant
.fields
.iter_enumerated()
.map(|(i, field)| (field.ident(tcx).normalize_to_macros_2_0(), (i, field)))
.collect::<UnordMap<_, _>>();
let mut seen_fields = FxHashMap::default();
let mut error_happened = false;
// Type-check each field.
for (idx, field) in hir_fields.iter().enumerate() {
let ident = tcx.adjust_ident(field.ident, variant.def_id);
let field_type = if let Some((i, v_field)) = remaining_fields.remove(&ident) {
seen_fields.insert(ident, field.span);
// FIXME: handle nested fields
self.write_field_index(field.hir_id, i, Vec::new());
// We don't look at stability attributes on
// struct-like enums (yet...), but it's definitely not
// a bug to have constructed one.
if adt_kind != AdtKind::Enum {
tcx.check_stability(v_field.did, Some(expr.hir_id), field.span, None);
}
self.field_ty(field.span, v_field, args)
} else {
error_happened = true;
let guar = if let Some(prev_span) = seen_fields.get(&ident) {
tcx.dcx().emit_err(FieldMultiplySpecifiedInInitializer {
span: field.ident.span,
prev_span: *prev_span,
ident,
})
} else {
self.report_unknown_field(
adt_ty,
variant,
expr,
field,
hir_fields,
adt.variant_descr(),
)
};
Ty::new_error(tcx, guar)
};
// Make sure to give a type to the field even if there's
// an error, so we can continue type-checking.
let ty = self.check_expr_with_hint(field.expr, field_type);
let (_, diag) =
self.demand_coerce_diag(field.expr, ty, field_type, None, AllowTwoPhase::No);
if let Some(diag) = diag {
if idx == hir_fields.len() - 1 {
if remaining_fields.is_empty() {
self.suggest_fru_from_range_and_emit(field, variant, args, diag);
} else {
diag.stash(field.span, StashKey::MaybeFruTypo);
}
} else {
diag.emit();
}
}
}
// Make sure the programmer specified correct number of fields.
if adt_kind == AdtKind::Union {
if hir_fields.len() != 1 {
struct_span_code_err!(
tcx.dcx(),
span,
E0784,
"union expressions should have exactly one field",
)
.emit();
}
}
// 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.
if error_happened {
if let Some(base_expr) = base_expr {
self.check_expr(base_expr);
}
return;
}
if let Some(base_expr) = base_expr {
// FIXME: We are currently creating two branches here in order to maintain
// consistency. But they should be merged as much as possible.
let fru_tys = if self.tcx.features().type_changing_struct_update {
if adt.is_struct() {
// Make some fresh generic parameters for our ADT type.
let fresh_args = self.fresh_args_for_item(base_expr.span, adt.did());
// We do subtyping on the FRU fields first, so we can
// learn exactly what types we expect the base expr
// needs constrained to be compatible with the struct
// type we expect from the expectation value.
let fru_tys = variant
.fields
.iter()
.map(|f| {
let fru_ty = self
.normalize(expr.span, self.field_ty(base_expr.span, f, fresh_args));
let ident = self.tcx.adjust_ident(f.ident(self.tcx), variant.def_id);
if let Some(_) = remaining_fields.remove(&ident) {
let target_ty = self.field_ty(base_expr.span, f, args);
let cause = self.misc(base_expr.span);
match self.at(&cause, self.param_env).sup(
// We're already using inference variables for any params, and don't allow converting
// between different structs, so there is no way this ever actually defines an opaque type.
// Thus choosing `Yes` is fine.
DefineOpaqueTypes::Yes,
target_ty,
fru_ty,
) {
Ok(InferOk { obligations, value: () }) => {
self.register_predicates(obligations)
}
Err(_) => {
span_bug!(
cause.span(),
"subtyping remaining fields of type changing FRU failed: {target_ty} != {fru_ty}: {}::{}",
variant.name,
ident.name,
);
}
}
}
self.resolve_vars_if_possible(fru_ty)
})
.collect();
// The use of fresh args that we have subtyped against
// our base ADT type's fields allows us to guide inference
// along so that, e.g.
// ```
// MyStruct<'a, F1, F2, const C: usize> {
// f: F1,
// // Other fields that reference `'a`, `F2`, and `C`
// }
//
// let x = MyStruct {
// f: 1usize,
// ..other_struct
// };
// ```
// will have the `other_struct` expression constrained to
// `MyStruct<'a, _, F2, C>`, as opposed to just `_`...
// This is important to allow coercions to happen in
// `other_struct` itself. See `coerce-in-base-expr.rs`.
let fresh_base_ty = Ty::new_adt(self.tcx, *adt, fresh_args);
self.check_expr_has_type_or_error(
base_expr,
self.resolve_vars_if_possible(fresh_base_ty),
|_| {},
);
fru_tys
} else {
// Check the base_expr, regardless of a bad expected adt_ty, so we can get
// type errors on that expression, too.
self.check_expr(base_expr);
self.dcx().emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
return;
}
} else {
self.check_expr_has_type_or_error(base_expr, adt_ty, |_| {
let base_ty = self.typeck_results.borrow().expr_ty(*base_expr);
let same_adt = matches!((adt_ty.kind(), base_ty.kind()),
(ty::Adt(adt, _), ty::Adt(base_adt, _)) if adt == base_adt);
if self.tcx.sess.is_nightly_build() && same_adt {
feature_err(
&self.tcx.sess,
sym::type_changing_struct_update,
base_expr.span,
"type changing struct updating is experimental",
)
.emit();
}
});
match adt_ty.kind() {
ty::Adt(adt, args) if adt.is_struct() => variant
.fields
.iter()
.map(|f| self.normalize(expr.span, f.ty(self.tcx, args)))
.collect(),
_ => {
self.dcx()
.emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
return;
}
}
};
self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr.hir_id, fru_tys);
} else if adt_kind != AdtKind::Union && !remaining_fields.is_empty() {
debug!(?remaining_fields);
let private_fields: Vec<&ty::FieldDef> = variant
.fields
.iter()
.filter(|field| !field.vis.is_accessible_from(tcx.parent_module(expr.hir_id), tcx))
.collect();
if !private_fields.is_empty() {
self.report_private_fields(adt_ty, span, expr.span, private_fields, hir_fields);
} else {
self.report_missing_fields(
adt_ty,
span,
remaining_fields,
variant,
hir_fields,
args,
);
}
}
}
fn check_struct_fields_on_error(
&self,
fields: &'tcx [hir::ExprField<'tcx>],
base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>,
) {
for field in fields {
self.check_expr(field.expr);
}
if let Some(base) = *base_expr {
self.check_expr(base);
}
}
/// Report an error for a struct field expression when there are fields which aren't provided.
///
/// ```text
/// error: missing field `you_can_use_this_field` in initializer of `foo::Foo`
/// --> src/main.rs:8:5
/// |
/// 8 | foo::Foo {};
/// | ^^^^^^^^ missing `you_can_use_this_field`
///
/// error: aborting due to 1 previous error
/// ```
fn report_missing_fields(
&self,
adt_ty: Ty<'tcx>,
span: Span,
remaining_fields: UnordMap<Ident, (FieldIdx, &ty::FieldDef)>,
variant: &'tcx ty::VariantDef,
hir_fields: &'tcx [hir::ExprField<'tcx>],
args: GenericArgsRef<'tcx>,
) {
let len = remaining_fields.len();
let displayable_field_names: Vec<&str> =
remaining_fields.items().map(|(ident, _)| ident.as_str()).into_sorted_stable_ord();
let mut truncated_fields_error = String::new();
let remaining_fields_names = match &displayable_field_names[..] {
[field1] => format!("`{field1}`"),
[field1, field2] => format!("`{field1}` and `{field2}`"),
[field1, field2, field3] => format!("`{field1}`, `{field2}` and `{field3}`"),
_ => {
truncated_fields_error =
format!(" and {} other field{}", len - 3, pluralize!(len - 3));
displayable_field_names
.iter()
.take(3)
.map(|n| format!("`{n}`"))
.collect::<Vec<_>>()
.join(", ")
}
};
let mut err = struct_span_code_err!(
self.dcx(),
span,
E0063,
"missing field{} {}{} in initializer of `{}`",
pluralize!(len),
remaining_fields_names,
truncated_fields_error,
adt_ty
);
err.span_label(span, format!("missing {remaining_fields_names}{truncated_fields_error}"));
if let Some(hir_field) = hir_fields.last() {
self.suggest_fru_from_range_and_emit(hir_field, variant, args, err);
} else {
err.emit();
}
}
/// If the last field is a range literal, but it isn't supposed to be, then they probably
/// meant to use functional update syntax.
fn suggest_fru_from_range_and_emit(
&self,
last_expr_field: &hir::ExprField<'tcx>,
variant: &ty::VariantDef,
args: GenericArgsRef<'tcx>,
mut err: Diag<'_>,
) {
// I don't use 'is_range_literal' because only double-sided, half-open ranges count.
if let ExprKind::Struct(QPath::LangItem(LangItem::Range, ..), [range_start, range_end], _) =
last_expr_field.expr.kind
&& let variant_field =
variant.fields.iter().find(|field| field.ident(self.tcx) == last_expr_field.ident)
&& let range_def_id = self.tcx.lang_items().range_struct()
&& variant_field
.and_then(|field| field.ty(self.tcx, args).ty_adt_def())
.map(|adt| adt.did())
!= range_def_id
{
// Use a (somewhat arbitrary) filtering heuristic to avoid printing
// expressions that are either too long, or have control character
// such as newlines in them.
let expr = self
.tcx
.sess
.source_map()
.span_to_snippet(range_end.expr.span)
.ok()
.filter(|s| s.len() < 25 && !s.contains(|c: char| c.is_control()));
let fru_span = self
.tcx
.sess
.source_map()
.span_extend_while_whitespace(range_start.span)
.shrink_to_hi()
.to(range_end.span);
err.subdiagnostic(
self.dcx(),
TypeMismatchFruTypo { expr_span: range_start.span, fru_span, expr },
);
// Suppress any range expr type mismatches
self.dcx().try_steal_replace_and_emit_err(
last_expr_field.span,
StashKey::MaybeFruTypo,
err,
);
} else {
err.emit();
}
}
/// Report an error for a struct field expression when there are invisible fields.
///
/// ```text
/// error: cannot construct `Foo` with struct literal syntax due to private fields
/// --> src/main.rs:8:5
/// |
/// 8 | foo::Foo {};
/// | ^^^^^^^^
///
/// error: aborting due to 1 previous error
/// ```
fn report_private_fields(
&self,
adt_ty: Ty<'tcx>,
span: Span,
expr_span: Span,
private_fields: Vec<&ty::FieldDef>,
used_fields: &'tcx [hir::ExprField<'tcx>],
) {
let mut err =
self.dcx().struct_span_err(
span,
format!(
"cannot construct `{adt_ty}` with struct literal syntax due to private fields",
),
);
let (used_private_fields, remaining_private_fields): (
Vec<(Symbol, Span, bool)>,
Vec<(Symbol, Span, bool)>,
) = private_fields
.iter()
.map(|field| {
match used_fields.iter().find(|used_field| field.name == used_field.ident.name) {
Some(used_field) => (field.name, used_field.span, true),
None => (field.name, self.tcx.def_span(field.did), false),
}
})
.partition(|field| field.2);
err.span_labels(used_private_fields.iter().map(|(_, span, _)| *span), "private field");
if !remaining_private_fields.is_empty() {
let remaining_private_fields_len = remaining_private_fields.len();
let names = match &remaining_private_fields
.iter()
.map(|(name, _, _)| name)
.collect::<Vec<_>>()[..]
{
_ if remaining_private_fields_len > 6 => String::new(),
[name] => format!("`{name}` "),
[names @ .., last] => {
let names = names.iter().map(|name| format!("`{name}`")).collect::<Vec<_>>();
format!("{} and `{last}` ", names.join(", "))
}
[] => bug!("expected at least one private field to report"),
};
err.note(format!(
"{}private field{s} {names}that {were} not provided",
if used_fields.is_empty() { "" } else { "...and other " },
s = pluralize!(remaining_private_fields_len),
were = pluralize!("was", remaining_private_fields_len),
));
}
if let ty::Adt(def, _) = adt_ty.kind() {
let def_id = def.did();
let mut items = self
.tcx
.inherent_impls(def_id)
.into_iter()
.flatten()
.flat_map(|i| self.tcx.associated_items(i).in_definition_order())
// Only assoc fn with no receivers.
.filter(|item| {
matches!(item.kind, ty::AssocKind::Fn) && !item.fn_has_self_parameter
})
.filter_map(|item| {
// Only assoc fns that return `Self`
let fn_sig = self.tcx.fn_sig(item.def_id).skip_binder();
let ret_ty = fn_sig.output();
let ret_ty =
self.tcx.normalize_erasing_late_bound_regions(self.param_env, ret_ty);
if !self.can_eq(self.param_env, ret_ty, adt_ty) {
return None;
}
let input_len = fn_sig.inputs().skip_binder().len();
let order = !item.name.as_str().starts_with("new");
Some((order, item.name, input_len))
})
.collect::<Vec<_>>();
items.sort_by_key(|(order, _, _)| *order);
let suggestion = |name, args| {
format!(
"::{name}({})",
std::iter::repeat("_").take(args).collect::<Vec<_>>().join(", ")
)
};
match &items[..] {
[] => {}
[(_, name, args)] => {
err.span_suggestion_verbose(
span.shrink_to_hi().with_hi(expr_span.hi()),
format!("you might have meant to use the `{name}` associated function"),
suggestion(name, *args),
Applicability::MaybeIncorrect,
);
}
_ => {
err.span_suggestions(
span.shrink_to_hi().with_hi(expr_span.hi()),
"you might have meant to use an associated function to build this type",
items.iter().map(|(_, name, args)| suggestion(name, *args)),
Applicability::MaybeIncorrect,
);
}
}
if let Some(default_trait) = self.tcx.get_diagnostic_item(sym::Default)
&& self
.infcx
.type_implements_trait(default_trait, [adt_ty], self.param_env)
.may_apply()
{
err.multipart_suggestion(
"consider using the `Default` trait",
vec![
(span.shrink_to_lo(), "<".to_string()),
(
span.shrink_to_hi().with_hi(expr_span.hi()),
" as std::default::Default>::default()".to_string(),
),
],
Applicability::MaybeIncorrect,
);
}
}
err.emit();
}
fn report_unknown_field(
&self,
ty: Ty<'tcx>,
variant: &'tcx ty::VariantDef,
expr: &hir::Expr<'_>,
field: &hir::ExprField<'_>,
skip_fields: &[hir::ExprField<'_>],
kind_name: &str,
) -> ErrorGuaranteed {
if variant.is_recovered() {
let guar =
self.dcx().span_delayed_bug(expr.span, "parser recovered but no error was emitted");
self.set_tainted_by_errors(guar);
return guar;
}
let mut err = self.err_ctxt().type_error_struct_with_diag(
field.ident.span,
|actual| match ty.kind() {
ty::Adt(adt, ..) if adt.is_enum() => struct_span_code_err!(
self.dcx(),
field.ident.span,
E0559,
"{} `{}::{}` has no field named `{}`",
kind_name,
actual,
variant.name,
field.ident
),
_ => struct_span_code_err!(
self.dcx(),
field.ident.span,
E0560,
"{} `{}` has no field named `{}`",
kind_name,
actual,
field.ident
),
},
ty,
);
let variant_ident_span = self.tcx.def_ident_span(variant.def_id).unwrap();
match variant.ctor_kind() {
Some(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.name,
),
);
err.span_label(field.ident.span, "field does not exist");
err.span_suggestion_verbose(
expr.span,
format!(
"`{adt}::{variant}` is a tuple {kind_name}, use the appropriate syntax",
adt = ty,
variant = variant.name,
),
format!(
"{adt}::{variant}(/* fields */)",
adt = ty,
variant = variant.name,
),
Applicability::HasPlaceholders,
);
}
_ => {
err.span_label(variant_ident_span, format!("`{ty}` defined here"));
err.span_label(field.ident.span, "field does not exist");
err.span_suggestion_verbose(
expr.span,
format!("`{ty}` is a tuple {kind_name}, use the appropriate syntax",),
format!("{ty}(/* fields */)"),
Applicability::HasPlaceholders,
);
}
},
_ => {
// prevent all specified fields from being suggested
let available_field_names = self.available_field_names(variant, expr, skip_fields);
if let Some(field_name) =
find_best_match_for_name(&available_field_names, field.ident.name, None)
{
err.span_label(field.ident.span, "unknown field");
err.span_suggestion_verbose(
field.ident.span,
"a field with a similar name exists",
field_name,
Applicability::MaybeIncorrect,
);
} else {
match ty.kind() {
ty::Adt(adt, ..) => {
if adt.is_enum() {
err.span_label(
field.ident.span,
format!("`{}::{}` does not have this field", ty, variant.name),
);
} else {
err.span_label(
field.ident.span,
format!("`{ty}` does not have this field"),
);
}
if available_field_names.is_empty() {
err.note("all struct fields are already assigned");
} else {
err.note(format!(
"available fields are: {}",
self.name_series_display(available_field_names)
));
}
}
_ => bug!("non-ADT passed to report_unknown_field"),
}
};
}
}
err.emit()
}
fn available_field_names(
&self,
variant: &'tcx ty::VariantDef,
expr: &hir::Expr<'_>,
skip_fields: &[hir::ExprField<'_>],
) -> Vec<Symbol> {
variant
.fields
.iter()
.filter(|field| {
skip_fields.iter().all(|&skip| skip.ident.name != field.name)
&& self.is_field_suggestable(field, expr.hir_id, expr.span)
})
.map(|field| field.name)
.collect()
}
fn name_series_display(&self, names: Vec<Symbol>) -> String {
// dynamic limit, to never omit just one field
let limit = if names.len() == 6 { 6 } else { 5 };
let mut display =
names.iter().take(limit).map(|n| format!("`{n}`")).collect::<Vec<_>>().join(", ");
if names.len() > limit {
display = format!("{} ... and {} others", display, names.len() - limit);
}
display
}
// Check field access expressions
fn check_field(
&self,
expr: &'tcx hir::Expr<'tcx>,
base: &'tcx hir::Expr<'tcx>,
field: Ident,
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
debug!("check_field(expr: {:?}, base: {:?}, field: {:?})", expr, base, field);
let base_ty = self.check_expr(base);
let base_ty = self.structurally_resolve_type(base.span, base_ty);
let mut private_candidate = None;
let mut autoderef = self.autoderef(expr.span, base_ty);
while let Some((deref_base_ty, _)) = autoderef.next() {
debug!("deref_base_ty: {:?}", deref_base_ty);
match deref_base_ty.kind() {
ty::Adt(base_def, args) if !base_def.is_enum() => {
debug!("struct named {:?}", deref_base_ty);
let body_hir_id = self.tcx.local_def_id_to_hir_id(self.body_id);
let (ident, def_scope) =
self.tcx.adjust_ident_and_get_scope(field, base_def.did(), body_hir_id);
let mut adt_def = *base_def;
let mut last_ty = None;
let mut nested_fields = Vec::new();
let mut index = None;
while let Some(idx) = self.tcx.find_field((adt_def.did(), ident)) {
let &mut first_idx = index.get_or_insert(idx);
let field = &adt_def.non_enum_variant().fields[idx];
let field_ty = self.field_ty(expr.span, field, args);
if let Some(ty) = last_ty {
nested_fields.push((ty, idx));
}
if field.ident(self.tcx).normalize_to_macros_2_0() == ident {
// Save the index of all fields regardless of their visibility in case
// of error recovery.
self.write_field_index(expr.hir_id, first_idx, nested_fields);
let adjustments = self.adjust_steps(&autoderef);
if field.vis.is_accessible_from(def_scope, self.tcx) {
self.apply_adjustments(base, adjustments);
self.register_predicates(autoderef.into_obligations());
self.tcx.check_stability(
field.did,
Some(expr.hir_id),
expr.span,
None,
);
return field_ty;
}
private_candidate = Some((adjustments, base_def.did()));
break;
}
last_ty = Some(field_ty);
adt_def = field_ty.ty_adt_def().expect("expect Adt for unnamed field");
}
}
ty::Tuple(tys) => {
if let Ok(index) = field.as_str().parse::<usize>() {
if field.name == sym::integer(index) {
if let Some(&field_ty) = tys.get(index) {
let adjustments = self.adjust_steps(&autoderef);
self.apply_adjustments(base, adjustments);
self.register_predicates(autoderef.into_obligations());
self.write_field_index(
expr.hir_id,
FieldIdx::from_usize(index),
Vec::new(),
);
return field_ty;
}
}
}
}
_ => {}
}
}
self.structurally_resolve_type(autoderef.span(), autoderef.final_ty(false));
if let Some((adjustments, did)) = private_candidate {
// (#90483) apply adjustments to avoid ExprUseVisitor from
// creating erroneous projection.
self.apply_adjustments(base, adjustments);
let guar = self.ban_private_field_access(
expr,
base_ty,
field,
did,
expected.only_has_type(self),
);
return Ty::new_error(self.tcx(), guar);
}
let guar = if field.name == kw::Empty {
self.dcx().span_delayed_bug(field.span, "field name with no name")
} else if self.method_exists(field, base_ty, expr.hir_id, expected.only_has_type(self)) {
self.ban_take_value_of_method(expr, base_ty, field)
} else if !base_ty.is_primitive_ty() {
self.ban_nonexisting_field(field, base, expr, base_ty)
} else {
let field_name = field.to_string();
let mut err = type_error_struct!(
self.dcx(),
field.span,
base_ty,
E0610,
"`{base_ty}` is a primitive type and therefore doesn't have fields",
);
let is_valid_suffix = |field: &str| {
if field == "f32" || field == "f64" {
return true;
}
let mut chars = field.chars().peekable();
match chars.peek() {
Some('e') | Some('E') => {
chars.next();
if let Some(c) = chars.peek()
&& !c.is_numeric()
&& *c != '-'
&& *c != '+'
{
return false;
}
while let Some(c) = chars.peek() {
if !c.is_numeric() {
break;
}
chars.next();
}
}
_ => (),
}
let suffix = chars.collect::<String>();
suffix.is_empty() || suffix == "f32" || suffix == "f64"
};
let maybe_partial_suffix = |field: &str| -> Option<&str> {
let first_chars = ['f', 'l'];
if field.len() >= 1
&& field.to_lowercase().starts_with(first_chars)
&& field[1..].chars().all(|c| c.is_ascii_digit())
{
if field.to_lowercase().starts_with(['f']) { Some("f32") } else { Some("f64") }
} else {
None
}
};
if let ty::Infer(ty::IntVar(_)) = base_ty.kind()
&& let ExprKind::Lit(Spanned {
node: ast::LitKind::Int(_, ast::LitIntType::Unsuffixed),
..
}) = base.kind
&& !base.span.from_expansion()
{
if is_valid_suffix(&field_name) {
err.span_suggestion_verbose(
field.span.shrink_to_lo(),
"if intended to be a floating point literal, consider adding a `0` after the period",
'0',
Applicability::MaybeIncorrect,
);
} else if let Some(correct_suffix) = maybe_partial_suffix(&field_name) {
err.span_suggestion_verbose(
field.span,
format!("if intended to be a floating point literal, consider adding a `0` after the period and a `{correct_suffix}` suffix"),
format!("0{correct_suffix}"),
Applicability::MaybeIncorrect,
);
}
}
err.emit()
};
Ty::new_error(self.tcx(), guar)
}
fn suggest_await_on_field_access(
&self,
err: &mut Diag<'_>,
field_ident: Ident,
base: &'tcx hir::Expr<'tcx>,
ty: Ty<'tcx>,
) {
let Some(output_ty) = self.get_impl_future_output_ty(ty) else {
err.span_label(field_ident.span, "unknown field");
return;
};
let ty::Adt(def, _) = output_ty.kind() else {
err.span_label(field_ident.span, "unknown field");
return;
};
// no field access on enum type
if def.is_enum() {
err.span_label(field_ident.span, "unknown field");
return;
}
if !def.non_enum_variant().fields.iter().any(|field| field.ident(self.tcx) == field_ident) {
err.span_label(field_ident.span, "unknown field");
return;
}
err.span_label(
field_ident.span,
"field not available in `impl Future`, but it is available in its `Output`",
);
err.span_suggestion_verbose(
base.span.shrink_to_hi(),
"consider `await`ing on the `Future` and access the field of its `Output`",
".await",
Applicability::MaybeIncorrect,
);
}
fn ban_nonexisting_field(
&self,
ident: Ident,
base: &'tcx hir::Expr<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
base_ty: Ty<'tcx>,
) -> ErrorGuaranteed {
debug!(
"ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, base_ty={:?}",
ident, base, expr, base_ty
);
let mut err = self.no_such_field_err(ident, base_ty, base.hir_id);
match *base_ty.peel_refs().kind() {
ty::Array(_, len) => {
self.maybe_suggest_array_indexing(&mut err, expr, base, ident, len);
}
ty::RawPtr(..) => {
self.suggest_first_deref_field(&mut err, expr, base, ident);
}
ty::Param(param_ty) => {
err.span_label(ident.span, "unknown field");
self.point_at_param_definition(&mut err, param_ty);
}
ty::Alias(ty::Opaque, _) => {
self.suggest_await_on_field_access(&mut err, ident, base, base_ty.peel_refs());
}
_ => {
err.span_label(ident.span, "unknown field");
}
}
self.suggest_fn_call(&mut err, base, base_ty, |output_ty| {
if let ty::Adt(def, _) = output_ty.kind()
&& !def.is_enum()
{
def.non_enum_variant().fields.iter().any(|field| {
field.ident(self.tcx) == ident
&& field.vis.is_accessible_from(expr.hir_id.owner.def_id, self.tcx)
})
} else if let ty::Tuple(tys) = output_ty.kind()
&& let Ok(idx) = ident.as_str().parse::<usize>()
{
idx < tys.len()
} else {
false
}
});
if ident.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 or later");
HelpUseLatestEdition::new().add_to_diag(&mut err);
}
err.emit()
}
fn ban_private_field_access(
&self,
expr: &hir::Expr<'tcx>,
expr_t: Ty<'tcx>,
field: Ident,
base_did: DefId,
return_ty: Option<Ty<'tcx>>,
) -> ErrorGuaranteed {
let mut err = self.private_field_err(field, base_did);
// Also check if an accessible method exists, which is often what is meant.
if self.method_exists(field, expr_t, expr.hir_id, return_ty)
&& !self.expr_in_place(expr.hir_id)
{
self.suggest_method_call(
&mut err,
format!("a method `{field}` also exists, call it with parentheses"),
field,
expr_t,
expr,
None,
);
}
err.emit()
}
fn ban_take_value_of_method(
&self,
expr: &hir::Expr<'tcx>,
expr_t: Ty<'tcx>,
field: Ident,
) -> ErrorGuaranteed {
let mut err = type_error_struct!(
self.dcx(),
field.span,
expr_t,
E0615,
"attempted to take value of method `{field}` on type `{expr_t}`",
);
err.span_label(field.span, "method, not a field");
let expr_is_call =
if let hir::Node::Expr(hir::Expr { kind: ExprKind::Call(callee, _args), .. }) =
self.tcx.parent_hir_node(expr.hir_id)
{
expr.hir_id == callee.hir_id
} else {
false
};
let expr_snippet =
self.tcx.sess.source_map().span_to_snippet(expr.span).unwrap_or_default();
let is_wrapped = expr_snippet.starts_with('(') && expr_snippet.ends_with(')');
let after_open = expr.span.lo() + rustc_span::BytePos(1);
let before_close = expr.span.hi() - rustc_span::BytePos(1);
if expr_is_call && is_wrapped {
err.multipart_suggestion(
"remove wrapping parentheses to call the method",
vec![
(expr.span.with_hi(after_open), String::new()),
(expr.span.with_lo(before_close), String::new()),
],
Applicability::MachineApplicable,
);
} else if !self.expr_in_place(expr.hir_id) {
// Suggest call parentheses inside the wrapping parentheses
let span = if is_wrapped {
expr.span.with_lo(after_open).with_hi(before_close)
} else {
expr.span
};
self.suggest_method_call(
&mut err,
"use parentheses to call the method",
field,
expr_t,
expr,
Some(span),
);
} else if let ty::RawPtr(ptr_ty, _) = expr_t.kind()
&& let ty::Adt(adt_def, _) = ptr_ty.kind()
&& let ExprKind::Field(base_expr, _) = expr.kind
&& adt_def.variants().len() == 1
&& adt_def
.variants()
.iter()
.next()
.unwrap()
.fields
.iter()
.any(|f| f.ident(self.tcx) == field)
{
err.multipart_suggestion(
"to access the field, dereference first",
vec![
(base_expr.span.shrink_to_lo(), "(*".to_string()),
(base_expr.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MaybeIncorrect,
);
} else {
err.help("methods are immutable and cannot be assigned to");
}
err.emit()
}
fn point_at_param_definition(&self, err: &mut Diag<'_>, param: ty::ParamTy) {
let generics = self.tcx.generics_of(self.body_id);
let generic_param = generics.type_param(param, self.tcx);
if let ty::GenericParamDefKind::Type { synthetic: true, .. } = generic_param.kind {
return;
}
let param_def_id = generic_param.def_id;
let param_hir_id = match param_def_id.as_local() {
Some(x) => self.tcx.local_def_id_to_hir_id(x),
None => return,
};
let param_span = self.tcx.hir().span(param_hir_id);
let param_name = self.tcx.hir().ty_param_name(param_def_id.expect_local());
err.span_label(param_span, format!("type parameter '{param_name}' declared here"));
}
fn maybe_suggest_array_indexing(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
base: &hir::Expr<'_>,
field: Ident,
len: ty::Const<'tcx>,
) {
err.span_label(field.span, "unknown field");
if let (Some(len), Ok(user_index)) =
(len.try_eval_target_usize(self.tcx, self.param_env), field.as_str().parse::<u64>())
&& let Ok(base) = self.tcx.sess.source_map().span_to_snippet(base.span)
{
let help = "instead of using tuple indexing, use array indexing";
let suggestion = format!("{base}[{field}]");
let applicability = if len < user_index {
Applicability::MachineApplicable
} else {
Applicability::MaybeIncorrect
};
err.span_suggestion(expr.span, help, suggestion, applicability);
}
}
fn suggest_first_deref_field(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
base: &hir::Expr<'_>,
field: Ident,
) {
err.span_label(field.span, "unknown field");
if let Ok(base) = self.tcx.sess.source_map().span_to_snippet(base.span) {
let msg = format!("`{base}` is a raw pointer; try dereferencing it");
let suggestion = format!("(*{base}).{field}");
err.span_suggestion(expr.span, msg, suggestion, Applicability::MaybeIncorrect);
}
}
fn no_such_field_err(&self, field: Ident, expr_t: Ty<'tcx>, id: HirId) -> Diag<'_> {
let span = field.span;
debug!("no_such_field_err(span: {:?}, field: {:?}, expr_t: {:?})", span, field, expr_t);
let mut err = type_error_struct!(
self.dcx(),
span,
expr_t,
E0609,
"no field `{field}` on type `{expr_t}`",
);
// try to add a suggestion in case the field is a nested field of a field of the Adt
let mod_id = self.tcx.parent_module(id).to_def_id();
let (ty, unwrap) = if let ty::Adt(def, args) = expr_t.kind()
&& (self.tcx.is_diagnostic_item(sym::Result, def.did())
|| self.tcx.is_diagnostic_item(sym::Option, def.did()))
&& let Some(arg) = args.get(0)
&& let Some(ty) = arg.as_type()
{
(ty, "unwrap().")
} else {
(expr_t, "")
};
for (found_fields, args) in
self.get_field_candidates_considering_privacy(span, ty, mod_id, id)
{
let field_names = found_fields.iter().map(|field| field.name).collect::<Vec<_>>();
let mut candidate_fields: Vec<_> = found_fields
.into_iter()
.filter_map(|candidate_field| {
self.check_for_nested_field_satisfying(
span,
&|candidate_field, _| candidate_field.ident(self.tcx()) == field,
candidate_field,
args,
vec![],
mod_id,
id,
)
})
.map(|mut field_path| {
field_path.pop();
field_path
.iter()
.map(|id| format!("{}.", id.name.to_ident_string()))
.collect::<String>()
})
.collect::<Vec<_>>();
candidate_fields.sort();
let len = candidate_fields.len();
if len > 0 {
err.span_suggestions(
field.span.shrink_to_lo(),
format!(
"{} of the expressions' fields {} a field of the same name",
if len > 1 { "some" } else { "one" },
if len > 1 { "have" } else { "has" },
),
candidate_fields.iter().map(|path| format!("{unwrap}{path}")),
Applicability::MaybeIncorrect,
);
} else {
if let Some(field_name) = find_best_match_for_name(&field_names, field.name, None) {
err.span_suggestion_verbose(
field.span,
"a field with a similar name exists",
format!("{unwrap}{}", field_name),
Applicability::MaybeIncorrect,
);
} else if !field_names.is_empty() {
let is = if field_names.len() == 1 { " is" } else { "s are" };
err.note(format!(
"available field{is}: {}",
self.name_series_display(field_names),
));
}
}
}
err
}
fn private_field_err(&self, field: Ident, base_did: DefId) -> Diag<'_> {
let struct_path = self.tcx().def_path_str(base_did);
let kind_name = self.tcx().def_descr(base_did);
struct_span_code_err!(
self.dcx(),
field.span,
E0616,
"field `{field}` of {kind_name} `{struct_path}` is private",
)
.with_span_label(field.span, "private field")
}
pub(crate) fn get_field_candidates_considering_privacy(
&self,
span: Span,
base_ty: Ty<'tcx>,
mod_id: DefId,
hir_id: HirId,
) -> Vec<(Vec<&'tcx ty::FieldDef>, GenericArgsRef<'tcx>)> {
debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty);
self.autoderef(span, base_ty)
.filter_map(move |(base_t, _)| {
match base_t.kind() {
ty::Adt(base_def, args) if !base_def.is_enum() => {
let tcx = self.tcx;
let fields = &base_def.non_enum_variant().fields;
// Some struct, e.g. some that impl `Deref`, have all private fields
// because you're expected to deref them to access the _real_ fields.
// This, for example, will help us suggest accessing a field through a `Box<T>`.
if fields.iter().all(|field| !field.vis.is_accessible_from(mod_id, tcx)) {
return None;
}
return Some((
fields
.iter()
.filter(move |field| {
field.vis.is_accessible_from(mod_id, tcx)
&& self.is_field_suggestable(field, hir_id, span)
})
// For compile-time reasons put a limit on number of fields we search
.take(100)
.collect::<Vec<_>>(),
*args,
));
}
_ => None,
}
})
.collect()
}
/// This method is called after we have encountered a missing field error to recursively
/// search for the field
pub(crate) fn check_for_nested_field_satisfying(
&self,
span: Span,
matches: &impl Fn(&ty::FieldDef, Ty<'tcx>) -> bool,
candidate_field: &ty::FieldDef,
subst: GenericArgsRef<'tcx>,
mut field_path: Vec<Ident>,
mod_id: DefId,
hir_id: HirId,
) -> Option<Vec<Ident>> {
debug!(
"check_for_nested_field_satisfying(span: {:?}, candidate_field: {:?}, field_path: {:?}",
span, candidate_field, field_path
);
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 {
field_path.push(candidate_field.ident(self.tcx).normalize_to_macros_2_0());
let field_ty = candidate_field.ty(self.tcx, subst);
if matches(candidate_field, field_ty) {
return Some(field_path);
} else {
for (nested_fields, subst) in
self.get_field_candidates_considering_privacy(span, field_ty, mod_id, hir_id)
{
// recursively search fields of `candidate_field` if it's a ty::Adt
for field in nested_fields {
if let Some(field_path) = self.check_for_nested_field_satisfying(
span,
matches,
field,
subst,
field_path.clone(),
mod_id,
hir_id,
) {
return Some(field_path);
}
}
}
}
None
}
}
fn check_expr_index(
&self,
base: &'tcx hir::Expr<'tcx>,
idx: &'tcx hir::Expr<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
brackets_span: Span,
) -> Ty<'tcx> {
let base_t = self.check_expr(base);
let idx_t = self.check_expr(idx);
if base_t.references_error() {
base_t
} else if idx_t.references_error() {
idx_t
} else {
let base_t = self.structurally_resolve_type(base.span, base_t);
match self.lookup_indexing(expr, base, base_t, idx, idx_t) {
Some((index_ty, element_ty)) => {
// two-phase not needed because index_ty is never mutable
self.demand_coerce(idx, idx_t, index_ty, None, AllowTwoPhase::No);
self.select_obligations_where_possible(|errors| {
self.point_at_index(errors, idx.span);
});
element_ty
}
None => {
// Attempt to *shallowly* search for an impl which matches,
// but has nested obligations which are unsatisfied.
for (base_t, _) in self.autoderef(base.span, base_t).silence_errors() {
if let Some((_, index_ty, element_ty)) =
self.find_and_report_unsatisfied_index_impl(base, base_t)
{
self.demand_coerce(idx, idx_t, index_ty, None, AllowTwoPhase::No);
return element_ty;
}
}
let mut err = type_error_struct!(
self.dcx(),
brackets_span,
base_t,
E0608,
"cannot index into a value of type `{base_t}`",
);
// Try to give some advice about indexing tuples.
if let ty::Tuple(types) = base_t.kind() {
let mut needs_note = true;
// If the index is an integer, we can show the actual
// fixed expression:
if let ExprKind::Lit(lit) = idx.kind
&& let ast::LitKind::Int(i, ast::LitIntType::Unsuffixed) = lit.node
&& i.get()
< types
.len()
.try_into()
.expect("expected tuple index to be < usize length")
{
err.span_suggestion(
brackets_span,
"to access tuple elements, use",
format!(".{i}"),
Applicability::MachineApplicable,
);
needs_note = false;
} else if let ExprKind::Path(..) = idx.peel_borrows().kind {
err.span_label(
idx.span,
"cannot access tuple elements at a variable index",
);
}
if needs_note {
err.help(
"to access tuple elements, use tuple indexing \
syntax (e.g., `tuple.0`)",
);
}
}
if base_t.is_unsafe_ptr() && idx_t.is_integral() {
err.multipart_suggestion(
"consider using `wrapping_add` or `add` for indexing into raw pointer",
vec![
(base.span.between(idx.span), ".wrapping_add(".to_owned()),
(
idx.span.shrink_to_hi().until(expr.span.shrink_to_hi()),
")".to_owned(),
),
],
Applicability::MaybeIncorrect,
);
}
let reported = err.emit();
Ty::new_error(self.tcx, reported)
}
}
}
}
/// Try to match an implementation of `Index` against a self type, and report
/// the unsatisfied predicates that result from confirming this impl.
///
/// Given an index expression, sometimes the `Self` type shallowly but does not
/// deeply satisfy an impl predicate. Instead of simply saying that the type
/// does not support being indexed, we want to point out exactly what nested
/// predicates cause this to be, so that the user can add them to fix their code.
fn find_and_report_unsatisfied_index_impl(
&self,
base_expr: &hir::Expr<'_>,
base_ty: Ty<'tcx>,
) -> Option<(ErrorGuaranteed, Ty<'tcx>, Ty<'tcx>)> {
let index_trait_def_id = self.tcx.lang_items().index_trait()?;
let index_trait_output_def_id = self.tcx.get_diagnostic_item(sym::IndexOutput)?;
let mut relevant_impls = vec![];
self.tcx.for_each_relevant_impl(index_trait_def_id, base_ty, |impl_def_id| {
relevant_impls.push(impl_def_id);
});
let [impl_def_id] = relevant_impls[..] else {
// Only report unsatisfied impl predicates if there's one impl
return None;
};
self.commit_if_ok(|snapshot| {
let outer_universe = self.universe();
let ocx = ObligationCtxt::new(self);
let impl_args = self.fresh_args_for_item(base_expr.span, impl_def_id);
let impl_trait_ref =
self.tcx.impl_trait_ref(impl_def_id).unwrap().instantiate(self.tcx, impl_args);
let cause = self.misc(base_expr.span);
// Match the impl self type against the base ty. If this fails,
// we just skip this impl, since it's not particularly useful.
let impl_trait_ref = ocx.normalize(&cause, self.param_env, impl_trait_ref);
ocx.eq(&cause, self.param_env, base_ty, impl_trait_ref.self_ty())?;
// Register the impl's predicates. One of these predicates
// must be unsatisfied, or else we wouldn't have gotten here
// in the first place.
ocx.register_obligations(traits::predicates_for_generics(
|idx, span| {
cause.clone().derived_cause(
ty::Binder::dummy(ty::TraitPredicate {
trait_ref: impl_trait_ref,
polarity: ty::PredicatePolarity::Positive,
}),
|derived| {
traits::ImplDerivedObligation(Box::new(
traits::ImplDerivedObligationCause {
derived,
impl_or_alias_def_id: impl_def_id,
impl_def_predicate_index: Some(idx),
span,
},
))
},
)
},
self.param_env,
self.tcx.predicates_of(impl_def_id).instantiate(self.tcx, impl_args),
));
// Normalize the output type, which we can use later on as the
// return type of the index expression...
let element_ty = ocx.normalize(
&cause,
self.param_env,
Ty::new_projection(self.tcx, index_trait_output_def_id, impl_trait_ref.args),
);
let true_errors = ocx.select_where_possible();
// Do a leak check -- we can't really report report a useful error here,
// but it at least avoids an ICE when the error has to do with higher-ranked
// lifetimes.
self.leak_check(outer_universe, Some(snapshot))?;
// Bail if we have ambiguity errors, which we can't report in a useful way.
let ambiguity_errors = ocx.select_all_or_error();
if true_errors.is_empty() && !ambiguity_errors.is_empty() {
return Err(NoSolution);
}
// There should be at least one error reported. If not, we
// will still delay a span bug in `report_fulfillment_errors`.
Ok::<_, NoSolution>((
self.err_ctxt().report_fulfillment_errors(true_errors),
impl_trait_ref.args.type_at(1),
element_ty,
))
})
.ok()
}
fn point_at_index(&self, errors: &mut Vec<traits::FulfillmentError<'tcx>>, span: Span) {
let mut seen_preds = FxHashSet::default();
// We re-sort here so that the outer most root obligations comes first, as we have the
// subsequent weird logic to identify *every* relevant obligation for proper deduplication
// of diagnostics.
errors.sort_by_key(|error| error.root_obligation.recursion_depth);
for error in errors {
match (
error.root_obligation.predicate.kind().skip_binder(),
error.obligation.predicate.kind().skip_binder(),
) {
(ty::PredicateKind::Clause(ty::ClauseKind::Trait(predicate)), _)
if self.tcx.lang_items().index_trait() == Some(predicate.trait_ref.def_id) =>
{
seen_preds.insert(error.obligation.predicate.kind().skip_binder());
}
(_, ty::PredicateKind::Clause(ty::ClauseKind::Trait(predicate)))
if self.tcx.is_diagnostic_item(sym::SliceIndex, predicate.trait_ref.def_id) =>
{
seen_preds.insert(error.obligation.predicate.kind().skip_binder());
}
(root, pred) if seen_preds.contains(&pred) || seen_preds.contains(&root) => {}
_ => continue,
}
error.obligation.cause.span = span;
}
}
fn check_expr_yield(
&self,
value: &'tcx hir::Expr<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
match self.coroutine_types {
Some(CoroutineTypes { resume_ty, yield_ty }) => {
self.check_expr_coercible_to_type(value, yield_ty, None);
resume_ty
}
_ => {
self.dcx().emit_err(YieldExprOutsideOfCoroutine { span: expr.span });
// Avoid expressions without types during writeback (#78653).
self.check_expr(value);
self.tcx.types.unit
}
}
}
fn check_expr_asm_operand(&self, expr: &'tcx hir::Expr<'tcx>, is_input: bool) {
let needs = if is_input { Needs::None } else { Needs::MutPlace };
let ty = self.check_expr_with_needs(expr, needs);
self.require_type_is_sized(ty, expr.span, traits::InlineAsmSized);
if !is_input && !expr.is_syntactic_place_expr() {
self.dcx()
.struct_span_err(expr.span, "invalid asm output")
.with_span_label(expr.span, "cannot assign to this expression")
.emit();
}
// If this is an input value, we require its type to be fully resolved
// at this point. This allows us to provide helpful coercions which help
// pass the type candidate list in a later pass.
//
// We don't require output types to be resolved at this point, which
// allows them to be inferred based on how they are used later in the
// function.
if is_input {
let ty = self.structurally_resolve_type(expr.span, ty);
match *ty.kind() {
ty::FnDef(..) => {
let fnptr_ty = Ty::new_fn_ptr(self.tcx, ty.fn_sig(self.tcx));
self.demand_coerce(expr, ty, fnptr_ty, None, AllowTwoPhase::No);
}
ty::Ref(_, base_ty, mutbl) => {
let ptr_ty = Ty::new_ptr(self.tcx, base_ty, mutbl);
self.demand_coerce(expr, ty, ptr_ty, None, AllowTwoPhase::No);
}
_ => {}
}
}
}
fn check_expr_asm(&self, asm: &'tcx hir::InlineAsm<'tcx>) -> Ty<'tcx> {
let mut diverge = asm.options.contains(ast::InlineAsmOptions::NORETURN);
for (op, _op_sp) in asm.operands {
match op {
hir::InlineAsmOperand::In { expr, .. } => {
self.check_expr_asm_operand(expr, true);
}
hir::InlineAsmOperand::Out { expr: Some(expr), .. }
| hir::InlineAsmOperand::InOut { expr, .. } => {
self.check_expr_asm_operand(expr, false);
}
hir::InlineAsmOperand::Out { expr: None, .. } => {}
hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => {
self.check_expr_asm_operand(in_expr, true);
if let Some(out_expr) = out_expr {
self.check_expr_asm_operand(out_expr, false);
}
}
// `AnonConst`s have their own body and is type-checked separately.
// As they don't flow into the type system we don't need them to
// be well-formed.
hir::InlineAsmOperand::Const { .. } | hir::InlineAsmOperand::SymFn { .. } => {}
hir::InlineAsmOperand::SymStatic { .. } => {}
hir::InlineAsmOperand::Label { block } => {
let previous_diverges = self.diverges.get();
// The label blocks should have unit return value or diverge.
let ty =
self.check_block_with_expected(block, ExpectHasType(self.tcx.types.unit));
if !ty.is_never() {
self.demand_suptype(block.span, self.tcx.types.unit, ty);
diverge = false;
}
// We need this to avoid false unreachable warning when a label diverges.
self.diverges.set(previous_diverges);
}
}
}
if diverge { self.tcx.types.never } else { self.tcx.types.unit }
}
fn check_offset_of(
&self,
container: &'tcx hir::Ty<'tcx>,
fields: &[Ident],
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
let container = self.lower_ty(container).normalized;
if let Some(ident_2) = fields.get(1)
&& !self.tcx.features().offset_of_nested
{
rustc_session::parse::feature_err(
&self.tcx.sess,
sym::offset_of_nested,
ident_2.span,
"only a single ident or integer is stable as the field in offset_of",
)
.emit();
}
let mut field_indices = Vec::with_capacity(fields.len());
let mut current_container = container;
let mut fields = fields.into_iter();
while let Some(&field) = fields.next() {
let container = self.structurally_resolve_type(expr.span, current_container);
match container.kind() {
ty::Adt(container_def, args) if container_def.is_enum() => {
let block = self.tcx.local_def_id_to_hir_id(self.body_id);
let (ident, _def_scope) =
self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block);
if !self.tcx.features().offset_of_enum {
rustc_session::parse::feature_err(
&self.tcx.sess,
sym::offset_of_enum,
ident.span,
"using enums in offset_of is experimental",
)
.emit();
}
let Some((index, variant)) = container_def
.variants()
.iter_enumerated()
.find(|(_, v)| v.ident(self.tcx).normalize_to_macros_2_0() == ident)
else {
type_error_struct!(
self.dcx(),
ident.span,
container,
E0599,
"no variant named `{ident}` found for enum `{container}`",
)
.with_span_label(field.span, "variant not found")
.emit();
break;
};
let Some(&subfield) = fields.next() else {
type_error_struct!(
self.dcx(),
ident.span,
container,
E0795,
"`{ident}` is an enum variant; expected field at end of `offset_of`",
)
.with_span_label(field.span, "enum variant")
.emit();
break;
};
let (subident, sub_def_scope) =
self.tcx.adjust_ident_and_get_scope(subfield, variant.def_id, block);
let Some((subindex, field)) = variant
.fields
.iter_enumerated()
.find(|(_, f)| f.ident(self.tcx).normalize_to_macros_2_0() == subident)
else {
type_error_struct!(
self.dcx(),
ident.span,
container,
E0609,
"no field named `{subfield}` on enum variant `{container}::{ident}`",
)
.with_span_label(field.span, "this enum variant...")
.with_span_label(subident.span, "...does not have this field")
.emit();
break;
};
let field_ty = self.field_ty(expr.span, field, args);
// FIXME: DSTs with static alignment should be allowed
self.require_type_is_sized(field_ty, expr.span, traits::MiscObligation);
if field.vis.is_accessible_from(sub_def_scope, self.tcx) {
self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span, None);
} else {
self.private_field_err(ident, container_def.did()).emit();
}
// Save the index of all fields regardless of their visibility in case
// of error recovery.
field_indices.push((index, subindex));
current_container = field_ty;
continue;
}
ty::Adt(container_def, args) => {
let block = self.tcx.local_def_id_to_hir_id(self.body_id);
let (ident, def_scope) =
self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block);
let fields = &container_def.non_enum_variant().fields;
if let Some((index, field)) = fields
.iter_enumerated()
.find(|(_, f)| f.ident(self.tcx).normalize_to_macros_2_0() == ident)
{
let field_ty = self.field_ty(expr.span, field, args);
// FIXME: DSTs with static alignment should be allowed
self.require_type_is_sized(field_ty, expr.span, traits::MiscObligation);
if field.vis.is_accessible_from(def_scope, self.tcx) {
self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span, None);
} else {
self.private_field_err(ident, container_def.did()).emit();
}
// Save the index of all fields regardless of their visibility in case
// of error recovery.
field_indices.push((FIRST_VARIANT, index));
current_container = field_ty;
continue;
}
}
ty::Tuple(tys) => {
if let Ok(index) = field.as_str().parse::<usize>()
&& field.name == sym::integer(index)
{
for ty in tys.iter().take(index + 1) {
self.require_type_is_sized(ty, expr.span, traits::MiscObligation);
}
if let Some(&field_ty) = tys.get(index) {
field_indices.push((FIRST_VARIANT, index.into()));
current_container = field_ty;
continue;
}
}
}
_ => (),
};
self.no_such_field_err(field, container, expr.hir_id).emit();
break;
}
self.typeck_results
.borrow_mut()
.offset_of_data_mut()
.insert(expr.hir_id, (container, field_indices));
self.tcx.types.usize
}
}