Skip to content

Commit

Permalink
Also note struct access, and fix macro expansion from foreign crates
Browse files Browse the repository at this point in the history
  • Loading branch information
compiler-errors committed Mar 4, 2025
1 parent 0607246 commit 09e5846
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 18 deletions.
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
sugg: None,
// Try to get the span of the identifier within the path's syntax context
// (if that's different).
within_macro_span: assoc_name.span.within_macro(span),
within_macro_span: assoc_name.span.within_macro(span, tcx.sess.source_map()),
};

if is_dummy {
Expand Down
35 changes: 23 additions & 12 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3069,7 +3069,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
"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);
let mut err = self.no_such_field_err(ident, base_ty, expr);

match *base_ty.peel_refs().kind() {
ty::Array(_, len) => {
Expand Down Expand Up @@ -3282,29 +3282,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
}

fn no_such_field_err(&self, field: Ident, expr_t: Ty<'tcx>, id: HirId) -> Diag<'_> {
fn no_such_field_err(
&self,
field: Ident,
base_ty: Ty<'tcx>,
expr: &hir::Expr<'tcx>,
) -> Diag<'_> {
let span = field.span;
debug!("no_such_field_err(span: {:?}, field: {:?}, expr_t: {:?})", span, field, expr_t);
debug!("no_such_field_err(span: {:?}, field: {:?}, expr_t: {:?})", span, field, base_ty);

let mut err = self.dcx().create_err(NoFieldOnType { span, ty: expr_t, field });
if expr_t.references_error() {
let mut err = self.dcx().create_err(NoFieldOnType { span, ty: base_ty, field });
if base_ty.references_error() {
err.downgrade_to_delayed_bug();
}

if let Some(within_macro_span) = span.within_macro(expr.span, self.tcx.sess.source_map()) {
err.span_label(within_macro_span, "due to this macro variable");
}

// 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()
let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id();
let (ty, unwrap) = if let ty::Adt(def, args) = base_ty.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, "")
(base_ty, "")
};
for (found_fields, args) in
self.get_field_candidates_considering_privacy_for_diag(span, ty, mod_id, id)
self.get_field_candidates_considering_privacy_for_diag(span, ty, mod_id, expr.hir_id)
{
let field_names = found_fields.iter().map(|field| field.name).collect::<Vec<_>>();
let mut candidate_fields: Vec<_> = found_fields
Expand All @@ -3317,7 +3326,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
args,
vec![],
mod_id,
id,
expr.hir_id,
)
})
.map(|mut field_path| {
Expand All @@ -3328,7 +3337,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
candidate_fields.sort();

let len = candidate_fields.len();
if len > 0 {
// Don't suggest `.field` if the base expr is from a different
// syntax context than the field.
if len > 0 && expr.span.eq_ctxt(field.span) {
err.span_suggestions(
field.span.shrink_to_lo(),
format!(
Expand Down Expand Up @@ -3963,7 +3974,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
_ => (),
};

self.no_such_field_err(field, container, expr.hir_id).emit();
self.no_such_field_err(field, container, expr).emit();

break;
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_typeck/src/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

// Try to get the span of the identifier within the expression's syntax context
// (if that's different).
let within_macro_span = span.within_macro(expr_span);
let within_macro_span = span.within_macro(expr_span, self.tcx.sess.source_map());

// Avoid suggestions when we don't know what's going on.
if let Err(guar) = rcvr_ty.error_reported() {
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,10 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {

// Try to get the span of the identifier within the path's syntax context
// (if that's different).
if let Some(within_macro_span) = base_error.span.within_macro(span) {
err.span_label(within_macro_span, "within this macro");
if let Some(within_macro_span) =
base_error.span.within_macro(span, self.r.tcx.sess.source_map())
{
err.span_label(within_macro_span, "due to this macro variable");
}

self.detect_missing_binding_available_from_pattern(&mut err, path, following_seg);
Expand Down
12 changes: 10 additions & 2 deletions compiler/rustc_span/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,9 +1071,17 @@ impl Span {
///
/// If "self" is the span of the outer_ident, and "within" is the span of the `($ident,)`
/// expr, then this will return the span of the `$ident` macro variable.
pub fn within_macro(self, within: Span) -> Option<Span> {
pub fn within_macro(self, within: Span, sm: &SourceMap) -> Option<Span> {
match Span::prepare_to_combine(self, within) {
Ok((self_, _, parent)) if self_.lo != self.lo() && self.hi() != self_.hi => {
// Only return something if it doesn't overlap with the original span,
// and the span isn't "imported" (i.e. from unavailable sources).
// FIXME: This does limit the usefulness of the error when the macro is
// from a foreign crate; we could also take into account `-Zmacro-backtrace`,
// which doesn't redact this span (but that would mean passing in even more
// args to this function, lol).
Ok((self_, _, parent))
if self_.hi < self.lo() || self.hi() < self_.lo && !sm.is_imported(within) =>
{
Some(Span::new(self_.lo, self_.hi, self_.ctxt, parent))
}
_ => None,
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/structs/ident-from-macro-expansion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
struct Foo {
inner: Inner,
}

struct Inner {
y: i32,
}

macro_rules! access {
($expr:expr, $ident:ident) => {
$expr.$ident
}
}

fn main() {
let k = Foo { inner: Inner { y: 0 } };
access!(k, y);
//~^ ERROR no field `y` on type `Foo`
}
14 changes: 14 additions & 0 deletions tests/ui/structs/ident-from-macro-expansion.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0609]: no field `y` on type `Foo`
--> $DIR/ident-from-macro-expansion.rs:17:16
|
LL | $expr.$ident
| ------ due to this macro variable
...
LL | access!(k, y);
| ^ unknown field
|
= note: available field is: `inner`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0609`.

0 comments on commit 09e5846

Please sign in to comment.