Skip to content

Commit 802e06f

Browse files
authored
new lint: unconditionally_sealed_trait_became_pub_api_sealed (#1167)
Resolves #1123
1 parent 0057293 commit 802e06f

File tree

7 files changed

+198
-0
lines changed

7 files changed

+198
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
SemverQuery(
2+
id: "unconditionally_sealed_trait_became_pub_api_sealed",
3+
human_readable_name: "unconditionally sealed trait became public API sealed",
4+
description: "An unconditionally sealed trait has become sealed only at the public API level, allowing all downstream crates to implement it via 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+
unconditionally_sealed @filter(op: "=", value: ["$true"])
16+
17+
importable_path {
18+
path @output @tag
19+
public_api @filter(op: "=", value: ["$true"])
20+
}
21+
}
22+
}
23+
}
24+
current {
25+
item {
26+
... on Trait {
27+
visibility_limit @filter(op: "=", value: ["$public"])
28+
public_api_sealed @filter(op: "=", value: ["$true"])
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: "An unconditionally sealed trait has become sealed only at the public API level, allowing all downstream crates to implement it via non-public API, bypassing SemVer guarantees.",
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
@@ -1356,6 +1356,7 @@ add_lints!(
13561356
type_mismatched_generic_lifetimes,
13571357
type_requires_more_const_generic_params,
13581358
type_requires_more_generic_type_params,
1359+
unconditionally_sealed_trait_became_pub_api_sealed,
13591360
unconditionally_sealed_trait_became_unsealed,
13601361
union_field_added_with_all_pub_fields,
13611362
union_field_added_with_non_pub_fields,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
publish = false
3+
name = "unconditionally_sealed_trait_became_pub_api_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 Unconditionally Sealed -> Public API Sealed (Lint should detect these)
2+
pub mod unconditionally_sealed_to_public_api_sealed {
3+
#[doc(hidden)]
4+
pub mod hidden {
5+
pub trait Sealed {}
6+
pub struct Token;
7+
}
8+
9+
pub trait ExtendsTraitInHiddenModule: hidden::Sealed {}
10+
11+
pub trait ReturnsTraitInHiddenModule {
12+
fn method(&self) -> hidden::Token;
13+
}
14+
15+
pub trait AcceptsTraitInHiddenModule {
16+
fn method(&self, token: hidden::Token);
17+
}
18+
19+
pub trait SealedWithWhereSelfBound
20+
where
21+
Self: hidden::Sealed,
22+
{
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
publish = false
3+
name = "unconditionally_sealed_trait_became_pub_api_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 Unconditionally Sealed -> Public API Sealed (Lint should detect these)
2+
pub mod unconditionally_sealed_to_public_api_sealed {
3+
mod hidden {
4+
pub trait Sealed {}
5+
pub struct Token;
6+
}
7+
8+
pub trait ExtendsTraitInHiddenModule: hidden::Sealed {}
9+
10+
pub trait ReturnsTraitInHiddenModule {
11+
fn method(&self) -> hidden::Token;
12+
}
13+
14+
pub trait AcceptsTraitInHiddenModule {
15+
fn method(&self, token: hidden::Token);
16+
}
17+
18+
pub trait SealedWithWhereSelfBound
19+
where
20+
Self: hidden::Sealed,
21+
{
22+
}
23+
}
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/unconditionally_sealed_trait_became_pub_api_sealed/": [
7+
{
8+
"name": String("ExtendsTraitInHiddenModule"),
9+
"path": List([
10+
String("unconditionally_sealed_trait_became_pub_api_sealed"),
11+
String("unconditionally_sealed_to_public_api_sealed"),
12+
String("ExtendsTraitInHiddenModule"),
13+
]),
14+
"span_begin_line": Uint64(9),
15+
"span_end_line": Uint64(9),
16+
"span_filename": String("src/lib.rs"),
17+
"visibility_limit": String("public"),
18+
},
19+
{
20+
"name": String("ReturnsTraitInHiddenModule"),
21+
"path": List([
22+
String("unconditionally_sealed_trait_became_pub_api_sealed"),
23+
String("unconditionally_sealed_to_public_api_sealed"),
24+
String("ReturnsTraitInHiddenModule"),
25+
]),
26+
"span_begin_line": Uint64(11),
27+
"span_end_line": Uint64(13),
28+
"span_filename": String("src/lib.rs"),
29+
"visibility_limit": String("public"),
30+
},
31+
{
32+
"name": String("AcceptsTraitInHiddenModule"),
33+
"path": List([
34+
String("unconditionally_sealed_trait_became_pub_api_sealed"),
35+
String("unconditionally_sealed_to_public_api_sealed"),
36+
String("AcceptsTraitInHiddenModule"),
37+
]),
38+
"span_begin_line": Uint64(15),
39+
"span_end_line": Uint64(17),
40+
"span_filename": String("src/lib.rs"),
41+
"visibility_limit": String("public"),
42+
},
43+
{
44+
"name": String("SealedWithWhereSelfBound"),
45+
"path": List([
46+
String("unconditionally_sealed_trait_became_pub_api_sealed"),
47+
String("unconditionally_sealed_to_public_api_sealed"),
48+
String("SealedWithWhereSelfBound"),
49+
]),
50+
"span_begin_line": Uint64(19),
51+
"span_end_line": Uint64(23),
52+
"span_filename": String("src/lib.rs"),
53+
"visibility_limit": String("public"),
54+
},
55+
],
56+
"./test_crates/unconditionally_sealed_trait_became_unsealed/": [
57+
{
58+
"name": String("ExtendsTraitInHiddenModule"),
59+
"path": List([
60+
String("unconditionally_sealed_trait_became_unsealed"),
61+
String("unconditionally_sealed_to_public_api_sealed"),
62+
String("ExtendsTraitInHiddenModule"),
63+
]),
64+
"span_begin_line": Uint64(35),
65+
"span_end_line": Uint64(35),
66+
"span_filename": String("src/lib.rs"),
67+
"visibility_limit": String("public"),
68+
},
69+
{
70+
"name": String("ReturnsTraitInHiddenModule"),
71+
"path": List([
72+
String("unconditionally_sealed_trait_became_unsealed"),
73+
String("unconditionally_sealed_to_public_api_sealed"),
74+
String("ReturnsTraitInHiddenModule"),
75+
]),
76+
"span_begin_line": Uint64(37),
77+
"span_end_line": Uint64(39),
78+
"span_filename": String("src/lib.rs"),
79+
"visibility_limit": String("public"),
80+
},
81+
],
82+
}

0 commit comments

Comments
 (0)