fix: Smaller spans for unresolved field and method diagnostics

This commit is contained in:
Lukas Wirth 2023-12-08 18:46:36 +01:00
parent 9e82ab54e8
commit b1a8f83a0c
7 changed files with 99 additions and 18 deletions

4
Cargo.lock generated
View file

@ -1484,9 +1484,9 @@ dependencies = [
[[package]] [[package]]
name = "rowan" name = "rowan"
version = "0.15.14" version = "0.15.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9672ea408d491b517a4dc370159ec6dd7cb5c5fd2f41b02883830339109ac76" checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49"
dependencies = [ dependencies = [
"countme", "countme",
"hashbrown", "hashbrown",

View file

@ -314,6 +314,21 @@ impl InFile<TextRange> {
} }
} }
pub fn original_node_file_range_rooted(self, db: &dyn db::ExpandDatabase) -> FileRange {
match self.file_id.repr() {
HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value },
HirFileIdRepr::MacroFile(mac_file) => {
match ExpansionInfo::new(db, mac_file).map_node_range_up(db, self.value) {
Some((it, SyntaxContextId::ROOT)) => it,
_ => {
let loc = db.lookup_intern_macro_call(mac_file.macro_call_id);
loc.kind.original_call_range(db)
}
}
}
}
}
pub fn original_node_file_range_opt( pub fn original_node_file_range_opt(
self, self,
db: &dyn db::ExpandDatabase, db: &dyn db::ExpandDatabase,

View file

@ -8,7 +8,7 @@ use ide_db::{
use syntax::{ast, AstNode, AstPtr}; use syntax::{ast, AstNode, AstPtr};
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; use crate::{adjusted_display_range_new, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-field // Diagnostic: unresolved-field
// //
@ -22,15 +22,24 @@ pub(crate) fn unresolved_field(
} else { } else {
"" ""
}; };
Diagnostic::new_with_syntax_node_ptr( Diagnostic::new(
ctx,
DiagnosticCode::RustcHardError("E0559"), DiagnosticCode::RustcHardError("E0559"),
format!( format!(
"no field `{}` on type `{}`{method_suffix}", "no field `{}` on type `{}`{method_suffix}",
d.name.display(ctx.sema.db), d.name.display(ctx.sema.db),
d.receiver.display(ctx.sema.db) d.receiver.display(ctx.sema.db)
), ),
d.expr.clone().map(|it| it.into()), adjusted_display_range_new(ctx, d.expr, &|expr| {
Some(
match expr {
ast::Expr::MethodCallExpr(it) => it.name_ref(),
ast::Expr::FieldExpr(it) => it.name_ref(),
_ => None,
}?
.syntax()
.text_range(),
)
}),
) )
.with_fixes(fixes(ctx, d)) .with_fixes(fixes(ctx, d))
.experimental() .experimental()
@ -79,7 +88,7 @@ mod tests {
r#" r#"
fn main() { fn main() {
().foo; ().foo;
// ^^^^^^ error: no field `foo` on type `()` // ^^^ error: no field `foo` on type `()`
} }
"#, "#,
); );
@ -95,7 +104,7 @@ impl Foo {
} }
fn foo() { fn foo() {
Foo.bar; Foo.bar;
// ^^^^^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists // ^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists
} }
"#, "#,
); );
@ -112,7 +121,7 @@ trait Bar {
impl Bar for Foo {} impl Bar for Foo {}
fn foo() { fn foo() {
Foo.bar; Foo.bar;
// ^^^^^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists // ^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists
} }
"#, "#,
); );
@ -131,7 +140,7 @@ impl Bar for Foo {
} }
fn foo() { fn foo() {
Foo.bar; Foo.bar;
// ^^^^^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists // ^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists
} }
"#, "#,
); );

View file

