-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
non_local_definitions
lint fires for impl Trait for NonLocalType<SomeLocalType>
, probably shouldn't
#126768
Comments
It's not because it cannot be named that it cannot be inferred. This can be demonstrate with your example, here trait Tr {}
fn foo() {
struct Bar;
impl Tr for Box<Bar> {} // with this impl definition
}
fn do_stuff<U: Tr>() {}
fn main() {
do_stuff::<Box<_>>(); // this code will compile, as `_` will be resolved to `Bar`
} @rustbot labels -C-bug +C-discussion +L-non_local_definitions -needs-triage |
@rustbot labels +I-lang-nominated This is kind of a surprising interaction of things. Nominating as we should discuss for visibility and to confirm whether this is what we meant to do. (In general, we had wanted to support people making local impls for local types on outer traits. While it's clear the impls in this issue do have a non-local effect, the only workaround is to promote the local type to a broader scope outside of the function. That seems a bit strange and unfortunate, and probably isn't what the user wanted to do. There may or may not be a better option; we should just discuss to confirm.) |
@traviscross I agree that this interaction is surprising and I think that because it is surprising (and non-intuitive) we should warn about it. @workingjubilee brought up in #125068 (comment) an example where someone might write a private type that for someone might rely on privacy to not be constructible outside of the function:
|
@rustbot labels -I-lang-nominated -C-discussion +C-bug We discussed this in the lang call today. We had everyone there, and our unanimous consensus was that we do not want the RFC 3373 lint to fire in these cases. E.g., we do not want to lint against: trait Tr {}
fn foo() {
struct S {};
impl Tr for &S {}
} This has non-local effect due to the "only one thing could go here" inference rule. But still, we consider this beyond the intention of the lint, and if we were to later try to tighten things up (e.g. for the benefit of tooling), we may consider instead trying, in a new edition, to do away with or limit the inference rule (e.g. by adding a check to error if the type being inferred can't be named). For that, we discussed how there may be some precedent for it in the RFC 2145 work from @petrochenkov. That is, we'd expect people to be able to write (lint-free) the code above, and so we're OK with accepting the leakage here, as we tentatively plan to deal with that in a different way. Thanks to @jstarks for raising this point and filing this issue. |
as request T-lang is requesting some major changes in the lint inner workings in rust-lang#126768#issuecomment-2192634762
Switch back `non_local_definitions` lint to allow-by-default This PR switch back (again) the `non_local_definitions` lint to allow-by-default as T-lang is requesting some (major) changes in the lint inner workings in rust-lang#126768 (comment). This PR will need to be beta-backported, as the lint is currently warn-by-default in beta.
Switch back `non_local_definitions` lint to allow-by-default This PR switch back (again) the `non_local_definitions` lint to allow-by-default as T-lang is requesting some (major) changes in the lint inner workings in rust-lang#126768 (comment). This PR will need to be beta-backported, as the lint is currently warn-by-default in beta.
Switch back `non_local_definitions` lint to allow-by-default This PR switch back (again) the `non_local_definitions` lint to allow-by-default as T-lang is requesting some (major) changes in the lint inner workings in rust-lang#126768 (comment). This PR will need to be beta-backported, as the lint is currently warn-by-default in beta.
Switch back `non_local_definitions` lint to allow-by-default This PR switch back (again) the `non_local_definitions` lint to allow-by-default as T-lang is requesting some (major) changes in the lint inner workings in rust-lang#126768 (comment). This PR will need to be beta-backported, as the lint is currently warn-by-default in beta.
Switch back `non_local_definitions` lint to allow-by-default This PR switch back (again) the `non_local_definitions` lint to allow-by-default as T-lang is requesting some (major) changes in the lint inner workings in rust-lang#126768 (comment). This PR will need to be beta-backported, as the lint is currently warn-by-default in beta.
Switch back `non_local_definitions` lint to allow-by-default This PR switch back (again) the `non_local_definitions` lint to allow-by-default as T-lang is requesting some (major) changes in the lint inner workings in rust-lang#126768 (comment). This PR will need to be beta-backported, as the lint is currently warn-by-default in beta.
Rollup merge of rust-lang#127015 - Urgau:non_local_def-tmp-allow, r=lqd Switch back `non_local_definitions` lint to allow-by-default This PR switch back (again) the `non_local_definitions` lint to allow-by-default as T-lang is requesting some (major) changes in the lint inner workings in rust-lang#126768 (comment). This PR will need to be beta-backported, as the lint is currently warn-by-default in beta.
Switch back `non_local_definitions` lint to allow-by-default This PR switch back (again) the `non_local_definitions` lint to allow-by-default as T-lang is requesting some (major) changes in the lint inner workings in rust-lang/rust#126768 (comment). This PR will need to be beta-backported, as the lint is currently warn-by-default in beta.
as request T-lang is requesting some major changes in the lint inner workings in rust-lang#126768#issuecomment-2192634762 (cherry picked from commit 0c0dfb8)
Feedback from reviewing the new PR to implement the new heuristic is that it isn't clear why the lint is being implemented at all if this new decision is being made. I myself am slightly concerned about the summary of the consensus. "Edicts from on high" by T-lang... by which I mean, "we want this, not that, so do it"... are okay when it's clarifying something very small (sometimes someone just needs to make a decision and people don't feel they have authority to make that decision). However, when something is as fundamental as the raison d'être for a lint's very existence and the implementation of it raises fundamental questions like "wait, why?" then a larger explanation is in order, unfortunately. So it's no longer clear what the goals of this lint are. I realize this is just asking for someone to basically just repeat the already-accepted RFC in summary form, but with the current information we have from implementation woes and experimentation. However, this is not a waste of time: it is worth reevaluating if RFC 3373 + this recent comment make sense when put together. |
We were trying to achieve a certain language outcome with this lint. We wanted to lint against "sneaky inner impls" that look like this: trait Tr {}
struct S {}
fn foo() {
impl Tr for S {} //~ Sneaky!
} We wanted this because these are just weird, and we can't think of any good reasons people should write them. We specifically wanted to allow this: trait Tr {}
fn foo() {
struct S {}
impl Tr for S {} //~ Not sneaky!
} And this: struct S {}
fn foo() {
trait Tr {}
impl Tr for S {} //~ Not sneaky!
} It's clear why someone would want to do this sort of thing. We didn't think, when adopting the RFC, about the fact that, due to the 1-impl rule, this has non-local effect: trait Tr {}
fn foo() {
struct S {}
impl Tr for &S {} //~ Sneaky?
} We didn't mean for that to be a "sneaky inner impl". That just isn't in the same bucket for us, as a language matter. It's just as clear to us why someone would want to write that as it is why someone would want to write the So the perfect implementation of this, in a types sense, results in an undesirable language outcome. It's undesirable, because, in our view, the "sneaky" part is what the language is doing due to the 1-impl rule rather than what the user wrote. We should have caught this when it happened, here, and then in #122747. My view is that none of us really grasped at that time what the effect of that work would be. That resulted in wasting other people's time and effort, and we're sorry about that. We'll try to do better. The key question, with respect to this lint, is whether its purpose should be to:
We chose Option 2, and felt that's what we had meant to do in adopting RFC 3373.1 For now, the ...and beta-backported. That all seems the right thing to do. Does RFC 3373 still make sense at all? We felt it did, from the perspective of wanting to lint against the specific patterns that we felt were undesirable and unmotivated, but I'm interested to hear reasons if people don't think this makes sense, in light of the context here about what we were trying to achieve. Footnotes
|
Motivation part of RFC 3373 specifically names tooling as a reason for the lint:
From the perspective of e.g. “find references” or “find implementations”, the second case is sneaky. “find implementations” on trait Tr {
fn method() -> Self;
}
fn foo() {
struct S;
impl Tr for &S {
fn method() -> Self { &S }
}
}
fn main() {
let x = <&_ as Tr>::method(); // ← go-to-definition here
} would also require tooling to scan method bodies. Unless I’m missing something, the lint in its currently proposed form can’t really help tooling even if strictly enforced, so the first part of the motivation is no longer relevant? |
Sometimes when RFCs get pared back to match a consensus, the normative sections don't end up matching the non-normative sections exactly. That's what happened here.1 We didn't achieve consensus on committing ourselves to forbidding these patterns, and without a commitment to forbidding these, we're not actually addressing that motivation, regardless of how we implement this lint. A E.g., from our minutes:
I.e., we didn't end up having consensus on that motivation. We had consensus on linting at See also the footnote in my comment above. Footnotes
|
From the perspective of human readability (which seems to be the only remaining motivation), is it useful to trigger the lint in macro expansion results? Humans don’t usually read these, and it’s often impossible to tell apart e.g. named and unnamed consts on the macro callsite. Any implementations coming from macros are invisible for humans, regardless of whether they’re on the top level or not. Macros seemed to be a big source of churn with this lint, and I’m not sure having it in the macro expansions is worth it. |
I think a particular point @traviscross mentioned is worth highlighting here:
In other words, if we do want to try to achieve the goal of perfectly forbidding "sneaky inner impls", we may want to consider in part doing this by preventing the "only one possible type" rule from allowing those impls to be extracted from a function. Also, an implementation note: that should be "can't name" (as in a type hidden inside a function), not "don't have in scope"; it's important to be able to infer types that haven't been imported, so that |
@traviscross Thank you for the summary! I have a question regarding this passage:
trait Tr {}
fn foo() {
struct S {}
impl Tr for &S {} //~ Sneaky?
}
My understanding is that this had been brought up in RFC 3373, hereabouts, and that this comment was neither resolved nor decided on, nor even explicitly accepted as an unknown question. In fact it was also brought up some time afterwards in the then-closed issue (not very helpful as a venue, I know, but...). I believe these discussions are both referencing, essentially, the same thing that this issue is about (and that other issues are about, as a consequence...). Is my understanding correct: that this question was known and raised before the RFC completed its FCP (...by about 5 hours, but nevertheless) and not addressed? |
Looking now at the comment by @petrochenkov that you noted, it does look like we missed it, and certainly that we missed the applied consequences of it. We're planning a retrospective on this topic to look at what went wrong in processes and communication so that we can do better going forward. |
I am still somewhat unclear on the purpose of this lint. If it's not there to help tooling then what is the point, I see vague references to it being an "undesirable" or "weird" pattern but.... why? I do understand that it's... a slightly peculiar thing to write but it doesn't necessarily seem confusing or even dangerous to me. "Lint against certain patterns the lang team finds undesirable and ...." to me implies that T-lang has talked about this but somehow this reasoning was not communicated on this issue, only the "final decision" that T-lang came to. I find it a bit disappointing that after jubilee's comment asking for clarification on the motivation behind this lint there was... a lot of text! It basically just amounts to "we find this pattern undesirable and weird", but that seems slightly self evident by the fact that T-lang wants this lint even with it not being "fully correct". I have come away from reading this thread with still no idea why this lint is desirable. In general I find the motivation for this lint to be very weird. There were two motivations:
The first one has been completely discarded. The second one is weird? I'm not sure how many rust users there are (other than @workingjubilee, hi) that try to find out "does this type implement this trait" or "what traits does this type implement" by manually looking through every crate and module that could possibly write such an impl. That seems like a significant amount of work and also error prone as it relies on having a good mental model of coherence/orphan rules. Almost everybody who wants to answer that question ought to be able to ask a tool this question and get the correct answer, (e.g. look at rustdoc on the trait or type, or go to the type and look at the little "N implementations" pop up r-a provides). For people without the "official tooling", I would expect general purpose text search tools to also be used to find The fact that the first motivation is completely gone, and the set of users that would benefit from this lint seems likely small, makes me wonder why this is a valuable lint to have in rustc instead of an opt-in clippy lint for "interesting" code that somebody might prefer not to write for stylistic reasons. Primarily that kind of thing is what I'd T-lang to give input on. What is the benefit of having this lint? Why is Nominating this (again 😭) to hopefully get this info. All in all, thanks to everybody who's worked on this, I imagine this has been pretty stressful for everybody involved ❤️ |
As a side note @traviscross you mentioned:
The original RFC specified this would wind up being deny by default in some future edition so I do not think it is accurate to say that a lint would never have addressed the tooling motivation. |
To also comment from rust-analyzer's side, code like trait Tr {
fn fun() {}
}
fn foo() {
struct S {};
impl Tr for &S {}
}
fn bar() {
<&_>::fun();
// ^^^^^^ error: no such associated item
} will simply always error in r-a. There is no heuristic rust-analyzer can apply to resolve this or at least figure out that this might exist in a local impl. So moving back to syntactic instead of type-driven hurts tooling in this regard. Now this step would be fine in theory if you are actually planning on getting rid of the 1-impl rule in the future, but will that ever happen? I kind of doubt that. Also note one other smaller thing regarding the questioning of the lints usefulness after this proposed change, there are two things being checked here: Leaking local impls and leaking local |
I am personally not even convinced that warn-by-default is not helpful: even a warning helps tooling by discouraging people from writing edge cases that the tooling will do poorly on, which allows it to deprioritize satisfying those edge cases. I mean, we would all like our tools to be fast and correct even in unusual edge cases, but in reality, those tools are necessarily being written in less time than the compiler has had to mature and accumulate complexity. Something something, arrow of time and all that. |
Some on lang may have thought this was a useful first step in that way. Some had reservations we could or would want to ever go much further. Some thought it would get us more information for whether that is possible or desirable. Everyone could agree to lint against these specific cases. And everyone on lang cares deeply about tooling. |
Can this sort of thing not be done in Clippy, as boxy suggested? |
We, on rust-analyzer, get questions about this decently frequently. I'd say we've received about 4-5 questions about this over the past year in new and existing issues, but knowing the ratio of people complaining to simply putting up with an issue, I'd say the ratio is roughly 1:100. Which is to say: it's a decently prevalent issue and having lint would help a bunch of people who are otherwise confused. As @GoldsteinE said, the second case is sneaky for rust-analyzer. I'd like to that add to that statement by citing this blog post by matklad where he noted that the second case is not sneaky for @traviscross are these the meeting minutes? I'm not able to find where Niko said "I'm a bit worried about this. First of all, I'm unconvinced that this is as important as people think it is for performance. Secondly, I'm worried about fallout from trying to drive this, e.g. for the reason that TC noted." To respond to Niko's comment, I'm not sure what sort of data we can reasonably provide because it feels like a chicken-and-egg issue. However, I can say that our efforts to shrink the search space of |
We decided to pare back the RFC in our meeting on 2024-01-03. The minutes for that are as follows:
We decided to not warn about the
|
In the relatively short period that this lint was in nightly and beta, there were a number of duplicate issues filed over the surprising behavior of it. That is, extending the lint to these |
Rework `non_local_definitions` lint to only use a syntactic heuristic This PR reworks the `non_local_definitions` lint to only use a syntactic heuristic, i.e. not use a type-system logic for whenever an `impl` is local or not. Instead the new logic wanted by T-lang in rust-lang/rust#126768 (comment), which is to consider every paths in `Self` and `Trait` and to no longer use the type-system inference trick. `@rustbot` labels +L-non_local_definitions Fixes #126768
#120363 introduces some warnings that seem like bad advice. Nightly now warns if a function has an internal implementation of a trait on a non-internal (to that function) type. That seems like a reasonable warning in the general case, but it doesn't seem so reasonable for types like
Box<Inner>
, whereInner
is defined within the function and cannot be named outside it.It rather seems like something like the orphan rule should apply. You should be able to impl traits on
Foo<T>
as long asT
is defined at the same level as theimpl
.Playground link
I tried this code on nightly:
I expected to see this happen: no errors/warnings
Instead, this happened:
Meta
rustc --version --verbose
(well, from the playground):Backtrace
The text was updated successfully, but these errors were encountered: