-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Fix needless_borrow
false positive
#9674
Conversation
566bbcd
to
5fb6db7
Compare
Requiring nested obligations is overly restrictive here. The problem for the given example comes from switching which impl is being used, essentially causing a different function to be called. In the example, The fix would be to check that the target of the type propagation is introduced by the current function. Should be as easy as checking if the parameter index is greater than the number of parameters in the parent generics list. |
I agree with this 100%.
I may be misunderstanding your solution, but wouldn't that allow examples like this to get through? #[derive(Clone, Copy)]
struct A;
trait Foo {
fn bar(self);
}
impl Foo for &A {
fn bar(self) {
println!("okay");
}
}
impl Foo for A {
fn bar(self) {
panic!();
}
}
fn call_bar(foo: impl Foo) {
foo.bar();
}
fn main() {
call_bar(&A);
} |
That is a pretty pathological case. It requires writing a trait impl for some type and it's reference which do different things. This is a thing which is already surprising. Also note you can do the same thing even with a nested obligation. trait Tr { fn foo() }
impl Tr for u8 { fn foo() { panic!() } }
impl<'a, T: 'a + Tr> Tr for &'a T { fn foo() { call_some_other_fn() } } This issue is the reason I had you add the exception for |
I agree with you it's pathological. But, to be honest, I would consider any false positive that doesn't involve nested obligations to be pathological. Would you disagree? For example, if you run |
The lack of nested obligations will come into play when the impl for the reference isn't generic. e.g. trait Tr {}
impl Tr for u8 {}
impl<'a> Tr &'a u8 {} Here the impl on the reference has no nested obligation, but will still most likely be equivalent to the non-reference impl. This is also the only way to implement this for foreign traits on local types. |
I'm not convinced, but I'll adjust the PR to implement your fix. Thank you for your patient explanation. |
5fb6db7
to
d7fe086
Compare
I think the most recent commit implements your idea in #9674 (comment). Please let me know if I misunderstood. |
That looks like your blocking any The predicates that need to be blocked are |
d7fe086
to
f92fa85
Compare
f92fa85
to
83771c5
Compare
Is this more like what you had in mind? |
That looks like it. Thank you. @bors r+ |
☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test |
The PR fixes the false positive exposed by @BusyJay's example in: #9111 (comment)
The current approach is described in #9674 (comment) and #9674 (comment).
The original approach appears below.
The proposed fix is to flag only "simple" trait implementations involving references, a concept
that I introduce next.
Intuitively, a trait implementation is "simple" if all it does is dereference and apply the trait
implementation of a type named by a type parameter.
AsRef
provides a good example of a simpleimplementation: https://doc.rust-lang.org/std/convert/trait.AsRef.html#impl-AsRef%3CU%3E-for-%26T
We can make this idea more precise as follows. Given a trait implementation, first determine
whether the implementation is "used defined." If so, then examine its nested obligations.
Consider the implementation simple if-and-only-if:
X
in the nested obligation's substitution, eitherX
is the same as that ofthe original obligation's substitution, or the original type is
&X
For example, the following implementation from @BusyJay's example is "complex" (i.e., not simple)
because it produces no nested obligations:
On the other hand, the following slightly modified implementation is simple, because it produces
a nested obligation for
Extend<X>
:How does flagging only simple implementations help? One way of interpreting the false positive in
@BusyJay's example is that it separates a reference from a concrete type. Doing so turns a
successful type inference into a failing one. By flagging only simple implementations, we
separate references from type variables only, thereby eliminating this class of false positives.
Note that
Deref
is a special case, as the obligations generated for it already involve theunderlying type.
r? @Jarcho (Sorry to keep pinging you with
needless_borrow
stuff. But my impression is no one knows this code better than you.)changelog: fix
needless_borrow
false positive