Skip to content

Commit 8b117c9

Browse files
committed
new lint: unconditionally_sealed_trait_became_unsealed
1 parent 7d72104 commit 8b117c9

File tree

7 files changed

+228
-0
lines changed

7 files changed

+228
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
SemverQuery(
2+
id: "unconditionally_sealed_trait_became_unsealed",
3+
human_readable_name: "Unconditionally sealed trait became unsealed",
4+
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",
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+
unconditionally_sealed @filter(op: "=", value: ["$true"])
16+
importable_path {
17+
path @output @tag
18+
public_api @filter(op: "=", value: ["$true"])
19+
}
20+
}
21+
}
22+
}
23+
current {
24+
item {
25+
... on Trait {
26+
visibility_limit @filter(op: "=", value: ["$public"])
27+
unconditionally_sealed @filter(op: "!=", value: ["$true"])
28+
public_api_sealed @filter(op: "!=", value: ["$true"])
29+
name @output
30+
importable_path {
31+
path @filter(op: "=", value: ["%path"])
32+
public_api @filter(op: "=", value: ["$true"])
33+
}
34+
span_: span @optional {
35+
filename @output
36+
begin_line @output
37+
end_line @output
38+
}
39+
}
40+
}
41+
}
42+
}
43+
}"#,
44+
arguments: {
45+
"public": "public",
46+
"true": true,
47+
},
48+
error_message: "An unconditionally sealed trait is now unsealed, allowing downstream crates to implement it.
49+
This change introduces potential stability risks because if it needs to be re-sealed in the future,
50+
it would be a breaking change.",
51+
per_result_error_template: Some("trait {{join \"::\" path}} in file {{span_filename}}:{{span_begin_line}}"),
52+
witness: None,
53+
)

