Skip to content

Commit

Permalink
intial draft of struct_without_pub_fields_changed
Browse files Browse the repository at this point in the history
  • Loading branch information
CommanderStorm committed Oct 5, 2024
1 parent 868b095 commit 5b5ab32
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 2 deletions.
77 changes: 77 additions & 0 deletions src/lints/struct_with_no_pub_fields_changed.ron
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
SemverQuery(
id: "struct_with_no_pub_fields_changed",
human_readable_name: "public API struct with no public fields is no longer a struct",
description: "A struct without pub fields was converted into an enum or union, breaking pattern matching.",
required_update: Major,
lint_level: Deny,
reference_link: Some("https://internals.rust-lang.org/t/rest-patterns-foo-should-match-non-struct-types/21607"),
reference: Some(
r#"\
Even if a structs does not expose pub fields, pattern matching like `matches!(value, Example { .. })` is accessible outside their defining crate. \
Changing such a struct to make it an enum or union will break this pattern match, as their syntax is different.
More info: https://github.com/obi1kenobi/cargo-semver-checks/issues/954
"#
),
query: r#"
{
CrateDiff {
baseline {
item {
... on Struct {
struct_typename: __typename @tag @output
visibility_limit @filter(op: "=", value: ["$public"]) @output
struct_type @output
# Ensure the struct does not have pub fields to prevent overlap with struct_with_pub_fields_changed
field @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) {
visibility_limit @filter(op: "=", value: ["$public"])
public_api_eligible @filter(op: "=", value: ["$true"])
}
# Ensure the struct does have non-pub fields to prevent overlap with constructible_struct_changed_type
field @fold @transform(op: "count") @filter(op: ">", value: ["$zero"]) {
visibility_limit @filter(op: "!=", value: ["$public"])
}
importable_path {
path @output @tag
public_api @filter(op: "=", value: ["$true"])
}
}
}
}
current {
item {
... on ImplOwner {
current_typename: __typename @filter(op: "!=", value: ["%struct_typename"])
@output
visibility_limit @filter(op: "=", value: ["$public"])
name @output
importable_path {
path @filter(op: "=", value: ["%path"])
public_api @filter(op: "=", value: ["$true"])
}
span_: span @optional {
filename @output
begin_line @output
}
}
}
}
}
}"#,
arguments: {
"public": "public",
"true": true,
"zero": 0,
},
error_message: "A struct without pub fields became an enum or union, breaking pattern matching.",
per_result_error_template: Some("struct {{join \"::\" path}} became {{lowercase current_typename}} in file {{span_filename}}:{{span_begin_line}}"),
witness: (
hint_template: r#"matches!(value, {{join "::" path}}{...});"#,
witness_template: r#"matches!(value, {{join "::" path}}::Variant);"#,
),
)
4 changes: 2 additions & 2 deletions src/lints/struct_with_pub_fields_changed_type.ron
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
SemverQuery(
id: "struct_with_pub_fields_changed_type",
human_readable_name: "struct with pub fields became an enum or union",
description: "A struct was converted into an enum or union, breaking accesses to its fields.",
description: "A struct with pub fields was converted into an enum or union, breaking accesses to its public fields.",
required_update: Major,
lint_level: Deny,
reference_link: Some("https://github.com/obi1kenobi/cargo-semver-checks/issues/297#issuecomment-1399099659"),
Expand Down Expand Up @@ -64,6 +64,6 @@ More info: https://github.com/obi1kenobi/cargo-semver-checks/issues/297
"true": true,
"zero": 0,
},
error_message: "A struct became an enum or union, breaking accesses to its public fields.",
error_message: "A struct with pub fields became an enum or union, breaking accesses to its public fields.",
per_result_error_template: Some("struct {{join \"::\" path}} became {{lowercase current_typename}} in file {{span_filename}}:{{span_begin_line}}"),
)
1 change: 1 addition & 0 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,7 @@ add_lints!(
struct_pub_field_now_doc_hidden,
struct_repr_transparent_removed,
struct_with_pub_fields_changed_type,
struct_with_no_pub_fields_changed,
trait_added_supertrait,
trait_associated_const_added,
trait_associated_const_default_removed,
Expand Down
7 changes: 7 additions & 0 deletions test_crates/struct_with_no_pub_fields_changed/new/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
publish = false
name = "struct_with_no_pub_fields_changed"
version = "0.1.0"
edition = "2021"

[dependencies]
46 changes: 46 additions & 0 deletions test_crates/struct_with_no_pub_fields_changed/new/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
pub enum PubStructChangedEnum {
Foo,
}

pub union PubStructChangedUnion {
foo: usize
}

/// This struct should not be reported by the `struct_with_no_pub_fields_changed` rule:
/// This is `struct_missing` instead
pub type PubStructChangedType = u8;

/// This struct should not be reported by the `struct_with_no_pub_fields_changed` rule:
/// This is `constructible_struct_changed_type` instead
pub enum PubStructChangedNoFieldsEnum {}

/// This struct should not be reported by the `struct_pub_field_missing` rule:
/// since the struct is not pub in the first place, changing it does not change the API
enum NonPubStructChangedEnum {
Foo,
}

/// This struct should not be reported by the `struct_pub_field_missing` rule:
/// since the struct is not pub in the first place, changing it does not change the API
union NonPubStructChangedUnion {
foo: usize
}

type NonPubStructChangedType = u8;

mod not_pub_visible {
/// This struct should not be reported by the `struct_pub_field_missing` rule:
/// since the struct is not in a pub module, changing it does not change the API
pub enum NonReachabgeStructChangedEnum {
Foo,
}
/// This struct should not be reported by the `struct_pub_field_missing` rule:
/// since the struct is not in a pub module, changing it does not change the API
pub union NonReachabgeStructChangedUnion {
foo: usize
}
/// This struct should not be reported by the `struct_pub_field_missing` rule:
/// since the struct is not in a pub module, changing it does not change the API
pub type NonReachabgeStructChangedType = u8;
}

7 changes: 7 additions & 0 deletions test_crates/struct_with_no_pub_fields_changed/old/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
publish = false
name = "struct_with_no_pub_fields_changed"
version = "0.1.0"
edition = "2021"

[dependencies]
48 changes: 48 additions & 0 deletions test_crates/struct_with_no_pub_fields_changed/old/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
pub struct PubStructChangedEnum {
foo: usize,
}

pub struct PubStructChangedUnion {
foo: usize,
}

/// This struct should not be reported by the `struct_with_no_pub_fields_changed` rule:
/// This is `struct_missing` instead
pub struct PubStructChangedType(u8);

/// This struct should not be reported by the `struct_with_no_pub_fields_changed` rule:
/// This is `constructible_struct_changed_type` instead
pub struct PubStructChangedNoFieldsEnum {}

/// This struct should not be reported by the `struct_with_no_pub_fields_changed` rule:
/// since the struct is not pub in the first place, changing it does not change the API
struct NonPubStructChangedEnum {
foo: usize,
}

/// This struct should not be reported by the `struct_with_no_pub_fields_changed` rule:
/// since the struct is not pub in the first place, changing it does not change the API
struct NonPubStructChangedUnion {
foo: usize,
}

/// This struct should not be reported by the `struct_with_no_pub_fields_changed` rule:
/// since the struct is not pub in the first place, changing it does not change the API
struct NonPubStructChangedType(u8);

mod not_pub_visible {
/// This struct should not be reported by the `struct_with_no_pub_fields_changed` rule:
/// since the struct is not in a pub module, changing it does not change the API
pub struct NonReachabgeStructChangedEnum {
foo: usize,
}
/// This struct should not be reported by the `struct_with_no_pub_fields_changed` rule:
/// since the struct is not in a pub module, changing it does not change the API
pub struct NonReachabgeStructChangedUnion {
foo: usize,
}
/// This struct should not be reported by the `struct_pub_field_missing` rule:
/// since the struct is not in a pub module, changing it does not change the API
pub struct NonReachabgeStructChangedType(u8);
}

Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,19 @@ expression: "&query_execution_results"
"visibility_limit": String("public"),
},
],
"./test_crates/struct_with_no_pub_fields_changed/": [
{
"current_typename": String("Enum"),
"name": String("PubStructChangedNoFieldsEnum"),
"path": List([
String("struct_with_no_pub_fields_changed"),
String("PubStructChangedNoFieldsEnum"),
]),
"span_begin_line": Uint64(15),
"span_filename": String("src/lib.rs"),
"struct_type": String("plain"),
"struct_typename": String("Struct"),
"visibility_limit": String("public"),
},
],
}
13 changes: 13 additions & 0 deletions test_outputs/query_execution/struct_missing.snap
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ expression: "&query_execution_results"
"visibility_limit": String("public"),
},
],
"./test_crates/struct_with_no_pub_fields_changed/": [
{
"name": String("PubStructChangedType"),
"path": List([
String("struct_with_no_pub_fields_changed"),
String("PubStructChangedType"),
]),
"span_begin_line": Uint64(11),
"span_filename": String("src/lib.rs"),
"struct_type": String("tuple"),
"visibility_limit": String("public"),
},
],
"./test_crates/switch_to_reexport_as_underscore/": [
{
"name": String("Struct"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
source: src/query.rs
expression: "&query_execution_results"
---
{
"./test_crates/inherent_method_must_use_added/": [
{
"current_typename": String("Enum"),
"name": String("StructToEnumWithMustUseMethods"),
"path": List([
String("inherent_method_must_use_added"),
String("item_type_changed_inherent_method_must_use_added"),
String("StructToEnumWithMustUseMethods"),
]),
"span_begin_line": Uint64(68),
"span_filename": String("src/item_type_changed_inherent_method_must_use_added.rs"),
"struct_type": String("plain"),
"struct_typename": String("Struct"),
"visibility_limit": String("public"),
},
{
"current_typename": String("Union"),
"name": String("StructToUnionWithMustUseMethods"),
"path": List([
String("inherent_method_must_use_added"),
String("item_type_changed_inherent_method_must_use_added"),
String("StructToUnionWithMustUseMethods"),
]),
"span_begin_line": Uint64(99),
"span_filename": String("src/item_type_changed_inherent_method_must_use_added.rs"),
"struct_type": String("plain"),
"struct_typename": String("Struct"),
"visibility_limit": String("public"),
},
],
"./test_crates/struct_becomes_enum/": [
{
"current_typename": String("Enum"),
"name": String("TupleToEnum"),
"path": List([
String("struct_becomes_enum"),
String("TupleToEnum"),
]),
"span_begin_line": Uint64(45),
"span_filename": String("src/lib.rs"),
"struct_type": String("tuple"),
"struct_typename": String("Struct"),
"visibility_limit": String("public"),
},
{
"current_typename": String("Enum"),
"name": String("StructToEnum"),
"path": List([
String("struct_becomes_enum"),
String("StructToEnum"),
]),
"span_begin_line": Uint64(61),
"span_filename": String("src/lib.rs"),
"struct_type": String("plain"),
"struct_typename": String("Struct"),
"visibility_limit": String("public"),
},
],
"./test_crates/struct_with_no_pub_fields_changed/": [
{
"current_typename": String("Enum"),
"name": String("PubStructChangedEnum"),
"path": List([
String("struct_with_no_pub_fields_changed"),
String("PubStructChangedEnum"),
]),
"span_begin_line": Uint64(1),
"span_filename": String("src/lib.rs"),
"struct_type": String("plain"),
"struct_typename": String("Struct"),
"visibility_limit": String("public"),
},
{
"current_typename": String("Union"),
"name": String("PubStructChangedUnion"),
"path": List([
String("struct_with_no_pub_fields_changed"),
String("PubStructChangedUnion"),
]),
"span_begin_line": Uint64(5),
"span_filename": String("src/lib.rs"),
"struct_type": String("plain"),
"struct_typename": String("Struct"),
"visibility_limit": String("public"),
},
],
}
34 changes: 34 additions & 0 deletions test_outputs/witnesses/struct_with_no_pub_fields_changed.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
source: src/query.rs
description: "Lint `struct_with_no_pub_fields_changed` did not have the expected witness output.\nSee https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md#testing-witnesses\nfor more information."
expression: "&actual_witnesses"
---
[["./test_crates/inherent_method_must_use_added/"]]
filename = 'src/item_type_changed_inherent_method_must_use_added.rs'
begin_line = 68
hint = 'matches!(value, inherent_method_must_use_added::item_type_changed_inherent_method_must_use_added::StructToEnumWithMustUseMethods{...});'

[["./test_crates/inherent_method_must_use_added/"]]
filename = 'src/item_type_changed_inherent_method_must_use_added.rs'
begin_line = 99
hint = 'matches!(value, inherent_method_must_use_added::item_type_changed_inherent_method_must_use_added::StructToUnionWithMustUseMethods{...});'

[["./test_crates/struct_becomes_enum/"]]
filename = 'src/lib.rs'
begin_line = 45
hint = 'matches!(value, struct_becomes_enum::TupleToEnum{...});'

[["./test_crates/struct_becomes_enum/"]]
filename = 'src/lib.rs'
begin_line = 61
hint = 'matches!(value, struct_becomes_enum::StructToEnum{...});'

[["./test_crates/struct_with_no_pub_fields_changed/"]]
filename = 'src/lib.rs'
begin_line = 1
hint = 'matches!(value, struct_with_no_pub_fields_changed::PubStructChangedEnum{...});'

[["./test_crates/struct_with_no_pub_fields_changed/"]]
filename = 'src/lib.rs'
begin_line = 5
hint = 'matches!(value, struct_with_no_pub_fields_changed::PubStructChangedUnion{...});'

0 comments on commit 5b5ab32

Please sign in to comment.