Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-diagnostics/src/handlers/type_mismatch.rs129
1 files changed, 120 insertions, 9 deletions
diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
index b16be503ef..df815d6322 100644
--- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs
+++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
@@ -45,18 +45,31 @@ pub(crate) fn type_mismatch(
cov_mark::hit!(type_mismatch_range_adjustment);
Some(salient_token_range)
});
+
+ let expected = d
+ .expected
+ .display(ctx.db(), ctx.display_target)
+ .with_closure_style(ClosureStyle::ClosureWithId)
+ .to_string();
+ let actual = d
+ .actual
+ .display(ctx.db(), ctx.display_target)
+ .with_closure_style(ClosureStyle::ClosureWithId)
+ .to_string();
+
+ // The types differ (that's why we're here), yet they render the same, e.g. `foo::S` and
+ // `bar::S` both render as `S`. Retry with qualified paths so the message isn't a useless
+ // "expected S, found S".
+ let (expected, actual) = if expected == actual {
+ qualified_display(ctx, d).unwrap_or((expected, actual))
+ } else {
+ (expected, actual)
+ };
+
Some(
Diagnostic::new(
DiagnosticCode::RustcHardError("E0308"),
- format!(
- "expected {}, found {}",
- d.expected
- .display(ctx.db(), ctx.display_target)
- .with_closure_style(ClosureStyle::ClosureWithId),
- d.actual
- .display(ctx.db(), ctx.display_target)
- .with_closure_style(ClosureStyle::ClosureWithId),
- ),
+ format!("expected {expected}, found {actual}"),
display_range,
)
.stable()
@@ -64,6 +77,20 @@ pub(crate) fn type_mismatch(
)
}
+/// Renders both sides of the mismatch with qualified paths, for when their plain names collide.
+/// Returns `None` if either side has no renderable path, in which case both keep their plain
+/// names — mixing a qualified and an unqualified name would be more confusing, not less.
+fn qualified_display(
+ ctx: &DiagnosticsContext<'_, '_>,
+ d: &hir::TypeMismatch<'_>,
+) -> Option<(String, String)> {
+ let root = d.expr_or_pat.file_id.parse_or_expand(ctx.db());
+ let module = ctx.sema.scope(d.expr_or_pat.value.to_node(&root).syntax())?.module();
+ let expected = d.expected.display_source_code(ctx.db(), module.into(), true).ok()?;
+ let actual = d.actual.display_source_code(ctx.db(), module.into(), true).ok()?;
+ Some((expected, actual))
+}
+
fn fixes(ctx: &DiagnosticsContext<'_, '_>, d: &hir::TypeMismatch<'_>) -> Option<Vec<Assist>> {
let mut fixes = Vec::new();
@@ -1838,4 +1865,88 @@ pub use evil::Ordering;
"#,
);
}
+
+ // Tests for qualified paths on name collision (issue #22331)
+
+ #[test]
+ fn type_mismatch_collision_basic_structs() {
+ check_diagnostics(
+ r#"
+mod foo {
+ pub struct S;
+}
+mod bar {
+ pub struct S;
+}
+fn test(_: foo::S) {
+ test(bar::S);
+ //^^^^^^ error: expected foo::S, found bar::S
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn type_mismatch_collision_in_generic() {
+ check_diagnostics(
+ r#"
+//- minicore: option
+mod foo {
+ pub struct S;
+}
+mod bar {
+ pub struct S;
+}
+fn make() -> Option<bar::S> { loop {} }
+fn test(_: Option<foo::S>) {
+ test(make());
+ //^^^^^^ error: expected Option<foo::S>, found Option<bar::S>
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn type_mismatch_no_collision_unchanged() {
+ check_diagnostics(
+ r#"
+mod foo {
+ pub struct S;
+}
+mod bar {
+ pub struct T;
+}
+fn test(_: foo::S) {
+ test(bar::T);
+ //^^^^^^ error: expected S, found T
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn type_mismatch_multiple_collisions() {
+ check_diagnostics(
+ r#"
+//- minicore: result
+mod foo {
+ pub struct T;
+}
+mod bar {
+ pub struct T;
+}
+mod baz {
+ pub struct E;
+}
+mod qux {
+ pub struct E;
+}
+fn make() -> Result<bar::T, qux::E> { loop {} }
+fn test(_: Result<foo::T, baz::E>) {
+ test(make());
+ //^^^^^^ error: expected Result<foo::T, baz::E>, found Result<bar::T, qux::E>
+}
+"#,
+ );
+ }
}