-
-
Notifications
You must be signed in to change notification settings - Fork 88
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
Add enum variant discriminant changed lint #912
Changes from all commits
0e24e8e
583dc61
164eff7
5b9a24b
bd62f74
9cda225
907454e
9cb8353
23cd52e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
SemverQuery( | ||
id: "enum_no_repr_variant_discriminant_changed", | ||
human_readable_name: "enum variant had its discriminant change value", | ||
description: "A public enum's variant had its discriminant changed from its previous value.", | ||
reference: Some("A public enum's variant had its discriminant value change. This breaks downstream code that used its value via a numeric cast like `as isize`."), | ||
required_update: Major, | ||
lint_level: Deny, | ||
reference_link: Some("https://doc.rust-lang.org/reference/items/enumerations.html#assigning-discriminant-values"), | ||
query: r#" | ||
{ | ||
CrateDiff { | ||
baseline { | ||
item { | ||
... on Enum { | ||
visibility_limit @filter(op: "=", value: ["$public"]) @output | ||
enum_name: name @output @tag | ||
|
||
attribute @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { | ||
content { | ||
base @filter(op: "=", value: ["$repr"]) | ||
} | ||
} | ||
|
||
importable_path { | ||
path @output @tag | ||
public_api @filter(op: "=", value: ["$true"]) | ||
} | ||
|
||
variant { | ||
variant_name: name @output @tag | ||
|
||
discriminant { | ||
old_value: value @output @tag | ||
} | ||
} | ||
|
||
variant @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { | ||
attrs @filter(op: "contains", value: ["$non_exhaustive"]) | ||
} | ||
} | ||
} | ||
} | ||
current { | ||
item { | ||
... on Enum { | ||
visibility_limit @filter(op: "=", value: ["$public"]) | ||
name @filter(op: "=", value: ["%enum_name"]) | ||
|
||
importable_path { | ||
path @filter(op: "=", value: ["%path"]) | ||
public_api @filter(op: "=", value: ["$true"]) | ||
} | ||
|
||
variant { | ||
name @filter(op: "=", value: ["%variant_name"]) | ||
|
||
discriminant { | ||
new_value: value @output @filter(op: "!=", value: ["%old_value"]) | ||
} | ||
|
||
span_: span @optional { | ||
filename @output | ||
begin_line @output | ||
} | ||
} | ||
|
||
variant @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { | ||
attrs @filter(op: "contains", value: ["$non_exhaustive"]) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}"#, | ||
arguments: { | ||
"public": "public", | ||
"repr": "repr", | ||
"non_exhaustive": "#[non_exhaustive]", | ||
"zero": 0, | ||
"true": true, | ||
}, | ||
error_message: "The enum's variant had its discriminant value change. This breaks downstream code that used its value via a numeric cast like `as isize`.", | ||
per_result_error_template: Some("variant {{enum_name}}::{{variant_name}} {{old_value}} -> {{new_value}} in {{span_filename}}:{{span_begin_line}}"), | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
publish = false | ||
name = "enum_no_repr_variant_discriminant_changed" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Explicit discriminant changed values. By doing so, it changed the implicit | ||
// discriminant's value as well. Should be reported. | ||
pub enum ExplicitAndImplicitDiscriminantsAreChanged { | ||
First = 2, | ||
Second, | ||
Third = 5, | ||
} | ||
|
||
// Discriminant changed values. Having #[non_exhaustive] on the enum should not have any effect | ||
// on the *API* breakage. Should be reported. | ||
#[non_exhaustive] | ||
pub enum DiscriminantIsChanged { | ||
First = 1, | ||
} | ||
|
||
// Discriminant changed values and the variant is also marked as `non_exhaustive`. | ||
// This now implies that the discriminant is no longer `well-defined`, which means that a numeric | ||
// cast is no longer possible on the enum. Should not be reported. | ||
// https://github.com/rust-lang/reference/pull/1249#issuecomment-2339003824 | ||
#[non_exhaustive] | ||
pub enum DiscriminantIsChangedAndMarkedNonExhaustive { | ||
First, | ||
#[non_exhaustive] | ||
Second = 2, | ||
} | ||
|
||
// Discriminant changed values, but the variant is already `non_exhaustive`. | ||
// This means that the discriminant is already not `well-defined`, and the numeric cast | ||
// was never possible. Should not be reported. | ||
#[non_exhaustive] | ||
pub enum DiscriminantIsChangedButAlreadyNonExhaustive { | ||
First, | ||
#[non_exhaustive] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a test case where another variant is I think there's a bug in the lint query related to it: the lint is checking for "is the variant with the changed discriminant marked |
||
Second = 2, | ||
} | ||
|
||
// Variant became doc(hidden) while variant became explicit. Being doc(hidden) is not relevant | ||
// since it's still part of the public API. Should be reported. | ||
pub enum DiscriminantBecomesDocHiddenAndExplicit { | ||
First, | ||
#[doc(hidden)] | ||
Second = 2, | ||
} | ||
|
||
// Explicit discriminants changed values, but being private dominates. Should not be | ||
// reported. | ||
enum PrivateEnum { | ||
First = 10, | ||
Second = 11, | ||
} | ||
|
||
// Discriminant changed values, but the enum has a `repr` attr. Should not be reported as it | ||
// involves ABI breakage as well, and this is linted at *API* only breakage. | ||
#[repr(u8)] | ||
pub enum FieldlessWithDiscrimants { | ||
First = 10, | ||
Tuple(), | ||
Struct {}, | ||
Unit, | ||
Last = 21, | ||
} | ||
|
||
// Implicit discriminant value changed by becoming explicit. Should not be reported as it involves | ||
// ABI breakage as well, and this is linted at *API* only breakage. | ||
#[repr(u8)] | ||
pub enum FieldlessChangesToExplicitDescriminant { | ||
Tuple(), | ||
Struct {}, | ||
Unit = 5, | ||
} | ||
|
||
// Variant `Struct` acquires a field. By doing so, it makes the discriminant not `well-defined`, | ||
// meaning it is not possible to do a numeric cast anymore on the enum. Should not be reported. | ||
pub enum UnitOnlyBecomesUndefined { | ||
First, | ||
Second, | ||
Struct { a: i64 }, | ||
} | ||
|
||
// Discriminant changed values, but the other variant is marked `non_exhaustive`. | ||
// This means that a numeric cast is not possible on the enum. Should not be reported. | ||
#[non_exhaustive] | ||
pub enum DiscriminantIsChangedButAVariantIsNonExhaustive { | ||
#[non_exhaustive] | ||
First, | ||
Second = 2, | ||
} | ||
|
||
// Discriminant changed values, and the variant is no longer `non_exhaustive`. | ||
// The fact that the variant is no longer `non_exhaustive` is not relevant, since a numeric cast | ||
// was impossible to begin with. Should not be reported. | ||
pub enum DiscriminantIsChangedAndVariantNoLongerNonExhaustive { | ||
First, | ||
Second = 2, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
publish = false | ||
name = "enum_no_repr_variant_discriminant_changed" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// Explicit discriminant changed values. By doing so, it changed the implicit | ||
// discriminant's value as well. Should be reported. | ||
pub enum ExplicitAndImplicitDiscriminantsAreChanged { | ||
First = 1, | ||
Second, | ||
Third = 5, | ||
} | ||
|
||
// Discriminant changed values. Having #[non_exhaustive] on the enum should not have any effect | ||
// on the *API* breakage. Should be reported. | ||
#[non_exhaustive] | ||
pub enum DiscriminantIsChanged { | ||
First, | ||
} | ||
|
||
// Discriminant changed values and the variant is also marked as `non_exhaustive`. | ||
// This now implies that the discriminant is no longer `well-defined`, which means that a numeric | ||
// cast is no longer possible on the enum. Should not be reported. | ||
// https://github.com/rust-lang/reference/pull/1249#issuecomment-2339003824 | ||
#[non_exhaustive] | ||
pub enum DiscriminantIsChangedAndMarkedNonExhaustive { | ||
First, | ||
Second, | ||
} | ||
|
||
// Discriminant changed values, but the variant is already `non_exhaustive`. | ||
// This means that the discriminant is already not `well-defined`, and the numeric cast | ||
// was never possible. Should not be reported. | ||
#[non_exhaustive] | ||
pub enum DiscriminantIsChangedButAlreadyNonExhaustive { | ||
First, | ||
#[non_exhaustive] | ||
Second, | ||
} | ||
|
||
// Variant became doc(hidden) while variant became explicit. Being doc(hidden) is not relevant | ||
// since it's still part of the public API. Should be reported. | ||
pub enum DiscriminantBecomesDocHiddenAndExplicit { | ||
First, | ||
Second, | ||
} | ||
|
||
// Explicit discriminants changed values, but being private dominates. Should not be | ||
// reported. | ||
enum PrivateEnum { | ||
First = 1, | ||
Second = 2, | ||
} | ||
|
||
// Discriminant changed values, but the enum has a `repr` attr. Should not be reported as it | ||
// involves ABI breakage as well, and this is linted at *API* only breakage. | ||
#[repr(u8)] | ||
pub enum FieldlessWithDiscrimants { | ||
First = 10, | ||
Tuple(), | ||
Struct {}, | ||
Unit, | ||
Last = 20, | ||
} | ||
|
||
// Implicit discriminant value changed by becoming explicit. Should not be reported as it involves | ||
// ABI breakage as well, and this is linted at *API* only breakage. | ||
pub enum FieldlessChangesToExplicitDescriminant { | ||
Tuple(), | ||
Struct {}, | ||
Unit, | ||
} | ||
|
||
// Variant `Struct` acquires a field. By doing so, it makes the discriminant not `well-defined`, | ||
// meaning it is not possible to do a numeric cast anymore on the enum. Should not be reported. | ||
pub enum UnitOnlyBecomesUndefined { | ||
First, | ||
Second, | ||
Struct {}, | ||
} | ||
|
||
// Discriminant changed values, but the other variant is marked `non_exhaustive`. | ||
// This means that a numeric cast is not possible on the enum. Should not be reported. | ||
#[non_exhaustive] | ||
pub enum DiscriminantIsChangedButAVariantIsNonExhaustive { | ||
#[non_exhaustive] | ||
First, | ||
Second, | ||
} | ||
|
||
// Discriminant changed values, and the variant is no longer `non_exhaustive`. | ||
// The fact that the variant is no longer `non_exhaustive` is not relevant, since a numeric cast | ||
// was impossible to begin with. Should not be reported. | ||
pub enum DiscriminantIsChangedAndVariantNoLongerNonExhaustive { | ||
#[non_exhaustive] | ||
First, | ||
Second, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
{ | ||
"./test_crates/enum_no_repr_variant_discriminant_changed/": [ | ||
{ | ||
"enum_name": String("ExplicitAndImplicitDiscriminantsAreChanged"), | ||
"new_value": String("2"), | ||
"old_value": String("1"), | ||
"path": List([ | ||
String("enum_no_repr_variant_discriminant_changed"), | ||
String("ExplicitAndImplicitDiscriminantsAreChanged"), | ||
]), | ||
"span_begin_line": Uint64(4), | ||
"span_filename": String("src/lib.rs"), | ||
"variant_name": String("First"), | ||
"visibility_limit": String("public"), | ||
}, | ||
{ | ||
"enum_name": String("ExplicitAndImplicitDiscriminantsAreChanged"), | ||
"new_value": String("3"), | ||
"old_value": String("2"), | ||
"path": List([ | ||
String("enum_no_repr_variant_discriminant_changed"), | ||
String("ExplicitAndImplicitDiscriminantsAreChanged"), | ||
]), | ||
"span_begin_line": Uint64(5), | ||
"span_filename": String("src/lib.rs"), | ||
"variant_name": String("Second"), | ||
"visibility_limit": String("public"), | ||
}, | ||
{ | ||
"enum_name": String("DiscriminantIsChanged"), | ||
"new_value": String("1"), | ||
"old_value": String("0"), | ||
"path": List([ | ||
String("enum_no_repr_variant_discriminant_changed"), | ||
String("DiscriminantIsChanged"), | ||
]), | ||
"span_begin_line": Uint64(13), | ||
"span_filename": String("src/lib.rs"), | ||
"variant_name": String("First"), | ||
"visibility_limit": String("public"), | ||
}, | ||
{ | ||
"enum_name": String("DiscriminantBecomesDocHiddenAndExplicit"), | ||
"new_value": String("2"), | ||
"old_value": String("1"), | ||
"path": List([ | ||
String("enum_no_repr_variant_discriminant_changed"), | ||
String("DiscriminantBecomesDocHiddenAndExplicit"), | ||
]), | ||
"span_begin_line": Uint64(42), | ||
"span_filename": String("src/lib.rs"), | ||
"variant_name": String("Second"), | ||
"visibility_limit": String("public"), | ||
}, | ||
] | ||
} |
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.
Naming is hard. This is not the best name, and not the worst. Nothing better comes to my mind, but if you have other suggestions, it would be good to consider them.