typeck/expr.rs: move check_field + struct helpers here.
This commit is contained in:
parent
5ee36b7eca
commit
5057552dc6
2 changed files with 412 additions and 408 deletions
|
|
@ -15,12 +15,15 @@ use crate::check::TupleArgumentsFlag::DontTupleArguments;
|
|||
use crate::check::method::SelfSource;
|
||||
use crate::middle::lang_items;
|
||||
use crate::util::common::ErrorReported;
|
||||
use crate::util::nodemap::FxHashMap;
|
||||
use crate::astconv::AstConv as _;
|
||||
|
||||
use errors::Applicability;
|
||||
use errors::{Applicability, DiagnosticBuilder};
|
||||
use syntax::ast;
|
||||
use syntax::ptr::P;
|
||||
use syntax::symbol::{kw, sym};
|
||||
use syntax::symbol::{Symbol, LocalInternedString, kw, sym};
|
||||
use syntax::source_map::Span;
|
||||
use syntax::util::lev_distance::find_best_match_for_name;
|
||||
use rustc::hir;
|
||||
use rustc::hir::{ExprKind, QPath};
|
||||
use rustc::hir::def::{CtorKind, Res, DefKind};
|
||||
|
|
@ -31,11 +34,14 @@ use rustc::ty;
|
|||
use rustc::ty::adjustment::{
|
||||
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
|
||||
};
|
||||
use rustc::ty::{AdtKind, Visibility};
|
||||
use rustc::ty::Ty;
|
||||
use rustc::ty::TypeFoldable;
|
||||
use rustc::ty::subst::InternalSubsts;
|
||||
use rustc::traits::{self, ObligationCauseCode};
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
fn check_expr_eq_type(&self, expr: &'tcx hir::Expr, expected: Ty<'tcx>) {
|
||||
let ty = self.check_expr_with_hint(expr, expected);
|
||||
|
|
@ -1057,6 +1063,407 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
adt_ty
|
||||
}
|
||||
|
||||
fn check_expr_struct_fields(
|
||||
&self,
|
||||
adt_ty: Ty<'tcx>,
|
||||
expected: Expectation<'tcx>,
|
||||
expr_id: hir::HirId,
|
||||
span: Span,
|
||||
variant: &'tcx ty::VariantDef,
|
||||
ast_fields: &'tcx [hir::Field],
|
||||
check_completeness: bool,
|
||||
) -> bool {
|
||||
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);
|
||||
// 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.sty {
|
||||
&ty::Adt(adt, substs) => {
|
||||
(substs, adt.adt_kind(), adt.variant_descr())
|
||||
}
|
||||
_ => span_bug!(span, "non-ADT passed to check_expr_struct_fields")
|
||||
};
|
||||
|
||||
let mut remaining_fields = variant.fields.iter().enumerate().map(|(i, field)|
|
||||
(field.ident.modern(), (i, field))
|
||||
).collect::<FxHashMap<_, _>>();
|
||||
|
||||
let mut seen_fields = FxHashMap::default();
|
||||
|
||||
let mut error_happened = false;
|
||||
|
||||
// Type-check each field.
|
||||
for field in ast_fields {
|
||||
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);
|
||||
self.write_field_index(field.hir_id, i);
|
||||
|
||||
// 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_id), field.span);
|
||||
}
|
||||
|
||||
self.field_ty(field.span, v_field, substs)
|
||||
} else {
|
||||
error_happened = true;
|
||||
if let Some(prev_span) = seen_fields.get(&ident) {
|
||||
let mut err = struct_span_err!(self.tcx.sess,
|
||||
field.ident.span,
|
||||
E0062,
|
||||
"field `{}` specified more than once",
|
||||
ident);
|
||||
|
||||
err.span_label(field.ident.span, "used more than once");
|
||||
err.span_label(*prev_span, format!("first use of `{}`", ident));
|
||||
|
||||
err.emit();
|
||||
} else {
|
||||
self.report_unknown_field(adt_ty, variant, field, ast_fields, kind_name);
|
||||
}
|
||||
|
||||
tcx.types.err
|
||||
};
|
||||
|
||||
// Make sure to give a type to the field even if there's
|
||||
// an error, so we can continue type-checking.
|
||||
self.check_expr_coercable_to_type(&field.expr, field_type);
|
||||
}
|
||||
|
||||
// Make sure the programmer specified correct number of fields.
|
||||
if kind_name == "union" {
|
||||
if ast_fields.len() != 1 {
|
||||
tcx.sess.span_err(span, "union expressions should have exactly one field");
|
||||
}
|
||||
} else if check_completeness && !error_happened && !remaining_fields.is_empty() {
|
||||
let len = remaining_fields.len();
|
||||
|
||||
let mut displayable_field_names = remaining_fields
|
||||
.keys()
|
||||
.map(|ident| ident.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
displayable_field_names.sort();
|
||||
|
||||
let truncated_fields_error = if len <= 3 {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" and {} other field{}", (len - 3), if len - 3 == 1 {""} else {"s"})
|
||||
};
|
||||
|
||||
let remaining_fields_names = displayable_field_names.iter().take(3)
|
||||
.map(|n| format!("`{}`", n))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
struct_span_err!(tcx.sess, span, E0063,
|
||||
"missing field{} {}{} in initializer of `{}`",
|
||||
if remaining_fields.len() == 1 { "" } else { "s" },
|
||||
remaining_fields_names,
|
||||
truncated_fields_error,
|
||||
adt_ty)
|
||||
.span_label(span, format!("missing {}{}",
|
||||
remaining_fields_names,
|
||||
truncated_fields_error))
|
||||
.emit();
|
||||
}
|
||||
error_happened
|
||||
}
|
||||
|
||||
fn check_struct_fields_on_error(
|
||||
&self,
|
||||
fields: &'tcx [hir::Field],
|
||||
base_expr: &'tcx Option<P<hir::Expr>>,
|
||||
) {
|
||||
for field in fields {
|
||||
self.check_expr(&field.expr);
|
||||
}
|
||||
if let Some(ref base) = *base_expr {
|
||||
self.check_expr(&base);
|
||||
}
|
||||
}
|
||||
|
||||
fn report_unknown_field(
|
||||
&self,
|
||||
ty: Ty<'tcx>,
|
||||
variant: &'tcx ty::VariantDef,
|
||||
field: &hir::Field,
|
||||
skip_fields: &[hir::Field],
|
||||
kind_name: &str,
|
||||
) {
|
||||
if variant.recovered {
|
||||
return;
|
||||
}
|
||||
let mut err = self.type_error_struct_with_diag(
|
||||
field.ident.span,
|
||||
|actual| match ty.sty {
|
||||
ty::Adt(adt, ..) if adt.is_enum() => {
|
||||
struct_span_err!(self.tcx.sess, field.ident.span, E0559,
|
||||
"{} `{}::{}` has no field named `{}`",
|
||||
kind_name, actual, variant.ident, field.ident)
|
||||
}
|
||||
_ => {
|
||||
struct_span_err!(self.tcx.sess, field.ident.span, E0560,
|
||||
"{} `{}` has no field named `{}`",
|
||||
kind_name, actual, field.ident)
|
||||
}
|
||||
},
|
||||
ty);
|
||||
// prevent all specified fields from being suggested
|
||||
let skip_fields = skip_fields.iter().map(|ref x| x.ident.as_str());
|
||||
if let Some(field_name) = Self::suggest_field_name(variant,
|
||||
&field.ident.as_str(),
|
||||
skip_fields.collect()) {
|
||||
err.span_suggestion(
|
||||
field.ident.span,
|
||||
"a field with a similar name exists",
|
||||
field_name.to_string(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
match ty.sty {
|
||||
ty::Adt(adt, ..) => {
|
||||
if adt.is_enum() {
|
||||
err.span_label(field.ident.span,
|
||||
format!("`{}::{}` does not have this field",
|
||||
ty, variant.ident));
|
||||
} else {
|
||||
err.span_label(field.ident.span,
|
||||
format!("`{}` does not have this field", ty));
|
||||
}
|
||||
let available_field_names = self.available_field_names(variant);
|
||||
if !available_field_names.is_empty() {
|
||||
err.note(&format!("available fields are: {}",
|
||||
self.name_series_display(available_field_names)));
|
||||
}
|
||||
}
|
||||
_ => bug!("non-ADT passed to report_unknown_field")
|
||||
}
|
||||
};
|
||||
err.emit();
|
||||
}
|
||||
|
||||
// Return an hint about the closest match in field names
|
||||
fn suggest_field_name(variant: &'tcx ty::VariantDef,
|
||||
field: &str,
|
||||
skip: Vec<LocalInternedString>)
|
||||
-> Option<Symbol> {
|
||||
let names = variant.fields.iter().filter_map(|field| {
|
||||
// ignore already set fields and private fields from non-local crates
|
||||
if skip.iter().any(|x| *x == field.ident.as_str()) ||
|
||||
(!variant.def_id.is_local() && field.vis != Visibility::Public)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(&field.ident.name)
|
||||
}
|
||||
});
|
||||
|
||||
find_best_match_for_name(names, field, None)
|
||||
}
|
||||
|
||||
fn available_field_names(&self, variant: &'tcx ty::VariantDef) -> Vec<ast::Name> {
|
||||
variant.fields.iter().filter(|field| {
|
||||
let def_scope =
|
||||
self.tcx.adjust_ident_and_get_scope(field.ident, variant.def_id, self.body_id).1;
|
||||
field.vis.is_accessible_from(def_scope, self.tcx)
|
||||
})
|
||||
.map(|field| field.ident.name)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn name_series_display(&self, names: Vec<ast::Name>) -> 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,
|
||||
needs: Needs,
|
||||
base: &'tcx hir::Expr,
|
||||
field: ast::Ident,
|
||||
) -> Ty<'tcx> {
|
||||
let expr_t = self.check_expr_with_needs(base, needs);
|
||||
let expr_t = self.structurally_resolved_type(base.span,
|
||||
expr_t);
|
||||
let mut private_candidate = None;
|
||||
let mut autoderef = self.autoderef(expr.span, expr_t);
|
||||
while let Some((base_t, _)) = autoderef.next() {
|
||||
match base_t.sty {
|
||||
ty::Adt(base_def, substs) if !base_def.is_enum() => {
|
||||
debug!("struct named {:?}", base_t);
|
||||
let (ident, def_scope) =
|
||||
self.tcx.adjust_ident_and_get_scope(field, base_def.did, self.body_id);
|
||||
let fields = &base_def.non_enum_variant().fields;
|
||||
if let Some(index) = fields.iter().position(|f| f.ident.modern() == ident) {
|
||||
let field = &fields[index];
|
||||
let field_ty = self.field_ty(expr.span, field, substs);
|
||||
// Save the index of all fields regardless of their visibility in case
|
||||
// of error recovery.
|
||||
self.write_field_index(expr.hir_id, index);
|
||||
if field.vis.is_accessible_from(def_scope, self.tcx) {
|
||||
let adjustments = autoderef.adjust_steps(self, needs);
|
||||
self.apply_adjustments(base, adjustments);
|
||||
autoderef.finalize(self);
|
||||
|
||||
self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span);
|
||||
return field_ty;
|
||||
}
|
||||
private_candidate = Some((base_def.did, field_ty));
|
||||
}
|
||||
}
|
||||
ty::Tuple(ref tys) => {
|
||||
let fstr = field.as_str();
|
||||
if let Ok(index) = fstr.parse::<usize>() {
|
||||
if fstr == index.to_string() {
|
||||
if let Some(field_ty) = tys.get(index) {
|
||||
let adjustments = autoderef.adjust_steps(self, needs);
|
||||
self.apply_adjustments(base, adjustments);
|
||||
autoderef.finalize(self);
|
||||
|
||||
self.write_field_index(expr.hir_id, index);
|
||||
return field_ty.expect_ty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
autoderef.unambiguous_final_ty(self);
|
||||
|
||||
if let Some((did, field_ty)) = private_candidate {
|
||||
let struct_path = self.tcx().def_path_str(did);
|
||||
let mut err = struct_span_err!(self.tcx().sess, expr.span, E0616,
|
||||
"field `{}` of struct `{}` is private",
|
||||
field, struct_path);
|
||||
// Also check if an accessible method exists, which is often what is meant.
|
||||
if self.method_exists(field, expr_t, expr.hir_id, false)
|
||||
&& !self.expr_in_place(expr.hir_id)
|
||||
{
|
||||
self.suggest_method_call(
|
||||
&mut err,
|
||||
&format!("a method `{}` also exists, call it with parentheses", field),
|
||||
field,
|
||||
expr_t,
|
||||
expr.hir_id,
|
||||
);
|
||||
}
|
||||
err.emit();
|
||||
field_ty
|
||||
} else if field.name == kw::Invalid {
|
||||
self.tcx().types.err
|
||||
} else if self.method_exists(field, expr_t, expr.hir_id, true) {
|
||||
let mut err = type_error_struct!(self.tcx().sess, field.span, expr_t, E0615,
|
||||
"attempted to take value of method `{}` on type `{}`",
|
||||
field, expr_t);
|
||||
|
||||
if !self.expr_in_place(expr.hir_id) {
|
||||
self.suggest_method_call(
|
||||
&mut err,
|
||||
"use parentheses to call the method",
|
||||
field,
|
||||
expr_t,
|
||||
expr.hir_id
|
||||
);
|
||||
} else {
|
||||
err.help("methods are immutable and cannot be assigned to");
|
||||
}
|
||||
|
||||
err.emit();
|
||||
self.tcx().types.err
|
||||
} else {
|
||||
if !expr_t.is_primitive_ty() {
|
||||
let mut err = self.no_such_field_err(field.span, field, expr_t);
|
||||
|
||||
match expr_t.sty {
|
||||
ty::Adt(def, _) if !def.is_enum() => {
|
||||
if let Some(suggested_field_name) =
|
||||
Self::suggest_field_name(def.non_enum_variant(),
|
||||
&field.as_str(), vec![]) {
|
||||
err.span_suggestion(
|
||||
field.span,
|
||||
"a field with a similar name exists",
|
||||
suggested_field_name.to_string(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
err.span_label(field.span, "unknown field");
|
||||
let struct_variant_def = def.non_enum_variant();
|
||||
let field_names = self.available_field_names(struct_variant_def);
|
||||
if !field_names.is_empty() {
|
||||
err.note(&format!("available fields are: {}",
|
||||
self.name_series_display(field_names)));
|
||||
}
|
||||
};
|
||||
}
|
||||
ty::Array(_, len) => {
|
||||
if let (Some(len), Ok(user_index)) = (
|
||||
len.assert_usize(self.tcx),
|
||||
field.as_str().parse::<u64>()
|
||||
) {
|
||||
let base = self.tcx.sess.source_map()
|
||||
.span_to_snippet(base.span)
|
||||
.unwrap_or_else(|_|
|
||||
self.tcx.hir().hir_to_pretty_string(base.hir_id));
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
ty::RawPtr(..) => {
|
||||
let base = self.tcx.sess.source_map()
|
||||
.span_to_snippet(base.span)
|
||||
.unwrap_or_else(|_| self.tcx.hir().hir_to_pretty_string(base.hir_id));
|
||||
let msg = format!("`{}` is a raw pointer; try dereferencing it", base);
|
||||
let suggestion = format!("(*{}).{}", base, field);
|
||||
err.span_suggestion(
|
||||
expr.span,
|
||||
&msg,
|
||||
suggestion,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
err
|
||||
} else {
|
||||
type_error_struct!(self.tcx().sess, field.span, expr_t, E0610,
|
||||
"`{}` is a primitive type and therefore doesn't have fields",
|
||||
expr_t)
|
||||
}.emit();
|
||||
self.tcx().types.err
|
||||
}
|
||||
}
|
||||
|
||||
fn no_such_field_err<T: Display>(&self, span: Span, field: T, expr_t: &ty::TyS<'_>)
|
||||
-> DiagnosticBuilder<'_> {
|
||||
type_error_struct!(self.tcx().sess, span, expr_t, E0609,
|
||||
"no field `{}` on type `{}`",
|
||||
field, expr_t)
|
||||
}
|
||||
|
||||
fn check_expr_index(
|
||||
&self,
|
||||
base: &'tcx hir::Expr,
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ use rustc::middle::region;
|
|||
use rustc::mir::interpret::{ConstValue, GlobalId};
|
||||
use rustc::traits::{self, ObligationCause, ObligationCauseCode, TraitEngine};
|
||||
use rustc::ty::{
|
||||
self, AdtKind, CanonicalUserType, Ty, TyCtxt, Const, GenericParamDefKind, Visibility,
|
||||
self, AdtKind, CanonicalUserType, Ty, TyCtxt, Const, GenericParamDefKind,
|
||||
ToPolyTraitRef, ToPredicate, RegionKind, UserType
|
||||
};
|
||||
use rustc::ty::adjustment::{
|
||||
|
|
@ -124,13 +124,11 @@ use syntax::attr;
|
|||
use syntax::feature_gate::{GateIssue, emit_feature_err};
|
||||
use syntax::ptr::P;
|
||||
use syntax::source_map::{DUMMY_SP, original_sp};
|
||||
use syntax::symbol::{Symbol, LocalInternedString, kw, sym};
|
||||
use syntax::util::lev_distance::find_best_match_for_name;
|
||||
use syntax::symbol::{kw, sym};
|
||||
|
||||
use std::cell::{Cell, RefCell, Ref, RefMut};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::cmp;
|
||||
use std::fmt::Display;
|
||||
use std::iter;
|
||||
use std::mem::replace;
|
||||
use std::ops::{self, Deref};
|
||||
|
|
@ -143,7 +141,7 @@ use crate::TypeAndSubsts;
|
|||
use crate::lint;
|
||||
use crate::util::captures::Captures;
|
||||
use crate::util::common::{ErrorReported, indenter};
|
||||
use crate::util::nodemap::{DefIdMap, DefIdSet, FxHashMap, FxHashSet, HirIdMap};
|
||||
use crate::util::nodemap::{DefIdMap, DefIdSet, FxHashSet, HirIdMap};
|
||||
|
||||
pub use self::Expectation::*;
|
||||
use self::autoderef::Autoderef;
|
||||
|
|
@ -3266,407 +3264,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
expect_args
|
||||
}
|
||||
|
||||
// Check field access expressions
|
||||
fn check_field(
|
||||
&self,
|
||||
expr: &'tcx hir::Expr,
|
||||
needs: Needs,
|
||||
base: &'tcx hir::Expr,
|
||||
field: ast::Ident,
|
||||
) -> Ty<'tcx> {
|
||||
let expr_t = self.check_expr_with_needs(base, needs);
|
||||
let expr_t = self.structurally_resolved_type(base.span,
|
||||
expr_t);
|
||||
let mut private_candidate = None;
|
||||
let mut autoderef = self.autoderef(expr.span, expr_t);
|
||||
while let Some((base_t, _)) = autoderef.next() {
|
||||
match base_t.sty {
|
||||
ty::Adt(base_def, substs) if !base_def.is_enum() => {
|
||||
debug!("struct named {:?}", base_t);
|
||||
let (ident, def_scope) =
|
||||
self.tcx.adjust_ident_and_get_scope(field, base_def.did, self.body_id);
|
||||
let fields = &base_def.non_enum_variant().fields;
|
||||
if let Some(index) = fields.iter().position(|f| f.ident.modern() == ident) {
|
||||
let field = &fields[index];
|
||||
let field_ty = self.field_ty(expr.span, field, substs);
|
||||
// Save the index of all fields regardless of their visibility in case
|
||||
// of error recovery.
|
||||
self.write_field_index(expr.hir_id, index);
|
||||
if field.vis.is_accessible_from(def_scope, self.tcx) {
|
||||
let adjustments = autoderef.adjust_steps(self, needs);
|
||||
self.apply_adjustments(base, adjustments);
|
||||
autoderef.finalize(self);
|
||||
|
||||
self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span);
|
||||
return field_ty;
|
||||
}
|
||||
private_candidate = Some((base_def.did, field_ty));
|
||||
}
|
||||
}
|
||||
ty::Tuple(ref tys) => {
|
||||
let fstr = field.as_str();
|
||||
if let Ok(index) = fstr.parse::<usize>() {
|
||||
if fstr == index.to_string() {
|
||||
if let Some(field_ty) = tys.get(index) {
|
||||
let adjustments = autoderef.adjust_steps(self, needs);
|
||||
self.apply_adjustments(base, adjustments);
|
||||
autoderef.finalize(self);
|
||||
|
||||
self.write_field_index(expr.hir_id, index);
|
||||
return field_ty.expect_ty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
autoderef.unambiguous_final_ty(self);
|
||||
|
||||
if let Some((did, field_ty)) = private_candidate {
|
||||
let struct_path = self.tcx().def_path_str(did);
|
||||
let mut err = struct_span_err!(self.tcx().sess, expr.span, E0616,
|
||||
"field `{}` of struct `{}` is private",
|
||||
field, struct_path);
|
||||
// Also check if an accessible method exists, which is often what is meant.
|
||||
if self.method_exists(field, expr_t, expr.hir_id, false)
|
||||
&& !self.expr_in_place(expr.hir_id)
|
||||
{
|
||||
self.suggest_method_call(
|
||||
&mut err,
|
||||
&format!("a method `{}` also exists, call it with parentheses", field),
|
||||
field,
|
||||
expr_t,
|
||||
expr.hir_id,
|
||||
);
|
||||
}
|
||||
err.emit();
|
||||
field_ty
|
||||
} else if field.name == kw::Invalid {
|
||||
self.tcx().types.err
|
||||
} else if self.method_exists(field, expr_t, expr.hir_id, true) {
|
||||
let mut err = type_error_struct!(self.tcx().sess, field.span, expr_t, E0615,
|
||||
"attempted to take value of method `{}` on type `{}`",
|
||||
field, expr_t);
|
||||
|
||||
if !self.expr_in_place(expr.hir_id) {
|
||||
self.suggest_method_call(
|
||||
&mut err,
|
||||
"use parentheses to call the method",
|
||||
field,
|
||||
expr_t,
|
||||
expr.hir_id
|
||||
);
|
||||
} else {
|
||||
err.help("methods are immutable and cannot be assigned to");
|
||||
}
|
||||
|
||||
err.emit();
|
||||
self.tcx().types.err
|
||||
} else {
|
||||
if !expr_t.is_primitive_ty() {
|
||||
let mut err = self.no_such_field_err(field.span, field, expr_t);
|
||||
|
||||
match expr_t.sty {
|
||||
ty::Adt(def, _) if !def.is_enum() => {
|
||||
if let Some(suggested_field_name) =
|
||||
Self::suggest_field_name(def.non_enum_variant(),
|
||||
&field.as_str(), vec![]) {
|
||||
err.span_suggestion(
|
||||
field.span,
|
||||
"a field with a similar name exists",
|
||||
suggested_field_name.to_string(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
err.span_label(field.span, "unknown field");
|
||||
let struct_variant_def = def.non_enum_variant();
|
||||
let field_names = self.available_field_names(struct_variant_def);
|
||||
if !field_names.is_empty() {
|
||||
err.note(&format!("available fields are: {}",
|
||||
self.name_series_display(field_names)));
|
||||
}
|
||||
};
|
||||
}
|
||||
ty::Array(_, len) => {
|
||||
if let (Some(len), Ok(user_index)) = (
|
||||
len.assert_usize(self.tcx),
|
||||
field.as_str().parse::<u64>()
|
||||
) {
|
||||
let base = self.tcx.sess.source_map()
|
||||
.span_to_snippet(base.span)
|
||||
.unwrap_or_else(|_|
|
||||
self.tcx.hir().hir_to_pretty_string(base.hir_id));
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
ty::RawPtr(..) => {
|
||||
let base = self.tcx.sess.source_map()
|
||||
.span_to_snippet(base.span)
|
||||
.unwrap_or_else(|_| self.tcx.hir().hir_to_pretty_string(base.hir_id));
|
||||
let msg = format!("`{}` is a raw pointer; try dereferencing it", base);
|
||||
let suggestion = format!("(*{}).{}", base, field);
|
||||
err.span_suggestion(
|
||||
expr.span,
|
||||
&msg,
|
||||
suggestion,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
err
|
||||
} else {
|
||||
type_error_struct!(self.tcx().sess, field.span, expr_t, E0610,
|
||||
"`{}` is a primitive type and therefore doesn't have fields",
|
||||
expr_t)
|
||||
}.emit();
|
||||
self.tcx().types.err
|
||||
}
|
||||
}
|
||||
|
||||
// Return an hint about the closest match in field names
|
||||
fn suggest_field_name(variant: &'tcx ty::VariantDef,
|
||||
field: &str,
|
||||
skip: Vec<LocalInternedString>)
|
||||
-> Option<Symbol> {
|
||||
let names = variant.fields.iter().filter_map(|field| {
|
||||
// ignore already set fields and private fields from non-local crates
|
||||
if skip.iter().any(|x| *x == field.ident.as_str()) ||
|
||||
(!variant.def_id.is_local() && field.vis != Visibility::Public)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(&field.ident.name)
|
||||
}
|
||||
});
|
||||
|
||||
find_best_match_for_name(names, field, None)
|
||||
}
|
||||
|
||||
fn available_field_names(&self, variant: &'tcx ty::VariantDef) -> Vec<ast::Name> {
|
||||
variant.fields.iter().filter(|field| {
|
||||
let def_scope =
|
||||
self.tcx.adjust_ident_and_get_scope(field.ident, variant.def_id, self.body_id).1;
|
||||
field.vis.is_accessible_from(def_scope, self.tcx)
|
||||
})
|
||||
.map(|field| field.ident.name)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn name_series_display(&self, names: Vec<ast::Name>) -> 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
|
||||
}
|
||||
|
||||
fn no_such_field_err<T: Display>(&self, span: Span, field: T, expr_t: &ty::TyS<'_>)
|
||||
-> DiagnosticBuilder<'_> {
|
||||
type_error_struct!(self.tcx().sess, span, expr_t, E0609,
|
||||
"no field `{}` on type `{}`",
|
||||
field, expr_t)
|
||||
}
|
||||
|
||||
fn report_unknown_field(
|
||||
&self,
|
||||
ty: Ty<'tcx>,
|
||||
variant: &'tcx ty::VariantDef,
|
||||
field: &hir::Field,
|
||||
skip_fields: &[hir::Field],
|
||||
kind_name: &str,
|
||||
) {
|
||||
if variant.recovered {
|
||||
return;
|
||||
}
|
||||
let mut err = self.type_error_struct_with_diag(
|
||||
field.ident.span,
|
||||
|actual| match ty.sty {
|
||||
ty::Adt(adt, ..) if adt.is_enum() => {
|
||||
struct_span_err!(self.tcx.sess, field.ident.span, E0559,
|
||||
"{} `{}::{}` has no field named `{}`",
|
||||
kind_name, actual, variant.ident, field.ident)
|
||||
}
|
||||
_ => {
|
||||
struct_span_err!(self.tcx.sess, field.ident.span, E0560,
|
||||
"{} `{}` has no field named `{}`",
|
||||
kind_name, actual, field.ident)
|
||||
}
|
||||
},
|
||||
ty);
|
||||
// prevent all specified fields from being suggested
|
||||
let skip_fields = skip_fields.iter().map(|ref x| x.ident.as_str());
|
||||
if let Some(field_name) = Self::suggest_field_name(variant,
|
||||
&field.ident.as_str(),
|
||||
skip_fields.collect()) {
|
||||
err.span_suggestion(
|
||||
field.ident.span,
|
||||
"a field with a similar name exists",
|
||||
field_name.to_string(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
match ty.sty {
|
||||
ty::Adt(adt, ..) => {
|
||||
if adt.is_enum() {
|
||||
err.span_label(field.ident.span,
|
||||
format!("`{}::{}` does not have this field",
|
||||
ty, variant.ident));
|
||||
} else {
|
||||
err.span_label(field.ident.span,
|
||||
format!("`{}` does not have this field", ty));
|
||||
}
|
||||
let available_field_names = self.available_field_names(variant);
|
||||
if !available_field_names.is_empty() {
|
||||
err.note(&format!("available fields are: {}",
|
||||
self.name_series_display(available_field_names)));
|
||||
}
|
||||
}
|
||||
_ => bug!("non-ADT passed to report_unknown_field")
|
||||
}
|
||||
};
|
||||
err.emit();
|
||||
}
|
||||
|
||||
fn check_expr_struct_fields(
|
||||
&self,
|
||||
adt_ty: Ty<'tcx>,
|
||||
expected: Expectation<'tcx>,
|
||||
expr_id: hir::HirId,
|
||||
span: Span,
|
||||
variant: &'tcx ty::VariantDef,
|
||||
ast_fields: &'tcx [hir::Field],
|
||||
check_completeness: bool,
|
||||
) -> bool {
|
||||
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);
|
||||
// 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.sty {
|
||||
&ty::Adt(adt, substs) => {
|
||||
(substs, adt.adt_kind(), adt.variant_descr())
|
||||
}
|
||||
_ => span_bug!(span, "non-ADT passed to check_expr_struct_fields")
|
||||
};
|
||||
|
||||
let mut remaining_fields = variant.fields.iter().enumerate().map(|(i, field)|
|
||||
(field.ident.modern(), (i, field))
|
||||
).collect::<FxHashMap<_, _>>();
|
||||
|
||||
let mut seen_fields = FxHashMap::default();
|
||||
|
||||
let mut error_happened = false;
|
||||
|
||||
// Type-check each field.
|
||||
for field in ast_fields {
|
||||
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);
|
||||
self.write_field_index(field.hir_id, i);
|
||||
|
||||
// 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_id), field.span);
|
||||
}
|
||||
|
||||
self.field_ty(field.span, v_field, substs)
|
||||
} else {
|
||||
error_happened = true;
|
||||
if let Some(prev_span) = seen_fields.get(&ident) {
|
||||
let mut err = struct_span_err!(self.tcx.sess,
|
||||
field.ident.span,
|
||||
E0062,
|
||||
"field `{}` specified more than once",
|
||||
ident);
|
||||
|
||||
err.span_label(field.ident.span, "used more than once");
|
||||
err.span_label(*prev_span, format!("first use of `{}`", ident));
|
||||
|
||||
err.emit();
|
||||
} else {
|
||||
self.report_unknown_field(adt_ty, variant, field, ast_fields, kind_name);
|
||||
}
|
||||
|
||||
tcx.types.err
|
||||
};
|
||||
|
||||
// Make sure to give a type to the field even if there's
|
||||
// an error, so we can continue type-checking.
|
||||
self.check_expr_coercable_to_type(&field.expr, field_type);
|
||||
}
|
||||
|
||||
// Make sure the programmer specified correct number of fields.
|
||||
if kind_name == "union" {
|
||||
if ast_fields.len() != 1 {
|
||||
tcx.sess.span_err(span, "union expressions should have exactly one field");
|
||||
}
|
||||
} else if check_completeness && !error_happened && !remaining_fields.is_empty() {
|
||||
let len = remaining_fields.len();
|
||||
|
||||
let mut displayable_field_names = remaining_fields
|
||||
.keys()
|
||||
.map(|ident| ident.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
displayable_field_names.sort();
|
||||
|
||||
let truncated_fields_error = if len <= 3 {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" and {} other field{}", (len - 3), if len - 3 == 1 {""} else {"s"})
|
||||
};
|
||||
|
||||
let remaining_fields_names = displayable_field_names.iter().take(3)
|
||||
.map(|n| format!("`{}`", n))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
struct_span_err!(tcx.sess, span, E0063,
|
||||
"missing field{} {}{} in initializer of `{}`",
|
||||
if remaining_fields.len() == 1 { "" } else { "s" },
|
||||
remaining_fields_names,
|
||||
truncated_fields_error,
|
||||
adt_ty)
|
||||
.span_label(span, format!("missing {}{}",
|
||||
remaining_fields_names,
|
||||
truncated_fields_error))
|
||||
.emit();
|
||||
}
|
||||
error_happened
|
||||
}
|
||||
|
||||
fn check_struct_fields_on_error(
|
||||
&self,
|
||||
fields: &'tcx [hir::Field],
|
||||
base_expr: &'tcx Option<P<hir::Expr>>,
|
||||
) {
|
||||
for field in fields {
|
||||
self.check_expr(&field.expr);
|
||||
}
|
||||
if let Some(ref base) = *base_expr {
|
||||
self.check_expr(&base);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_struct_path(&self,
|
||||
qpath: &QPath,
|
||||
hir_id: hir::HirId)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue