use rustc_ast as ast;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::stack::ensure_sufficient_stack;
-use rustc_errors::Diagnostic;
-use rustc_errors::EmissionGuarantee;
-use rustc_errors::ErrorGuaranteed;
-use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, DiagnosticId};
+use rustc_errors::{
+ pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, DiagnosticId,
+ EmissionGuarantee, ErrorGuaranteed,
+};
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_infer::infer;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::InferOk;
+use rustc_infer::traits::ObligationCause;
use rustc_middle::middle::stability;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
-use rustc_middle::ty::error::ExpectedFound;
-use rustc_middle::ty::error::TypeError::{FieldMisMatch, Sorts};
+use rustc_middle::ty::error::TypeError::FieldMisMatch;
use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TypeFoldable};
use rustc_session::parse::feature_err;
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::{BytePos, Pos};
+use rustc_target::spec::abi::Abi::RustIntrinsic;
+use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::{self, ObligationCauseCode};
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&self,
expr: &'tcx hir::Expr<'tcx>,
expected: Ty<'tcx>,
- extend_err: impl Fn(&mut Diagnostic),
+ extend_err: impl FnMut(&mut Diagnostic),
) -> Ty<'tcx> {
self.check_expr_meets_expectation_or_error(expr, ExpectHasType(expected), extend_err)
}
&self,
expr: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
- extend_err: impl Fn(&mut Diagnostic),
+ mut extend_err: impl FnMut(&mut Diagnostic),
) -> Ty<'tcx> {
let expected_ty = expected.to_option(&self).unwrap_or(self.tcx.types.bool);
let mut ty = self.check_expr_with_expectation(expr, expected);
self.check_lang_item_path(lang_item, expr, hir_id)
}
ExprKind::Path(ref qpath) => self.check_expr_path(qpath, expr, &[]),
- ExprKind::InlineAsm(asm) => self.check_expr_asm(asm),
+ 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::Break(destination, ref expr_opt) => {
self.check_expr_break(destination, expr_opt.as_deref(), expr)
}
ExprKind::Match(discrim, arms, match_src) => {
self.check_match(expr, &discrim, arms, expected, match_src)
}
- ExprKind::Closure(capture, decl, body_id, _, gen) => {
- self.check_expr_closure(expr, capture, &decl, body_id, gen, expected)
+ ExprKind::Closure { capture_clause, fn_decl, body, movability, .. } => {
+ self.check_expr_closure(expr, capture_clause, &fn_decl, body, movability, expected)
}
ExprKind::Block(body, _) => self.check_block_with_expected(&body, expected),
ExprKind::Call(callee, args) => self.check_call(expr, &callee, args, expected),
_ => self.instantiate_value_path(segs, opt_ty, res, expr.span, expr.hir_id).0,
};
- if let ty::FnDef(..) = ty.kind() {
+ if let ty::FnDef(did, ..) = *ty.kind() {
let fn_sig = ty.fn_sig(tcx);
+ if tcx.fn_sig(did).abi() == RustIntrinsic && tcx.item_name(did) == sym::transmute {
+ let from = fn_sig.inputs().skip_binder()[0];
+ 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.span));
+ }
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.
// 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
- .replace_bound_vars_with_fresh_vars(
- span,
- infer::LateBoundRegionConversionTime::FnCall,
- fn_sig.input(i),
- )
- .0;
+ let input = self.replace_bound_vars_with_fresh_vars(
+ span,
+ infer::LateBoundRegionConversionTime::FnCall,
+ fn_sig.input(i),
+ );
self.require_type_is_sized_deferred(
input,
span,
// 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
- .replace_bound_vars_with_fresh_vars(
- expr.span,
- infer::LateBoundRegionConversionTime::FnCall,
- fn_sig.output(),
- )
- .0;
+ let output = self.replace_bound_vars_with_fresh_vars(
+ expr.span,
+ infer::LateBoundRegionConversionTime::FnCall,
+ fn_sig.output(),
+ );
self.require_type_is_sized_deferred(output, expr.span, traits::SizedReturnType);
}
return_expr,
return_expr_ty,
);
+
+ if self.return_type_has_opaque {
+ // Point any obligations that were registered due to opaque type
+ // inference at the return expression.
+ self.select_obligations_where_possible(false, |errors| {
+ self.point_at_return_for_opaque_ty_error(errors, span, return_expr_ty);
+ });
+ }
+ }
+
+ fn point_at_return_for_opaque_ty_error(
+ &self,
+ errors: &mut Vec<traits::FulfillmentError<'tcx>>,
+ span: Span,
+ return_expr_ty: Ty<'tcx>,
+ ) {
+ // Don't point at the whole block if it's empty
+ if span == self.tcx.hir().span(self.body_id) {
+ 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(
lhs: &'tcx hir::Expr<'tcx>,
err_code: &'static str,
op_span: Span,
+ adjust_err: impl FnOnce(&mut DiagnosticBuilder<'tcx, ErrorGuaranteed>),
) {
if lhs.is_syntactic_place_expr() {
return;
err.span_suggestion_verbose(
expr.span.shrink_to_lo(),
"you might have meant to use pattern destructuring",
- "let ".to_string(),
+ "let ",
Applicability::MachineApplicable,
);
});
+ adjust_err(&mut err);
+
err.emit();
}
} else {
(Applicability::MaybeIncorrect, false)
};
- if !lhs.is_syntactic_place_expr() && !matches!(lhs.kind, hir::ExprKind::Lit(_)) {
+ 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.
let hir = self.tcx.hir();
if let hir::Node::Expr(hir::Expr { kind: ExprKind::If { .. }, .. }) =
err.span_suggestion_verbose(
expr.span.shrink_to_lo(),
"you might have meant to use pattern matching",
- "let ".to_string(),
+ "let ",
applicability,
);
};
err.span_suggestion_verbose(
span,
"you might have meant to compare for equality",
- "==".to_string(),
+ "==",
applicability,
);
}
return self.tcx.ty_error();
}
- self.check_lhs_assignable(lhs, "E0070", span);
-
let lhs_ty = self.check_expr_with_needs(&lhs, Needs::MutPlace);
- let rhs_ty = self.check_expr_coercable_to_type(&rhs, lhs_ty, Some(lhs));
+
+ let suggest_deref_binop = |err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
+ 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.lang_items().sized_trait().unwrap(),
+ lhs_deref_ty,
+ ty::List::empty(),
+ 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,
+ );
+ }
+ }
+ };
+
+ self.check_lhs_assignable(lhs, "E0070", span, |err| {
+ let rhs_ty = self.check_expr(&rhs);
+ suggest_deref_binop(err, rhs_ty);
+ });
+
+ // This is (basically) inlined `check_expr_coercable_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.require_type_is_sized(lhs_ty, lhs.span, traits::AssignmentLhsSized);
}
}
- fn check_expr_let(&self, let_expr: &'tcx hir::Let<'tcx>) -> Ty<'tcx> {
+ pub(super) fn check_expr_let(&self, let_expr: &'tcx hir::Let<'tcx>) -> 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");
) {
let tcx = self.tcx;
- let adt_ty_hint = self
- .expected_inputs_for_expected_output(span, expected, adt_ty, &[adt_ty])
- .get(0)
- .cloned()
- .unwrap_or(adt_ty);
+ 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 (substs, adt_kind, kind_name) = match adt_ty.kind() {
- ty::Adt(adt, substs) => (substs, adt.adt_kind(), adt.variant_descr()),
- _ => span_bug!(span, "non-ADT passed to check_expr_struct_fields"),
+ let ty::Adt(adt, substs) = 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
});
} else {
self.report_unknown_field(
- adt_ty, variant, field, ast_fields, kind_name, expr_span,
+ adt_ty,
+ variant,
+ field,
+ ast_fields,
+ adt.variant_descr(),
+ expr_span,
);
}
}
// Make sure the programmer specified correct number of fields.
- if kind_name == "union" {
+ if adt_kind == AdtKind::Union {
if ast_fields.len() != 1 {
struct_span_err!(
tcx.sess,
// 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 {
- let base_ty = self.check_expr(base_expr);
- match adt_ty.kind() {
- ty::Adt(adt, substs) if adt.is_struct() => {
- match base_ty.kind() {
- ty::Adt(base_adt, base_subs) if adt == base_adt => {
- variant
- .fields
- .iter()
- .map(|f| {
- let fru_ty = self.normalize_associated_types_in(
- expr_span,
- self.field_ty(base_expr.span, f, base_subs),
- );
- 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, substs);
- let cause = self.misc(base_expr.span);
- match self
- .at(&cause, self.param_env)
- .sup(target_ty, fru_ty)
- {
- Ok(InferOk { obligations, value: () }) => {
- self.register_predicates(obligations)
- }
- // FIXME: Need better diagnostics for `FieldMisMatch` error
- Err(_) => {
- self.report_mismatched_types(
- &cause,
- target_ty,
- fru_ty,
- FieldMisMatch(variant.name, ident.name),
- )
- .emit();
- }
- }
- }
- fru_ty
- })
- .collect()
- }
- _ => {
- self.report_mismatched_types(
- &self.misc(base_expr.span),
- adt_ty,
- base_ty,
- Sorts(ExpectedFound::new(true, adt_ty, base_ty)),
- )
- .emit();
- return;
+ if adt.is_struct() {
+ // Make some fresh substitutions for our ADT type.
+ let fresh_substs = self.fresh_substs_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_associated_types_in(
+ expr_span,
+ self.field_ty(base_expr.span, f, fresh_substs),
+ );
+ 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, substs);
+ let cause = self.misc(base_expr.span);
+ match self.at(&cause, self.param_env).sup(target_ty, fru_ty) {
+ Ok(InferOk { obligations, value: () }) => {
+ self.register_predicates(obligations)
+ }
+ Err(_) => {
+ // This should never happen, since we're just subtyping the
+ // remaining_fields, but it's fine to emit this, I guess.
+ self.report_mismatched_types(
+ &cause,
+ target_ty,
+ fru_ty,
+ FieldMisMatch(variant.name, ident.name),
+ )
+ .emit();
+ }
+ }
}
- }
- }
- _ => {
- self.tcx
- .sess
- .emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
- return;
- }
+ self.resolve_vars_if_possible(fru_ty)
+ })
+ .collect();
+ // The use of fresh substs 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 = self.tcx.mk_adt(*adt, fresh_substs);
+ 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.tcx
+ .sess
+ .emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span });
+ return;
}
} else {
self.check_expr_has_type_or_error(base_expr, adt_ty, |_| {
}
};
self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr_id, fru_tys);
- } else if kind_name != "union" && !remaining_fields.is_empty() {
- let inaccessible_remaining_fields = remaining_fields.iter().any(|(_, (_, field))| {
- !field.vis.is_accessible_from(tcx.parent_module(expr_id).to_def_id(), tcx)
- });
+ } 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_id).to_def_id(), tcx)
+ })
+ .collect();
- if inaccessible_remaining_fields {
- self.report_inaccessible_fields(adt_ty, span);
+ if !private_fields.is_empty() {
+ self.report_private_fields(adt_ty, span, private_fields, ast_fields);
} else {
self.report_missing_fields(
adt_ty,
err.span_suggestion(
range_start.span.shrink_to_hi(),
&format!("to set the remaining fields{instead}, separate the last named field with a comma"),
- ",".to_string(),
+ ",",
Applicability::MaybeIncorrect,
);
}
/// Report an error for a struct field expression when there are invisible fields.
///
/// ```text
- /// error: cannot construct `Foo` with struct literal syntax due to inaccessible fields
+ /// error: cannot construct `Foo` with struct literal syntax due to private fields
/// --> src/main.rs:8:5
/// |
/// 8 | foo::Foo {};
///
/// error: aborting due to previous error
/// ```
- fn report_inaccessible_fields(&self, adt_ty: Ty<'tcx>, span: Span) {
- self.tcx.sess.span_err(
+ fn report_private_fields(
+ &self,
+ adt_ty: Ty<'tcx>,
+ span: Span,
+ private_fields: Vec<&ty::FieldDef>,
+ used_fields: &'tcx [hir::ExprField<'tcx>],
+ ) {
+ let mut err = self.tcx.sess.struct_span_err(
span,
&format!(
- "cannot construct `{adt_ty}` with struct literal syntax due to inaccessible fields",
+ "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.to_string())
+ .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(", "))
+ }
+ [] => unreachable!(),
+ };
+ err.note(format!(
+ "... and other private field{s} {names}that {were} not provided",
+ s = pluralize!(remaining_private_fields_len),
+ were = pluralize!("was", remaining_private_fields_len),
+ ));
+ }
+ err.emit();
}
fn report_unknown_field(
err.span_suggestion(
field.ident.span,
"a field with a similar name exists",
- field_name.to_string(),
+ field_name,
Applicability::MaybeIncorrect,
);
} else {
err.span_suggestion_verbose(
base.span.shrink_to_hi(),
"consider `await`ing on the `Future` and access the field of its `Output`",
- ".await".to_string(),
+ ".await",
Applicability::MaybeIncorrect,
);
}
err.span_suggestion(
field.span,
"a field with a similar name exists",
- suggested_field_name.to_string(),
+ suggested_field_name,
Applicability::MaybeIncorrect,
);
} else {
err
}
- crate fn get_field_candidates(
+ pub(crate) fn get_field_candidates(
&self,
span: Span,
base_t: Ty<'tcx>,
- ) -> Option<(&Vec<ty::FieldDef>, SubstsRef<'tcx>)> {
+ ) -> Option<(&[ty::FieldDef], SubstsRef<'tcx>)> {
debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_t);
for (base_t, _) in self.autoderef(span, base_t) {
/// This method is called after we have encountered a missing field error to recursively
/// search for the field
- crate fn check_for_nested_field_satisfying(
+ pub(crate) fn check_for_nested_field_satisfying(
&self,
span: Span,
matches: &impl Fn(&ty::FieldDef, Ty<'tcx>) -> bool,
self.check_expr_asm_operand(out_expr, false);
}
}
- hir::InlineAsmOperand::Const { anon_const }
- | hir::InlineAsmOperand::SymFn { anon_const } => {
- self.to_const(anon_const);
- }
+ // `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 { .. } => {}
}
}