Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-diagnostics/src/handlers/no_such_field.rs')
| -rw-r--r-- | crates/ide-diagnostics/src/handlers/no_such_field.rs | 89 |
1 files changed, 85 insertions, 4 deletions
diff --git a/crates/ide-diagnostics/src/handlers/no_such_field.rs b/crates/ide-diagnostics/src/handlers/no_such_field.rs index 6fb8702c53..e3364c1a37 100644 --- a/crates/ide-diagnostics/src/handlers/no_such_field.rs +++ b/crates/ide-diagnostics/src/handlers/no_such_field.rs @@ -1,11 +1,11 @@ use either::Either; -use hir::{HasSource, HirDisplay, Semantics, VariantId}; +use hir::{HasSource, HirDisplay, InMacroFile, Semantics, VariantId}; use ide_db::text_edit::TextEdit; use ide_db::{ EditionedFileId, RootDatabase, helpers::is_editable_crate, source_change::SourceChange, }; use syntax::{ - AstNode, + AstNode, TextSize, ast::{self, edit::IndentLevel, make}, }; @@ -87,7 +87,6 @@ fn missing_record_expr_field_fixes( record_field_list(fields)? } }; - let def_file_id = def_file_id.original_file(sema.db); if !is_editable_crate(module.krate(sema.db), sema.db) { return None; @@ -103,13 +102,19 @@ fn missing_record_expr_field_fixes( make::ty(&new_field_type.display_source_code(sema.db, module.into(), true).ok()?), ); - let (indent, offset, postfix, needs_comma) = + let (mut indent, offset, postfix, needs_comma) = if let Some(last_field) = record_fields.fields().last() { let indent = IndentLevel::from_node(last_field.syntax()); let offset = last_field.syntax().text_range().end(); let needs_comma = !last_field.to_string().ends_with(','); (indent, offset, String::new(), needs_comma) } else { + // We don't have enough whitespace information in a macro-defined empty struct + // to compute the correct indent. + if def_file_id.is_macro() { + return None; + } + let indent = IndentLevel::from_node(record_fields.syntax()); let offset = record_fields.l_curly_token()?.text_range().end(); let postfix = if record_fields.syntax().text().contains_char('\n') { @@ -119,6 +124,20 @@ fn missing_record_expr_field_fixes( }; (indent + 1, offset, postfix, false) }; + let (def_file_id, offset) = if let Some(macro_file) = def_file_id.macro_file() { + // Map the preceding token so the source insertion uses the end of that token. + let (range, _) = + InMacroFile::new(macro_file, offset - TextSize::new(1)).original_file_range(sema.db); + let anchor = sema + .parse(range.file_id) + .syntax() + .token_at_offset(range.range.start()) + .right_biased()?; + indent = IndentLevel::from_token(&anchor); + (range.file_id, range.range.end()) + } else { + (def_file_id.file_id()?, offset) + }; let mut new_field = new_field.to_string(); // FIXME: check submodule instead of FileId @@ -456,6 +475,68 @@ fn main() { } #[test] + fn test_add_field_macro_defined_struct() { + check_fix( + r#" +macro_rules! identity { ($($t:tt)*) => { $($t)* }; } +identity! { + struct S { + a: i32 + } +} +fn f() { let _ = S { a: 1, b$0: false }; } +"#, + r#" +macro_rules! identity { ($($t:tt)*) => { $($t)* }; } +identity! { + struct S { + a: i32, + b: bool + } +} +fn f() { let _ = S { a: 1, b: false }; } +"#, + ); + } + + #[test] + fn test_add_field_macro_defined_struct_large_offset() { + // Regression test: we should handle macro definitions whose offset is larger than + // the max position in main.rs. + check_fix( + r#" +//- /main.rs +#[macro_use] +mod m; +make_items!(); +fn f() { let _ = S { a: 1, bbb$0: 2 }; } +//- /m.rs +macro_rules! make_items { + () => { + pub struct Padding { + pub p0: i32, pub p1: i32, pub p2: i32, + pub p3: i32, pub p4: i32, pub p5: i32, + } + pub struct S { pub a: i32 } + }; +} +"#, + r#" +macro_rules! make_items { + () => { + pub struct Padding { + pub p0: i32, pub p1: i32, pub p2: i32, + pub p3: i32, pub p4: i32, pub p5: i32, + } + pub struct S { pub a: i32, + pub(crate) bbb: i32 } + }; +} +"#, + ) + } + + #[test] fn no_such_field_no_fix_for_struct_in_library_crate() { check_no_fix( r#" |