From 9b3cc6c5b12d44686dcf166021ebac7121376607 Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Thu, 27 Feb 2025 00:08:02 +0530 Subject: [PATCH 01/10] feat: lint for public API sealed trait became unsealed --- src/lints/trait_sealed_becomes_unsealed.ron | 54 +++++++++++++++ src/query.rs | 1 + .../new/Cargo.toml | 7 ++ .../new/src/lib.rs | 16 +++++ .../old/Cargo.toml | 7 ++ .../old/src/lib.rs | 18 +++++ .../trait_sealed_becomes_unsealed.snap | 69 +++++++++++++++++++ 7 files changed, 172 insertions(+) create mode 100644 src/lints/trait_sealed_becomes_unsealed.ron create mode 100644 test_crates/trait_sealed_becomes_unsealed/new/Cargo.toml create mode 100644 test_crates/trait_sealed_becomes_unsealed/new/src/lib.rs create mode 100644 test_crates/trait_sealed_becomes_unsealed/old/Cargo.toml create mode 100644 test_crates/trait_sealed_becomes_unsealed/old/src/lib.rs create mode 100644 test_outputs/query_execution/trait_sealed_becomes_unsealed.snap diff --git a/src/lints/trait_sealed_becomes_unsealed.ron b/src/lints/trait_sealed_becomes_unsealed.ron new file mode 100644 index 00000000..58f0cebf --- /dev/null +++ b/src/lints/trait_sealed_becomes_unsealed.ron @@ -0,0 +1,54 @@ +SemverQuery( + id: "trait_sealed_becomes_unsealed", + human_readable_name: "trait sealed becomes unsealed", + description: "A public sealed trait is unsealed, so users of this trait can implement it from now", + required_update: Minor, + lint_level: Deny, + reference_link: Some("https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed"), // TODO + query: r#" + { + CrateDiff { + baseline { + item { + ... on Trait { + visibility_limit @filter(op: "=", value: ["$public"]) @output + public_api_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"]) + 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: "A publicly-visible sealed trait became unsealed, so now downstream crates are able to implement it", + 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 1e8026ac..b2992493 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1340,6 +1340,7 @@ add_lints!( trait_removed_supertrait, trait_requires_more_const_generic_params, trait_requires_more_generic_type_params, + trait_sealed_becomes_unsealed, trait_unsafe_added, trait_unsafe_removed, tuple_struct_to_plain_struct, diff --git a/test_crates/trait_sealed_becomes_unsealed/new/Cargo.toml b/test_crates/trait_sealed_becomes_unsealed/new/Cargo.toml new file mode 100644 index 00000000..5d8932ca --- /dev/null +++ b/test_crates/trait_sealed_becomes_unsealed/new/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "trait_sealed_becomes_unsealed" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/trait_sealed_becomes_unsealed/new/src/lib.rs b/test_crates/trait_sealed_becomes_unsealed/new/src/lib.rs new file mode 100644 index 00000000..9c055295 --- /dev/null +++ b/test_crates/trait_sealed_becomes_unsealed/new/src/lib.rs @@ -0,0 +1,16 @@ +// This trait is public API sealed. +pub trait PublicAPIToBeUnsealed { + type Hidden; +} + +pub mod hidden_module { + /// This trait is public-API-sealed because implementing it + /// requires going outside the public API. + pub trait HiddenSealed {} + + pub struct Token; +} + +/// This trait is public-API-sealed since implementing it requires naming +/// its non-public-API supertrait. +pub trait HiddenSealedInherited: hidden_module::HiddenSealed {} diff --git a/test_crates/trait_sealed_becomes_unsealed/old/Cargo.toml b/test_crates/trait_sealed_becomes_unsealed/old/Cargo.toml new file mode 100644 index 00000000..5d8932ca --- /dev/null +++ b/test_crates/trait_sealed_becomes_unsealed/old/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "trait_sealed_becomes_unsealed" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/trait_sealed_becomes_unsealed/old/src/lib.rs b/test_crates/trait_sealed_becomes_unsealed/old/src/lib.rs new file mode 100644 index 00000000..352e2d52 --- /dev/null +++ b/test_crates/trait_sealed_becomes_unsealed/old/src/lib.rs @@ -0,0 +1,18 @@ +// This trait is public API sealed. +pub trait PublicAPIToBeUnsealed { + #[doc(hidden)] + type Hidden; +} + +#[doc(hidden)] +pub mod hidden_module { + /// This trait is public-API-sealed because implementing it + /// requires going outside the public API. + pub trait HiddenSealed {} + + pub struct Token; +} + +/// This trait is public-API-sealed since implementing it requires naming +/// its non-public-API supertrait. +pub trait HiddenSealedInherited: hidden_module::HiddenSealed {} diff --git a/test_outputs/query_execution/trait_sealed_becomes_unsealed.snap b/test_outputs/query_execution/trait_sealed_becomes_unsealed.snap new file mode 100644 index 00000000..faa62f8e --- /dev/null +++ b/test_outputs/query_execution/trait_sealed_becomes_unsealed.snap @@ -0,0 +1,69 @@ +--- +source: src/query.rs +expression: "&query_execution_results" +--- +{ + "./test_crates/trait_associated_type_marked_deprecated/": [ + { + "name": String("PublicTraitWithHiddenType"), + "path": List([ + String("trait_associated_type_marked_deprecated"), + String("PublicTraitWithHiddenType"), + ]), + "span_begin_line": Uint64(40), + "span_end_line": Uint64(44), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], + "./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/trait_method_marked_deprecated/": [ + { + "name": String("PublicTraitWithHiddenMethod"), + "path": List([ + String("trait_method_marked_deprecated"), + String("PublicTraitWithHiddenMethod"), + ]), + "span_begin_line": Uint64(47), + "span_end_line": Uint64(51), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/trait_sealed_becomes_unsealed/": [ + { + "name": String("PublicAPIToBeUnsealed"), + "path": List([ + String("trait_sealed_becomes_unsealed"), + String("PublicAPIToBeUnsealed"), + ]), + "span_begin_line": Uint64(2), + "span_end_line": Uint64(4), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("HiddenSealedInherited"), + "path": List([ + String("trait_sealed_becomes_unsealed"), + String("HiddenSealedInherited"), + ]), + "span_begin_line": Uint64(16), + "span_end_line": Uint64(16), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], +} From 3fd4dc56ee73f6d341957249d48d7d34ee254a18 Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Thu, 27 Feb 2025 10:29:57 +0530 Subject: [PATCH 02/10] feat: more examples on test_crates --- src/lints/trait_sealed_becomes_unsealed.ron | 2 +- .../new/src/lib.rs | 24 ++++++++++ .../old/src/lib.rs | 24 ++++++++++ .../trait_sealed_becomes_unsealed.snap | 44 +++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/lints/trait_sealed_becomes_unsealed.ron b/src/lints/trait_sealed_becomes_unsealed.ron index 58f0cebf..1f2db480 100644 --- a/src/lints/trait_sealed_becomes_unsealed.ron +++ b/src/lints/trait_sealed_becomes_unsealed.ron @@ -3,7 +3,7 @@ SemverQuery( human_readable_name: "trait sealed becomes unsealed", description: "A public sealed trait is unsealed, so users of this trait can implement it from now", required_update: Minor, - lint_level: Deny, + lint_level: Warn, reference_link: Some("https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed"), // TODO query: r#" { diff --git a/test_crates/trait_sealed_becomes_unsealed/new/src/lib.rs b/test_crates/trait_sealed_becomes_unsealed/new/src/lib.rs index 9c055295..87e59a38 100644 --- a/test_crates/trait_sealed_becomes_unsealed/new/src/lib.rs +++ b/test_crates/trait_sealed_becomes_unsealed/new/src/lib.rs @@ -14,3 +14,27 @@ pub mod hidden_module { /// This trait is public-API-sealed since implementing it requires naming /// its non-public-API supertrait. pub trait HiddenSealedInherited: hidden_module::HiddenSealed {} + +/// This trait is public-API-sealed because its method's return type is doc-hidden, +/// so external implementers would have to name a non-public-API type to write the impl. +pub trait MethodReturnHiddenSealed { + fn method(&self) -> hidden_module::Token; +} + +/// This trait is public-API-sealed transitively because of its supertrait. +pub trait TransitivelyHiddenSealed: HiddenSealedInherited {} + +/// This trait is public-API-sealed, since `Self: hidden_module::HiddenSealed` +/// still requires that `Self` implement a public-API-sealed trait, +/// even though the public-API-sealed trait isn't *exactly* a supertrait. +pub trait HiddenSealedWithWhereSelfBound +where + Self: hidden_module::HiddenSealed, +{ +} + +/// This trait is public-API-sealed because its method's argument type is doc-hidden, +/// so external implementers would have to name a non-public-API type to write the impl. +pub trait MethodHiddenSealed { + fn method(&self, token: hidden_module::Token); +} diff --git a/test_crates/trait_sealed_becomes_unsealed/old/src/lib.rs b/test_crates/trait_sealed_becomes_unsealed/old/src/lib.rs index 352e2d52..574e9374 100644 --- a/test_crates/trait_sealed_becomes_unsealed/old/src/lib.rs +++ b/test_crates/trait_sealed_becomes_unsealed/old/src/lib.rs @@ -16,3 +16,27 @@ pub mod hidden_module { /// This trait is public-API-sealed since implementing it requires naming /// its non-public-API supertrait. pub trait HiddenSealedInherited: hidden_module::HiddenSealed {} + +/// This trait is public-API-sealed because its method's return type is doc-hidden, +/// so external implementers would have to name a non-public-API type to write the impl. +pub trait MethodReturnHiddenSealed { + fn method(&self) -> hidden_module::Token; +} + +/// This trait is public-API-sealed transitively because of its supertrait. +pub trait TransitivelyHiddenSealed: HiddenSealedInherited {} + +/// This trait is public-API-sealed, since `Self: hidden_module::HiddenSealed` +/// still requires that `Self` implement a public-API-sealed trait, +/// even though the public-API-sealed trait isn't *exactly* a supertrait. +pub trait HiddenSealedWithWhereSelfBound +where + Self: hidden_module::HiddenSealed, +{ +} + +/// This trait is public-API-sealed because its method's argument type is doc-hidden, +/// so external implementers would have to name a non-public-API type to write the impl. +pub trait MethodHiddenSealed { + fn method(&self, token: hidden_module::Token); +} diff --git a/test_outputs/query_execution/trait_sealed_becomes_unsealed.snap b/test_outputs/query_execution/trait_sealed_becomes_unsealed.snap index faa62f8e..f66e15ba 100644 --- a/test_outputs/query_execution/trait_sealed_becomes_unsealed.snap +++ b/test_outputs/query_execution/trait_sealed_becomes_unsealed.snap @@ -65,5 +65,49 @@ expression: "&query_execution_results" "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, + { + "name": String("MethodReturnHiddenSealed"), + "path": List([ + String("trait_sealed_becomes_unsealed"), + String("MethodReturnHiddenSealed"), + ]), + "span_begin_line": Uint64(20), + "span_end_line": Uint64(22), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("TransitivelyHiddenSealed"), + "path": List([ + String("trait_sealed_becomes_unsealed"), + String("TransitivelyHiddenSealed"), + ]), + "span_begin_line": Uint64(25), + "span_end_line": Uint64(25), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("HiddenSealedWithWhereSelfBound"), + "path": List([ + String("trait_sealed_becomes_unsealed"), + String("HiddenSealedWithWhereSelfBound"), + ]), + "span_begin_line": Uint64(30), + "span_end_line": Uint64(34), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("MethodHiddenSealed"), + "path": List([ + String("trait_sealed_becomes_unsealed"), + String("MethodHiddenSealed"), + ]), + "span_begin_line": Uint64(38), + "span_end_line": Uint64(40), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, ], } From bd51869f2ca27514b7f3ef218f237fe633c8845f Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:44:16 +0530 Subject: [PATCH 03/10] rename lint --- ...sealed.ron => trait_sealed_became_unsealed.ron} | 4 ++-- src/query.rs | 2 +- .../new/Cargo.toml | 2 +- .../new/src/lib.rs | 0 .../old/Cargo.toml | 2 +- .../old/src/lib.rs | 0 ...aled.snap => trait_sealed_became_unsealed.snap} | 14 +++++++------- 7 files changed, 12 insertions(+), 12 deletions(-) rename src/lints/{trait_sealed_becomes_unsealed.ron => trait_sealed_became_unsealed.ron} (95%) rename test_crates/{trait_sealed_becomes_unsealed => trait_sealed_became_unsealed}/new/Cargo.toml (66%) rename test_crates/{trait_sealed_becomes_unsealed => trait_sealed_became_unsealed}/new/src/lib.rs (100%) rename test_crates/{trait_sealed_becomes_unsealed => trait_sealed_became_unsealed}/old/Cargo.toml (66%) rename test_crates/{trait_sealed_becomes_unsealed => trait_sealed_became_unsealed}/old/src/lib.rs (100%) rename test_outputs/query_execution/{trait_sealed_becomes_unsealed.snap => trait_sealed_became_unsealed.snap} (89%) diff --git a/src/lints/trait_sealed_becomes_unsealed.ron b/src/lints/trait_sealed_became_unsealed.ron similarity index 95% rename from src/lints/trait_sealed_becomes_unsealed.ron rename to src/lints/trait_sealed_became_unsealed.ron index 1f2db480..b72cb8f3 100644 --- a/src/lints/trait_sealed_becomes_unsealed.ron +++ b/src/lints/trait_sealed_became_unsealed.ron @@ -1,6 +1,6 @@ SemverQuery( - id: "trait_sealed_becomes_unsealed", - human_readable_name: "trait sealed becomes unsealed", + id: "trait_sealed_became_unsealed", + human_readable_name: "trait sealed became unsealed", description: "A public sealed trait is unsealed, so users of this trait can implement it from now", required_update: Minor, lint_level: Warn, diff --git a/src/query.rs b/src/query.rs index b2992493..59a85a48 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1340,7 +1340,7 @@ add_lints!( trait_removed_supertrait, trait_requires_more_const_generic_params, trait_requires_more_generic_type_params, - trait_sealed_becomes_unsealed, + trait_sealed_became_unsealed, trait_unsafe_added, trait_unsafe_removed, tuple_struct_to_plain_struct, diff --git a/test_crates/trait_sealed_becomes_unsealed/new/Cargo.toml b/test_crates/trait_sealed_became_unsealed/new/Cargo.toml similarity index 66% rename from test_crates/trait_sealed_becomes_unsealed/new/Cargo.toml rename to test_crates/trait_sealed_became_unsealed/new/Cargo.toml index 5d8932ca..5be476bf 100644 --- a/test_crates/trait_sealed_becomes_unsealed/new/Cargo.toml +++ b/test_crates/trait_sealed_became_unsealed/new/Cargo.toml @@ -1,6 +1,6 @@ [package] publish = false -name = "trait_sealed_becomes_unsealed" +name = "trait_sealed_became_unsealed" version = "0.1.0" edition = "2021" diff --git a/test_crates/trait_sealed_becomes_unsealed/new/src/lib.rs b/test_crates/trait_sealed_became_unsealed/new/src/lib.rs similarity index 100% rename from test_crates/trait_sealed_becomes_unsealed/new/src/lib.rs rename to test_crates/trait_sealed_became_unsealed/new/src/lib.rs diff --git a/test_crates/trait_sealed_becomes_unsealed/old/Cargo.toml b/test_crates/trait_sealed_became_unsealed/old/Cargo.toml similarity index 66% rename from test_crates/trait_sealed_becomes_unsealed/old/Cargo.toml rename to test_crates/trait_sealed_became_unsealed/old/Cargo.toml index 5d8932ca..5be476bf 100644 --- a/test_crates/trait_sealed_becomes_unsealed/old/Cargo.toml +++ b/test_crates/trait_sealed_became_unsealed/old/Cargo.toml @@ -1,6 +1,6 @@ [package] publish = false -name = "trait_sealed_becomes_unsealed" +name = "trait_sealed_became_unsealed" version = "0.1.0" edition = "2021" diff --git a/test_crates/trait_sealed_becomes_unsealed/old/src/lib.rs b/test_crates/trait_sealed_became_unsealed/old/src/lib.rs similarity index 100% rename from test_crates/trait_sealed_becomes_unsealed/old/src/lib.rs rename to test_crates/trait_sealed_became_unsealed/old/src/lib.rs diff --git a/test_outputs/query_execution/trait_sealed_becomes_unsealed.snap b/test_outputs/query_execution/trait_sealed_became_unsealed.snap similarity index 89% rename from test_outputs/query_execution/trait_sealed_becomes_unsealed.snap rename to test_outputs/query_execution/trait_sealed_became_unsealed.snap index f66e15ba..5b0220b9 100644 --- a/test_outputs/query_execution/trait_sealed_becomes_unsealed.snap +++ b/test_outputs/query_execution/trait_sealed_became_unsealed.snap @@ -42,11 +42,11 @@ expression: "&query_execution_results" "visibility_limit": String("public"), }, ], - "./test_crates/trait_sealed_becomes_unsealed/": [ + "./test_crates/trait_sealed_became_unsealed/": [ { "name": String("PublicAPIToBeUnsealed"), "path": List([ - String("trait_sealed_becomes_unsealed"), + String("trait_sealed_became_unsealed"), String("PublicAPIToBeUnsealed"), ]), "span_begin_line": Uint64(2), @@ -57,7 +57,7 @@ expression: "&query_execution_results" { "name": String("HiddenSealedInherited"), "path": List([ - String("trait_sealed_becomes_unsealed"), + String("trait_sealed_became_unsealed"), String("HiddenSealedInherited"), ]), "span_begin_line": Uint64(16), @@ -68,7 +68,7 @@ expression: "&query_execution_results" { "name": String("MethodReturnHiddenSealed"), "path": List([ - String("trait_sealed_becomes_unsealed"), + String("trait_sealed_became_unsealed"), String("MethodReturnHiddenSealed"), ]), "span_begin_line": Uint64(20), @@ -79,7 +79,7 @@ expression: "&query_execution_results" { "name": String("TransitivelyHiddenSealed"), "path": List([ - String("trait_sealed_becomes_unsealed"), + String("trait_sealed_became_unsealed"), String("TransitivelyHiddenSealed"), ]), "span_begin_line": Uint64(25), @@ -90,7 +90,7 @@ expression: "&query_execution_results" { "name": String("HiddenSealedWithWhereSelfBound"), "path": List([ - String("trait_sealed_becomes_unsealed"), + String("trait_sealed_became_unsealed"), String("HiddenSealedWithWhereSelfBound"), ]), "span_begin_line": Uint64(30), @@ -101,7 +101,7 @@ expression: "&query_execution_results" { "name": String("MethodHiddenSealed"), "path": List([ - String("trait_sealed_becomes_unsealed"), + String("trait_sealed_became_unsealed"), String("MethodHiddenSealed"), ]), "span_begin_line": Uint64(38), From d4c745df28ebbe8d3a456049875e9dfc6790239d Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Thu, 27 Feb 2025 18:46:47 +0530 Subject: [PATCH 04/10] feat: better descriptive example traits --- .../new/src/lib.rs | 53 ++++++++---------- .../old/src/lib.rs | 56 +++++++++---------- .../trait_sealed_became_unsealed.snap | 50 +++++------------ 3 files changed, 62 insertions(+), 97 deletions(-) diff --git a/test_crates/trait_sealed_became_unsealed/new/src/lib.rs b/test_crates/trait_sealed_became_unsealed/new/src/lib.rs index 87e59a38..4a700e46 100644 --- a/test_crates/trait_sealed_became_unsealed/new/src/lib.rs +++ b/test_crates/trait_sealed_became_unsealed/new/src/lib.rs @@ -1,40 +1,33 @@ -// This trait is public API sealed. -pub trait PublicAPIToBeUnsealed { - type Hidden; +pub mod public_api_hidden_module_tobe_exposed { + pub trait Sealed {} + pub struct Token; } -pub mod hidden_module { - /// This trait is public-API-sealed because implementing it - /// requires going outside the public API. - pub trait HiddenSealed {} - +mod public_api_hidden_module_to_be_unconditionally_hidden { + pub trait Sealed {} pub struct Token; } -/// This trait is public-API-sealed since implementing it requires naming -/// its non-public-API supertrait. -pub trait HiddenSealedInherited: hidden_module::HiddenSealed {} - -/// This trait is public-API-sealed because its method's return type is doc-hidden, -/// so external implementers would have to name a non-public-API type to write the impl. -pub trait MethodReturnHiddenSealed { - fn method(&self) -> hidden_module::Token; +// Traits transitioning from Public API Sealed → Unsealed (Lint should detect these) +pub trait PublicAPIToBeUnsealed { + type Hidden; +} +pub trait TraitExtendsPublicAPIHiddenTrait: public_api_hidden_module_tobe_exposed::Sealed {} +pub trait MethodReturnPublicAPIHiddenToken { + fn method(&self) -> public_api_hidden_module_tobe_exposed::Token; +} +pub trait MethodTakingPublicAPIHiddenToken { + fn method(&self, token: public_api_hidden_module_tobe_exposed::Token); } -/// This trait is public-API-sealed transitively because of its supertrait. -pub trait TransitivelyHiddenSealed: HiddenSealedInherited {} - -/// This trait is public-API-sealed, since `Self: hidden_module::HiddenSealed` -/// still requires that `Self` implement a public-API-sealed trait, -/// even though the public-API-sealed trait isn't *exactly* a supertrait. -pub trait HiddenSealedWithWhereSelfBound -where - Self: hidden_module::HiddenSealed, +// Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should ignore these) +pub trait TraitExtendsUnconditionallyHiddenTrait: + public_api_hidden_module_to_be_unconditionally_hidden::Sealed { } - -/// This trait is public-API-sealed because its method's argument type is doc-hidden, -/// so external implementers would have to name a non-public-API type to write the impl. -pub trait MethodHiddenSealed { - fn method(&self, token: hidden_module::Token); +pub trait MethodReturningUnconditionallyHiddenToken { + fn method(&self) -> public_api_hidden_module_to_be_unconditionally_hidden::Token; +} +pub trait MethodTakingUnconditionallyHiddenToken { + fn method(&self, token: public_api_hidden_module_to_be_unconditionally_hidden::Token); } diff --git a/test_crates/trait_sealed_became_unsealed/old/src/lib.rs b/test_crates/trait_sealed_became_unsealed/old/src/lib.rs index 574e9374..2619d116 100644 --- a/test_crates/trait_sealed_became_unsealed/old/src/lib.rs +++ b/test_crates/trait_sealed_became_unsealed/old/src/lib.rs @@ -1,42 +1,36 @@ -// This trait is public API sealed. -pub trait PublicAPIToBeUnsealed { - #[doc(hidden)] - type Hidden; +#[doc(hidden)] +pub mod public_api_hidden_module_tobe_exposed { + pub trait Sealed {} + pub struct Token; } #[doc(hidden)] -pub mod hidden_module { - /// This trait is public-API-sealed because implementing it - /// requires going outside the public API. - pub trait HiddenSealed {} - +pub mod public_api_hidden_module_to_be_unconditionally_hidden { + pub trait Sealed {} pub struct Token; } -/// This trait is public-API-sealed since implementing it requires naming -/// its non-public-API supertrait. -pub trait HiddenSealedInherited: hidden_module::HiddenSealed {} - -/// This trait is public-API-sealed because its method's return type is doc-hidden, -/// so external implementers would have to name a non-public-API type to write the impl. -pub trait MethodReturnHiddenSealed { - fn method(&self) -> hidden_module::Token; +// Traits transitioning from Public API Sealed → Unsealed (Lint should detect these) +pub trait PublicAPIToBeUnsealed { + #[doc(hidden)] + type Hidden; +} +pub trait TraitExtendsPublicAPIHiddenTrait: public_api_hidden_module_tobe_exposed::Sealed {} +pub trait MethodReturnPublicAPIHiddenToken { + fn method(&self) -> public_api_hidden_module_tobe_exposed::Token; +} +pub trait MethodTakingPublicAPIHiddenToken { + fn method(&self, token: public_api_hidden_module_tobe_exposed::Token); } -/// This trait is public-API-sealed transitively because of its supertrait. -pub trait TransitivelyHiddenSealed: HiddenSealedInherited {} - -/// This trait is public-API-sealed, since `Self: hidden_module::HiddenSealed` -/// still requires that `Self` implement a public-API-sealed trait, -/// even though the public-API-sealed trait isn't *exactly* a supertrait. -pub trait HiddenSealedWithWhereSelfBound -where - Self: hidden_module::HiddenSealed, +// Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should ignore these) +pub trait TraitExtendsUnconditionallyHiddenTrait: + public_api_hidden_module_to_be_unconditionally_hidden::Sealed { } - -/// This trait is public-API-sealed because its method's argument type is doc-hidden, -/// so external implementers would have to name a non-public-API type to write the impl. -pub trait MethodHiddenSealed { - fn method(&self, token: hidden_module::Token); +pub trait MethodReturningUnconditionallyHiddenToken { + fn method(&self) -> public_api_hidden_module_to_be_unconditionally_hidden::Token; +} +pub trait MethodTakingUnconditionallyHiddenToken { + fn method(&self, token: public_api_hidden_module_to_be_unconditionally_hidden::Token); } diff --git a/test_outputs/query_execution/trait_sealed_became_unsealed.snap b/test_outputs/query_execution/trait_sealed_became_unsealed.snap index 5b0220b9..b9f46893 100644 --- a/test_outputs/query_execution/trait_sealed_became_unsealed.snap +++ b/test_outputs/query_execution/trait_sealed_became_unsealed.snap @@ -49,63 +49,41 @@ expression: "&query_execution_results" String("trait_sealed_became_unsealed"), String("PublicAPIToBeUnsealed"), ]), - "span_begin_line": Uint64(2), - "span_end_line": Uint64(4), + "span_begin_line": Uint64(12), + "span_end_line": Uint64(14), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, { - "name": String("HiddenSealedInherited"), + "name": String("TraitExtendsPublicAPIHiddenTrait"), "path": List([ String("trait_sealed_became_unsealed"), - String("HiddenSealedInherited"), + String("TraitExtendsPublicAPIHiddenTrait"), ]), - "span_begin_line": Uint64(16), - "span_end_line": Uint64(16), - "span_filename": String("src/lib.rs"), - "visibility_limit": String("public"), - }, - { - "name": String("MethodReturnHiddenSealed"), - "path": List([ - String("trait_sealed_became_unsealed"), - String("MethodReturnHiddenSealed"), - ]), - "span_begin_line": Uint64(20), - "span_end_line": Uint64(22), + "span_begin_line": Uint64(15), + "span_end_line": Uint64(15), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, { - "name": String("TransitivelyHiddenSealed"), + "name": String("MethodReturnPublicAPIHiddenToken"), "path": List([ String("trait_sealed_became_unsealed"), - String("TransitivelyHiddenSealed"), + String("MethodReturnPublicAPIHiddenToken"), ]), - "span_begin_line": Uint64(25), - "span_end_line": Uint64(25), - "span_filename": String("src/lib.rs"), - "visibility_limit": String("public"), - }, - { - "name": String("HiddenSealedWithWhereSelfBound"), - "path": List([ - String("trait_sealed_became_unsealed"), - String("HiddenSealedWithWhereSelfBound"), - ]), - "span_begin_line": Uint64(30), - "span_end_line": Uint64(34), + "span_begin_line": Uint64(16), + "span_end_line": Uint64(18), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, { - "name": String("MethodHiddenSealed"), + "name": String("MethodTakingPublicAPIHiddenToken"), "path": List([ String("trait_sealed_became_unsealed"), - String("MethodHiddenSealed"), + String("MethodTakingPublicAPIHiddenToken"), ]), - "span_begin_line": Uint64(38), - "span_end_line": Uint64(40), + "span_begin_line": Uint64(19), + "span_end_line": Uint64(21), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, From 8e55ce5a64b29cb1ddd9ca9d8ee157a8c3fbce8e Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Thu, 27 Feb 2025 22:00:50 +0530 Subject: [PATCH 05/10] rename lint --- ... pub_api_sealed_trait_became_unsealed.ron} | 15 ++-- src/query.rs | 2 +- .../new/Cargo.toml | 2 +- .../new/src/lib.rs | 48 +++++++++++ .../old/Cargo.toml | 2 +- .../old/src/lib.rs | 51 +++++++++++ .../new/src/lib.rs | 33 -------- .../old/src/lib.rs | 36 -------- ...pub_api_sealed_trait_became_unsealed.snap} | 84 +++++++++---------- 9 files changed, 153 insertions(+), 120 deletions(-) rename src/lints/{trait_sealed_became_unsealed.ron => pub_api_sealed_trait_became_unsealed.ron} (67%) rename test_crates/{trait_sealed_became_unsealed => pub_api_sealed_trait_became_unsealed}/new/Cargo.toml (62%) create mode 100644 test_crates/pub_api_sealed_trait_became_unsealed/new/src/lib.rs rename test_crates/{trait_sealed_became_unsealed => pub_api_sealed_trait_became_unsealed}/old/Cargo.toml (62%) create mode 100644 test_crates/pub_api_sealed_trait_became_unsealed/old/src/lib.rs delete mode 100644 test_crates/trait_sealed_became_unsealed/new/src/lib.rs delete mode 100644 test_crates/trait_sealed_became_unsealed/old/src/lib.rs rename test_outputs/query_execution/{trait_sealed_became_unsealed.snap => pub_api_sealed_trait_became_unsealed.snap} (82%) diff --git a/src/lints/trait_sealed_became_unsealed.ron b/src/lints/pub_api_sealed_trait_became_unsealed.ron similarity index 67% rename from src/lints/trait_sealed_became_unsealed.ron rename to src/lints/pub_api_sealed_trait_became_unsealed.ron index b72cb8f3..988ce000 100644 --- a/src/lints/trait_sealed_became_unsealed.ron +++ b/src/lints/pub_api_sealed_trait_became_unsealed.ron @@ -1,7 +1,9 @@ SemverQuery( - id: "trait_sealed_became_unsealed", - human_readable_name: "trait sealed became unsealed", - description: "A public sealed trait is unsealed, so users of this trait can implement it from now", + id: "pub_api_sealed_trait_became_unsealed", + human_readable_name: "Sealed trait in the public API became unsealed", + description: "A trait in the public API that was previously sealed is now unsealed, allowing downstream crates to implement it. + This could be risky: if this change needs to be reverted, it would be a breaking change, as downstream crates may already depend on their implementations of this trait. + Review this change carefully to avoid unintentionally breaking compatibility in the future", 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"), // TODO @@ -42,13 +44,14 @@ SemverQuery( } } } - } - "#, + }"#, arguments: { "public": "public", "true": true, }, - error_message: "A publicly-visible sealed trait became unsealed, so now downstream crates are able to implement it", + error_message: "A sealed trait in the public api has become unsealed, allowing external crates to implement it. + If you later need to re-seal this trait, it will be a breaking change. + Consider whether this change is intentional and whether it aligns with your stability guarantees", 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 59a85a48..396bc79e 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1340,7 +1340,7 @@ add_lints!( trait_removed_supertrait, trait_requires_more_const_generic_params, trait_requires_more_generic_type_params, - trait_sealed_became_unsealed, + pub_api_sealed_trait_became_unsealed, trait_unsafe_added, trait_unsafe_removed, tuple_struct_to_plain_struct, diff --git a/test_crates/trait_sealed_became_unsealed/new/Cargo.toml b/test_crates/pub_api_sealed_trait_became_unsealed/new/Cargo.toml similarity index 62% rename from test_crates/trait_sealed_became_unsealed/new/Cargo.toml rename to test_crates/pub_api_sealed_trait_became_unsealed/new/Cargo.toml index 5be476bf..8a7a0dca 100644 --- a/test_crates/trait_sealed_became_unsealed/new/Cargo.toml +++ b/test_crates/pub_api_sealed_trait_became_unsealed/new/Cargo.toml @@ -1,6 +1,6 @@ [package] publish = false -name = "trait_sealed_became_unsealed" +name = "pub_api_sealed_trait_became_unsealed" version = "0.1.0" edition = "2021" diff --git a/test_crates/pub_api_sealed_trait_became_unsealed/new/src/lib.rs b/test_crates/pub_api_sealed_trait_became_unsealed/new/src/lib.rs new file mode 100644 index 00000000..c3126de2 --- /dev/null +++ b/test_crates/pub_api_sealed_trait_became_unsealed/new/src/lib.rs @@ -0,0 +1,48 @@ +pub mod public_api_hidden_module_to_be_exposed { + pub trait Sealed {} + pub struct Token; +} + +mod public_api_hidden_module_to_be_unconditionally_hidden { + pub trait Sealed {} + pub struct Token; +} + +// ───────────────────────────────────────────────────── +// Traits transitioning from Public API Sealed → Unsealed (Lint should detect these) +// ───────────────────────────────────────────────────── + +// Start: Public API Sealed → Unsealed (Lint should detect these) +pub trait PublicAPIToBeUnsealed { + type Hidden; +} + +pub trait TraitExtendsPublicAPIHiddenTrait: public_api_hidden_module_to_be_exposed::Sealed {} + +pub trait MethodReturnPublicAPIHiddenToken { + fn method(&self) -> public_api_hidden_module_to_be_exposed::Token; +} + +pub trait MethodTakingPublicAPIHiddenToken { + fn method(&self, token: public_api_hidden_module_to_be_exposed::Token); +} +// End: Public API Sealed → Unsealed + +// ───────────────────────────────────────────────────── +// Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should ignore these) +// ───────────────────────────────────────────────────── + +// Start: Public API Sealed → Unconditionally Sealed (Lint should ignore these) +pub trait TraitExtendsUnconditionallyHiddenTrait: + public_api_hidden_module_to_be_unconditionally_hidden::Sealed +{ +} + +pub trait MethodReturningUnconditionallyHiddenToken { + fn method(&self) -> public_api_hidden_module_to_be_unconditionally_hidden::Token; +} + +pub trait MethodTakingUnconditionallyHiddenToken { + fn method(&self, token: public_api_hidden_module_to_be_unconditionally_hidden::Token); +} +// End: Public API Sealed → Unconditionally Sealed diff --git a/test_crates/trait_sealed_became_unsealed/old/Cargo.toml b/test_crates/pub_api_sealed_trait_became_unsealed/old/Cargo.toml similarity index 62% rename from test_crates/trait_sealed_became_unsealed/old/Cargo.toml rename to test_crates/pub_api_sealed_trait_became_unsealed/old/Cargo.toml index 5be476bf..8a7a0dca 100644 --- a/test_crates/trait_sealed_became_unsealed/old/Cargo.toml +++ b/test_crates/pub_api_sealed_trait_became_unsealed/old/Cargo.toml @@ -1,6 +1,6 @@ [package] publish = false -name = "trait_sealed_became_unsealed" +name = "pub_api_sealed_trait_became_unsealed" version = "0.1.0" edition = "2021" diff --git a/test_crates/pub_api_sealed_trait_became_unsealed/old/src/lib.rs b/test_crates/pub_api_sealed_trait_became_unsealed/old/src/lib.rs new file mode 100644 index 00000000..b8129ba1 --- /dev/null +++ b/test_crates/pub_api_sealed_trait_became_unsealed/old/src/lib.rs @@ -0,0 +1,51 @@ +#[doc(hidden)] +pub mod public_api_hidden_module_to_be_exposed { + pub trait Sealed {} + pub struct Token; +} + +#[doc(hidden)] +pub mod public_api_hidden_module_to_be_unconditionally_hidden { + pub trait Sealed {} + pub struct Token; +} + +// ───────────────────────────────────────────────────── +// Traits transitioning from Public API Sealed → Unsealed (Lint should detect these) +// ───────────────────────────────────────────────────── + +// Start: Public API Sealed → Unsealed (Lint should detect these) +pub trait PublicAPIToBeUnsealed { + #[doc(hidden)] + type Hidden; +} + +pub trait TraitExtendsPublicAPIHiddenTrait: public_api_hidden_module_to_be_exposed::Sealed {} + +pub trait MethodReturnPublicAPIHiddenToken { + fn method(&self) -> public_api_hidden_module_to_be_exposed::Token; +} + +pub trait MethodTakingPublicAPIHiddenToken { + fn method(&self, token: public_api_hidden_module_to_be_exposed::Token); +} +// End: Public API Sealed → Unsealed + +// ───────────────────────────────────────────────────── +// Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should ignore these) +// ───────────────────────────────────────────────────── + +// Start: Public API Sealed → Unconditionally Sealed (Lint should ignore these) +pub trait TraitExtendsUnconditionallyHiddenTrait: + public_api_hidden_module_to_be_unconditionally_hidden::Sealed +{ +} + +pub trait MethodReturningUnconditionallyHiddenToken { + fn method(&self) -> public_api_hidden_module_to_be_unconditionally_hidden::Token; +} + +pub trait MethodTakingUnconditionallyHiddenToken { + fn method(&self, token: public_api_hidden_module_to_be_unconditionally_hidden::Token); +} +// End: Public API Sealed → Unconditionally Sealed diff --git a/test_crates/trait_sealed_became_unsealed/new/src/lib.rs b/test_crates/trait_sealed_became_unsealed/new/src/lib.rs deleted file mode 100644 index 4a700e46..00000000 --- a/test_crates/trait_sealed_became_unsealed/new/src/lib.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub mod public_api_hidden_module_tobe_exposed { - pub trait Sealed {} - pub struct Token; -} - -mod public_api_hidden_module_to_be_unconditionally_hidden { - pub trait Sealed {} - pub struct Token; -} - -// Traits transitioning from Public API Sealed → Unsealed (Lint should detect these) -pub trait PublicAPIToBeUnsealed { - type Hidden; -} -pub trait TraitExtendsPublicAPIHiddenTrait: public_api_hidden_module_tobe_exposed::Sealed {} -pub trait MethodReturnPublicAPIHiddenToken { - fn method(&self) -> public_api_hidden_module_tobe_exposed::Token; -} -pub trait MethodTakingPublicAPIHiddenToken { - fn method(&self, token: public_api_hidden_module_tobe_exposed::Token); -} - -// Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should ignore these) -pub trait TraitExtendsUnconditionallyHiddenTrait: - public_api_hidden_module_to_be_unconditionally_hidden::Sealed -{ -} -pub trait MethodReturningUnconditionallyHiddenToken { - fn method(&self) -> public_api_hidden_module_to_be_unconditionally_hidden::Token; -} -pub trait MethodTakingUnconditionallyHiddenToken { - fn method(&self, token: public_api_hidden_module_to_be_unconditionally_hidden::Token); -} diff --git a/test_crates/trait_sealed_became_unsealed/old/src/lib.rs b/test_crates/trait_sealed_became_unsealed/old/src/lib.rs deleted file mode 100644 index 2619d116..00000000 --- a/test_crates/trait_sealed_became_unsealed/old/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -#[doc(hidden)] -pub mod public_api_hidden_module_tobe_exposed { - pub trait Sealed {} - pub struct Token; -} - -#[doc(hidden)] -pub mod public_api_hidden_module_to_be_unconditionally_hidden { - pub trait Sealed {} - pub struct Token; -} - -// Traits transitioning from Public API Sealed → Unsealed (Lint should detect these) -pub trait PublicAPIToBeUnsealed { - #[doc(hidden)] - type Hidden; -} -pub trait TraitExtendsPublicAPIHiddenTrait: public_api_hidden_module_tobe_exposed::Sealed {} -pub trait MethodReturnPublicAPIHiddenToken { - fn method(&self) -> public_api_hidden_module_tobe_exposed::Token; -} -pub trait MethodTakingPublicAPIHiddenToken { - fn method(&self, token: public_api_hidden_module_tobe_exposed::Token); -} - -// Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should ignore these) -pub trait TraitExtendsUnconditionallyHiddenTrait: - public_api_hidden_module_to_be_unconditionally_hidden::Sealed -{ -} -pub trait MethodReturningUnconditionallyHiddenToken { - fn method(&self) -> public_api_hidden_module_to_be_unconditionally_hidden::Token; -} -pub trait MethodTakingUnconditionallyHiddenToken { - fn method(&self, token: public_api_hidden_module_to_be_unconditionally_hidden::Token); -} diff --git a/test_outputs/query_execution/trait_sealed_became_unsealed.snap b/test_outputs/query_execution/pub_api_sealed_trait_became_unsealed.snap similarity index 82% rename from test_outputs/query_execution/trait_sealed_became_unsealed.snap rename to test_outputs/query_execution/pub_api_sealed_trait_became_unsealed.snap index b9f46893..4d3e300d 100644 --- a/test_outputs/query_execution/trait_sealed_became_unsealed.snap +++ b/test_outputs/query_execution/pub_api_sealed_trait_became_unsealed.snap @@ -3,87 +3,87 @@ source: src/query.rs expression: "&query_execution_results" --- { - "./test_crates/trait_associated_type_marked_deprecated/": [ + "./test_crates/pub_api_sealed_trait_became_unsealed/": [ { - "name": String("PublicTraitWithHiddenType"), + "name": String("PublicAPIToBeUnsealed"), "path": List([ - String("trait_associated_type_marked_deprecated"), - String("PublicTraitWithHiddenType"), + String("pub_api_sealed_trait_became_unsealed"), + String("PublicAPIToBeUnsealed"), ]), - "span_begin_line": Uint64(40), - "span_end_line": Uint64(44), + "span_begin_line": Uint64(16), + "span_end_line": Uint64(18), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, - ], - "./test_crates/trait_method_added/": [ { - "name": String("WillGainMethodWithoutDefaultAndLoseSeal"), + "name": String("TraitExtendsPublicAPIHiddenTrait"), "path": List([ - String("trait_method_added"), - String("WillGainMethodWithoutDefaultAndLoseSeal"), + String("pub_api_sealed_trait_became_unsealed"), + String("TraitExtendsPublicAPIHiddenTrait"), ]), - "span_begin_line": Uint64(43), - "span_end_line": Uint64(45), + "span_begin_line": Uint64(20), + "span_end_line": Uint64(20), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, - ], - "./test_crates/trait_method_marked_deprecated/": [ { - "name": String("PublicTraitWithHiddenMethod"), + "name": String("MethodReturnPublicAPIHiddenToken"), "path": List([ - String("trait_method_marked_deprecated"), - String("PublicTraitWithHiddenMethod"), + String("pub_api_sealed_trait_became_unsealed"), + String("MethodReturnPublicAPIHiddenToken"), ]), - "span_begin_line": Uint64(47), - "span_end_line": Uint64(51), + "span_begin_line": Uint64(22), + "span_end_line": Uint64(24), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, - ], - "./test_crates/trait_sealed_became_unsealed/": [ { - "name": String("PublicAPIToBeUnsealed"), + "name": String("MethodTakingPublicAPIHiddenToken"), "path": List([ - String("trait_sealed_became_unsealed"), - String("PublicAPIToBeUnsealed"), + String("pub_api_sealed_trait_became_unsealed"), + String("MethodTakingPublicAPIHiddenToken"), ]), - "span_begin_line": Uint64(12), - "span_end_line": Uint64(14), + "span_begin_line": Uint64(26), + "span_end_line": Uint64(28), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, + ], + "./test_crates/trait_associated_type_marked_deprecated/": [ { - "name": String("TraitExtendsPublicAPIHiddenTrait"), + "name": String("PublicTraitWithHiddenType"), "path": List([ - String("trait_sealed_became_unsealed"), - String("TraitExtendsPublicAPIHiddenTrait"), + String("trait_associated_type_marked_deprecated"), + String("PublicTraitWithHiddenType"), ]), - "span_begin_line": Uint64(15), - "span_end_line": Uint64(15), + "span_begin_line": Uint64(40), + "span_end_line": Uint64(44), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, + ], + "./test_crates/trait_method_added/": [ { - "name": String("MethodReturnPublicAPIHiddenToken"), + "name": String("WillGainMethodWithoutDefaultAndLoseSeal"), "path": List([ - String("trait_sealed_became_unsealed"), - String("MethodReturnPublicAPIHiddenToken"), + String("trait_method_added"), + String("WillGainMethodWithoutDefaultAndLoseSeal"), ]), - "span_begin_line": Uint64(16), - "span_end_line": Uint64(18), + "span_begin_line": Uint64(43), + "span_end_line": Uint64(45), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, + ], + "./test_crates/trait_method_marked_deprecated/": [ { - "name": String("MethodTakingPublicAPIHiddenToken"), + "name": String("PublicTraitWithHiddenMethod"), "path": List([ - String("trait_sealed_became_unsealed"), - String("MethodTakingPublicAPIHiddenToken"), + String("trait_method_marked_deprecated"), + String("PublicTraitWithHiddenMethod"), ]), - "span_begin_line": Uint64(19), - "span_end_line": Uint64(21), + "span_begin_line": Uint64(47), + "span_end_line": Uint64(51), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, From 0c7b5500936cab5ab01a45770ebcc4b9d1d45fe0 Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Thu, 27 Feb 2025 22:54:23 +0530 Subject: [PATCH 06/10] Removed TODO comment --- src/lints/pub_api_sealed_trait_became_unsealed.ron | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lints/pub_api_sealed_trait_became_unsealed.ron b/src/lints/pub_api_sealed_trait_became_unsealed.ron index 988ce000..9ad2e117 100644 --- a/src/lints/pub_api_sealed_trait_became_unsealed.ron +++ b/src/lints/pub_api_sealed_trait_became_unsealed.ron @@ -6,7 +6,7 @@ SemverQuery( Review this change carefully to avoid unintentionally breaking compatibility in the future", 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"), // TODO + reference_link: Some("https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed"), query: r#" { CrateDiff { From 5426f017093d7095ae8b50aabbbf7aeef954a373 Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Fri, 28 Feb 2025 14:56:39 +0530 Subject: [PATCH 07/10] fix: lint detecting unconditionally_sealed -> unsealed traits --- .../pub_api_sealed_trait_became_unsealed.ron | 1 + .../new/src/lib.rs | 63 ++++++++--------- .../old/src/lib.rs | 69 ++++++++----------- .../pub_api_sealed_trait_became_unsealed.snap | 37 ++++------ 4 files changed, 72 insertions(+), 98 deletions(-) diff --git a/src/lints/pub_api_sealed_trait_became_unsealed.ron b/src/lints/pub_api_sealed_trait_became_unsealed.ron index 9ad2e117..cc4b8750 100644 --- a/src/lints/pub_api_sealed_trait_became_unsealed.ron +++ b/src/lints/pub_api_sealed_trait_became_unsealed.ron @@ -15,6 +15,7 @@ SemverQuery( ... on Trait { visibility_limit @filter(op: "=", value: ["$public"]) @output public_api_sealed @filter(op: "=", value: ["$true"]) + unconditionally_sealed @filter(op: "!=", value: ["$true"]) importable_path { path @output @tag diff --git a/test_crates/pub_api_sealed_trait_became_unsealed/new/src/lib.rs b/test_crates/pub_api_sealed_trait_became_unsealed/new/src/lib.rs index c3126de2..c3ac8d82 100644 --- a/test_crates/pub_api_sealed_trait_became_unsealed/new/src/lib.rs +++ b/test_crates/pub_api_sealed_trait_became_unsealed/new/src/lib.rs @@ -1,48 +1,39 @@ -pub mod public_api_hidden_module_to_be_exposed { - pub trait Sealed {} - pub struct Token; -} - -mod public_api_hidden_module_to_be_unconditionally_hidden { - pub trait Sealed {} - pub struct Token; -} - -// ───────────────────────────────────────────────────── // Traits transitioning from Public API Sealed → Unsealed (Lint should detect these) -// ───────────────────────────────────────────────────── +pub mod public_api_sealed_to_unsealed { + pub mod hidden { + pub trait Sealed {} + pub struct Token; + } -// Start: Public API Sealed → Unsealed (Lint should detect these) -pub trait PublicAPIToBeUnsealed { - type Hidden; -} + pub trait PublicAPIToBeUnsealed { + type Hidden; + } -pub trait TraitExtendsPublicAPIHiddenTrait: public_api_hidden_module_to_be_exposed::Sealed {} + pub trait TraitExtendsHiddenPublicAPITrait: hidden::Sealed {} -pub trait MethodReturnPublicAPIHiddenToken { - fn method(&self) -> public_api_hidden_module_to_be_exposed::Token; -} + pub trait MethodReturnPublicAPIHiddenToken { + fn method(&self) -> hidden::Token; + } -pub trait MethodTakingPublicAPIHiddenToken { - fn method(&self, token: public_api_hidden_module_to_be_exposed::Token); + pub trait MethodTakingPublicAPIHiddenToken { + fn method(&self, token: hidden::Token); + } } -// End: Public API Sealed → Unsealed -// ───────────────────────────────────────────────────── // Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should ignore these) -// ───────────────────────────────────────────────────── +pub mod public_api_sealed_to_unconditionally_sealed { + mod hidden { + pub trait Sealed {} + pub struct Token; + } -// Start: Public API Sealed → Unconditionally Sealed (Lint should ignore these) -pub trait TraitExtendsUnconditionallyHiddenTrait: - public_api_hidden_module_to_be_unconditionally_hidden::Sealed -{ -} + pub trait TraitExtendsUnconditionallyHiddenTrait: hidden::Sealed {} -pub trait MethodReturningUnconditionallyHiddenToken { - fn method(&self) -> public_api_hidden_module_to_be_unconditionally_hidden::Token; -} + pub trait MethodReturningUnconditionallyHiddenToken { + fn method(&self) -> hidden::Token; + } -pub trait MethodTakingUnconditionallyHiddenToken { - fn method(&self, token: public_api_hidden_module_to_be_unconditionally_hidden::Token); + pub trait MethodTakingUnconditionallyHiddenToken { + fn method(&self, token: hidden::Token); + } } -// End: Public API Sealed → Unconditionally Sealed diff --git a/test_crates/pub_api_sealed_trait_became_unsealed/old/src/lib.rs b/test_crates/pub_api_sealed_trait_became_unsealed/old/src/lib.rs index b8129ba1..c8506d29 100644 --- a/test_crates/pub_api_sealed_trait_became_unsealed/old/src/lib.rs +++ b/test_crates/pub_api_sealed_trait_became_unsealed/old/src/lib.rs @@ -1,51 +1,42 @@ -#[doc(hidden)] -pub mod public_api_hidden_module_to_be_exposed { - pub trait Sealed {} - pub struct Token; -} - -#[doc(hidden)] -pub mod public_api_hidden_module_to_be_unconditionally_hidden { - pub trait Sealed {} - pub struct Token; -} - -// ───────────────────────────────────────────────────── // Traits transitioning from Public API Sealed → Unsealed (Lint should detect these) -// ───────────────────────────────────────────────────── - -// Start: Public API Sealed → Unsealed (Lint should detect these) -pub trait PublicAPIToBeUnsealed { +pub mod public_api_sealed_to_unsealed { #[doc(hidden)] - type Hidden; -} + pub mod hidden { + pub trait Sealed {} + pub struct Token; + } -pub trait TraitExtendsPublicAPIHiddenTrait: public_api_hidden_module_to_be_exposed::Sealed {} + pub trait PublicAPIToBeUnsealed { + #[doc(hidden)] + type Hidden; + } -pub trait MethodReturnPublicAPIHiddenToken { - fn method(&self) -> public_api_hidden_module_to_be_exposed::Token; -} + pub trait TraitExtendsHiddenPublicAPITrait: hidden::Sealed {} + + pub trait MethodReturnPublicAPIHiddenToken { + fn method(&self) -> hidden::Token; + } -pub trait MethodTakingPublicAPIHiddenToken { - fn method(&self, token: public_api_hidden_module_to_be_exposed::Token); + pub trait MethodTakingPublicAPIHiddenToken { + fn method(&self, token: hidden::Token); + } } -// End: Public API Sealed → Unsealed -// ───────────────────────────────────────────────────── // Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should ignore these) -// ───────────────────────────────────────────────────── +pub mod public_api_sealed_to_unconditionally_sealed { + #[doc(hidden)] + pub mod hidden { + pub trait Sealed {} + pub struct Token; + } -// Start: Public API Sealed → Unconditionally Sealed (Lint should ignore these) -pub trait TraitExtendsUnconditionallyHiddenTrait: - public_api_hidden_module_to_be_unconditionally_hidden::Sealed -{ -} + pub trait TraitExtendsUnconditionallyHiddenTrait: hidden::Sealed {} -pub trait MethodReturningUnconditionallyHiddenToken { - fn method(&self) -> public_api_hidden_module_to_be_unconditionally_hidden::Token; -} + pub trait MethodReturningUnconditionallyHiddenToken { + fn method(&self) -> hidden::Token; + } -pub trait MethodTakingUnconditionallyHiddenToken { - fn method(&self, token: public_api_hidden_module_to_be_unconditionally_hidden::Token); + pub trait MethodTakingUnconditionallyHiddenToken { + fn method(&self, token: hidden::Token); + } } -// End: Public API Sealed → Unconditionally Sealed diff --git a/test_outputs/query_execution/pub_api_sealed_trait_became_unsealed.snap b/test_outputs/query_execution/pub_api_sealed_trait_became_unsealed.snap index 4d3e300d..fe591aed 100644 --- a/test_outputs/query_execution/pub_api_sealed_trait_became_unsealed.snap +++ b/test_outputs/query_execution/pub_api_sealed_trait_became_unsealed.snap @@ -8,21 +8,23 @@ expression: "&query_execution_results" "name": String("PublicAPIToBeUnsealed"), "path": List([ String("pub_api_sealed_trait_became_unsealed"), + String("public_api_sealed_to_unsealed"), String("PublicAPIToBeUnsealed"), ]), - "span_begin_line": Uint64(16), - "span_end_line": Uint64(18), + "span_begin_line": Uint64(8), + "span_end_line": Uint64(10), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, { - "name": String("TraitExtendsPublicAPIHiddenTrait"), + "name": String("TraitExtendsHiddenPublicAPITrait"), "path": List([ String("pub_api_sealed_trait_became_unsealed"), - String("TraitExtendsPublicAPIHiddenTrait"), + String("public_api_sealed_to_unsealed"), + String("TraitExtendsHiddenPublicAPITrait"), ]), - "span_begin_line": Uint64(20), - "span_end_line": Uint64(20), + "span_begin_line": Uint64(12), + "span_end_line": Uint64(12), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, @@ -30,10 +32,11 @@ expression: "&query_execution_results" "name": String("MethodReturnPublicAPIHiddenToken"), "path": List([ String("pub_api_sealed_trait_became_unsealed"), + String("public_api_sealed_to_unsealed"), String("MethodReturnPublicAPIHiddenToken"), ]), - "span_begin_line": Uint64(22), - "span_end_line": Uint64(24), + "span_begin_line": Uint64(14), + "span_end_line": Uint64(16), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, @@ -41,10 +44,11 @@ expression: "&query_execution_results" "name": String("MethodTakingPublicAPIHiddenToken"), "path": List([ String("pub_api_sealed_trait_became_unsealed"), + String("public_api_sealed_to_unsealed"), String("MethodTakingPublicAPIHiddenToken"), ]), - "span_begin_line": Uint64(26), - "span_end_line": Uint64(28), + "span_begin_line": Uint64(18), + "span_end_line": Uint64(20), "span_filename": String("src/lib.rs"), "visibility_limit": String("public"), }, @@ -62,19 +66,6 @@ expression: "&query_execution_results" "visibility_limit": String("public"), }, ], - "./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/trait_method_marked_deprecated/": [ { "name": String("PublicTraitWithHiddenMethod"), From 733e7750100ba2b5c3f91d1b0fe99e4fae1492dc Mon Sep 17 00:00:00 2001 From: Lovelin <100030865+lovelindhoni@users.noreply.github.com> Date: Sat, 1 Mar 2025 09:00:18 +0530 Subject: [PATCH 08/10] formatted error and description messages --- src/lints/pub_api_sealed_trait_became_unsealed.ron | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lints/pub_api_sealed_trait_became_unsealed.ron b/src/lints/pub_api_sealed_trait_became_unsealed.ron index cc4b8750..b7da8f15 100644 --- a/src/lints/pub_api_sealed_trait_became_unsealed.ron +++ b/src/lints/pub_api_sealed_trait_became_unsealed.ron @@ -1,9 +1,7 @@ SemverQuery( id: "pub_api_sealed_trait_became_unsealed", human_readable_name: "Sealed trait in the public API became unsealed", - description: "A trait in the public API that was previously sealed is now unsealed, allowing downstream crates to implement it. - This could be risky: if this change needs to be reverted, it would be a breaking change, as downstream crates may already depend on their implementations of this trait. - Review this change carefully to avoid unintentionally breaking compatibility in the future", + description: "An public API 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"), @@ -50,9 +48,7 @@ SemverQuery( "public": "public", "true": true, }, - error_message: "A sealed trait in the public api has become unsealed, allowing external crates to implement it. - If you later need to re-seal this trait, it will be a breaking change. - Consider whether this change is intentional and whether it aligns with your stability guarantees", + error_message: "An public API sealed trait is now unsealed, allowing downstream crates to implement it. Resealing this later would be a breaking change.", per_result_error_template: Some("trait {{join \"::\" path}} in file {{span_filename}}:{{span_begin_line}}"), witness: None, ) From 5153c041fd831af000823282ecb4577c5207a931 Mon Sep 17 00:00:00 2001 From: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> Date: Fri, 28 Feb 2025 23:52:07 -0500 Subject: [PATCH 09/10] Apply suggestions from code review --- src/lints/pub_api_sealed_trait_became_unsealed.ron | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lints/pub_api_sealed_trait_became_unsealed.ron b/src/lints/pub_api_sealed_trait_became_unsealed.ron index b7da8f15..33a66c8b 100644 --- a/src/lints/pub_api_sealed_trait_became_unsealed.ron +++ b/src/lints/pub_api_sealed_trait_became_unsealed.ron @@ -1,7 +1,7 @@ SemverQuery( id: "pub_api_sealed_trait_became_unsealed", - human_readable_name: "Sealed trait in the public API became unsealed", - description: "An public API sealed trait is now unsealed, allowing downstream crates to implement it. Reverting this in the future would be a breaking change", + human_readable_name: "Public API sealed trait became unsealed", + description: "A public API 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 public API sealed trait is now unsealed, allowing downstream crates to implement it. Resealing this later would be a breaking change.", + error_message: "A public API 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, ) From fedbdc34d327b438749d1c2ead55175c10252650 Mon Sep 17 00:00:00 2001 From: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> Date: Fri, 28 Feb 2025 23:54:09 -0500 Subject: [PATCH 10/10] Update src/lints/pub_api_sealed_trait_became_unsealed.ron --- src/lints/pub_api_sealed_trait_became_unsealed.ron | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lints/pub_api_sealed_trait_became_unsealed.ron b/src/lints/pub_api_sealed_trait_became_unsealed.ron index 33a66c8b..61242b3b 100644 --- a/src/lints/pub_api_sealed_trait_became_unsealed.ron +++ b/src/lints/pub_api_sealed_trait_became_unsealed.ron @@ -1,6 +1,6 @@ SemverQuery( id: "pub_api_sealed_trait_became_unsealed", - human_readable_name: "Public API sealed trait became unsealed", + human_readable_name: "public API sealed trait became unsealed", description: "A public API 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,