-
Notifications
You must be signed in to change notification settings - Fork 1.8k
add default_mismatches_new lint
#14234
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
Conversation
new_without_default lintdefault_mismatches_new lint
c634158 to
da55789
Compare
default_mismatches_new lintdefault_mismatches_new lint
da55789 to
fc5efa3
Compare
default_mismatches_new lintdefault_mismatches_new lint
|
There will be a false positive on this one and the suggestion won't apply: #[derive(Default)]
struct S<T> {
o: Option<T>,
}
impl<T> S<T> {
fn new() -> Self {
S { o: None }
}
} |
|
@samueltardieu good catch! Auto-derive implements it with a impl<T: Default> Default for S<T> {
fn default() -> S<T> {
S { o: Default::default() }
}
}What logic does the Default derive uses? Does it simply Possible solutions
|
|
For now, I restricted this lint to non-generic types. Lets do generics separately. |
Constraints are additive, you would have also to consider the constraints on the enclosing |
|
@samueltardieu thx, so lets skip types with generics for now - even without generics this catches all the same cases as part of CI lint check. We can track it in a separate issue/PR. |
b9ece48 to
b36567a
Compare
|
@samueltardieu should @llogiq or someone else be assigned as the reviewer? (per your priv comment) |
This should not be necessary as the issue is assigned to him already, but just in case I've requested his review. |
| /// } | ||
| /// ``` | ||
| /// | ||
| /// Alternatively, if the `new()` method requires a non-default initialization, implement a custom `Default`. |
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.
This would unify the new and default implementation, losing either one or the other. I think the docs should suggest renaming new to clarify the difference.
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.
@llogiq thx for review! Could you clarify how you think the docs should change? The rustc issue that actually caused me to write this lint was that new() would set the separator to ' ', whereas the default() set it to '\0'. So unifying them should loose one or the other, and consolidate the code, but keep the original function as new()
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.
P.S. are there any blockers to start the FCP?
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.
@llogiq thx for review! Could you clarify how you think the docs should change? The
rustcissue that actually caused me to write this lint was thatnew()would set the separator to' ', whereas thedefault()set it to'\0'. So unifying them should loose one or the other, and consolidate the code, but keep the original function asnew()
I understand it as "Another less standard method name would be better than new() if it is different from default(), you could suggest renaming the new implementation".
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.
thx @samueltardieu , fixed
This comment has been minimized.
This comment has been minimized.
|
I've started the FCP on Zulip. Could you please rebase? |
If a type has an auto-derived `Default` trait and a `fn new() -> Self`,
this lint checks if the `new()` method performs custom logic rather
than simply calling the `default()` method.
Users expect the `new()` method to be equivalent to `default()`,
so if the `Default` trait is auto-derived, the `new()` method should
not perform custom logic. Otherwise, there is a risk of different
behavior between the two instantiation methods.
```rust
struct MyStruct(i32);
impl MyStruct {
fn new() -> Self {
Self(42)
}
}
```
Users are unlikely to notice that `MyStruct::new()` and `MyStruct::default()` would produce
different results. The `new()` method should use auto-derived `default()` instead to be consistent:
```rust
struct MyStruct(i32);
impl MyStruct {
fn new() -> Self {
Self::default()
}
}
```
Alternatively, if the `new()` method requires a non-default initialization, implement a custom `Default`.
This also allows you to mark the `new()` implementation as `const`:
```rust
struct MyStruct(i32);
impl MyStruct {
const fn new() -> Self {
Self(42)
}
}
impl Default for MyStruct {
fn default() -> Self {
Self::new()
}
}
```
b36567a to
706a961
Compare
d926950 to
2a2d119
Compare
| DEFAULT_MISMATCHES_NEW, | ||
| id.into(), | ||
| block.span, | ||
| format!("consider delegating to the auto-derived `Default` for `{self_type_snip}`"), |
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.
You are supposed to explain the problem here, not give the solution. Right now, there seems to be no indication of what is wrong in the stderr file, just a suggestion to delegate. Nowhere does the notion of "mismatch" appear in the output. This text should probably appear as a diag.help() message.
Also, is the suggestion accurate when new() and default() don't match? Shouldn't it suggest to either be consistent and delegate, or (as discussed in the PR comments) rename the new() associated function to something more specific?
Should the case where new() and default() return the same value be considered a mismatch? Or just a redundancy, in which case the lint may be misnamed.
| // This would replace any comments, and we could work around the first comment, | ||
| // but in case of a block of code with multiple statements and comment lines, | ||
| // we can't do much. For now, we always mark this as a MaybeIncorrect suggestion. | ||
| diag.span_suggestion(span, "use", "Self::default()", Applicability::Unspecified); |
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.
From a recent discussion with @Alexendoo, it looks like we should avoid adding more Unspecified suggestions; MaybeIncorrect would probably be more appropriate.
| // FIXME: what should the whole_call_expr (3rd arg) be? | ||
| && is_default_equivalent_call(cx, callee, 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.
From what I get from the code, if you pass Some(expr), it should look inside callee and look if its content is similar to the default implementation.
In your case, since you only lint on #[derive(Default)], I'm not sure it would make a difference, but it probably wouldn't hurt.
| /// Self::new() | ||
| /// } | ||
| /// } | ||
| #[clippy::version = "1.86.0"] |
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.
| #[clippy::version = "1.86.0"] | |
| #[clippy::version = "1.88.0"] |
| error: consider delegating to the auto-derived `Default` for `MultiStatements` | ||
| --> tests/ui/default_mismatches_new.rs:163:22 | ||
| | | ||
| LL | fn new() -> Self { | ||
| | _______________________^ | ||
| LL | |/ | ||
| LL | || println!("Hello, world!"); | ||
| LL | || let i = 42; | ||
| LL | || Self(i) | ||
| | ||_______________- help: use: `Self::default()` | ||
| LL | | } | ||
| | |______^ |
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.
Here is an example of an unhelpful error message: it doesn't tell what the problem is, it only gives a suggestion to "fix" it.
|
☔ The latest upstream changes (possibly #14896) made this pull request unmergeable. Please resolve the merge conflicts. |
|
Ping @nyurik from triage. Do you plan to return to working on this? |
|
Closing since there's been no response. @nyurik if you wish to return to this feel free to reopen or create a new PR. |
Implement #14075. Prevents rust-lang/rust#135977.
What it does
If a type has an auto-derived
Defaulttrait and afn new() -> Self, this lint checks if thenew()method performs custom logic rather than callingSelf::default()Why is this bad?
Users expect the
new()method to be equivalent todefault(), so if theDefaulttrait is auto-derived, thenew()method should not perform custom logic. Otherwise, there is a risk of different behavior between the two instantiation methods.Example
Users are unlikely to notice that
MyStruct::new()andMyStruct::default()would produce different results. Thenew()method should use auto-deriveddefault()instead to be consistent:Alternatively, if the
new()method requires non-default initialization, implement a customDefault. This also allows you to mark thenew()implementation asconst:.stderrfile)cargo testpasses locallycargo dev update_lintscargo dev fmtchangelog: [
default_mismatches_new]: new lint to catchfn new() -> Selfmethod not matching theDefaultimplementationNotable cases in the wild
default()andnew()are actually different - likely was a bug that glob team wants to fix (per comments)new()creates cache withVec::with_capacity(), whereasdefault()usesVec::default(). Possibly a bug.Self(())-- should this be ignored?Self { _p: () }forstruct Identity { _p: () }-- probably better to use::default()TODO/TBD
fn new() -> Selfimplementations are OK? (rather than delegating toSelf::default())Selfis a unit typerustc_hir::hir::ImplItemof afn new() -> Self, get the body of that functionreturn Self::default();and no other logic (in all variants likeDefault::default(), etc.clippy_lints/src/default_constructed_unit_structs.rswhile trying to understand its logic - I think we may want to have ais_unit_type(ty)utility function?