rust-lang/rust-analyzer · error

Can't rename local that is defined in a macro declaration

Error message

Can't rename local that is defined in a macro declaration

What it means

In `source_edit_from_def`, rust_analyzer must find the textual source of the definition being renamed to place the edit. When the local is declared inside a macro (the pattern comes from macro expansion and has no mappable original syntax node), no source range exists and it bails with "Can't rename local that is defined in a macro declaration".

Source

Thrown at crates/ide-db/src/rename.rs:725

                        .and_then(|x| x.name())
                        .and_then(|x| sema.original_range_opt(x.syntax()))
                        .or_else(|| {
                            source
                                .source
                                .syntax()
                                .original_file_range_opt(sema.db)
                                .map(TupleExt::head)
                        }) {
                        Some(FileRange { file_id: file_id2, range }) => {
                            file_id = Some(file_id2);
                            edit.replace(
                                range,
                                new_name.display(sema.db, file_id2.edition(sema.db)).to_string(),
                            );
                            continue;
                        }
                        None => {
                            bail!("Can't rename local that is defined in a macro declaration")
                        }
                    }
                }
            };
            file_id = Some(source.file_id);
            if let Either::Left(pat) = source.value {
                let name_range = pat.name().unwrap().syntax().text_range();

                // special cases required for renaming fields/locals in Record patterns
                if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
                    if let Some(name_ref) = pat_field.name_ref() {
                        if new_name.as_str() == name_ref.text().trim_start_matches("r#")
                            && pat.at_token().is_none()
                        {
                            // Foo { field: ref mut local } -> Foo { ref mut field }
                            //       ^^^^^^ delete this
                            //                      ^^^^^ replace this with `field`
                            cov_mark::hit!(test_rename_local_put_init_shorthand_pat);

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Rename the local at its real (non-macro) definition site, or inline the macro expansion and rename there.
  2. Restructure the code so the variable is bound outside the macro.
  3. In tooling, detect that the definition's source is inside a macro file and disable the rename action upfront.

Example fix

// before
macro_rules! m { ($v:ident) => { let x = 1; let _ = $v; } }
// renaming `x` (declared inside the macro) fails
// after: declare the local outside the macro
let x = 1;
macro_rules! m { ($v:ident) => { let _ = $v + x; } }
rename(x_local, "y")?; // ok
Defensive patterns

Strategy: try-catch

Validate before calling

fn is_in_macro_file(def: &Definition, sema: &Semantics<'_, RootDatabase>) -> bool {
    def.source(sema)
        .map(|s| s.file_id.is_macro())
        .unwrap_or(true)
}

Type guard

fn has_real_source(def: &Definition, sema: &Semantics<'_, RootDatabase>) -> bool {
    def.source(sema).map(|s| !s.file_id.is_macro()).unwrap_or(false)
}

Try / catch

match def.rename(sema, new_name, ...) {
    Err(e) if e.to_string().contains("defined in a macro declaration") => eprintln!("expand the macro and rename there"),
    other => other?,
}

Prevention

When it happens

Trigger: Renaming a `Definition::Local` whose binding pattern was produced by a macro expansion — e.g. a variable introduced by `macro_rules!` or `vec![]` destructuring — such that `source.value` pattern resolution returns `None` at crates/ide-db/src/rename.rs:725.

Common situations: User places the cursor on a variable generated by a macro (`let (a, b) = tuple;` from a macro, or identifiers emitted by declarative macros) and invokes rename; the IDE cannot produce text edits inside macro input.

Related errors


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/be23892ae4e88391. Report an issue: GitHub.