src/query.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,7 @@ add_lints!(
13521352
type_mismatched_generic_lifetimes,
13531353
type_requires_more_const_generic_params,
13541354
type_requires_more_generic_type_params,
1355+
unconditionally_sealed_trait_became_unsealed,
13551356
union_field_added_with_all_pub_fields,
13561357
union_field_added_with_non_pub_fields,
13571358
union_field_missing,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
publish = false
3+
name = "unconditionally_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,40 @@
1+
// Traits transitioning from Unconditionally Sealed → Unsealed (Lint should detect these)
2+
pub mod unconditionally_sealed_to_unsealed {
3+
pub mod hidden {
4+
pub trait Sealed {}
5+
pub struct Token;
6+
}
7+
8+
pub trait ExtendsTraitInHiddenModule: hidden::Sealed {}
9+
10+
pub trait TransitivelyTraitSealed: ExtendsTraitInHiddenModule {}
11+
12+
pub trait ReturnsTraitInHiddenModule {
13+
fn method(&self) -> hidden::Token;
14+
}
15+
16+
pub trait AcceptsTraitInHiddenModule {
17+
fn method(&self, token: hidden::Token);
18+
}
19+
20+
pub trait SealedWithWhereSelfBound
21+
where
22+
Self: hidden::Sealed,
23+
{
24+
}
25+
}
26+
27+
// Traits transitioning from Unconditionally Sealed -> Public API Sealed (Lint should not detect these)
28+
pub mod unconditionally_sealed_to_public_api_sealed {
29+
#[doc(hidden)]
30+
pub mod hidden {
31+
pub trait Sealed {}
32+
pub struct Token;
33+
}
34+
35+
pub trait ExtendsTraitInHiddenModule: hidden::Sealed {}
36+
37+
pub trait ReturnsTraitInHiddenModule {
38+
fn method(&self) -> hidden::Token;
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
publish = false
3+
name = "unconditionally_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 Unconditionally Sealed → Unsealed (Lint should detect these)
2+
pub mod unconditionally_sealed_to_unsealed {
3+
mod hidden {
4+
pub trait Sealed {}
5+
pub struct Token;
6+
}
7+
8+
pub trait ExtendsTraitInHiddenModule: hidden::Sealed {}
9+
10+
pub trait TransitivelyTraitSealed: ExtendsTraitInHiddenModule {}
11+
12+
pub trait ReturnsTraitInHiddenModule {
13+
fn method(&self) -> hidden::Token;
14+
}
15+
16+
pub trait AcceptsTraitInHiddenModule {
17+
fn method(&self, token: hidden::Token);
18+
}
19+
20+
pub trait SealedWithWhereSelfBound
21+
where
22+
Self: hidden::Sealed,
23+
{
24+
}
25+
}
26+
27+
// Traits transitioning from Unconditionally Sealed -> Public API Sealed (Lint should not detect these)
28+
pub mod unconditionally_sealed_to_public_api_sealed {
29+
mod hidden {
30+
pub trait Sealed {}
31+
pub struct Token;
32+
}
33+
34+
pub trait ExtendsTraitInHiddenModule: hidden::Sealed {}
35+
36+
pub trait ReturnsTraitInHiddenModule {
37+
fn method(&self) -> hidden::Token;
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
source: src/query.rs
3+
expression: "&query_execution_results"
4+
---
5+
{
6+
"./test_crates/trait_method_added/": [
7+
{
8+
"name": String("WillGainMethodWithoutDefaultAndLoseSeal"),
9+
"path": List([
10+
String("trait_method_added"),
11+
String("WillGainMethodWithoutDefaultAndLoseSeal"),
12+
]),
13+
"span_begin_line": Uint64(43),
14+
"span_end_line": Uint64(45),
15+
"span_filename": String("src/lib.rs"),
16+
"visibility_limit": String("public"),
17+
},
18+
],
19+
"./test_crates/unconditionally_sealed_trait_became_unsealed/": [
20+
{
21+
"name": String("ExtendsTraitInHiddenModule"),
22+
"path": List([
23+
String("unconditionally_sealed_trait_became_unsealed"),
24+
String("unconditionally_sealed_to_unsealed"),
25+
String("ExtendsTraitInHiddenModule"),
26+
]),
27+
"span_begin_line": Uint64(8),
28+
"span_end_line": Uint64(8),
29+
"span_filename": String("src/lib.rs"),
30+
"visibility_limit": String("public"),
31+
},
32+
{
33+
"name": String("TransitivelyTraitSealed"),
34+
"path": List([
35+
String("unconditionally_sealed_trait_became_unsealed"),
36+
String("unconditionally_sealed_to_unsealed"),
37+
String("TransitivelyTraitSealed"),
38+
]),
39+
"span_begin_line": Uint64(10),
40+
"span_end_line": Uint64(10),
41+
"span_filename": String("src/lib.rs"),
42+
"visibility_limit": String("public"),
43+
},
44+
{
45+
"name": String("ReturnsTraitInHiddenModule"),
46+
"path": List([
47+
String("unconditionally_sealed_trait_became_unsealed"),
48+
String("unconditionally_sealed_to_unsealed"),
49+
String("ReturnsTraitInHiddenModule"),
50+
]),
51+
"span_begin_line": Uint64(12),
52+
"span_end_line": Uint64(14),
53+
"span_filename": String("src/lib.rs"),
54+
"visibility_limit": String("public"),
55+
},
56+
{
57+
"name": String("AcceptsTraitInHiddenModule"),
58+
"path": List([
59+
String("unconditionally_sealed_trait_became_unsealed"),
60+
String("unconditionally_sealed_to_unsealed"),
61+
String("AcceptsTraitInHiddenModule"),
62+
]),
63+
"span_begin_line": Uint64(16),
64+
"span_end_line": Uint64(18),
65+
"span_filename": String("src/lib.rs"),
66+
"visibility_limit": String("public"),
67+
},
68+
{
69+
"name": String("SealedWithWhereSelfBound"),
70+
"path": List([
71+
String("unconditionally_sealed_trait_became_unsealed"),
72+
String("unconditionally_sealed_to_unsealed"),
73+
String("SealedWithWhereSelfBound"),
74+
]),
75+
"span_begin_line": Uint64(20),
76+
"span_end_line": Uint64(24),
77+
"span_filename": String("src/lib.rs"),
78+
"visibility_limit": String("public"),
79+
},
80+
],
81+
}

0 commit comments

Comments
 (0)