-
-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add two lints for cases where enum discriminants are no longer defined.
- `enum_discriminants_undefined_non_exhaustive_variant` checks for enums that gain a new non-exhaustive variant, which then causes the discriminant to become undefined. - `enum_discriminants_undefined_non_unit_variant` checks for enums that gain a new non-unit variant, which then causes the discriminant to become undefined. Resolves #898.
- Loading branch information
1 parent
fe144fc
commit 040e66d
Showing
9 changed files
with
413 additions
and
0 deletions.
There are no files selected for viewing
83 changes: 83 additions & 0 deletions
83
src/lints/enum_discriminants_undefined_non_exhaustive_variant.ron
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
SemverQuery( | ||
id: "enum_discriminants_undefined_non_exhaustive_variant", | ||
human_readable_name: "enum's variants no longer have defined discriminants due to a non-exhaustive variant", | ||
description: "A public enum's variants no longer have well-defined discriminants value due to a non-exhaustive variant.", | ||
reference: Some("A public enum's variants no longer have well-defined discriminants due to a non-exhaustive variant. This breaks downstream code that accessed the discriminant 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 @fold @transform(op: "count") @filter(op: ">", value: ["$zero"]) { | ||
discriminant { | ||
value | ||
} | ||
} | ||
variant @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { | ||
__typename @filter(op: "!=", value: ["$plain_variant"]) | ||
} | ||
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 @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { | ||
__typename @filter(op: "!=", value: ["$plain_variant"]) | ||
} | ||
variant @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { | ||
attrs @filter(op: "contains", value: ["$non_exhaustive"]) | ||
} | ||
span_: span @optional { | ||
filename @output | ||
begin_line @output | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}"#, | ||
arguments: { | ||
"public": "public", | ||
"repr": "repr", | ||
"non_exhaustive": "#[non_exhaustive]", | ||
"plain_variant": "PlainVariant", | ||
"zero": 0, | ||
"true": true, | ||
}, | ||
error_message: "An enum's variants no longer have well-defined discriminant values due to a non-exhaustive variant in the enum. This breaks downstream code that accesses discriminants via a numeric cast like `as isize`.", | ||
per_result_error_template: Some("enum {{enum_name}} in {{span_filename}}:{{span_begin_line}}"), | ||
) |
79 changes: 79 additions & 0 deletions
79
src/lints/enum_discriminants_undefined_non_unit_variant.ron
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
SemverQuery( | ||
id: "enum_discriminants_undefined_non_unit_variant", | ||
human_readable_name: "enum's variants no longer have defined discriminants due to non-unit variant", | ||
description: "A public enum's variants no longer have well-defined discriminants value due to a non-unit variant.", | ||
reference: Some("A public enum's variants no longer have well-defined discriminants due to a non-unit variant. This breaks downstream code that accessed the discriminant 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 @fold @transform(op: "count") @filter(op: ">", value: ["$zero"]) { | ||
discriminant { | ||
value | ||
} | ||
} | ||
variant @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { | ||
__typename @filter(op: "!=", value: ["$plain_variant"]) | ||
} | ||
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 @fold @transform(op: "count") @filter(op: ">", value: ["$zero"]) { | ||
__typename @filter(op: "!=", value: ["$plain_variant"]) | ||
} | ||
span_: span @optional { | ||
filename @output | ||
begin_line @output | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}"#, | ||
arguments: { | ||
"public": "public", | ||
"repr": "repr", | ||
"non_exhaustive": "#[non_exhaustive]", | ||
"plain_variant": "PlainVariant", | ||
"zero": 0, | ||
"true": true, | ||
}, | ||
error_message: "An enum's variants no longer have well-defined discriminant values due to a tuple or struct variant in the enum. This breaks downstream code that accesses discriminants via a numeric cast like `as isize`.", | ||
per_result_error_template: Some("enum {{enum_name}} in {{span_filename}}:{{span_begin_line}}"), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
test_crates/enum_discriminant_no_longer_defined/new/Cargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
publish = false | ||
name = "enum_discriminant_no_longer_defined" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] |
68 changes: 68 additions & 0 deletions
68
test_crates/enum_discriminant_no_longer_defined/new/src/lib.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// It's not allowed to use `as isize` to cast another crate's enum when | ||
// the enum contains a non-exhaustive variant: | ||
// | ||
// ---- src/lib.rs - GainsNonExhaustiveVariant (line 1) stdout ---- | ||
// error[E0606]: casting `GainsNonExhaustiveVariant` as `isize` is invalid | ||
// --> src/lib.rs:4:5 | ||
// | | ||
// 6 | value as isize; | ||
// | ^^^^^^^^^^^^^^ | ||
// | | ||
// = note: cannot cast an enum with a non-exhaustive variant when it's defined in another crate | ||
// | ||
// error: aborting due to 1 previous error | ||
// | ||
// To see this, run the following doctest: | ||
/// ```rust | ||
/// fn example(value: enum_discriminant_no_longer_defined::GainsNonExhaustiveVariant) { | ||
/// value as isize; | ||
/// } | ||
/// ``` | ||
#[non_exhaustive] | ||
pub enum GainsNonExhaustiveVariant { | ||
First, | ||
Second, | ||
#[non_exhaustive] // TODO: this needs to be flagged but currently isn't. | ||
Third, | ||
} | ||
|
||
/// This shouldn't be reported: this enum's discriminants were not well-defined to begin with | ||
/// because of the non-unit variant. | ||
#[non_exhaustive] | ||
pub enum NonUnitVariantButGainsNonExhaustiveVariant { | ||
First, | ||
Second(u16), | ||
#[non_exhaustive] | ||
Third, | ||
} | ||
|
||
// It's not allowed to use `as isize` to cast another crate's enum when | ||
// the enum contains a non-unit variant: | ||
// | ||
// ---- src/lib.rs - GainsTupleVariant (line 1) stdout ---- | ||
// error[E0605]: non-primitive cast: `GainsTupleVariant` as `isize` | ||
// --> src/lib.rs:4:5 | ||
// | | ||
// 6 | value as isize; | ||
// | ^^^^^^^^^^^^^^ an `as` expression can be used to convert enum types to numeric types only if the enum type is unit-only or field-less | ||
// | | ||
// = note: see https://doc.rust-lang.org/reference/items/enumerations.html#casting for more information | ||
// | ||
// To see this, run the following doctest: | ||
/// ```rust | ||
/// fn example(value: enum_discriminant_no_longer_defined::GainsTupleVariant) { | ||
/// value as isize; | ||
/// } | ||
/// ``` | ||
#[non_exhaustive] | ||
pub enum GainsTupleVariant { | ||
None, | ||
Never, | ||
Some(core::num::NonZeroUsize), | ||
} | ||
|
||
// Same as above, just with a struct variant instead of a tuple variant. | ||
pub enum GainsStructVariant { | ||
None, | ||
Some { value: core::num::NonZeroUsize }, | ||
} |
7 changes: 7 additions & 0 deletions
7
test_crates/enum_discriminant_no_longer_defined/old/Cargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
publish = false | ||
name = "enum_discriminant_no_longer_defined" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] |
62 changes: 62 additions & 0 deletions
62
test_crates/enum_discriminant_no_longer_defined/old/src/lib.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// It's not allowed to use `as isize` to cast another crate's enum when | ||
// the enum contains a non-exhaustive variant: | ||
// | ||
// ---- src/lib.rs - GainsNonExhaustiveVariant (line 1) stdout ---- | ||
// error[E0606]: casting `GainsNonExhaustiveVariant` as `isize` is invalid | ||
// --> src/lib.rs:4:5 | ||
// | | ||
// 6 | value as isize; | ||
// | ^^^^^^^^^^^^^^ | ||
// | | ||
// = note: cannot cast an enum with a non-exhaustive variant when it's defined in another crate | ||
// | ||
// error: aborting due to 1 previous error | ||
// | ||
// To see this, run the following doctest: | ||
/// ```rust | ||
/// fn example(value: enum_discriminant_no_longer_defined::GainsNonExhaustiveVariant) { | ||
/// value as isize; | ||
/// } | ||
/// ``` | ||
#[non_exhaustive] | ||
pub enum GainsNonExhaustiveVariant { | ||
First, | ||
Second, | ||
} | ||
|
||
/// This shouldn't be reported: this enum's discriminants were not well-defined to begin with | ||
/// because of the non-unit variant. | ||
#[non_exhaustive] | ||
pub enum NonUnitVariantButGainsNonExhaustiveVariant { | ||
First, | ||
Second(u16), | ||
} | ||
|
||
// It's not allowed to use `as isize` to cast another crate's enum when | ||
// the enum contains a non-unit variant: | ||
// | ||
// ---- src/lib.rs - GainsTupleVariant (line 1) stdout ---- | ||
// error[E0605]: non-primitive cast: `GainsTupleVariant` as `isize` | ||
// --> src/lib.rs:4:5 | ||
// | | ||
// 6 | value as isize; | ||
// | ^^^^^^^^^^^^^^ an `as` expression can be used to convert enum types to numeric types only if the enum type is unit-only or field-less | ||
// | | ||
// = note: see https://doc.rust-lang.org/reference/items/enumerations.html#casting for more information | ||
// | ||
// To see this, run the following doctest: | ||
/// ```rust | ||
/// fn example(value: enum_discriminant_no_longer_defined::GainsTupleVariant) { | ||
/// value as isize; | ||
/// } | ||
/// ``` | ||
#[non_exhaustive] | ||
pub enum GainsTupleVariant { | ||
None, | ||
Never, | ||
} | ||
|
||
// Same as above, just with a struct variant instead of a tuple variant. | ||
pub enum GainsStructVariant { | ||
None, | ||
} |
Oops, something went wrong.