Skip to content

Commit af24e84

Browse files
new lint: pub_api_sealed_trait_became_unsealed (#1162)
Resolves #1120 --------- Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com>
1 parent cf04c08 commit af24e84

File tree

7 files changed

+232
-0
lines changed

7 files changed

+232
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
SemverQuery(
2+
id: "pub_api_sealed_trait_became_unsealed",
3+
human_readable_name: "public API sealed trait became unsealed",
4+
description: "A public API sealed trait has become unsealed, allowing downstream crates to implement it. Reverting this would be a major breaking change.",
5+
required_update: Minor,
6+
lint_level: Warn,
7+
reference_link: Some("https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed"),
8+
query: r#"
9+
{
10+
CrateDiff {
11+
baseline {
12+
item {
13+
... on Trait {
14+
visibility_limit @filter(op: "=", value: ["$public"]) @output
15+
public_api_sealed @filter(op: "=", value: ["$true"])
16+
unconditionally_sealed @filter(op: "!=", value: ["$true"])
17+
18+
importable_path {
19+
path @output @tag
20+
public_api @filter(op: "=", value: ["$true"])
21+
}
22+
}
23+
}
24+
}
25+
current {
26+
item {
27+
... on Trait {
28+
visibility_limit @filter(op: "=", value: ["$public"])
29+
public_api_sealed @filter(op: "!=", value: ["$true"])
30+
name @output
31+
32+
importable_path {
33+
path @filter(op: "=", value: ["%path"])
34+
public_api @filter(op: "=", value: ["$true"])
35+
}
36+
37+
span_: span @optional {
38+
filename @output
39+
begin_line @output
40+
end_line @output
41+
}
42+
}
43+
}
44+
}
45+
}
46+
}"#,
47+
arguments: {
48+
"public": "public",
49+
"true": true,
50+
},
51+
error_message: "A public API sealed trait has become unsealed, allowing downstream crates to implement it. Reverting this would be a major breaking change.",
52+
per_result_error_template: Some("trait {{join \"::\" path}} in file {{span_filename}}:{{span_begin_line}}"),
53+
witness: None,
54+
)

src/query.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,7 @@ add_lints!(
13431343
trait_removed_supertrait,
13441344
trait_requires_more_const_generic_params,
13451345
trait_requires_more_generic_type_params,
1346+
pub_api_sealed_trait_became_unsealed,
13461347
trait_unsafe_added,
13471348
trait_unsafe_removed,
13481349
tuple_struct_to_plain_struct,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
publish = false
3+
name = "pub_api_sealed_trait_became_unsealed"
4+
version = "0.1.0"
5+
edition = "2021"
6+
7+
[dependencies]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Traits transitioning from Public API Sealed → Unsealed (Lint should detect these)
2+
pub mod public_api_sealed_to_unsealed {
3+
pub mod hidden {
4+
pub trait Sealed {}
5+
pub struct Token;
6+
}
7+
8+
pub trait PublicAPIToBeUnsealed {
9+
type Hidden;
10+
}
11+
12+
pub trait TraitExtendsHiddenPublicAPITrait: hidden::Sealed {}
13+
14+
pub trait MethodReturnPublicAPIHiddenToken {
15+
fn method(&self) -> hidden::Token;
16+
}
17+
18+
pub trait MethodTakingPublicAPIHiddenToken {
19+
fn method(&self, token: hidden::Token);
20+
}
21+
}
22+
23+
// Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should ignore these)
24+
pub mod public_api_sealed_to_unconditionally_sealed {
25+
mod hidden {
26+
pub trait Sealed {}
27+
pub struct Token;
28+
}
29+
30+
pub trait TraitExtendsUnconditionallyHiddenTrait: hidden::Sealed {}
31+
32+
pub trait MethodReturningUnconditionallyHiddenToken {
33+
fn method(&self) -> hidden::Token;
34+
}
35+
36+
pub trait MethodTakingUnconditionallyHiddenToken {
37+
fn method(&self, token: hidden::Token);
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
publish = false
3+
name = "pub_api_sealed_trait_became_unsealed"
4+
version = "0.1.0"
5+
edition = "2021"
6+
7+
[dependencies]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Traits transitioning from Public API Sealed → Unsealed (Lint should detect these)
2+
pub mod public_api_sealed_to_unsealed {
3+
#[doc(hidden)]
4+
pub mod hidden {
5+
pub trait Sealed {}
6+
pub struct Token;
7+
}
8+
9+
pub trait PublicAPIToBeUnsealed {
10+
#[doc(hidden)]
11+
type Hidden;
12+
}
13+
14+
pub trait TraitExtendsHiddenPublicAPITrait: hidden::Sealed {}
15+
16+
pub trait MethodReturnPublicAPIHiddenToken {
17+
fn method(&self) -> hidden::Token;
18+
}
19+
20+
pub trait MethodTakingPublicAPIHiddenToken {
21+
fn method(&self, token: hidden::Token);
22+
}
23+
}
24+
25+
// Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should ignore these)
26+
pub mod public_api_sealed_to_unconditionally_sealed {
27+
#[doc(hidden)]
28+
pub mod hidden {
29+
pub trait Sealed {}
30+
pub struct Token;
31+
}
32+
33+
pub trait TraitExtendsUnconditionallyHiddenTrait: hidden::Sealed {}
34+
35+
pub trait MethodReturningUnconditionallyHiddenToken {
36+
fn method(&self) -> hidden::Token;
37+
}
38+
39+
pub trait MethodTakingUnconditionallyHiddenToken {
40+
fn method(&self, token: hidden::Token);
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
source: src/query.rs
3+
expression: "&query_execution_results"
4+
---
5+
{
6+
"./test_crates/pub_api_sealed_trait_became_unsealed/": [
7+
{
8+
"name": String("PublicAPIToBeUnsealed"),
9+
"path": List([
10+
String("pub_api_sealed_trait_became_unsealed"),
11+
String("public_api_sealed_to_unsealed"),
12+
String("PublicAPIToBeUnsealed"),
13+
]),
14+
"span_begin_line": Uint64(8),
15+
"span_end_line": Uint64(10),
16+
"span_filename": String("src/lib.rs"),
17+
"visibility_limit": String("public"),
18+
},
19+
{
20+
"name": String("TraitExtendsHiddenPublicAPITrait"),
21+
"path": List([
22+
String("pub_api_sealed_trait_became_unsealed"),
23+
String("public_api_sealed_to_unsealed"),
24+
String("TraitExtendsHiddenPublicAPITrait"),
25+
]),
26+
"span_begin_line": Uint64(12),
27+
"span_end_line": Uint64(12),
28+
"span_filename": String("src/lib.rs"),
29+
"visibility_limit": String("public"),
30+
},
31+
{
32+
"name": String("MethodReturnPublicAPIHiddenToken"),
33+
"path": List([
34+
String("pub_api_sealed_trait_became_unsealed"),
35+
String("public_api_sealed_to_unsealed"),
36+
String("MethodReturnPublicAPIHiddenToken"),
37+
]),
38+
"span_begin_line": Uint64(14),
39+
"span_end_line": Uint64(16),
40+
"span_filename": String("src/lib.rs"),
41+
"visibility_limit": String("public"),
42+
},
43+
{
44+
"name": String("MethodTakingPublicAPIHiddenToken"),
45+
"path": List([
46+
String("pub_api_sealed_trait_became_unsealed"),
47+
String("public_api_sealed_to_unsealed"),
48+
String("MethodTakingPublicAPIHiddenToken"),
49+
]),
50+
"span_begin_line": Uint64(18),
51+
"span_end_line": Uint64(20),
52+
"span_filename": String("src/lib.rs"),
53+
"visibility_limit": String("public"),
54+
},
55+
],
56+
"./test_crates/trait_associated_type_marked_deprecated/": [
57+
{
58+
"name": String("PublicTraitWithHiddenType"),
59+
"path": List([
60+
String("trait_associated_type_marked_deprecated"),
61+
String("PublicTraitWithHiddenType"),
62+
]),
63+
"span_begin_line": Uint64(40),
64+
"span_end_line": Uint64(44),
65+
"span_filename": String("src/lib.rs"),
66+
"visibility_limit": String("public"),
67+
},
68+
],
69+
"./test_crates/trait_method_marked_deprecated/": [
70+
{
71+
"name": String("PublicTraitWithHiddenMethod"),
72+
"path": List([
73+
String("trait_method_marked_deprecated"),
74+
String("PublicTraitWithHiddenMethod"),
75+
]),
76+
"span_begin_line": Uint64(47),
77+
"span_end_line": Uint64(51),
78+
"span_filename": String("src/lib.rs"),
79+
"visibility_limit": String("public"),
80+
},
81+
],
82+
}

0 commit comments

Comments
 (0)