diff --git a/src/lints/union_missing.ron b/src/lints/union_missing.ron new file mode 100644 index 00000000..85cd04d1 --- /dev/null +++ b/src/lints/union_missing.ron @@ -0,0 +1,51 @@ +SemverQuery( + id: "union_missing", + human_readable_name: "pub union removed or renamed", + description: "A union can no longer be imported by its prior path.", + required_update: Major, + reference_link: Some("https://doc.rust-lang.org/cargo/reference/semver.html#item-remove"), + query: r#" + { + CrateDiff { + baseline { + item { + ... on Union { + visibility_limit @filter(op: "=", value: ["$public"]) @output + name @output + + importable_path { + path @output @tag + public_api @filter(op: "=", value: ["$true"]) + } + + span_: span @optional { + filename @output + begin_line @output + } + } + } + } + current @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { + item { + # We coerce to `ImplOwner` here, not `Union`, because it's sometimes possible + # to change a union into an enum or struct without causing a breaking change. + # More info: https://github.com/obi1kenobi/cargo-semver-checks/issues/297 + ... on ImplOwner { + visibility_limit @filter(op: "=", value: ["$public"]) + + importable_path { + path @filter(op: "=", value: ["%path"]) + } + } + } + } + } + }"#, + arguments: { + "public": "public", + "zero": 0, + "true": true, + }, + error_message: "A publicly-visible union cannot be imported by its prior path. A `pub use` may have been removed, or the union itself may have been renamed or removed entirely.", + per_result_error_template: Some("union {{join \"::\" path}}, previously in file {{span_filename}}:{{span_begin_line}}"), +) diff --git a/src/query.rs b/src/query.rs index f73b4e70..74d6d086 100644 --- a/src/query.rs +++ b/src/query.rs @@ -476,6 +476,7 @@ macro_rules! add_lints { } add_lints!( + union_missing, function_export_name_changed, union_field_missing, pub_static_now_doc_hidden, diff --git a/test_crates/union_field_missing/new/src/lib.rs b/test_crates/union_field_missing/new/src/lib.rs index 20d1b397..2f7ce269 100644 --- a/test_crates/union_field_missing/new/src/lib.rs +++ b/test_crates/union_field_missing/new/src/lib.rs @@ -3,20 +3,24 @@ union PrivateUnion { f3: f32, } - // pub union with private fields shouldn't cause any breaking changes pub union PubUnionPrivateField { f3: f32, } - // pub union with pub fields renamed should cause breaking changes -pub union PubUnionPubFieldRenamed{ +pub union PubUnionPubFieldRenamed { pub f1: u32, pub f3: f32, } // pub union with pub fields removed should cause breaking changes -pub union PubUnionPubFieldRemoved{ +pub union PubUnionPubFieldRemoved { + pub f1: u32, +} + +// this should only trigger the union_missing lint, not union_field_missing +union PubUnionBecomesPrivateAndPubFieldRemoved { pub f1: u32, + f2: f32, } diff --git a/test_crates/union_field_missing/old/src/lib.rs b/test_crates/union_field_missing/old/src/lib.rs index 4e361ae7..524bc7fb 100644 --- a/test_crates/union_field_missing/old/src/lib.rs +++ b/test_crates/union_field_missing/old/src/lib.rs @@ -4,22 +4,26 @@ union PrivateUnion { f2: f32, } - // pub union with private fields shouldn't cause any breaking changes pub union PubUnionPrivateField { f1: u32, f2: f32, } - // pub union with pub fields renamed should cause breaking changes -pub union PubUnionPubFieldRenamed{ +pub union PubUnionPubFieldRenamed { pub f1: u32, pub f2: f32, } // pub union with pub fields renamed should cause breaking changes -pub union PubUnionPubFieldRemoved{ +pub union PubUnionPubFieldRemoved { + pub f1: u32, + pub f2: f32, +} + +// this should only trigger the union_missing lint, not union_field_missing +pub union PubUnionBecomesPrivateAndPubFieldRemoved { pub f1: u32, pub f2: f32, } diff --git a/test_crates/union_missing/new/Cargo.toml b/test_crates/union_missing/new/Cargo.toml new file mode 100644 index 00000000..6589b7b0 --- /dev/null +++ b/test_crates/union_missing/new/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "union_missing" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/union_missing/new/src/lib.rs b/test_crates/union_missing/new/src/lib.rs new file mode 100644 index 00000000..30ea7fbb --- /dev/null +++ b/test_crates/union_missing/new/src/lib.rs @@ -0,0 +1,14 @@ +union PubUnionBecomesPrivate { + f1: i32, +} + +pub mod public_mod { + pub union PubUnion { + f1: i32, + } +} + +// should not trigger union_missing so long as path remains importable +pub struct PubUnionBecomesStruct { + f1: i32, +} diff --git a/test_crates/union_missing/old/Cargo.toml b/test_crates/union_missing/old/Cargo.toml new file mode 100644 index 00000000..6589b7b0 --- /dev/null +++ b/test_crates/union_missing/old/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "union_missing" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/union_missing/old/src/lib.rs b/test_crates/union_missing/old/src/lib.rs new file mode 100644 index 00000000..1b574ce6 --- /dev/null +++ b/test_crates/union_missing/old/src/lib.rs @@ -0,0 +1,36 @@ +pub union PubUnionRemoved { + f1: i32, +} + +pub union PubUnionBecomesPrivate { + f1: i32, +} + +pub mod public_mod { + pub union PubUnion { + f1: i32, + } +} + +pub use public_mod::PubUnion as PubUseUnionRemoved; + +// should not trigger union_missing so long as path remains importable +pub union PubUnionBecomesStruct { + f1: i32, +} + +// the following unions should not trigger lints when removed +union PrivateUnion { + f1: i32, +} + +mod private_mod { + pub union PrivatePathPubUnion { + f1: i32, + } +} + +#[doc(hidden)] +pub union DocHiddenUnion { + f1: i32, +} diff --git a/test_outputs/union_field_missing.output.ron b/test_outputs/union_field_missing.output.ron index 6b988a87..7c132ce1 100644 --- a/test_outputs/union_field_missing.output.ron +++ b/test_outputs/union_field_missing.output.ron @@ -6,7 +6,7 @@ String("union_field_missing"), String("PubUnionPubFieldRenamed"), ]), - "span_begin_line": Uint64(18), + "span_begin_line": Uint64(16), "span_filename": String("src/lib.rs"), "union_name": String("PubUnionPubFieldRenamed"), }, @@ -16,7 +16,7 @@ String("union_field_missing"), String("PubUnionPubFieldRemoved"), ]), - "span_begin_line": Uint64(24), + "span_begin_line": Uint64(22), "span_filename": String("src/lib.rs"), "union_name": String("PubUnionPubFieldRemoved"), }, diff --git a/test_outputs/union_missing.output.ron b/test_outputs/union_missing.output.ron new file mode 100644 index 00000000..a118e12d --- /dev/null +++ b/test_outputs/union_missing.output.ron @@ -0,0 +1,68 @@ +{ + "./test_crates/repr_packed_added_removed/": [ + { + "name": String("UnionBecomesPackedAndPrivate"), + "path": List([ + String("repr_packed_added_removed"), + String("UnionBecomesPackedAndPrivate"), + ]), + "span_begin_line": Uint64(41), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("UnionBecomesUnpackedAndPrivate"), + "path": List([ + String("repr_packed_added_removed"), + String("UnionBecomesUnpackedAndPrivate"), + ]), + "span_begin_line": Uint64(47), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/union_field_missing/": [ + { + "name": String("PubUnionBecomesPrivateAndPubFieldRemoved"), + "path": List([ + String("union_field_missing"), + String("PubUnionBecomesPrivateAndPubFieldRemoved"), + ]), + "span_begin_line": Uint64(26), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/union_missing/": [ + { + "name": String("PubUnionRemoved"), + "path": List([ + String("union_missing"), + String("PubUnionRemoved"), + ]), + "span_begin_line": Uint64(1), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("PubUnionBecomesPrivate"), + "path": List([ + String("union_missing"), + String("PubUnionBecomesPrivate"), + ]), + "span_begin_line": Uint64(5), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("PubUnion"), + "path": List([ + String("union_missing"), + String("PubUseUnionRemoved"), + ]), + "span_begin_line": Uint64(10), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], +}