Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions compiler/rustc_hir_typeck/src/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
unsatisfied_predicates,
)
};
if let SelfSource::MethodCall(rcvr_expr) = source {
self.err_ctxt().note_field_shadowed_by_private_candidate(
&mut err,
rcvr_expr.hir_id,
self.param_env,
);
}

self.set_label_for_method_error(
&mut err,
Expand Down
27 changes: 27 additions & 0 deletions compiler/rustc_hir_typeck/src/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
rhs_ty_var,
Some(lhs_expr),
|err, ty| {
self.err_ctxt().note_field_shadowed_by_private_candidate(
err,
rhs_expr.hir_id,
self.param_env,
);
if let Op::BinOp(binop) = op
&& binop.node == hir::BinOpKind::Eq
{
Expand Down Expand Up @@ -331,6 +336,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
lhs_expr.span,
format!("cannot use `{}` on type `{}`", s, lhs_ty_str),
);
let err_ctxt = self.err_ctxt();
err_ctxt.note_field_shadowed_by_private_candidate(
&mut err,
lhs_expr.hir_id,
self.param_env,
);
err_ctxt.note_field_shadowed_by_private_candidate(
&mut err,
rhs_expr.hir_id,
self.param_env,
);
self.note_unmet_impls_on_type(&mut err, &errors, false);
(err, None)
}
Expand Down Expand Up @@ -391,6 +407,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err.span_label(lhs_expr.span, lhs_ty_str.clone());
err.span_label(rhs_expr.span, rhs_ty_str);
}
let err_ctxt = self.err_ctxt();
err_ctxt.note_field_shadowed_by_private_candidate(
&mut err,
lhs_expr.hir_id,
self.param_env,
);
err_ctxt.note_field_shadowed_by_private_candidate(
&mut err,
rhs_expr.hir_id,
self.param_env,
);
let suggest_derive = self.can_eq(self.param_env, lhs_ty, rhs_ty);
self.note_unmet_impls_on_type(&mut err, &errors, suggest_derive);
(err, output_def_id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1528,6 +1528,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
label_or_note(span, terr.to_string(self.tcx));
}

if let Some(param_env) = param_env {
self.note_field_shadowed_by_private_candidate_in_cause(diag, cause, param_env);
}

if self.check_and_note_conflicting_crates(diag, terr) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
);
}

