From a7e3c516ea245d1ab5027ad4ed2e8221c80aee09 Mon Sep 17 00:00:00 2001 From: Milo Moisson Date: Sun, 25 Aug 2024 17:47:23 +0200 Subject: [PATCH] add lint `trait_associated_const_added` (#875) * feat: add trait_associated_const_added lint * switching current and baseline in query * add description, reference link and error message template * use builtin null ops * tests might prove useful ;) * missing one trait trigger * make all test trait object safe * fix foreign lint test * resolve review comments --- src/lints/trait_associated_const_added.ron | 59 +++++++++++++++++ src/query.rs | 1 + .../new/Cargo.toml | 7 +++ .../new/src/lib.rs | 63 +++++++++++++++++++ .../old/Cargo.toml | 7 +++ .../old/src/lib.rs | 48 ++++++++++++++ .../trait_associated_const_added.output.ron | 61 ++++++++++++++++++ test_outputs/trait_newly_sealed.output.ron | 14 ++++- 8 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 src/lints/trait_associated_const_added.ron create mode 100644 test_crates/trait_associated_const_added/new/Cargo.toml create mode 100644 test_crates/trait_associated_const_added/new/src/lib.rs create mode 100644 test_crates/trait_associated_const_added/old/Cargo.toml create mode 100644 test_crates/trait_associated_const_added/old/src/lib.rs create mode 100644 test_outputs/trait_associated_const_added.output.ron diff --git a/src/lints/trait_associated_const_added.ron b/src/lints/trait_associated_const_added.ron new file mode 100644 index 00000000..bf130189 --- /dev/null +++ b/src/lints/trait_associated_const_added.ron @@ -0,0 +1,59 @@ +SemverQuery( + id: "trait_associated_const_added", + human_readable_name: "non-sealed trait added associated constant without default value", + description: "A non-sealed trait has gained an associated constant without a default value, which breaks downstream implementations of the trait", + required_update: Major, + lint_level: Deny, + reference_link: Some("https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-item-no-default"), + query: r#" + { + CrateDiff { + current { + item { + ... on Trait { + visibility_limit @filter(op: "=", value: ["$public"]) + + importable_path { + path @output @tag + public_api @filter(op: "=", value: ["$true"]) + } + + associated_constant { + associated_constant: name @output @tag + default @filter(op: "is_null") @output + + span_: span @optional { + filename @output + begin_line @output + } + } + } + } + } + baseline { + item { + ... on Trait { + visibility_limit @filter(op: "=", value: ["$public"]) @output + sealed @filter(op: "!=", value: ["$true"]) + + importable_path { + path @filter(op: "=", value: ["%path"]) + public_api @filter(op: "=", value: ["$true"]) + } + + associated_constant @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { + name @filter(op: "=", value: ["%associated_constant"]) + } + } + } + } + } + }"#, + arguments: { + "public": "public", + "true": true, + "zero": 0, + }, + error_message: "A non-sealed trait has gained an associated constant without a default value, which breaks downstream implementations of the trait", + per_result_error_template: Some("trait constant {{join \"::\" path}}::{{associated_constant}} in file {{span_filename}}:{{span_begin_line}}"), +) diff --git a/src/query.rs b/src/query.rs index c66dcefe..e8e3a6b0 100644 --- a/src/query.rs +++ b/src/query.rs @@ -833,6 +833,7 @@ add_lints!( struct_pub_field_now_doc_hidden, struct_repr_transparent_removed, struct_with_pub_fields_changed_type, + trait_associated_const_added, trait_associated_const_now_doc_hidden, trait_associated_type_now_doc_hidden, trait_method_missing, diff --git a/test_crates/trait_associated_const_added/new/Cargo.toml b/test_crates/trait_associated_const_added/new/Cargo.toml new file mode 100644 index 00000000..977825ca --- /dev/null +++ b/test_crates/trait_associated_const_added/new/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "trait_associated_const_added" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/trait_associated_const_added/new/src/lib.rs b/test_crates/trait_associated_const_added/new/src/lib.rs new file mode 100644 index 00000000..eb75da8e --- /dev/null +++ b/test_crates/trait_associated_const_added/new/src/lib.rs @@ -0,0 +1,63 @@ +mod sealed { + pub(crate) trait Sealed {} +} + +pub trait WillGainConstWithoutDefault { + const BAR: bool; + + fn make_me_non_object_safe() -> Self; +} + +pub trait WillGainConstWithoutDefaultSealed: sealed::Sealed { + const BAR: bool; + + fn make_me_non_object_safe() -> Self; +} +pub trait WillGainConstWithoutDefaultAndSeal: sealed::Sealed { + const BAR: bool; + + fn make_me_non_object_safe() -> Self; +} + +pub trait WillGainConstWithDefault { + const BAR: bool = true; + + fn make_me_non_object_safe() -> Self; +} +pub trait WillGainConstWithDefaultSealed: sealed::Sealed { + const BAR: bool = true; + + fn make_me_non_object_safe() -> Self; +} + +pub trait WillGainAnotherConstWithoutDefault { + const ONE: bool; + const TWO: bool; + + fn make_me_non_object_safe() -> Self; +} +pub trait WillGainAnotherConstWithoutDefaultSealed: sealed::Sealed { + const ONE: bool; + const TWO: bool; + + fn make_me_non_object_safe() -> Self; +} + +pub trait WillGainADocHiddenConst { + #[doc(hidden)] + const BAR: bool; + + fn make_me_non_object_safe() -> Self; +} + +pub trait ConstWithoutDefaultUnchanged { + const BAR: bool; + + fn make_me_non_object_safe() -> Self; +} +pub trait ConstDocHidden { + #[doc(hidden)] + const BAR: bool = true; + + fn make_me_non_object_safe() -> Self; +} diff --git a/test_crates/trait_associated_const_added/old/Cargo.toml b/test_crates/trait_associated_const_added/old/Cargo.toml new file mode 100644 index 00000000..977825ca --- /dev/null +++ b/test_crates/trait_associated_const_added/old/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "trait_associated_const_added" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/trait_associated_const_added/old/src/lib.rs b/test_crates/trait_associated_const_added/old/src/lib.rs new file mode 100644 index 00000000..eacda234 --- /dev/null +++ b/test_crates/trait_associated_const_added/old/src/lib.rs @@ -0,0 +1,48 @@ +mod sealed { + pub(crate) trait Sealed {} +} + +pub trait WillGainConstWithoutDefault { + fn make_me_non_object_safe() -> Self; +} + +pub trait WillGainConstWithoutDefaultSealed: sealed::Sealed { + fn make_me_non_object_safe() -> Self; +} +pub trait WillGainConstWithoutDefaultAndSeal { + fn make_me_non_object_safe() -> Self; +} + +pub trait WillGainConstWithDefault { + fn make_me_non_object_safe() -> Self; +} +pub trait WillGainConstWithDefaultSealed: sealed::Sealed { + fn make_me_non_object_safe() -> Self; +} + +pub trait WillGainAnotherConstWithoutDefault { + const ONE: bool; + + fn make_me_non_object_safe() -> Self; +} +pub trait WillGainAnotherConstWithoutDefaultSealed: sealed::Sealed { + const ONE: bool; + + fn make_me_non_object_safe() -> Self; +} + +pub trait WillGainADocHiddenConst { + fn make_me_non_object_safe() -> Self; +} + +pub trait ConstWithoutDefaultUnchanged { + const BAR: bool; + + fn make_me_non_object_safe() -> Self; +} +pub trait ConstDocHidden { + #[doc(hidden)] + const BAR: bool = true; + + fn make_me_non_object_safe() -> Self; +} diff --git a/test_outputs/trait_associated_const_added.output.ron b/test_outputs/trait_associated_const_added.output.ron new file mode 100644 index 00000000..b03736db --- /dev/null +++ b/test_outputs/trait_associated_const_added.output.ron @@ -0,0 +1,61 @@ +{ + "./test_crates/inherent_associated_pub_const_missing/": [ + { + "associated_constant": String("N_B"), + "default": Null, + "path": List([ + String("inherent_associated_pub_const_missing"), + String("TraitB"), + ]), + "span_begin_line": Uint64(69), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/trait_associated_const_added/": [ + { + "associated_constant": String("BAR"), + "default": Null, + "path": List([ + String("trait_associated_const_added"), + String("WillGainConstWithoutDefault"), + ]), + "span_begin_line": Uint64(6), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "associated_constant": String("BAR"), + "default": Null, + "path": List([ + String("trait_associated_const_added"), + String("WillGainConstWithoutDefaultAndSeal"), + ]), + "span_begin_line": Uint64(17), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "associated_constant": String("TWO"), + "default": Null, + "path": List([ + String("trait_associated_const_added"), + String("WillGainAnotherConstWithoutDefault"), + ]), + "span_begin_line": Uint64(35), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "associated_constant": String("BAR"), + "default": Null, + "path": List([ + String("trait_associated_const_added"), + String("WillGainADocHiddenConst"), + ]), + "span_begin_line": Uint64(48), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], +} diff --git a/test_outputs/trait_newly_sealed.output.ron b/test_outputs/trait_newly_sealed.output.ron index cb4cea31..f2a186ff 100644 --- a/test_outputs/trait_newly_sealed.output.ron +++ b/test_outputs/trait_newly_sealed.output.ron @@ -1,5 +1,17 @@ { - "./test_crates/trait_newly_sealed/": [ + "./test_crates/trait_associated_const_added/": [ + { + "name": String("WillGainConstWithoutDefaultAndSeal"), + "path": List([ + String("trait_associated_const_added"), + String("WillGainConstWithoutDefaultAndSeal"), + ]), + "span_begin_line": Uint64(16), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/trait_newly_sealed/": [ { "name": String("TraitBecomesSealed"), "path": List([