diff --git a/src/lints/trait_added_supertrait.ron b/src/lints/trait_added_supertrait.ron new file mode 100644 index 00000000..e842809a --- /dev/null +++ b/src/lints/trait_added_supertrait.ron @@ -0,0 +1,58 @@ +SemverQuery( + id: "trait_added_supertrait", + human_readable_name: "non-sealed trait added new supertraits", + description: "A non-sealed trait added one or more supertraits, which breaks downstream implementations of the trait", + required_update: Major, + lint_level: Deny, + reference_link: Some("https://doc.rust-lang.org/cargo/reference/semver.html#generic-bounds-tighten"), + query: r#" + { + CrateDiff { + current { + item { + ... on Trait { + visibility_limit @filter(op: "=", value: ["$public"]) + + importable_path { + path @output @tag + public_api @filter(op: "=", value: ["$true"]) + } + + supertrait { + supertrait: name @output @tag + } + + span_: span @optional { + filename @output + begin_line @output + } + } + } + } + baseline { + item { + ... on Trait { + visibility_limit @filter(op: "=", value: ["$public"]) @output + sealed @filter(op: "!=", value: ["$true"]) + + importable_path { + path @filter(op: "=", value: ["%path"]) + public_api @filter(op: "=", value: ["$true"]) + } + + supertrait @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { + name @filter(op: "=", value: ["%supertrait"]) + } + } + } + } + } + }"#, + arguments: { + "public": "public", + "true": true, + "zero": 0, + }, + error_message: "A non-sealed trait added one or more supertraits, which breaks downstream implementations of the trait", + per_result_error_template: Some("trait {{join \"::\" path}} gained {{supertrait}} in file {{span_filename}}:{{span_begin_line}}"), +) diff --git a/src/query.rs b/src/query.rs index e9c23923..223d4abb 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1077,6 +1077,7 @@ add_lints!( struct_pub_field_now_doc_hidden, struct_repr_transparent_removed, struct_with_pub_fields_changed_type, + trait_added_supertrait, trait_associated_const_added, trait_associated_const_default_removed, trait_associated_const_now_doc_hidden, diff --git a/test_crates/trait_added_supertrait/new/Cargo.toml b/test_crates/trait_added_supertrait/new/Cargo.toml new file mode 100644 index 00000000..9368536f --- /dev/null +++ b/test_crates/trait_added_supertrait/new/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "trait_added_supertrait" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/trait_added_supertrait/new/src/lib.rs b/test_crates/trait_added_supertrait/new/src/lib.rs new file mode 100644 index 00000000..7f4770d8 --- /dev/null +++ b/test_crates/trait_added_supertrait/new/src/lib.rs @@ -0,0 +1,23 @@ +pub trait TraitOne {} +pub trait TraitTwo {} + +mod sealed { + pub trait Sealed {} +} + +pub trait Unchanged {} +pub trait UnchangedSealed: sealed::Sealed {} + +pub trait WillGainOne: TraitOne {} +pub trait WillGainOneSealed: TraitOne + sealed::Sealed {} + +pub trait WillGainAnotherOne: TraitOne + TraitTwo {} +pub trait WillGainAnotherOneSealed: TraitOne + TraitTwo + sealed::Sealed {} + +pub trait WillGainStdOne: Sync {} +pub trait WillGainStdTwo: core::fmt::Debug {} +pub trait WillGainStdThree: PartialEq { + fn make_me_non_object_safe() -> Self; +} + +pub trait WillChangeStdToCore: core::fmt::Debug {} diff --git a/test_crates/trait_added_supertrait/old/Cargo.toml b/test_crates/trait_added_supertrait/old/Cargo.toml new file mode 100644 index 00000000..9368536f --- /dev/null +++ b/test_crates/trait_added_supertrait/old/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "trait_added_supertrait" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/trait_added_supertrait/old/src/lib.rs b/test_crates/trait_added_supertrait/old/src/lib.rs new file mode 100644 index 00000000..c41c3171 --- /dev/null +++ b/test_crates/trait_added_supertrait/old/src/lib.rs @@ -0,0 +1,23 @@ +pub trait TraitOne {} +pub trait TraitTwo {} + +mod sealed { + pub trait Sealed {} +} + +pub trait Unchanged {} +pub trait UnchangedSealed: sealed::Sealed {} + +pub trait WillGainOne {} +pub trait WillGainOneSealed: sealed::Sealed {} + +pub trait WillGainAnotherOne: TraitOne {} +pub trait WillGainAnotherOneSealed: TraitOne + sealed::Sealed {} + +pub trait WillGainStdOne {} +pub trait WillGainStdTwo {} +pub trait WillGainStdThree { + fn make_me_non_object_safe() -> Self; +} + +pub trait WillChangeStdToCore: std::fmt::Debug {} diff --git a/test_outputs/trait_added_supertrait.output.ron b/test_outputs/trait_added_supertrait.output.ron new file mode 100644 index 00000000..bc16d62d --- /dev/null +++ b/test_outputs/trait_added_supertrait.output.ron @@ -0,0 +1,102 @@ +{ + "./test_crates/trait_added_supertrait/": [ + { + "path": List([ + String("trait_added_supertrait"), + String("WillGainOne"), + ]), + "span_begin_line": Uint64(11), + "span_filename": String("src/lib.rs"), + "supertrait": String("TraitOne"), + "visibility_limit": String("public"), + }, + { + "path": List([ + String("trait_added_supertrait"), + String("WillGainAnotherOne"), + ]), + "span_begin_line": Uint64(14), + "span_filename": String("src/lib.rs"), + "supertrait": String("TraitTwo"), + "visibility_limit": String("public"), + }, + { + "path": List([ + String("trait_added_supertrait"), + String("WillGainStdOne"), + ]), + "span_begin_line": Uint64(17), + "span_filename": String("src/lib.rs"), + "supertrait": String("Sync"), + "visibility_limit": String("public"), + }, + { + "path": List([ + String("trait_added_supertrait"), + String("WillGainStdTwo"), + ]), + "span_begin_line": Uint64(18), + "span_filename": String("src/lib.rs"), + "supertrait": String("Debug"), + "visibility_limit": String("public"), + }, + { + "path": List([ + String("trait_added_supertrait"), + String("WillGainStdThree"), + ]), + "span_begin_line": Uint64(19), + "span_filename": String("src/lib.rs"), + "supertrait": String("PartialEq"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/trait_associated_const_added/": [ + { + "path": List([ + String("trait_associated_const_added"), + String("WillGainConstWithoutDefaultAndSeal"), + ]), + "span_begin_line": Uint64(16), + "span_filename": String("src/lib.rs"), + "supertrait": String("Sealed"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/trait_associated_type_added/": [ + { + "path": List([ + String("trait_associated_type_added"), + String("WillGainTypeWithoutDefaultAndSeal"), + ]), + "span_begin_line": Uint64(19), + "span_filename": String("src/lib.rs"), + "supertrait": String("Sealed"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/trait_method_added/": [ + { + "path": List([ + String("trait_method_added"), + String("WillGainMethodWithoutDefaultAndSeal"), + ]), + "span_begin_line": Uint64(20), + "span_filename": String("src/lib.rs"), + "supertrait": String("Sealed"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/trait_newly_sealed/": [ + { + "path": List([ + String("trait_newly_sealed"), + String("TraitBecomesSealed"), + ]), + "span_begin_line": Uint64(7), + "span_filename": String("src/lib.rs"), + "supertrait": String("Sealed"), + "visibility_limit": String("public"), + }, + ], +}