SemverQuery(
id: "struct_repr_transparent_removed",
human_readable_name: "struct repr(transparent) removed",
description: "A struct is no longer repr(transparent).",
reference: Some(r#"
repr(transparent) was removed from a struct. This can cause its memory layout to change,
breaking FFI use cases.
repr(transparent) is only sometimes part of the ABI. Per the Rustonomicon:
> This can only be used on structs with a single non-zero-sized field
> (there may be additional zero-sized fields). [...]
> This repr is only considered part of the public ABI of a type if either the single field is pub,
> or if its layout is documented in prose. Otherwise, the layout should not be relied upon
> by other crates.
https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent
cargo-semver-checks currently can't check whether a field is zero-sized or not,
and it can't check whether the layout is documented in prose.
However, the most commonly-used kind of zero-sized field is core::marker::PhantomData,
and we can hardcode its detection and proper handling.
To avoid false-positives, this query is restricted to checking only structs that in the baseline:
- are repr(transparent);
- have exactly one field that isn't PhantomData, and
- that one field is public.
"#),
required_update: Major,
// TODO: Change the reference link to point to the cargo semver reference
// once it has a section on repr(transparent).
reference_link: Some("https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent"),
query: r#"
{
CrateDiff {
baseline {
item {
... on Struct {
visibility_limit @filter(op: "=", value: ["$public"]) @output
attribute {
old_attr: raw_attribute @output
content {
base @filter(op: "=", value: ["$repr"])
argument {
base @filter(op: "=", value: ["$transparent"])
}
}
}
# To avoid false-positives (as described above), we check two things:
# - this struct has a total of one field that isn't PhantomData
# - that one field happens to be public
field @fold @transform(op: "count") @filter(op: "=", value: ["$one"]) {
raw_type @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) {
... on ResolvedPathType {
name @filter(op: "one_of", value: ["$phantom_data"])
}
}
}
# This @fold has an extra predicate checking that the field is public,
# and is otherwise identical to the @fold above it.
#
# We already know the @fold above it has only one element.
# By construction, every element in this @fold must also
# be in the one above.
#
# Ergo, if this fold *also* has only one element, then the struct
# has exactly one non-PhantomData field, and that field is public.
field @fold @transform(op: "count") @filter(op: "=", value: ["$one"]) {
transparent_field_name: name @output
visibility_limit @filter(op: "=", value: ["$public"])
raw_type @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) {
... on ResolvedPathType {
name @filter(op: "one_of", value: ["$phantom_data"])
}
}
}
importable_path {
path @tag @output
public_api @filter(op: "=", value: ["$true"])
}
}
}
}
current {
item {
... on Struct {
visibility_limit @filter(op: "=", value: ["$public"])
name @output
attribute @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) {
content {
base @filter(op: "=", value: ["$repr"])
argument {
base @filter(op: "=", value: ["$transparent"])
}
}
}
importable_path {
path @filter(op: "=", value: ["%path"])
public_api @filter(op: "=", value: ["$true"])
}
span_: span @optional {
filename @output
begin_line @output
}
}
}
}
}
}"#,
arguments: {
"public": "public",
"repr": "repr",
"transparent": "transparent",
"phantom_data": ["core::marker::PhantomData", "std::marker::PhantomData"],
"true": true,
"one": 1,
"zero": 0,
},
error_message: "repr(transparent) was removed from a struct whose layout was part of the public ABI. This can cause its memory layout to change, breaking FFI use cases.",
per_result_error_template: Some("struct {{name}} in {{span_filename}}:{{span_begin_line}}"),
)