-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Spurious irrefutable_let_patterns warning with let-chain #139369
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
Comments
another example: #[allow(irrefutable_let_patterns)]
if let errCode = GetLastError() && errCode != ERROR_NO_MORE_ITEMS {
let _ = dbg!(errCode);
// logging the errCode to db, etc
} Honestly, I don't want to introduce the |
Do you want to weaken or remove this lint in general, or do you just want it to not fire on let chains? |
I think there's a reasonable case for not applying it to let chains, specifically. In the absence of let chains, I seem to recall at some point that we talked about making it not fire for any subsequent let in the chain, but keeping it for the first one since that one can be moved out. (But, of course, that's not true for |
That works for me. |
Both of the examples posted above argue against that: moving it out extends the scope of the binding, which might not be desired. |
If someone wants to put up a PR to make it not fire on let chains and nominate that, I'll propose FCP merge on it. I'd estimate that being likely to save us a step here. |
@RalfJung I'm aware, but that isn't necessarily a reason to allow it, just an argument that someone might want to write it that way. The downside of allowing it for let chains in general is that it may not be intentional, and sometimes this catches errors where you meant to write a refutable pattern. But nonetheless, I'd be 👍 for allowing it for let chains, for now, with the possibility of coming up with something more specific in the future to catch potential mistakes. |
Fair. It just seemed odd to make a suggestion that would change nothing for 100% of the examples considered in this issue. |
@rustbot labels -I-lang-nominated +I-lang-radar We talked about this in the @rust-lang/lang call today. We decided we just wanted to rip out the And CC @rust-lang/clippy in the event that clippy might want to add something here. |
I find the #![allow(irrefutable_let_patterns)]
fn foo() -> Option<u32> {
None
}
fn main() {
if let x = foo() {
println!("{:?}", x);
}
} This prints In most cases this mistake will lead to a type error really quickly because the Option isn't unwrapped in the body of the if statement -- I had to use |
We did talk about how some aspects of the lint might be most beneficial to new users, and that's why we pinged the clippy team here, as adding a lint to help such users might better fit in their domain. |
The comparison to Swift is interesting; I'd forgotten about that syntax. The specific case of That said, with example from @jnkr-ifx I've switched back to thinking most non-chain uses of What pushed me toward removing the lint is that I don't want overly paternalistic lints that limit expressiveness needlessly. It's true that we've already experienced that with some of the "irrefutable" lints (particularly in the where clause department where we've already relaxed it some). In fact I felt some annoyance about this arising in the discussion today. But I also don't buy the case I've heard so far for how it's nice to support if let Some(x) = foo() {
...
} else if let Ok(y) = bar() {
...
} else if let z = baz() {
...
} And I don't know, maybe I would want to write that for aesthetic reasons, but it isn't so bad to write In contrast, I find the example with a let chain brought up by @kurikomoe extremely motivating. Assigning to a variable and checking a condition on its value immediately is a very common pattern, and it's also common to only need that variable to exist inside the if block. Other languages have forms like this for the same reason. Outside of let chains, my thinking now is this is one of the places where Rust should err more in the direction of Go (simple and uniform) than Haskell (elegant and inscrutable). In terms of design goals, I would argue that "clarity of purpose" and "correctness by construction" are both on the side of keeping the lint but removing it for let chains. |
In that case, I feel like the lint you might want is about a trivially true condition rather than about an irrefutable pattern. These all seem about the same to me: if let () = () { .. }
if let () = () && true { .. }
if let () = () && 0 == 0 { .. }
if true { .. }
if 0 == 0 { .. } Similarly, a trailing |
We're already convinced on let chains, but for posterity, here's what the most motivating case for that looks like to me: if let x = op_one()
&& check_one(x)
&& let y = op_two(x)
&& check_two(y)
&& let z = op_three(y)
&& check_three(z)
{
..
} That is, we don't want to get |
I don't agree for |
Yes, that's actually why I feel the same about So I don't really see a principled or practical case for distinguishing an irrefutable let expression from any other trivially true expression. If we want to nag people to unwrap these blocks (or at least remove the |
Generally speaking, if I'm in the middle of debugging, and have deleted some code or added |
Should this lint be removed from rustc, it could of course be "down-lifted" to Clippy. I can see it in the |
I don't know the specifics for Java, but for C# at least the reason that And at least in C#, I like that these checks are type-based in Rust, and thus |
We discussed this in the lang call today without consensus. |
But to be clear, the consensus of not emitting the lint for chains still stands, right? So we could have a PR to adjust that while the question around non-chains remains open? (The issue was originally meant to be about chains only anyway.) |
We didn't discuss it particularly, but yes, my estimate would be that such a PR would be an easy FCP for us. Last we discussed the point, people also seemed OK with not firing the lint on |
Thinking about the Swift behavior further — that if let x = Some("hello") {
if format!("{x:?}") == "hello" {
do_important_work(); //~ Silently never reached.
}
} we could equally as well have type confusion with: if let x = Some("hello")
&& format!("{x:?}") == "hello"
{
do_important_work(); //~ Silently never reached.
} |
I think the fact that another language picked that convention (a convention that is rather at odds with how we handle option types in Rust) should not stop us from supporting idioms that are very natural in Rust, and that are awkward to express without firing this lint, such as the two examples at the top of this post. |
The reason is that the meaning of |
Because of Swift, I presume. The angle I was pushing there was that, regardless of how ambiguous or unambiguous we feel that So if we write: if let x = .. { .. } else { .. }
if let x = .. && true { .. } else { .. } I feel like I'd expect the same warnings (or lack thereof) on both of those independent of our feelings about the ambiguousness of if true { .. } else { .. } since the condition is "obviously" just as trivial as (@scottmcm made the point that the triviality of an arbitrary let expression is a type-space calculation rather than a value-space one, and I agree that's an interesting and distinguishing point, though the common case of The later point I'd press is similarly in the spirit that the chained case may not be so different from the unchained case, if we think that Footnotes
|
I think the lint should only warn for cases that are trivially true and where that is likely an accident. If someone writes The trouble with The argument for warning only in chains is that it helps us disambiguate those two cases: without a chain, the chance that this is intentional is much lower, since there's much less of a use for such an idiom on the Rust side, and thus the chances that this is a Swift-induced mistake is bigger and a lint is more likely to find a true mistake. |
I don't know. The only time there could be type confusion is when the user isn't otherwise constraining the type sufficiently, and it's hard for me to tell what's going to be more or less likely there. You have to work at it a bit anyway for this to be a problem, and it's not clear to me that if let x = foo_returns_option()
&& format!("{x:?}") == "hello"
{
do_important_work(); //~ Silently never reached.
} would be less likely to be a problem in that respect than anything else. Perhaps it would be a challenging lint to write, but it seems the appropriate lint for Swift users would be targeted on this, where you said (We could call the lint, e.g., |
I didn't say the Swift confusion is impossible in the chained case. But an irrefutable pattern is less likely to be a Swift confusion when there is a chain, mostly because there's a higher chance that someone actually wrote this on purpose without being confused. |
Code
Current output
Desired output
Rationale and extra context
This code naturally expresses "please call that function and then do something if the return value satisfies a condition". Putting the
let
binding outside theif
would be bad as then it remains in scope after theif
, which is not the intent. I could wrap it in an extra layer of curly braces but that increases the overall indentation level.I think this warning should never trigger for let chains.
Other cases
Rust Version
Anything else?
No response
The text was updated successfully, but these errors were encountered: