Skip to content

Commit 0057293

Browse files
new lint: pub_api_sealed_trait_became_unconditionally_sealed (#1166)
Resolves #1122 --------- Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com>
1 parent bf9416a commit 0057293

File tree

7 files changed

+223
-0
lines changed

7 files changed

+223
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
SemverQuery(
2+
id: "pub_api_sealed_trait_became_unconditionally_sealed",
3+
human_readable_name: "public API sealed trait became unconditionally sealed",
4+
description: "A public API sealed trait has become unconditionally sealed, blocking all downstream implementations including those from first-party crates that rely on the non-public API.",
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+
unconditionally_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 unconditionally sealed, blocking all downstream implementations including those from first-party crates that rely on the non-public API.",
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
@@ -1287,6 +1287,7 @@ add_lints!(
12871287
partial_ord_struct_fields_reordered,
12881288
proc_macro_marked_deprecated,
12891289
proc_macro_now_doc_hidden,
1290+
pub_api_sealed_trait_became_unconditionally_sealed,
12901291
pub_module_level_const_missing,
12911292
pub_module_level_const_now_doc_hidden,
12921293
pub_static_missing,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
publish = false
3+
name = "pub_api_sealed_trait_became_unconditionally_sealed"
4+
version = "0.1.0"
5+
edition = "2021"
6+
7+
[dependencies]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should detect these)
2+
pub mod public_api_sealed_to_unconditionally_sealed {
3+
mod hidden {
4+
pub trait Sealed {}
5+
pub struct Token;
6+
}
7+
8+
pub trait TraitExtendsUnconditionallyHiddenTrait: hidden::Sealed {}
9+
10+
pub trait MethodReturningUnconditionallyHiddenToken {
11+
fn method(&self) -> hidden::Token;
12+
}
13+
14+
pub trait MethodTakingUnconditionallyHiddenToken {
15+
fn method(&self, token: hidden::Token);
16+
}
17+
18+
pub trait HiddenSealedWithWhereSelfBound
19+
where
20+
Self: hidden::Sealed,
21+
{
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
publish = false
3+
name = "pub_api_sealed_trait_became_unconditionally_sealed"
4+
version = "0.1.0"
5+
edition = "2021"
6+
7+
[dependencies]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Traits transitioning from Public API Sealed → Unconditionally Sealed (Lint should detect these)
2+
pub mod public_api_sealed_to_unconditionally_sealed {
3+
#[doc(hidden)]
4+
pub mod hidden {
5+
pub trait Sealed {}
6+
pub struct Token;
7+
}
8+
9+
pub trait TraitExtendsUnconditionallyHiddenTrait: hidden::Sealed {}
10+
11+
pub trait MethodReturningUnconditionallyHiddenToken {
12+
fn method(&self) -> hidden::Token;
13+
}
14+
15+
pub trait MethodTakingUnconditionallyHiddenToken {
16+
fn method(&self, token: hidden::Token);
17+
}
18+
19+
pub trait HiddenSealedWithWhereSelfBound
20+
where
21+
Self: hidden::Sealed,
22+
{
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
source: src/query.rs
3+
expression: "&query_execution_results"
4+
---
5+
{
6+
"./test_crates/pub_api_sealed_trait_became_unconditionally_sealed/": [
7+
{
8+
"name": String("TraitExtendsUnconditionallyHiddenTrait"),
9+
"path": List([
10+
String("pub_api_sealed_trait_became_unconditionally_sealed"),
11+
String("public_api_sealed_to_unconditionally_sealed"),
12+
String("TraitExtendsUnconditionallyHiddenTrait"),
13+
]),
14+
"span_begin_line": Uint64(8),
15+
"span_end_line": Uint64(8),
16+
"span_filename": String("src/lib.rs"),
17+
"visibility_limit": String("public"),
18+
},
19+
{
20+
"name": String("MethodReturningUnconditionallyHiddenToken"),
21+
"path": List([
22+
String("pub_api_sealed_trait_became_unconditionally_sealed"),
23+
String("public_api_sealed_to_unconditionally_sealed"),
24+
String("MethodReturningUnconditionallyHiddenToken"),
25+
]),
26+
"span_begin_line": Uint64(10),
27+
"span_end_line": Uint64(12),
28+
"span_filename": String("src/lib.rs"),
29+
"visibility_limit": String("public"),
30+
},
31+
{
32+
"name": String("MethodTakingUnconditionallyHiddenToken"),
33+
"path": List([
34+
String("pub_api_sealed_trait_became_unconditionally_sealed"),
35+
String("public_api_sealed_to_unconditionally_sealed"),
36+
String("MethodTakingUnconditionallyHiddenToken"),
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("HiddenSealedWithWhereSelfBound"),
45+
"path": List([
46+
String("pub_api_sealed_trait_became_unconditionally_sealed"),
47+
String("public_api_sealed_to_unconditionally_sealed"),
48+
String("HiddenSealedWithWhereSelfBound"),
49+
]),
50+
"span_begin_line": Uint64(18),
51+
"span_end_line": Uint64(22),
52+
"span_filename": String("src/lib.rs"),
53+
"visibility_limit": String("public"),
54+
},
55+
],
56+
"./test_crates/pub_api_sealed_trait_became_unsealed/": [
57+
{
58+
"name": String("TraitExtendsUnconditionallyHiddenTrait"),
59+
"path": List([
60+
String("pub_api_sealed_trait_became_unsealed"),
61+
String("public_api_sealed_to_unconditionally_sealed"),
62+
String("TraitExtendsUnconditionallyHiddenTrait"),
63+
]),
64+
"span_begin_line": Uint64(30),
65+
"span_end_line": Uint64(30),
66+
"span_filename": String("src/lib.rs"),
67+
"visibility_limit": String("public"),
68+
},
69+
{
70+
"name": String("MethodReturningUnconditionallyHiddenToken"),
71+
"path": List([
72+
String("pub_api_sealed_trait_became_unsealed"),
73+
String("public_api_sealed_to_unconditionally_sealed"),
74+
String("MethodReturningUnconditionallyHiddenToken"),
75+
]),
76+
"span_begin_line": Uint64(32),
77+
"span_end_line": Uint64(34),
78+
"span_filename": String("src/lib.rs"),
79+
"visibility_limit": String("public"),
80+
},
81+
{
82+
"name": String("MethodTakingUnconditionallyHiddenToken"),
83+
"path": List([
84+
String("pub_api_sealed_trait_became_unsealed"),
85+
String("public_api_sealed_to_unconditionally_sealed"),
86+
String("MethodTakingUnconditionallyHiddenToken"),
87+
]),
88+
"span_begin_line": Uint64(36),
89+
"span_end_line": Uint64(38),
90+
"span_filename": String("src/lib.rs"),
91+
"visibility_limit": String("public"),
92+
},
93+
],
94+
"./test_crates/trait_newly_sealed/": [
95+
{
96+
"name": String("PublicAPISealed"),
97+
"path": List([
98+
String("trait_newly_sealed"),
99+
String("PublicAPISealed"),
100+
]),
101+
"span_begin_line": Uint64(29),
102+
"span_end_line": Uint64(32),
103+
"span_filename": String("src/lib.rs"),
104+
"visibility_limit": String("public"),
105+
},
106+
],
107+
}

0 commit comments

Comments
 (0)