From c738aa31cd030354e27a3ce68080f66489de1617 Mon Sep 17 00:00:00 2001 From: Predrag Gruevski Date: Wed, 11 Dec 2024 05:41:15 +0000 Subject: [PATCH] Add `type_mismatched_generic_lifetimes` lint. It catches structs, enums, and unions that have gained or lost generic lifetime parameters. Using such a type with a mismatching number of generic lifetime parameters is a compile error. --- .../type_mismatched_generic_lifetimes.ron | 71 ++++++++++++ src/query.rs | 1 + .../new/Cargo.toml | 7 ++ .../new/src/lib.rs | 30 +++++ .../old/Cargo.toml | 7 ++ .../old/src/lib.rs | 29 +++++ .../type_mismatched_generic_lifetimes.snap | 109 ++++++++++++++++++ 7 files changed, 254 insertions(+) create mode 100644 src/lints/type_mismatched_generic_lifetimes.ron create mode 100644 test_crates/type_mismatched_generic_lifetimes/new/Cargo.toml create mode 100644 test_crates/type_mismatched_generic_lifetimes/new/src/lib.rs create mode 100644 test_crates/type_mismatched_generic_lifetimes/old/Cargo.toml create mode 100644 test_crates/type_mismatched_generic_lifetimes/old/src/lib.rs create mode 100644 test_outputs/query_execution/type_mismatched_generic_lifetimes.snap diff --git a/src/lints/type_mismatched_generic_lifetimes.ron b/src/lints/type_mismatched_generic_lifetimes.ron new file mode 100644 index 00000000..3bbc48b7 --- /dev/null +++ b/src/lints/type_mismatched_generic_lifetimes.ron @@ -0,0 +1,71 @@ +SemverQuery( + id: "type_mismatched_generic_lifetimes", + human_readable_name: "type now takes a different number of generic lifetimes", + description: "A type now takes a different number of generic lifetime parameters, breaking uses of that type.", + required_update: Major, + lint_level: Deny, + // The cargo SemVer reference only has entries for generic *type* parameters. + // There's no passable place to link to when it comes to specifically lifetime parameters. + reference_link: None, + query: r#" + { + CrateDiff { + baseline { + item { + ... on ImplOwner { + visibility_limit @filter(op: "=", value: ["$public"]) + name @output + owner_type: __typename @tag @output + + importable_path { + path @tag @output + public_api @filter(op: "=", value: ["$true"]) + } + + generic_parameter @fold + @transform(op: "count") + @tag(name: "old_lifetimes_count") + @output(name: "old_lifetimes_count") { + ... on GenericLifetimeParameter { + old_lifetimes: name @output + } + } + } + } + } + current { + item { + ... on ImplOwner { + visibility_limit @filter(op: "=", value: ["$public"]) @output + __typename @filter(op: "=", value: ["%owner_type"]) + + importable_path { + path @filter(op: "=", value: ["%path"]) + public_api @filter(op: "=", value: ["$true"]) + } + + generic_parameter @fold + @transform(op: "count") + @filter(op: "!=", value: ["%old_lifetimes_count"]) + @output(name: "new_lifetimes_count") { + ... on GenericLifetimeParameter { + new_lifetimes: name @output + } + } + + span_: span @optional { + filename @output + begin_line @output + } + } + } + } + } + }"#, + arguments: { + "public": "public", + "true": true, + }, + error_message: "A type now takes a different number of generic lifetime parameters. Uses of this type that name the previous number of parameters will be broken.", + per_result_error_template: Some("{{owner_type}} {{name}} ({{old_lifetimes_count}} -> {{new_lifetimes_count}} lifetime params) in {{span_filename}}:{{span_begin_line}}"), +) diff --git a/src/query.rs b/src/query.rs index 0e5c2601..adda74c0 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1147,6 +1147,7 @@ add_lints!( trait_unsafe_removed, tuple_struct_to_plain_struct, type_marked_deprecated, + type_mismatched_generic_lifetimes, union_field_missing, union_missing, union_must_use_added, diff --git a/test_crates/type_mismatched_generic_lifetimes/new/Cargo.toml b/test_crates/type_mismatched_generic_lifetimes/new/Cargo.toml new file mode 100644 index 00000000..1ca71526 --- /dev/null +++ b/test_crates/type_mismatched_generic_lifetimes/new/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "type_mismatched_generic_lifetimes" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/type_mismatched_generic_lifetimes/new/src/lib.rs b/test_crates/type_mismatched_generic_lifetimes/new/src/lib.rs new file mode 100644 index 00000000..472fce89 --- /dev/null +++ b/test_crates/type_mismatched_generic_lifetimes/new/src/lib.rs @@ -0,0 +1,30 @@ +#![allow(dead_code)] + +pub struct Example<'a, 'b, 'c>(&'a i64, &'b i64, &'c i64); + +pub enum Either<'a, 'b, 'c> { + Left(&'a i64), + Right(&'b u64), + Mid(&'c isize), +} + +pub union OneOrTheOther<'a, 'b, 'c> { + left: &'a i64, + right: &'b u64, + center: &'c isize, +} + +// Renaming lifetimes while leaving them semantically identical is not breaking. +// AFAIK there's no way to refer to a lifetime's defined name while using the type. +pub struct RenamedLifetimes<'c, 'a>(&'c i64, &'a i64); + +// Attempting to specify lifetime parameters that don't exist is breaking. +// This should be reported. +pub struct NotGenericAnymore(&'static str); + +// This is breaking too and should be reported. The witness is just wrapping the type +// in another type, at which point all generics must be specified. +/// ```rust,compile_fail +/// struct Witness(type_mismatched_generic_lifetimes::BecameGeneric); +/// ``` +pub struct BecameGeneric<'a>(String, &'a str); diff --git a/test_crates/type_mismatched_generic_lifetimes/old/Cargo.toml b/test_crates/type_mismatched_generic_lifetimes/old/Cargo.toml new file mode 100644 index 00000000..1ca71526 --- /dev/null +++ b/test_crates/type_mismatched_generic_lifetimes/old/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "type_mismatched_generic_lifetimes" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/type_mismatched_generic_lifetimes/old/src/lib.rs b/test_crates/type_mismatched_generic_lifetimes/old/src/lib.rs new file mode 100644 index 00000000..82c8f04b --- /dev/null +++ b/test_crates/type_mismatched_generic_lifetimes/old/src/lib.rs @@ -0,0 +1,29 @@ +#![allow(dead_code)] + +pub struct Example<'a, 'b>(&'a i64, &'b i64); + +#[non_exhaustive] +pub enum Either<'a, 'b> { + Left(&'a i64), + Right(&'b u64), +} + +pub union OneOrTheOther<'a, 'b> { + left: &'a i64, + right: &'b u64, +} + +// Renaming lifetimes while leaving them semantically identical is not breaking. +// AFAIK there's no way to refer to a lifetime's defined name while using the type. +pub struct RenamedLifetimes<'a, 'b>(&'a i64, &'b i64); + +// Attempting to specify lifetime parameters that don't exist is breaking. +// This should be reported. +pub struct NotGenericAnymore<'a>(&'a str); + +// This is breaking too and should be reported. The witness is just wrapping the type +// in another type, at which point all generics must be specified. +/// ```rust +/// struct Witness(type_mismatched_generic_lifetimes::BecameGeneric); +/// ``` +pub struct BecameGeneric(String); diff --git a/test_outputs/query_execution/type_mismatched_generic_lifetimes.snap b/test_outputs/query_execution/type_mismatched_generic_lifetimes.snap new file mode 100644 index 00000000..012e977e --- /dev/null +++ b/test_outputs/query_execution/type_mismatched_generic_lifetimes.snap @@ -0,0 +1,109 @@ +--- +source: src/query.rs +expression: "&query_execution_results" +snapshot_kind: text +--- +{ + "./test_crates/type_mismatched_generic_lifetimes/": [ + { + "name": String("Example"), + "new_lifetimes": List([ + String("\'a"), + String("\'b"), + String("\'c"), + ]), + "new_lifetimes_count": Uint64(3), + "old_lifetimes": List([ + String("\'a"), + String("\'b"), + ]), + "old_lifetimes_count": Uint64(2), + "owner_type": String("Struct"), + "path": List([ + String("type_mismatched_generic_lifetimes"), + String("Example"), + ]), + "span_begin_line": Uint64(3), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("Either"), + "new_lifetimes": List([ + String("\'a"), + String("\'b"), + String("\'c"), + ]), + "new_lifetimes_count": Uint64(3), + "old_lifetimes": List([ + String("\'a"), + String("\'b"), + ]), + "old_lifetimes_count": Uint64(2), + "owner_type": String("Enum"), + "path": List([ + String("type_mismatched_generic_lifetimes"), + String("Either"), + ]), + "span_begin_line": Uint64(5), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("OneOrTheOther"), + "new_lifetimes": List([ + String("\'a"), + String("\'b"), + String("\'c"), + ]), + "new_lifetimes_count": Uint64(3), + "old_lifetimes": List([ + String("\'a"), + String("\'b"), + ]), + "old_lifetimes_count": Uint64(2), + "owner_type": String("Union"), + "path": List([ + String("type_mismatched_generic_lifetimes"), + String("OneOrTheOther"), + ]), + "span_begin_line": Uint64(11), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("NotGenericAnymore"), + "new_lifetimes": List([]), + "new_lifetimes_count": Uint64(0), + "old_lifetimes": List([ + String("\'a"), + ]), + "old_lifetimes_count": Uint64(1), + "owner_type": String("Struct"), + "path": List([ + String("type_mismatched_generic_lifetimes"), + String("NotGenericAnymore"), + ]), + "span_begin_line": Uint64(23), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("BecameGeneric"), + "new_lifetimes": List([ + String("\'a"), + ]), + "new_lifetimes_count": Uint64(1), + "old_lifetimes": List([]), + "old_lifetimes_count": Uint64(0), + "owner_type": String("Struct"), + "path": List([ + String("type_mismatched_generic_lifetimes"), + String("BecameGeneric"), + ]), + "span_begin_line": Uint64(30), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], +}