@ -8,7 +8,7 @@ use ide_db::{
use syntax::{ast, AstNode, TextRange}; use syntax::{ast, AstNode, TextRange};
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; use crate::{adjusted_display_range_new, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-method // Diagnostic: unresolved-method
// //
@ -22,15 +22,24 @@ pub(crate) fn unresolved_method(
} else { } else {
"" ""
}; };
Diagnostic::new_with_syntax_node_ptr( Diagnostic::new(
ctx,
DiagnosticCode::RustcHardError("E0599"), DiagnosticCode::RustcHardError("E0599"),
format!( format!(
"no method `{}` on type `{}`{field_suffix}", "no method `{}` on type `{}`{field_suffix}",
d.name.display(ctx.sema.db), d.name.display(ctx.sema.db),
d.receiver.display(ctx.sema.db) d.receiver.display(ctx.sema.db)
), ),
d.expr.clone().map(|it| it.into()), adjusted_display_range_new(ctx, d.expr, &|expr| {
Some(
match expr {
ast::Expr::MethodCallExpr(it) => it.name_ref(),
ast::Expr::FieldExpr(it) => it.name_ref(),
_ => None,
}?
.syntax()
.text_range(),
)
}),
) )
.with_fixes(fixes(ctx, d)) .with_fixes(fixes(ctx, d))
.experimental() .experimental()
@ -92,7 +101,41 @@ mod tests {
r#" r#"
fn main() { fn main() {
().foo(); ().foo();
// ^^^^^^^^ error: no method `foo` on type `()` // ^^^ error: no method `foo` on type `()`
}
"#,
);
}
#[test]
fn smoke_test_in_macro_def_site() {
check_diagnostics(
r#"
macro_rules! m {
($rcv:expr) => {
$rcv.foo()
}
}
fn main() {
m!(());
// ^^^^^^ error: no method `foo` on type `()`
}
"#,
);
}
#[test]
fn smoke_test_in_macro_call_site() {
check_diagnostics(
r#"
macro_rules! m {
($ident:ident) => {
().$ident()
}
}
fn main() {
m!(foo);
// ^^^ error: no method `foo` on type `()`
} }
"#, "#,
); );
@ -105,7 +148,7 @@ fn main() {
struct Foo { bar: i32 } struct Foo { bar: i32 }
fn foo() { fn foo() {
Foo { bar: i32 }.bar(); Foo { bar: i32 }.bar();
// ^^^^^^^^^^^^^^^^^^^^^^ error: no method `bar` on type `Foo`, but a field with a similar name exists // ^^^ error: no method `bar` on type `Foo`, but a field with a similar name exists
} }
"#, "#,
); );

View file

@ -90,7 +90,7 @@ use stdx::never;
use syntax::{ use syntax::{
algo::find_node_at_range, algo::find_node_at_range,
ast::{self, AstNode}, ast::{self, AstNode},
SyntaxNode, SyntaxNodePtr, TextRange, AstPtr, SyntaxNode, SyntaxNodePtr, TextRange,
}; };
// FIXME: Make this an enum // FIXME: Make this an enum
@ -584,3 +584,16 @@ fn adjusted_display_range<N: AstNode>(
.unwrap_or(range), .unwrap_or(range),
} }
} }
// FIXME Replace the one above with this one?
fn adjusted_display_range_new<N: AstNode>(
ctx: &DiagnosticsContext<'_>,
diag_ptr: InFile<AstPtr<N>>,
adj: &dyn Fn(N) -> Option<TextRange>,
) -> FileRange {
let source_file = ctx.sema.parse_or_expand(diag_ptr.file_id);
let node = diag_ptr.value.to_node(&source_file);
diag_ptr
.with_value(adj(node).unwrap_or_else(|| diag_ptr.value.text_range()))
.original_node_file_range_rooted(ctx.sema.db)
}

View file

@ -16,7 +16,7 @@ doctest = false
cov-mark = "2.0.0-pre.1" cov-mark = "2.0.0-pre.1"
either.workspace = true either.workspace = true
itertools.workspace = true itertools.workspace = true
rowan = "0.15.11" rowan = "0.15.15"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
once_cell = "1.17.0" once_cell = "1.17.0"
indexmap.workspace = true indexmap.workspace = true

View file

@ -33,6 +33,7 @@ impl<N: AstNode + std::fmt::Debug> std::fmt::Debug for AstPtr<N> {
} }
} }
impl<N: AstNode> Copy for AstPtr<N> {}
impl<N: AstNode> Clone for AstPtr<N> { impl<N: AstNode> Clone for AstPtr<N> {
fn clone(&self) -> AstPtr<N> { fn clone(&self) -> AstPtr<N> {
AstPtr { raw: self.raw.clone(), _ty: PhantomData } AstPtr { raw: self.raw.clone(), _ty: PhantomData }