self.note_field_shadowed_by_private_candidate_in_cause(
&mut err,
&obligation.cause,
obligation.param_env,
);
self.try_to_add_help_message(
&root_obligation,
&obligation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use itertools::{EitherOrBoth, Itertools};
use rustc_abi::ExternAbi;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_data_structures::unord::UnordMap;
use rustc_errors::codes::*;
use rustc_errors::{
Applicability, Diag, EmissionGuarantee, MultiSpan, Style, SuggestionStyle, pluralize,
Expand Down Expand Up @@ -242,6 +243,116 @@ pub fn suggest_restriction<'tcx, G: EmissionGuarantee>(
}

impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
pub fn note_field_shadowed_by_private_candidate_in_cause(
&self,
err: &mut Diag<'_>,
cause: &ObligationCause<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) {
let mut hir_ids = UnordMap::default();
// Walk the parent chain so we can recover
// the source expression from whichever layer carries them.
let mut next_code = Some(cause.code());
while let Some(cause_code) = next_code {
match cause_code {
ObligationCauseCode::BinOp { lhs_hir_id, rhs_hir_id, .. } => {
hir_ids.insert(lhs_hir_id.local_id.as_u32(), *lhs_hir_id);
hir_ids.insert(rhs_hir_id.local_id.as_u32(), *rhs_hir_id);
}
ObligationCauseCode::FunctionArg { arg_hir_id, .. }
| ObligationCauseCode::ReturnValue(arg_hir_id)
| ObligationCauseCode::AwaitableExpr(arg_hir_id)
| ObligationCauseCode::BlockTailExpression(arg_hir_id, _)
| ObligationCauseCode::UnOp { hir_id: arg_hir_id } => {
hir_ids.insert(arg_hir_id.local_id.as_u32(), *arg_hir_id);
}
ObligationCauseCode::OpaqueReturnType(Some((_, hir_id))) => {
hir_ids.insert(hir_id.local_id.as_u32(), *hir_id);
}
_ => {}
}
next_code = cause_code.parent();
}

if cause.span != DUMMY_SP
&& let Some(body) = self.tcx.hir_maybe_body_owned_by(cause.body_id)
{
let mut expr_finder = FindExprBySpan::new(cause.span, self.tcx);
expr_finder.visit_body(body);
if let Some(expr) = expr_finder.result {
hir_ids.insert(expr.hir_id.local_id.as_u32(), expr.hir_id);
}
}

let hir_ids = hir_ids.into_sorted_stable_ord();
Comment thread
chenyukang marked this conversation as resolved.
Outdated
for (_, hir_id) in hir_ids {
self.note_field_shadowed_by_private_candidate(err, hir_id, param_env);
}
}

pub fn note_field_shadowed_by_private_candidate(
&self,
err: &mut Diag<'_>,
hir_id: hir::HirId,
param_env: ty::ParamEnv<'tcx>,
) {
let Some(typeck_results) = &self.typeck_results else {
return;
};
let Node::Expr(expr) = self.tcx.hir_node(hir_id) else {
return;
};
let hir::ExprKind::Field(base_expr, field_ident) = expr.kind else {
return;
};

let Some(base_ty) = typeck_results.expr_ty_opt(base_expr) else {
return;
};
let base_ty = self.resolve_vars_if_possible(base_ty);
if base_ty.references_error() {
return;
}

let fn_body_hir_id = self.tcx.local_def_id_to_hir_id(typeck_results.hir_owner.def_id);
let mut private_candidate = None;

for (deref_base_ty, _) in (self.autoderef_steps)(base_ty) {
let ty::Adt(base_def, args) = deref_base_ty.kind() else {
continue;
};

if base_def.is_enum() {
continue;
}

let (adjusted_ident, def_scope) =
self.tcx.adjust_ident_and_get_scope(field_ident, base_def.did(), fn_body_hir_id);

let Some((_, field_def)) =
base_def.non_enum_variant().fields.iter_enumerated().find(|(_, field)| {
field.ident(self.tcx).normalize_to_macros_2_0() == adjusted_ident
})
else {
continue;
};

if field_def.vis.is_accessible_from(def_scope, self.tcx) {
let accessible_field_ty = field_def.ty(self.tcx, args);
if let Some((private_base_ty, private_field_ty)) = private_candidate
&& !self.can_eq(param_env, private_field_ty, accessible_field_ty)
{
err.note(format!(
"there is a field `{field_ident}` on `{private_base_ty}` with type `{private_field_ty}`, but it is private"
Comment thread
chenyukang marked this conversation as resolved.
Outdated
));
}
return;
}

private_candidate.get_or_insert((deref_base_ty, field_def.ty(self.tcx, args)));
}
}

pub fn suggest_restricting_param_bound(
&self,
err: &mut Diag<'_>,
Expand Down
137 changes: 137 additions & 0 deletions tests/ui/privacy/private-field-deref-confusion-issue-149546.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Field lookup still resolves to the public field on the Deref target, but
// follow-up diagnostics should explain that the original type has a same-named
// private field with a different type.

mod structs {
pub struct A {
field: usize,
b: B,
}

pub struct B {
pub field: bool,
}

impl std::ops::Deref for A {
type Target = B;

fn deref(&self) -> &Self::Target {
&self.b
}
}
}

use structs::A;

fn takes_usize(_: usize) {}
//~^ NOTE function defined here

trait Marker {}

impl Marker for usize {}
//~^ HELP the trait `Marker` is implemented for `usize`

struct Wrapper(i32);

impl<T: Marker> std::ops::Add<T> for Wrapper {
//~^ NOTE required for `Wrapper` to implement `Add<bool>`
//~| NOTE unsatisfied trait bound introduced here
type Output = ();

fn add(self, _: T) {}
}

fn by_value(a: A) {
a.field + 5;
//~^ ERROR cannot add `{integer}` to `bool`
//~| NOTE bool
//~| NOTE {integer}
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
}

fn by_ref(a: &A) {
a.field + 5;
//~^ ERROR cannot add `{integer}` to `bool`
//~| NOTE bool
//~| NOTE {integer}
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
}

fn rhs_by_value(a: A) {
5 + a.field;
//~^ ERROR cannot add `bool` to `{integer}`
//~| NOTE no implementation for `{integer} + bool`
//~| HELP the trait `Add<bool>` is not implemented for `{integer}`
//~| HELP the following other types implement trait `Add<Rhs>`:
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
}

fn rhs_by_ref(a: &A) {
5 + a.field;
//~^ ERROR cannot add `bool` to `{integer}`
//~| NOTE no implementation for `{integer} + bool`
//~| HELP the trait `Add<bool>` is not implemented for `{integer}`
//~| HELP the following other types implement trait `Add<Rhs>`:
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
}

fn rhs_assign_op_by_value(a: A) {
let mut n = 5;
n += a.field;
//~^ ERROR cannot add-assign `bool` to `{integer}`
//~| NOTE no implementation for `{integer} += bool`
//~| HELP the trait `AddAssign<bool>` is not implemented for `{integer}`
//~| HELP the following other types implement trait `AddAssign<Rhs>`:
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
}

fn rhs_assign_op_by_ref(a: &A) {
let mut n = 5;
n += a.field;
//~^ ERROR cannot add-assign `bool` to `{integer}`
//~| NOTE no implementation for `{integer} += bool`
//~| HELP the trait `AddAssign<bool>` is not implemented for `{integer}`
//~| HELP the following other types implement trait `AddAssign<Rhs>`:
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
}

fn rhs_nested_obligation(a: A) {
Wrapper(5) + a.field;
//~^ ERROR the trait bound `bool: Marker` is not satisfied
//~| NOTE the trait `Marker` is not implemented for `bool`
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
}

fn method_call(a: A) {
a.field.count_ones();
//~^ ERROR no method named `count_ones` found for type `bool` in the current scope
//~| NOTE method not found in `bool`
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
}

fn type_mismatch(a: A) {
let value: usize = a.field;
//~^ ERROR mismatched types
//~| NOTE expected `usize`, found `bool`
//~| NOTE expected due to this
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
eprintln!("value: {value}");
}

fn function_arg(a: A) {
takes_usize(a.field);
//~^ ERROR mismatched types
//~| NOTE expected `usize`, found `bool`
//~| NOTE arguments to this function are incorrect
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
}

fn return_value(a: &A) -> usize {
//~^ NOTE expected `usize` because of return type
a.field
//~^ ERROR mismatched types
//~| NOTE expected `usize`, found `bool`
//~| NOTE there is a field `field` on `A` with type `usize`, but it is private
}

fn main() {}
Loading
Loading