Skip to content

Fix debug assertion ICE in suggest_remove_deref with empty span#151508

Open
zzjas wants to merge 1 commit intorust-lang:mainfrom
zzjas:fix-empty-span
Open

Fix debug assertion ICE in suggest_remove_deref with empty span#151508
zzjas wants to merge 1 commit intorust-lang:mainfrom
zzjas:fix-empty-span

Conversation

@zzjas
Copy link
Contributor

@zzjas zzjas commented Jan 22, 2026

This fixes a debug assertion ICE in suggest_remove_deref when a proc macro creates a dereference expression with overlapping spans.

When the * token shares the same span as the inner expression, expr.span.until(inner.span) produces an empty span, which combined with an empty suggestion string triggers the debug assertion in diagnostic.rs.

This can be triggered with a proc macro like:

#[proc_macro]
pub fn same_span_deref(input: TokenStream) -> TokenStream {
    let first_token = input.clone().into_iter().next().unwrap();
    let span = first_token.span();
    let mut star = Punct::new('*', Spacing::Alone);
    star.set_span(span); // * now has same span as inner expr

    let mut result = vec![TokenTree::Punct(star)];
    result.extend(input);
    result.into_iter().collect()
}

And the following code:

fn needs_sized<T: Sized>(_: T) {}

fn main() {
    let s: &str = "hello";
    needs_sized(same_span_deref!(s)); // ICE: "Span must not be empty and have no suggestion"
}

The fix adds an is_empty() guard following several existing guards in the same file:

// Empty suggestions with empty spans ICE with debug assertions
if !prefix_span.is_empty() {
suggestion.push((prefix_span, String::new()));
}

I can provide a full runnable PoC if needed.

Thanks for looking into this!

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jan 22, 2026
@rustbot
Copy link
Collaborator

rustbot commented Jan 22, 2026

r? @mati865

rustbot has assigned @mati865.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@mati865
Copy link
Member

mati865 commented Jan 22, 2026

Can you add a test for it?

@zzjas
Copy link
Contributor Author

zzjas commented Jan 22, 2026

Can you add a test for it?

Yeah for sure, on it.

Add is_empty() guard before span_suggestion_verbose to handle proc
macros that create deref expressions with overlapping spans.
@zzjas
Copy link
Contributor Author

zzjas commented Jan 23, 2026

@mati865 Added a test case. Without the fix, running ./x test tests/ui/proc-macro/same-span-deref-ice.rs will crash with:

thread 'rustc' (505275) panicked at compiler/rustc_errors/src/diagnostic.rs:998:9:
Span must not be empty and have no suggestion
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: <rustc_errors::diagnostic::Diag>::span_suggestion_with_style::<&str, alloc::string::String>
   3: <rustc_trait_selection::error_reporting::TypeErrCtxt>::note_obligation_cause_code::<rustc_span::ErrorGuaranteed, rustc_type_ir::binder::Binder<rustc_middle::ty::context::TyCtxt, rustc_type_ir::predicate::TraitPredicate<rustc_middle::ty::context::TyCtxt>>>::{closure#0}
   4: <rustc_trait_selection::error_reporting::TypeErrCtxt>::note_obligation_cause_code::<rustc_span::ErrorGuaranteed, rustc_middle::ty::predicate::Predicate>
   5: <rustc_trait_selection::error_reporting::TypeErrCtxt>::note_obligation_cause_code::<rustc_span::ErrorGuaranteed, rustc_type_ir::binder::Binder<rustc_middle::ty::context::TyCtxt, rustc_type_ir::predicate::TraitPredicate<rustc_middle::ty::context::TyCtxt>>>::{closure#19}
   6: <rustc_trait_selection::error_reporting::TypeErrCtxt>::note_obligation_cause_code::<rustc_span::ErrorGuaranteed, rustc_middle::ty::predicate::Predicate>
   7: <rustc_trait_selection::error_reporting::TypeErrCtxt>::note_obligation_cause
...

@mati865
Copy link
Member

mati865 commented Jan 23, 2026

Thanks, I can't tell whether it's better to use dummy span or skip the error, so I'll reroll.

@rustbot reroll

@rustbot rustbot assigned fee1-dead and unassigned mati865 Jan 23, 2026
);
// Empty suggestions with empty spans ICE with debug assertions
let span = expr.span.until(inner.span);
if !span.is_empty() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a very ad-hoc fix, would using !span.from_expansion() do the trick as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! I tested locally and !span.from_expansion() alone wouldn't work here. The proc macro in the test uses set_span() to copy the input token's span onto the generated * token. Since that span comes from the original source, from_expansion() returns false, and the ICE still triggers. Do you think checking both (!expr.span.from_expansion() && !span.is_empty()) would be better here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i feel like i have/know a better solution here, but would able to demonstrate this only tomorrow since i don't remember a name of method and don't have an access to my pc :c

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 28, 2026
@rustbot
Copy link
Collaborator

rustbot commented Jan 28, 2026

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@Kivooeo
Copy link
Member

Kivooeo commented Jan 29, 2026

@fee1-dead, here is what i think, do you have opinion on that?

it adds a let Some(span) = expr.span.find_ancestor_in_same_ctxt(inner.span) check

        let suggest_remove_deref = |err: &mut Diag<'_, G>, expr: &hir::Expr<'_>| {
            if let Some(pred) = predicate.as_trait_clause()
                && tcx.is_lang_item(pred.def_id(), LangItem::Sized)
                && let hir::ExprKind::Unary(hir::UnOp::Deref, inner) = expr.kind
                && let Some(span) = expr.span.find_ancestor_in_same_ctxt(inner.span)
            {
                err.span_suggestion_verbose(
                    span.until(inner.span),
                    "references are always `Sized`, even if they point to unsized data; consider \
                     not dereferencing the expression",
                    String::new(),
                    Applicability::MaybeIncorrect,
                );
            }
        };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants