From 8b117c9d2a452947ccbb82bc511a6b23b09b5d22 Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Thu, 27 Feb 2025 23:42:57 +0530 Subject: [PATCH 1/5] new lint: `unconditionally_sealed_trait_became_unsealed` --- ...itionally_sealed_trait_became_unsealed.ron | 53 ++++++++++++ src/query.rs | 1 + .../new/Cargo.toml | 7 ++ .../new/src/lib.rs | 40 +++++++++ .../old/Cargo.toml | 7 ++ .../old/src/lib.rs | 39 +++++++++ ...tionally_sealed_trait_became_unsealed.snap | 81 +++++++++++++++++++ 7 files changed, 228 insertions(+) create mode 100644 src/lints/unconditionally_sealed_trait_became_unsealed.ron create mode 100644 test_crates/unconditionally_sealed_trait_became_unsealed/new/Cargo.toml create mode 100644 test_crates/unconditionally_sealed_trait_became_unsealed/new/src/lib.rs create mode 100644 test_crates/unconditionally_sealed_trait_became_unsealed/old/Cargo.toml create mode 100644 test_crates/unconditionally_sealed_trait_became_unsealed/old/src/lib.rs create mode 100644 test_outputs/query_execution/unconditionally_sealed_trait_became_unsealed.snap diff --git a/src/lints/unconditionally_sealed_trait_became_unsealed.ron b/src/lints/unconditionally_sealed_trait_became_unsealed.ron new file mode 100644 index 00000000..7659ae1d --- /dev/null +++ b/src/lints/unconditionally_sealed_trait_became_unsealed.ron @@ -0,0 +1,53 @@ +SemverQuery( + id: "unconditionally_sealed_trait_became_unsealed", + human_readable_name: "Unconditionally sealed trait became unsealed", + description: "A trait that was previously unconditionally sealed is now unsealed, allowing downstream crates to implement it. This change introduces potential long-term stability risks, as reverting it in the future would be a breaking change", + required_update: Minor, + lint_level: Warn, + reference_link: Some("https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed"), + query: r#" + { + CrateDiff { + baseline { + item { + ... on Trait { + visibility_limit @filter(op: "=", value: ["$public"]) @output + unconditionally_sealed @filter(op: "=", value: ["$true"]) + importable_path { + path @output @tag + public_api @filter(op: "=", value: ["$true"]) + } + } + } + } + current { + item { + ... on Trait { + visibility_limit @filter(op: "=", value: ["$public"]) + unconditionally_sealed @filter(op: "!=", value: ["$true"]) + public_api_sealed @filter(op: "!=", value: ["$true"]) + name @output + importable_path { + path @filter(op: "=", value: ["%path"]) + public_api @filter(op: "=", value: ["$true"]) + } + span_: span @optional { + filename @output + begin_line @output + end_line @output + } + } + } + } + } + }"#, + arguments: { + "public": "public", + "true": true, + }, + error_message: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it. + This change introduces potential stability risks because if it needs to be re-sealed in the future, + it would be a breaking change.", + per_result_error_template: Some("trait {{join \"::\" path}} in file {{span_filename}}:{{span_begin_line}}"), + witness: None, +) diff --git a/src/query.rs b/src/query.rs index 4d2f7ec0..030d4a44 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1352,6 +1352,7 @@ add_lints!( type_mismatched_generic_lifetimes, type_requires_more_const_generic_params, type_requires_more_generic_type_params, + unconditionally_sealed_trait_became_unsealed, union_field_added_with_all_pub_fields, union_field_added_with_non_pub_fields, union_field_missing, diff --git a/test_crates/unconditionally_sealed_trait_became_unsealed/new/Cargo.toml b/test_crates/unconditionally_sealed_trait_became_unsealed/new/Cargo.toml new file mode 100644 index 00000000..f46baa74 --- /dev/null +++ b/test_crates/unconditionally_sealed_trait_became_unsealed/new/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "unconditionally_sealed_trait_became_unsealed" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/unconditionally_sealed_trait_became_unsealed/new/src/lib.rs b/test_crates/unconditionally_sealed_trait_became_unsealed/new/src/lib.rs new file mode 100644 index 00000000..8b92e78c --- /dev/null +++ b/test_crates/unconditionally_sealed_trait_became_unsealed/new/src/lib.rs @@ -0,0 +1,40 @@ +// Traits transitioning from Unconditionally Sealed → Unsealed (Lint should detect these) +pub mod unconditionally_sealed_to_unsealed { + pub mod hidden { + pub trait Sealed {} + pub struct Token; + } + + pub trait ExtendsTraitInHiddenModule: hidden::Sealed {} + + pub trait TransitivelyTraitSealed: ExtendsTraitInHiddenModule {} + + pub trait ReturnsTraitInHiddenModule { + fn method(&self) -> hidden::Token; + } + + pub trait AcceptsTraitInHiddenModule { + fn method(&self, token: hidden::Token); + } + + pub trait SealedWithWhereSelfBound + where + Self: hidden::Sealed, + { + } +} + +// Traits transitioning from Unconditionally Sealed -> Public API Sealed (Lint should not detect these) +pub mod unconditionally_sealed_to_public_api_sealed { + #[doc(hidden)] + pub mod hidden { + pub trait Sealed {} + pub struct Token; + } + + pub trait ExtendsTraitInHiddenModule: hidden::Sealed {} + + pub trait ReturnsTraitInHiddenModule { + fn method(&self) -> hidden::Token; + } +} diff --git a/test_crates/unconditionally_sealed_trait_became_unsealed/old/Cargo.toml b/test_crates/unconditionally_sealed_trait_became_unsealed/old/Cargo.toml new file mode 100644 index 00000000..f46baa74 --- /dev/null +++ b/test_crates/unconditionally_sealed_trait_became_unsealed/old/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "unconditionally_sealed_trait_became_unsealed" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/unconditionally_sealed_trait_became_unsealed/old/src/lib.rs b/test_crates/unconditionally_sealed_trait_became_unsealed/old/src/lib.rs new file mode 100644 index 00000000..f0584834 --- /dev/null +++ b/test_crates/unconditionally_sealed_trait_became_unsealed/old/src/lib.rs @@ -0,0 +1,39 @@ +// Traits transitioning from Unconditionally Sealed → Unsealed (Lint should detect these) +pub mod unconditionally_sealed_to_unsealed { + mod hidden { + pub trait Sealed {} + pub struct Token; + } + + pub trait ExtendsTraitInHiddenModule: hidden::Sealed {} + + pub trait TransitivelyTraitSealed: ExtendsTraitInHiddenModule {} + + pub trait ReturnsTraitInHiddenModule { + fn method(&self) -> hidden::Token; + } + + pub trait AcceptsTraitInHiddenModule { + fn method(&self, token: hidden::Token); + } + + pub trait SealedWithWhereSelfBound + where + Self: hidden::Sealed, + { + } +} + +// Traits transitioning from Unconditionally Sealed -> Public API Sealed (Lint should not detect these) +pub mod unconditionally_sealed_to_public_api_sealed { + mod hidden { + pub trait Sealed {} + pub struct Token; + } + + pub trait ExtendsTraitInHiddenModule: hidden::Sealed {} + + pub trait ReturnsTraitInHiddenModule { + fn method(&self) -> hidden::Token; + } +} diff --git a/test_outputs/query_execution/unconditionally_sealed_trait_became_unsealed.snap b/test_outputs/query_execution/unconditionally_sealed_trait_became_unsealed.snap new file mode 100644 index 00000000..2844716f --- /dev/null +++ b/test_outputs/query_execution/unconditionally_sealed_trait_became_unsealed.snap @@ -0,0 +1,81 @@ +--- +source: src/query.rs +expression: "&query_execution_results" +--- +{ + "./test_crates/trait_method_added/": [ + { + "name": String("WillGainMethodWithoutDefaultAndLoseSeal"), + "path": List([ + String("trait_method_added"), + String("WillGainMethodWithoutDefaultAndLoseSeal"), + ]), + "span_begin_line": Uint64(43), + "span_end_line": Uint64(45), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/unconditionally_sealed_trait_became_unsealed/": [ + { + "name": String("ExtendsTraitInHiddenModule"), + "path": List([ + String("unconditionally_sealed_trait_became_unsealed"), + String("unconditionally_sealed_to_unsealed"), + String("ExtendsTraitInHiddenModule"), + ]), + "span_begin_line": Uint64(8), + "span_end_line": Uint64(8), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("TransitivelyTraitSealed"), + "path": List([ + String("unconditionally_sealed_trait_became_unsealed"), + String("unconditionally_sealed_to_unsealed"), + String("TransitivelyTraitSealed"), + ]), + "span_begin_line": Uint64(10), + "span_end_line": Uint64(10), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("ReturnsTraitInHiddenModule"), + "path": List([ + String("unconditionally_sealed_trait_became_unsealed"), + String("unconditionally_sealed_to_unsealed"), + String("ReturnsTraitInHiddenModule"), + ]), + "span_begin_line": Uint64(12), + "span_end_line": Uint64(14), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("AcceptsTraitInHiddenModule"), + "path": List([ + String("unconditionally_sealed_trait_became_unsealed"), + String("unconditionally_sealed_to_unsealed"), + String("AcceptsTraitInHiddenModule"), + ]), + "span_begin_line": Uint64(16), + "span_end_line": Uint64(18), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("SealedWithWhereSelfBound"), + "path": List([ + String("unconditionally_sealed_trait_became_unsealed"), + String("unconditionally_sealed_to_unsealed"), + String("SealedWithWhereSelfBound"), + ]), + "span_begin_line": Uint64(20), + "span_end_line": Uint64(24), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], +} From 343cf1845e03e7ec476e7dbe60f5699e1126cdd7 Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Sat, 1 Mar 2025 08:53:36 +0530 Subject: [PATCH 2/5] formatted error and description message --- .../unconditionally_sealed_trait_became_unsealed.ron | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lints/unconditionally_sealed_trait_became_unsealed.ron b/src/lints/unconditionally_sealed_trait_became_unsealed.ron index 7659ae1d..4869ee3d 100644 --- a/src/lints/unconditionally_sealed_trait_became_unsealed.ron +++ b/src/lints/unconditionally_sealed_trait_became_unsealed.ron @@ -1,7 +1,7 @@ SemverQuery( id: "unconditionally_sealed_trait_became_unsealed", human_readable_name: "Unconditionally sealed trait became unsealed", - description: "A trait that was previously unconditionally sealed is now unsealed, allowing downstream crates to implement it. This change introduces potential long-term stability risks, as reverting it in the future would be a breaking change", + description: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it and reverting this in the future would be a breaking change.", required_update: Minor, lint_level: Warn, reference_link: Some("https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed"), @@ -13,6 +13,7 @@ SemverQuery( ... on Trait { visibility_limit @filter(op: "=", value: ["$public"]) @output unconditionally_sealed @filter(op: "=", value: ["$true"]) + importable_path { path @output @tag public_api @filter(op: "=", value: ["$true"]) @@ -27,10 +28,12 @@ SemverQuery( unconditionally_sealed @filter(op: "!=", value: ["$true"]) public_api_sealed @filter(op: "!=", value: ["$true"]) name @output + importable_path { path @filter(op: "=", value: ["%path"]) public_api @filter(op: "=", value: ["$true"]) } + span_: span @optional { filename @output begin_line @output @@ -45,9 +48,7 @@ SemverQuery( "public": "public", "true": true, }, - error_message: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it. - This change introduces potential stability risks because if it needs to be re-sealed in the future, - it would be a breaking change.", + error_message: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it and resealing it later would be a breaking change.", per_result_error_template: Some("trait {{join \"::\" path}} in file {{span_filename}}:{{span_begin_line}}"), witness: None, ) From c0b2f897b389408c9694a4dd71ced0e6205420a8 Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Sat, 1 Mar 2025 09:11:33 +0530 Subject: [PATCH 3/5] rewrite text --- src/lints/unconditionally_sealed_trait_became_unsealed.ron | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lints/unconditionally_sealed_trait_became_unsealed.ron b/src/lints/unconditionally_sealed_trait_became_unsealed.ron index 4869ee3d..f6346a51 100644 --- a/src/lints/unconditionally_sealed_trait_became_unsealed.ron +++ b/src/lints/unconditionally_sealed_trait_became_unsealed.ron @@ -1,7 +1,7 @@ SemverQuery( id: "unconditionally_sealed_trait_became_unsealed", human_readable_name: "Unconditionally sealed trait became unsealed", - description: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it and reverting this in the future would be a breaking change.", + description: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it. Reverting this in the future would be a breaking change.", required_update: Minor, lint_level: Warn, reference_link: Some("https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed"), @@ -48,7 +48,7 @@ SemverQuery( "public": "public", "true": true, }, - error_message: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it and resealing it later would be a breaking change.", + error_message: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it. Resealing it later would be a breaking change.", per_result_error_template: Some("trait {{join \"::\" path}} in file {{span_filename}}:{{span_begin_line}}"), witness: None, ) From 654e35b55651a80768bfe21845d5da857ac6879d Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:49:15 +0530 Subject: [PATCH 4/5] rewrite text --- src/lints/unconditionally_sealed_trait_became_unsealed.ron | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lints/unconditionally_sealed_trait_became_unsealed.ron b/src/lints/unconditionally_sealed_trait_became_unsealed.ron index f6346a51..e0b5dd43 100644 --- a/src/lints/unconditionally_sealed_trait_became_unsealed.ron +++ b/src/lints/unconditionally_sealed_trait_became_unsealed.ron @@ -1,7 +1,7 @@ SemverQuery( id: "unconditionally_sealed_trait_became_unsealed", - human_readable_name: "Unconditionally sealed trait became unsealed", - description: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it. Reverting this in the future would be a breaking change.", + human_readable_name: "unconditionally sealed trait became unsealed", + description: "An unconditionally sealed trait became unsealed, allowing downstream crates to implement it. Reverting this would be a major breaking change.", required_update: Minor, lint_level: Warn, reference_link: Some("https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed"), @@ -48,7 +48,7 @@ SemverQuery( "public": "public", "true": true, }, - error_message: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it. Resealing it later would be a breaking change.", + error_message: "An unconditionally sealed trait became unsealed, allowing downstream crates to implement it. Reverting this would be a major breaking change.", per_result_error_template: Some("trait {{join \"::\" path}} in file {{span_filename}}:{{span_begin_line}}"), witness: None, ) From 1232ade44d4d46f11290f98d4fb256d774238b01 Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Sat, 1 Mar 2025 11:17:01 +0530 Subject: [PATCH 5/5] fix: typo --- src/lints/unconditionally_sealed_trait_became_unsealed.ron | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lints/unconditionally_sealed_trait_became_unsealed.ron b/src/lints/unconditionally_sealed_trait_became_unsealed.ron index e0b5dd43..7f3fdd7d 100644 --- a/src/lints/unconditionally_sealed_trait_became_unsealed.ron +++ b/src/lints/unconditionally_sealed_trait_became_unsealed.ron @@ -1,7 +1,7 @@ SemverQuery( id: "unconditionally_sealed_trait_became_unsealed", human_readable_name: "unconditionally sealed trait became unsealed", - description: "An unconditionally sealed trait became unsealed, allowing downstream crates to implement it. Reverting this would be a major breaking change.", + description: "An unconditionally sealed trait has become unsealed, allowing downstream crates to implement it. Reverting this would be a major breaking change.", required_update: Minor, lint_level: Warn, reference_link: Some("https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed"), @@ -48,7 +48,7 @@ SemverQuery( "public": "public", "true": true, }, - error_message: "An unconditionally sealed trait became unsealed, allowing downstream crates to implement it. Reverting this would be a major breaking change.", + error_message: "An unconditionally sealed trait has become unsealed, allowing downstream crates to implement it. Reverting this would be a major breaking change.", per_result_error_template: Some("trait {{join \"::\" path}} in file {{span_filename}}:{{span_begin_line}}"), witness: None, )