-
-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Implement method signature suggestion for trait mismatches error #149027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This comment has been minimized.
This comment has been minimized.
a989b57 to
3d5d61f
Compare
This comment has been minimized.
This comment has been minimized.
3d5d61f to
3cecb85
Compare
3cecb85 to
e408b20
Compare
This comment has been minimized.
This comment has been minimized.
e408b20 to
3cecb85
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay in getting back here, this slipped my notifications.
Did you have a look at the previous implementation? #107548
Some of the tests in that one contains //~| HELP attributes, which I think is useful for actually testing that the behaviour is what we desire.
| | | ||
| help: modify the signature to match the trait definition | ||
| | | ||
| LL | fn come_on_a_little_more_effort(_: (), _: (), _: ()) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that the help message is desirable for the cross-crate case (such as the serde example in the issue), since there the solution for the user is always to change the implementation (vs. e.g. changing the trait).
But here, it feels overly verbose? We're showing come_on_a_little_more_effort three times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. In the case of local traits, the trait definition may be wrong, so it would be good to just point out that the two are different.
tests/ui/impl-trait/in-trait/method-signature-matches.too_few.stderr
Outdated
Show resolved
Hide resolved
|
Reminder, once the PR becomes ready for a review, use |
3cecb85 to
9f65b05
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
9f65b05 to
34b2df8
Compare
|
@rustbot ready |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I had another look through, I like the test you've added, but I still feel that the approach with extracting the signature from spans is the wrong one (though maybe that's just me hating span calculation code), have expanded upon it in a comment.
tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.stderr
Outdated
Show resolved
Hide resolved
2a476e7 to
0e8c962
Compare
|
I've incorporated your suggestions! It looks much cleaner. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! I'm happy with the output, now it's just comments on the implementation.
tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.stderr
Outdated
Show resolved
Hide resolved
| //~^ ERROR: method `has_self` has 2 parameters but the declaration in trait `has_self` has 1 | ||
| //~| HELP: remove the extra parameter to match the trait | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a test for missing and extra &self as well?
| struct MyStruct5; | |
| impl Bar for MyStruct5 { | |
| fn no_params(&self) {} | |
| fn has_self() {} | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, please add a test like:
trait NonIdentArguments {
fn pattern_types_in_arguments(_: i32, (_a, _b): (i32, i32)) {}
}
struct MyStruct6;
impl NonIdentArguments for MyStruct6 {
fn pattern_types_in_arguments() {}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding or removing &self from the implementation causes E0185 and E0186 errors instead of E0050. So I don't see any suggestions working on this pr, but would it be a good idea to still add that test case?
error[E0185]: method `no_params` has a `&self` declaration in the impl, but not in the trait
--> $DIR/trait-impl-param-mismatched-cross-crate.rs:45:5
|
LL | fn no_params(&self) {}
| ^^^^^^^^^^^^^^^^^^^ `&self` used in impl
|
= note: `no_params` from trait: `fn()`
error[E0186]: method `has_self` has a `&self` declaration in the trait, but not in the impl
--> $DIR/trait-impl-param-mismatched-cross-crate.rs:48:5
|
LL | fn has_self() {}
| ^^^^^^^^^^^^^ expected `&self` in impl
|
= note: `has_self` from trait: `fn(&Self)`
Or would it be better to add HELP to E0185 and E0186 as well?
(I think it's going to get a bit longer)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that's fine then for now.
Maybe add a test for self: &Self as well, just to ensure that that works?
| let replacement = if kept.is_empty() { String::new() } else { kept.join(", ") }; | ||
| Some(("remove the extra parameter to match the trait", replacement)) | ||
| } else { | ||
| None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: We already checked trait_number_args != impl_number_args above, this case should be unreachable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to make it clear, use:
match trait_number_args.cmp(impl_number_args) {
Ordering::Less => { ... }
Ordering::Greater => { ... }
Ordering::Equal => unreachable!(),
}But I'm fine with just doing if trait_number_args > impl_number_args { ... } else { ... }.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to use .cmp and unreachable! rather than if {} else {}
| }; | ||
|
|
||
| if let (Some(span), Some((msg, replacement))) = (impl_inputs_span, suggestion_text) { | ||
| err.span_suggestion_verbose(span, msg, replacement, Applicability::MaybeIncorrect); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of always making the span the entire argument list, maybe you can shrink it to only be the relevant parts?
Something like:
let (span, replacement) = if trait_number_args > impl_number_args {
// Span is right before the end parenthesis:
// fn foo(a: i32 ) {}
// ^
let span = impl_inputs_span.shrink_to_hi();
let replacement = ", b: u32";
(span, replacement)
} else {
// Span of the arguments that there are too many of:
// fn foo(a: i32, b: u32) {}
// ^^^^^^^^
let span = impl_inputs_span.with_lo(impl_m_sig.decl.inputs[trait_number_args - 1].span);
let replacement = "";
(span, replacement)
};| ); | ||
|
|
||
| // Only emit verbose suggestions when the trait span isn’t local (e.g., cross-crate). | ||
| if trait_span.is_none() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Use !trait_m.def_id.is_local() instead.
| let trait_sig = tcx.fn_sig(trait_m.def_id); | ||
| let trait_arg_idents = tcx.fn_arg_idents(trait_m.def_id); | ||
| let sm = tcx.sess.source_map(); | ||
| let impl_inputs_span = (!impl_m_sig.span.is_dummy()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I don't think you need to guard span_to_snippet with is_dummy?
| let impl_inputs_span = (!impl_m_sig.span.is_dummy()) | ||
| .then(|| { | ||
| sm.span_to_snippet(impl_m_sig.span).ok().and_then(|snippet| { | ||
| find_param_bounds(&snippet).map(|(lo_rel, hi_rel)| { | ||
| let lo = impl_m_sig.span.lo() + BytePos(lo_rel as u32); | ||
| let hi = impl_m_sig.span.lo() + BytePos(hi_rel as u32); | ||
| impl_m_sig.span.with_lo(lo).with_hi(hi) | ||
| }) | ||
| }) | ||
| }) | ||
| .flatten(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like inspecting the source and looking for parentheses with find_param_bounds is brittle. For example, I think it would find the wrong span on something like:
trait Foo<T> {}
fn foo<T: Foo<()>>() {}That said, I recognize that it's gonna be annoying to find this span in any case, because the span of the parentheses that enclose parameters seems to be dropped early in the compilation pipeline (it's not even part of ast::Fn).
Maybe a more robust approach would be to:
- If there are parameters, grab the span from the first parameter identifier and last parameter type, and combine them.
- If there are no parameters, grab the span of the output, and walk backwards until we find
)and(.
In any case, maybe you could inline find_param_bounds, that might make it easier to follow what's going on?
And maybe you could add a comment about what it is that we're actually looking for? Something like:
// Find the span of the space between the parentheses in a method.
// fn foo(...) {}
// ^^^There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, I'm not sure what's the best option here. Neither the current solution, nor the solution that I'm proposing are gonna handle every edge-case, and maybe the best would be a blend somewhere in between.
No matter what, I'd lean towards whatever solution is the simplest in terms of LOC, it is just a suggestion that we're implementing, it doesn't have to be perfect (especially given that we're using Applicability::MaybeIncorrect).
0e8c962 to
ee0499f
Compare
|
thank you for the review:) @rustbot ready |
|
|
||
| struct MyStruct5; | ||
| impl NonIdentArguments for MyStruct5 { | ||
| fn pattern_types_in_arguments(_: i32, _: (i32, i32)) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the trait is fn pattern_types_in_arguments(_: i32, (_a, _b): (i32, i32)) {}
but replacing (_a, _b) with _
|
Hello, @madsmtm. I was wondering if there is anything else I should add to this PR. I would appreciate your review :) |
resolve: #106999