Files
linux/rust/macros/for_lt.rs
Danilo Krummrich 1b56056294 rust: types: introduce ForLt base trait for CovariantForLt
Add a new ForLt trait as a base for CovariantForLt:

  - ForLt (non-unsafe): represents a type generic over a lifetime, with
    no covariance guarantee.

  - CovariantForLt (unsafe): becomes a subtrait of ForLt that
    additionally proves the type is covariant over its lifetime
    parameter, providing a safe cast_ref() method.

This split allows non-covariant types (e.g. types behind a Mutex) to
implement ForLt and participate in DevresLt / registration data patterns
that use HRTB closures for sound access, without requiring a covariance
proof that would fail to compile.

Both macros share the UnsafeForLtImpl helper type, distinguished by
a const generic N: ForLt! emits N = 0 (no covariance proof),
CovariantForLt! emits N = 1 (with compile-time covariance proof).

Reviewed-by: Gary Guo <gary@garyguo.net>
Acked-by: Miguel Ojeda <ojeda@kernel.org>
Link: https://patch.msgid.link/20260626183630.2585057-3-dakr@kernel.org
[ Merge ForLt pub use, inline resolve_hrt/ty_static, add intra-doc
  links. - Danilo ]
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
2026-07-11 19:42:03 +02:00

266 lines
8.5 KiB
Rust

