Skip to content

Commit

Permalink
Add type_mismatched_generic_lifetimes lint. (#1039)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
obi1kenobi authored Dec 11, 2024
1 parent ea70a4c commit 649ae53
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 0 deletions.
71 changes: 71 additions & 0 deletions src/lints/type_mismatched_generic_lifetimes.ron
Original file line number Diff line number Diff line change
@@ -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}}"),
)
1 change: 1 addition & 0 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions test_crates/type_mismatched_generic_lifetimes/new/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
publish = false
name = "type_mismatched_generic_lifetimes"
version = "0.1.0"
edition = "2021"

[dependencies]
30 changes: 30 additions & 0 deletions test_crates/type_mismatched_generic_lifetimes/new/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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);
7 changes: 7 additions & 0 deletions test_crates/type_mismatched_generic_lifetimes/old/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
publish = false
name = "type_mismatched_generic_lifetimes"
version = "0.1.0"
edition = "2021"

[dependencies]
29 changes: 29 additions & 0 deletions test_crates/type_mismatched_generic_lifetimes/old/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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);
109 changes: 109 additions & 0 deletions test_outputs/query_execution/type_mismatched_generic_lifetimes.snap
Original file line number Diff line number Diff line change
@@ -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"),
},
],
}

0 comments on commit 649ae53

Please sign in to comment.