-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
base: master
Are you sure you want to change the base?
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
rustc
issue 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.
Implement #14075. Prevents rust-lang/rust#135977.
What it does
If a type has an auto-derived
Default
trait 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 theDefault
trait 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
:.stderr
file)cargo test
passes locallycargo dev update_lints
cargo dev fmt
changelog: [
default_mismatches_new
]: new lint to catchfn new() -> Self
method not matching theDefault
implementationNotable 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() -> Self
implementations are OK? (rather than delegating toSelf::default()
)Self
is a unit typerustc_hir::hir::ImplItem
of 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.rs
while trying to understand its logic - I think we may want to have ais_unit_type(ty)
utility function?