E0610,
"`{expr_t}` is a primitive type and therefore doesn't have fields",
);
- let is_valid_suffix = |field: String| {
+ let is_valid_suffix = |field: &str| {
if field == "f32" || field == "f64" {
return true;
}
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(_)) = expr_t.kind()
&& let ExprKind::Lit(Spanned {
node: ast::LitKind::Int(_, ast::LitIntType::Unsuffixed),
..
}) = base.kind
&& !base.span.from_expansion()
- && is_valid_suffix(field_name)
{
- err.span_suggestion_verbose(
- field.span.shrink_to_lo(),
- "If the number is meant to be a floating point number, consider adding a `0` after the period",
- '0',
- Applicability::MaybeIncorrect,
- );
+ 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();
}
);
// try to add a suggestion in case the field is a nested field of a field of the Adt
- if let Some((fields, substs)) = self.get_field_candidates(span, expr_t) {
- for candidate_field in fields.iter() {
+ let mod_id = self.tcx.parent_module(id).to_def_id();
+ if let Some((fields, substs)) =
+ self.get_field_candidates_considering_privacy(span, expr_t, mod_id)
+ {
+ for candidate_field in fields {
if let Some(mut field_path) = self.check_for_nested_field_satisfying(
span,
&|candidate_field, _| candidate_field.ident(self.tcx()) == field,
candidate_field,
substs,
vec![],
- self.tcx.parent_module(id).to_def_id(),
+ mod_id,
) {
// field_path includes `field` that we're looking for, so pop it.
field_path.pop();
err
}
- pub(crate) fn get_field_candidates(
+ pub(crate) fn get_field_candidates_considering_privacy(
&self,
span: Span,
- base_t: Ty<'tcx>,
- ) -> Option<(&[ty::FieldDef], SubstsRef<'tcx>)> {
- debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_t);
+ base_ty: Ty<'tcx>,
+ mod_id: DefId,
+ ) -> Option<(impl Iterator<Item = &'tcx ty::FieldDef> + 'tcx, SubstsRef<'tcx>)> {
+ debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty);
- for (base_t, _) in self.autoderef(span, base_t) {
+ for (base_t, _) in self.autoderef(span, base_ty) {
match base_t.kind() {
ty::Adt(base_def, substs) if !base_def.is_enum() => {
+ let tcx = self.tcx;
let fields = &base_def.non_enum_variant().fields;
- // For compile-time reasons put a limit on number of fields we search
- if fields.len() > 100 {
- return None;
+ // 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)) {
+ continue;
}
- return Some((fields, substs));
+ return Some((
+ fields
+ .iter()
+ .filter(move |field| field.vis.is_accessible_from(mod_id, tcx))
+ // For compile-time reasons put a limit on number of fields we search
+ .take(100),
+ substs,
+ ));
}
_ => {}
}
candidate_field: &ty::FieldDef,
subst: SubstsRef<'tcx>,
mut field_path: Vec<Ident>,
- id: DefId,
+ mod_id: DefId,
) -> Option<Vec<Ident>> {
debug!(
"check_for_nested_field_satisfying(span: {:?}, candidate_field: {:?}, field_path: {:?}",
// up to a depth of three
None
} else {
- // recursively search fields of `candidate_field` if it's a ty::Adt
field_path.push(candidate_field.ident(self.tcx).normalize_to_macros_2_0());
let field_ty = candidate_field.ty(self.tcx, subst);
- if let Some((nested_fields, subst)) = self.get_field_candidates(span, field_ty) {
- for field in nested_fields.iter() {
- if field.vis.is_accessible_from(id, self.tcx) {
- if matches(candidate_field, field_ty) {
- return Some(field_path);
- } else if let Some(field_path) = self.check_for_nested_field_satisfying(
- span,
- matches,
- field,
- subst,
- field_path.clone(),
- id,
- ) {
- return Some(field_path);
- }
+ if matches(candidate_field, field_ty) {
+ return Some(field_path);
+ } else if let Some((nested_fields, subst)) =
+ self.get_field_candidates_considering_privacy(span, field_ty, mod_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,
+ ) {
+ return Some(field_path);
}
}
}