// SPDX-License-Identifier: Apache-2.0 OR MIT
use proc_macro2::{
Span,
TokenStream, //
};
use quote::{
format_ident,
quote, //
};
use syn::{
parse::{
Parse,
ParseStream, //
},
visit::Visit,
visit_mut::VisitMut,
Lifetime,
Result,
Token,
Type, //
};
pub(crate) enum HigherRankedType {
Explicit {
_for_token: Token![for],
_lt_token: Token![<],
lifetime: Lifetime,
_gt_token: Token![>],
ty: Type,
},
Implicit {
ty: Type,
},
}
impl Parse for HigherRankedType {
fn parse(input: ParseStream<'_>) -> Result<Self> {
if input.peek(Token![for]) {
Ok(Self::Explicit {
_for_token: input.parse()?,
_lt_token: input.parse()?,
lifetime: input.parse()?,
_gt_token: input.parse()?,
ty: input.parse()?,
})
} else {
Ok(Self::Implicit { ty: input.parse()? })
}
}
}
trait TypeExt {
fn expand_elided_lifetime(&self, explicit_lt: &Lifetime) -> Type;
fn replace_lifetime(&self, src: &Lifetime, dst: &Lifetime) -> Type;
fn has_lifetime(&self, lt: &Lifetime) -> bool;
}
impl TypeExt for Type {
fn expand_elided_lifetime(&self, explicit_lt: &Lifetime) -> Type {
struct ElidedLifetimeExpander<'a>(&'a Lifetime);
impl VisitMut for ElidedLifetimeExpander<'_> {
fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
// Expand explicit `'_`
if lifetime.ident == "_" {
*lifetime = self.0.clone();
}
}
fn visit_type_reference_mut(&mut self, reference: &mut syn::TypeReference) {
syn::visit_mut::visit_type_reference_mut(self, reference);
if reference.lifetime.is_none() {
reference.lifetime = Some(self.0.clone());
}
}
}
let mut ret = self.clone();
ElidedLifetimeExpander(explicit_lt).visit_type_mut(&mut ret);
ret
}
fn replace_lifetime(&self, src: &Lifetime, dst: &Lifetime) -> Type {
struct LifetimeReplacer<'a>(&'a Lifetime, &'a Lifetime);
impl VisitMut for LifetimeReplacer<'_> {
fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
if lifetime.ident == self.0.ident {
*lifetime = self.1.clone();
}
}
}
let mut ret = self.clone();
LifetimeReplacer(src, dst).visit_type_mut(&mut ret);
ret
}
fn has_lifetime(&self, lt: &Lifetime) -> bool {
struct HasLifetime<'a>(&'a Lifetime, bool);
impl Visit<'_> for HasLifetime<'_> {
fn visit_lifetime(&mut self, lifetime: &Lifetime) {
if lifetime.ident == self.0.ident {
self.1 = true;
}
}
// Macro invocations are opaque; conservatively assume they may
// reference the lifetime.
fn visit_macro(&mut self, _: &syn::Macro) {
self.1 = true;
}
}
let mut visitor = HasLifetime(lt, false);
visitor.visit_type(self);
visitor.1
}
}
struct Prover<'a>(&'a Lifetime, Vec<&'a Type>);
impl<'a> Prover<'a> {
/// Prove that `ty` is covariant over `'lt`.
///
/// This also needs to prove that it'll be wellformed for any instance of `'lt`.
/// It can be assumed that `ty` will be wellformed if `'lt` is substituted to `'static`.
fn prove(&mut self, ty: &'a Type) {
match ty {
Type::Paren(ty) => self.prove(&ty.elem),
Type::Group(ty) => self.prove(&ty.elem),
// No lifetime involved
Type::Never(_) => {}
// `[T; N]` and `[T]` is covariant over `T`.
Type::Array(ty) => self.prove(&ty.elem),
Type::Slice(ty) => self.prove(&ty.elem),
Type::Tuple(ty) => {
for elem in &ty.elems {
self.prove(elem);
}
}
// `*const T` is covariant over `T`
Type::Ptr(ty) if ty.const_token.is_some() => self.prove(&ty.elem),
// `&T` is covariant over `T` and lifetime.
//
// Note that if we encounter `&'other_lt T`, then we still need to make sure the type
// is wellformed if `T` involves `&'lt`, so we defer to the compiler.
//
// This is to block cases like `CovariantForLt!(for<'a> &'static &'a u32)`, as the
// presence of the type implies `'a: 'static` but this is unsound.
Type::Reference(ty)
if ty.mutability.is_none() && ty.lifetime.as_ref() == Some(self.0) =>
{
self.prove(&ty.elem)
}
// `&[mut] T` is covariant over lifetime.
// In case we have `&[mut] NoLifetime`, we don't need to do additional checks.
Type::Reference(ty) if !ty.elem.has_lifetime(self.0) => (),
// No mention of lifetime at all, no need to perform compiler check.
ty if !ty.has_lifetime(self.0) => (),
// Otherwise, we need to emit checks so that compiler can determine if the types are
// actually covariant.
ty => self.1.push(ty),
}
}
}
/// Shared implementation for both `ForLt!` and `CovariantForLt!`.
///
/// Both macros run the prover and emit `ProveWf` structs to check well-formedness for all lifetime
/// instances (workaround for <https://github.com/rust-lang/rust/issues/152489>). `CovariantForLt!`
/// additionally emits covariance proof functions and sets `N = 1`.
fn for_lt_inner(input: HigherRankedType, prove_covariance: bool) -> TokenStream {
let (ty, lifetime) = match input {
HigherRankedType::Explicit { lifetime, ty, .. } => (ty, lifetime),
HigherRankedType::Implicit { ty } => {
// If there's no explicit `for<'a>` binder, inject a synthetic `'__elided` lifetime
// and expand elided sites.
let lifetime = Lifetime {
apostrophe: Span::mixed_site(),
ident: format_ident!("__elided", span = Span::mixed_site()),
};
(ty.expand_elided_lifetime(&lifetime), lifetime)
}
};
let mut prover = Prover(&lifetime, Vec::new());
prover.prove(&ty);
let mut proof = Vec::new();
// Emit proofs for every type that requires additional compiler help in proving covariance.
for (idx, required_proof) in prover.1.into_iter().enumerate() {
// Insert a proof that the type is well-formed.
//
// This is intended to workaround a Rust compiler soundness bug related to HRTB.
// https://github.com/rust-lang/rust/issues/152489
//
// This needs to be a struct instead of fn to avoid the implied WF bounds.
let wf_proof_name = format_ident!("ProveWf{idx}");
proof.push(quote!(
struct #wf_proof_name<#lifetime>(
::core::marker::PhantomData<&#lifetime ()>, #required_proof
);
));
// Insert a proof that the type is covariant.
if prove_covariance {
let cov_proof_name = format_ident!("prove_covariant_{idx}");
proof.push(quote!(
fn #cov_proof_name<'__short, '__long: '__short>(
long: #wf_proof_name<'__long>
) -> #wf_proof_name<'__short> {
long
}
));
}
}
// Make sure that the type is wellformed when substituting lifetime with `'static`.
//
// Currently the Rust compiler doesn't check this, see the above `ProveWf` documentation.
//
// We prefer to use this way of proving WF-ness as it can work when generics are involved.
let ty_static = ty.replace_lifetime(
&lifetime,
&Lifetime {
apostrophe: Span::mixed_site(),
ident: format_ident!("static"),
},
);
let n: usize = prove_covariance.into();
quote!(
::kernel::types::for_lt::UnsafeForLtImpl::<
dyn for<#lifetime> ::kernel::types::for_lt::WithLt<#lifetime, Of = #ty>,
#ty_static,
{
#(#proof)*
#n
}
>
)
}
pub(crate) fn for_lt(input: HigherRankedType) -> TokenStream {
for_lt_inner(input, false)
}
pub(crate) fn covariant_for_lt(input: HigherRankedType) -> TokenStream {
for_lt_inner(input, true)
}