From d71f852b812450333f3fc05a2e5aad73264a25d7 Mon Sep 17 00:00:00 2001 From: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> Date: Mon, 19 Dec 2022 22:21:25 -0500 Subject: [PATCH] Add lint for exhaustive struct variants gaining new fields. (#238) --- src/lints/enum_struct_variant_field_added.ron | 75 +++++++++++++++++++ src/query.rs | 3 +- .../new/Cargo.toml | 6 ++ .../new/src/lib.rs | 36 +++++++++ .../old/Cargo.toml | 6 ++ .../old/src/lib.rs | 31 ++++++++ ...enum_struct_variant_field_added.output.ron | 15 ++++ .../variant_marked_non_exhaustive.output.ron | 13 ++++ 8 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/lints/enum_struct_variant_field_added.ron create mode 100644 test_crates/enum_struct_variant_field_added/new/Cargo.toml create mode 100644 test_crates/enum_struct_variant_field_added/new/src/lib.rs create mode 100644 test_crates/enum_struct_variant_field_added/old/Cargo.toml create mode 100644 test_crates/enum_struct_variant_field_added/old/src/lib.rs create mode 100644 test_outputs/enum_struct_variant_field_added.output.ron diff --git a/src/lints/enum_struct_variant_field_added.ron b/src/lints/enum_struct_variant_field_added.ron new file mode 100644 index 00000000..ccb52587 --- /dev/null +++ b/src/lints/enum_struct_variant_field_added.ron @@ -0,0 +1,75 @@ +SemverQuery( + id: "enum_struct_variant_field_added", + human_readable_name: "pub enum struct variant field added", + description: "An enum's exhaustive struct variant has a new field.", + required_update: Major, + reference_link: Some("https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute"), + query: r#" + { + CrateDiff { + current { + item { + ... on Enum { + visibility_limit @filter(op: "=", value: ["$public"]) + enum_name: name @output @tag + + importable_path { + path @output @tag + } + + variant { + ... on StructVariant { + # If the variant is now marked `#[non_exhaustive]`, + # that's already a breaking change that has its own rule. + # Don't also report new field additions, since the programmer has + # clearly stated that they don't consider it exhaustive anymore. + attrs @filter(op: "not_contains", value: ["$non_exhaustive"]) + + variant_name: name @output @tag + + field { + field_name: name @output @tag + + span_: span @optional { + filename @output + begin_line @output + } + } + } + } + } + } + } + baseline { + item { + ... on Enum { + visibility_limit @filter(op: "=", value: ["$public"]) + name @filter(op: "=", value: ["%enum_name"]) + + importable_path { + path @filter(op: "=", value: ["%path"]) + } + + variant { + ... on StructVariant { + name @filter(op: "=", value: ["%variant_name"]) + attrs @filter(op: "not_contains", value: ["$non_exhaustive"]) + + field @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { + name @filter(op: "=", value: ["%field_name"]) + } + } + } + } + } + } + } + }"#, + arguments: { + "public": "public", + "zero": 0, + "non_exhaustive": "#[non_exhaustive]", + }, + error_message: "An enum's exhaustive struct variant has a new field, which has to be included when constructing or matching on this variant.", + per_result_error_template: Some("field {{field_name}} of variant {{enum_name}}::{{variant_name}} in {{span_filename}}:{{span_begin_line}}"), +) diff --git a/src/query.rs b/src/query.rs index 6337dfbe..48c647b6 100644 --- a/src/query.rs +++ b/src/query.rs @@ -342,9 +342,10 @@ add_lints!( enum_repr_c_removed, enum_repr_int_changed, enum_repr_int_removed, + enum_struct_variant_field_added, + enum_struct_variant_field_missing, enum_variant_added, enum_variant_missing, - enum_struct_variant_field_missing, function_const_removed, function_missing, function_parameter_count_changed, diff --git a/test_crates/enum_struct_variant_field_added/new/Cargo.toml b/test_crates/enum_struct_variant_field_added/new/Cargo.toml new file mode 100644 index 00000000..d412b285 --- /dev/null +++ b/test_crates/enum_struct_variant_field_added/new/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "enum_struct_variant_field_added" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/enum_struct_variant_field_added/new/src/lib.rs b/test_crates/enum_struct_variant_field_added/new/src/lib.rs new file mode 100644 index 00000000..ea3f6b27 --- /dev/null +++ b/test_crates/enum_struct_variant_field_added/new/src/lib.rs @@ -0,0 +1,36 @@ +pub enum PubEnum { + Foo { + x: i64, + y: usize, + } +} + +// This enum is not public, so it should not be reported by the lint, +// regardless of the new field in the variant. +pub(crate) enum PrivateEnum { + Foo { + x: i64, + y: usize, + } +} + +// This enum's variant is non-exhaustive, so it can't be externally constructed +// and can't be matched on without a `..`, and therefore should not be reported by the lint, +// regardless of the new field in the variant. +pub enum EnumWithNonExhaustiveVariant { + #[non_exhaustive] + Variant { + x: i64, + y: usize, + } +} + +// This enum's variant will become non-exhaustive while also gaining a field. +// That should trigger a separate lint, and not this one. +pub enum EnumWithExhaustiveVariant { + #[non_exhaustive] + ExhaustiveVariant { + x: i64, + y: usize, + } +} diff --git a/test_crates/enum_struct_variant_field_added/old/Cargo.toml b/test_crates/enum_struct_variant_field_added/old/Cargo.toml new file mode 100644 index 00000000..d412b285 --- /dev/null +++ b/test_crates/enum_struct_variant_field_added/old/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "enum_struct_variant_field_added" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/enum_struct_variant_field_added/old/src/lib.rs b/test_crates/enum_struct_variant_field_added/old/src/lib.rs new file mode 100644 index 00000000..59986462 --- /dev/null +++ b/test_crates/enum_struct_variant_field_added/old/src/lib.rs @@ -0,0 +1,31 @@ +pub enum PubEnum { + Foo { + x: i64, + } +} + +// This enum is not public, so it should not be reported by the lint, +// regardless of the new field in the variant. +pub(crate) enum PrivateEnum { + Foo { + x: i64, + } +} + +// This enum's variant is non-exhaustive, so it can't be externally constructed +// and can't be matched on without a `..`, and therefore should not be reported by the lint, +// regardless of the new field in the variant. +pub enum EnumWithNonExhaustiveVariant { + #[non_exhaustive] + Variant { + x: i64, + } +} + +// This enum's variant will become non-exhaustive while also gaining a field. +// That should trigger a separate lint, and not this one. +pub enum EnumWithExhaustiveVariant { + ExhaustiveVariant { + x: i64, + } +} diff --git a/test_outputs/enum_struct_variant_field_added.output.ron b/test_outputs/enum_struct_variant_field_added.output.ron new file mode 100644 index 00000000..a41e0110 --- /dev/null +++ b/test_outputs/enum_struct_variant_field_added.output.ron @@ -0,0 +1,15 @@ +{ + "./test_crates/enum_struct_variant_field_added/": [ + { + "enum_name": String("PubEnum"), + "field_name": String("y"), + "path": List([ + String("enum_struct_variant_field_added"), + String("PubEnum"), + ]), + "span_begin_line": Uint64(4), + "span_filename": String("src/lib.rs"), + "variant_name": String("Foo"), + }, + ], +} diff --git a/test_outputs/variant_marked_non_exhaustive.output.ron b/test_outputs/variant_marked_non_exhaustive.output.ron index 0295845f..07363504 100644 --- a/test_outputs/variant_marked_non_exhaustive.output.ron +++ b/test_outputs/variant_marked_non_exhaustive.output.ron @@ -1,4 +1,17 @@ { + "./test_crates/enum_struct_variant_field_added/": [ + { + "name": String("EnumWithExhaustiveVariant"), + "path": List([ + String("enum_struct_variant_field_added"), + String("EnumWithExhaustiveVariant"), + ]), + "span_begin_line": Uint64(32), + "span_filename": String("src/lib.rs"), + "variant_name": String("ExhaustiveVariant"), + "visibility_limit": String("public"), + }, + ], "./test_crates/non_exhaustive/": [ { "name": String("MyEnum"),