Skip to content

Commit 367dba7

Browse files
committed
Improve trait impl missing-parameter suggestions
1 parent 425ed6f commit 367dba7

File tree

4 files changed

+143
-27
lines changed

4 files changed

+143
-27
lines changed

compiler/rustc_hir_analysis/src/check/compare_impl_item.rs

Lines changed: 87 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,8 +1694,15 @@ fn compare_number_of_method_arguments<'tcx>(
16941694
) -> Result<(), ErrorGuaranteed> {
16951695
let impl_m_fty = tcx.fn_sig(impl_m.def_id);
16961696
let trait_m_fty = tcx.fn_sig(trait_m.def_id);
1697-
let trait_number_args = trait_m_fty.skip_binder().inputs().skip_binder().len();
1698-
let impl_number_args = impl_m_fty.skip_binder().inputs().skip_binder().len();
1697+
let trait_inputs = trait_m_fty.skip_binder().inputs().skip_binder();
1698+
let impl_inputs = impl_m_fty.skip_binder().inputs().skip_binder();
1699+
let trait_number_args = trait_inputs.len();
1700+
let impl_number_args = impl_inputs.len();
1701+
let params_prefix_match = impl_inputs
1702+
.iter()
1703+
.zip(trait_inputs)
1704+
.take(impl_number_args.min(trait_number_args))
1705+
.all(|(impl_ty, trait_ty)| impl_ty == trait_ty);
16991706

17001707
if trait_number_args != impl_number_args {
17011708
let trait_sig = trait_m.def_id.as_local().and_then(|def_id| {
@@ -1765,28 +1772,6 @@ fn compare_number_of_method_arguments<'tcx>(
17651772
);
17661773

17671774
let sm = tcx.sess.source_map();
1768-
1769-
fn find_param_bounds(snippet: &str) -> Option<(usize, usize)> {
1770-
let start = snippet.find('(')?;
1771-
let mut depth = 1usize;
1772-
let bytes = snippet.as_bytes();
1773-
let mut i = start + 1;
1774-
while i < bytes.len() {
1775-
match bytes[i] {
1776-
b'(' => depth += 1,
1777-
b')' => {
1778-
depth -= 1;
1779-
if depth == 0 {
1780-
return Some((start + 1, i));
1781-
}
1782-
}
1783-
_ => {}
1784-
}
1785-
i += 1;
1786-
}
1787-
None
1788-
}
1789-
17901775
let impl_inputs_span = (!impl_m_sig.span.is_dummy())
17911776
.then(|| {
17921777
sm.span_to_snippet(impl_m_sig.span).ok().and_then(|snippet| {
@@ -1799,7 +1784,28 @@ fn compare_number_of_method_arguments<'tcx>(
17991784
})
18001785
.flatten();
18011786

1802-
let suggestion = trait_sig
1787+
let missing_params = (trait_number_args > impl_number_args && params_prefix_match)
1788+
.then(|| {
1789+
trait_sig
1790+
.as_ref()
1791+
.and_then(|(_, trait_sig)| {
1792+
sm.span_to_snippet(trait_sig.span).ok().and_then(|snippet| {
1793+
find_param_bounds(&snippet).and_then(|(lo_rel, hi_rel)| {
1794+
tail_params(&snippet[lo_rel..hi_rel], impl_number_args)
1795+
})
1796+
})
1797+
})
1798+
.or_else(|| {
1799+
let signature = trait_m.signature(tcx);
1800+
find_param_bounds(&signature).and_then(|(lo, hi)| {
1801+
tail_params(&signature[lo..hi].trim(), impl_number_args)
1802+
})
1803+
})
1804+
})
1805+
.flatten();
1806+
1807+
let full_suggestion = trait_sig
1808+
.as_ref()
18031809
.and_then(|(_, trait_sig)| {
18041810
sm.span_to_snippet(trait_sig.span).ok().and_then(|snippet| {
18051811
find_param_bounds(&snippet)
@@ -1811,7 +1817,15 @@ fn compare_number_of_method_arguments<'tcx>(
18111817
find_param_bounds(&signature).map(|(lo, hi)| signature[lo..hi].trim().to_string())
18121818
});
18131819

1814-
if let (Some(span), Some(suggestion)) = (impl_inputs_span, suggestion) {
1820+
if let (Some(span), Some(missing)) = (impl_inputs_span, missing_params) {
1821+
let sep = if impl_number_args == 0 { "" } else { ", " };
1822+
err.span_suggestion_verbose(
1823+
span.shrink_to_hi(),
1824+
"add the missing parameter from the trait",
1825+
format!("{sep}{missing}"),
1826+
Applicability::MaybeIncorrect,
1827+
);
1828+
} else if let (Some(span), Some(suggestion)) = (impl_inputs_span, full_suggestion) {
18151829
err.span_suggestion_verbose(
18161830
span,
18171831
"modify the signature to match the trait definition",
@@ -1826,6 +1840,53 @@ fn compare_number_of_method_arguments<'tcx>(
18261840
Ok(())
18271841
}
18281842

1843+
fn find_param_bounds(snippet: &str) -> Option<(usize, usize)> {
1844+
let start = snippet.find('(')?;
1845+
let mut depth = 1usize;
1846+
let bytes = snippet.as_bytes();
1847+
let mut i = start + 1;
1848+
while i < bytes.len() {
1849+
match bytes[i] {
1850+
b'(' => depth += 1,
1851+
b')' => {
1852+
depth -= 1;
1853+
if depth == 0 {
1854+
return Some((start + 1, i));
1855+
}
1856+
}
1857+
_ => {}
1858+
}
1859+
i += 1;
1860+
}
1861+
None
1862+
}
1863+
1864+
fn split_top_level_params(params: &str) -> Vec<String> {
1865+
let mut depth = 0usize;
1866+
let mut start = 0;
1867+
let mut out = Vec::new();
1868+
for (idx, ch) in params.char_indices() {
1869+
match ch {
1870+
'(' | '<' | '[' | '{' => depth += 1,
1871+
')' | '>' | ']' | '}' => depth = depth.saturating_sub(1),
1872+
',' if depth == 0 => {
1873+
out.push(params[start..idx].trim().to_string());
1874+
start = idx + 1;
1875+
}
1876+
_ => {}
1877+
}
1878+
}
1879+
if start < params.len() {
1880+
out.push(params[start..].trim().to_string());
1881+
}
1882+
out
1883+
}
1884+
1885+
fn tail_params(params: &str, skip: usize) -> Option<String> {
1886+
let parts = split_top_level_params(params);
1887+
(parts.len() > skip).then(|| parts.into_iter().skip(skip).collect::<Vec<_>>().join(", "))
1888+
}
1889+
18291890
fn compare_synthetic_generics<'tcx>(
18301891
tcx: TyCtxt<'tcx>,
18311892
impl_m: ty::AssocItem,

tests/ui/impl-trait/in-trait/method-signature-matches.too_few.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ LL | fn come_on_a_little_more_effort(_: (), _: (), _: ()) -> impl Sized;
77
LL | fn come_on_a_little_more_effort() {}
88
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 3 parameters, found 0
99
|
10-
help: modify the signature to match the trait definition
10+
help: add the missing parameter from the trait
1111
|
1212
LL | fn come_on_a_little_more_effort(_: (), _: (), _: ()) {}
1313
| +++++++++++++++++++
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//! Tests that alias type suggestions when trait impl mismatched
2+
3+
trait Foo {
4+
fn foo(a: i32, b: u32);
5+
}
6+
7+
type AliasOfI32 = i32;
8+
impl Foo for () {
9+
fn foo(a: AliasOfI32) {}
10+
//~^ ERROR: method `foo` has 1 parameter but the declaration in trait `Foo::foo` has 2
11+
}
12+
13+
type AliasOfU32 = u32;
14+
impl Foo for u32 {
15+
fn foo(a: AliasOfU32) {}
16+
//~^ ERROR: method `foo` has 1 parameter but the declaration in trait `Foo::foo` has 2
17+
}
18+
19+
impl Foo for bool {
20+
fn foo(a: AliasOfI32, b: AliasOfU32) {} // Ok
21+
}
22+
23+
fn main() {}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error[E0050]: method `foo` has 1 parameter but the declaration in trait `Foo::foo` has 2
2+
--> $DIR/trait-impl-mismatched-with-alias.rs:9:15
3+
|
4+
LL | fn foo(a: i32, b: u32);
5+
| ----------- trait requires 2 parameters
6+
...
7+
LL | fn foo(a: AliasOfI32) {}
8+
| ^^^^^^^^^^ expected 2 parameters, found 1
9+
|
10+
help: add the missing parameter from the trait
11+
|
12+
LL | fn foo(a: AliasOfI32, b: u32) {}
13+
| ++++++++
14+
15+
error[E0050]: method `foo` has 1 parameter but the declaration in trait `Foo::foo` has 2
16+
--> $DIR/trait-impl-mismatched-with-alias.rs:15:15
17+
|
18+
LL | fn foo(a: i32, b: u32);
19+
| ----------- trait requires 2 parameters
20+
...
21+
LL | fn foo(a: AliasOfU32) {}
22+
| ^^^^^^^^^^ expected 2 parameters, found 1
23+
|
24+
help: modify the signature to match the trait definition
25+
|
26+
LL - fn foo(a: AliasOfU32) {}
27+
LL + fn foo(a: i32, b: u32) {}
28+
|
29+
30+
error: aborting due to 2 previous errors
31+
32+
For more information about this error, try `rustc --explain E0050`.

0 commit comments

Comments
 (0)