Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/core/cgp-field/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod map_field;
mod map_fields;
mod map_type;
mod map_type_ref;
mod option_as_ref;
mod partial_data;
mod static_string;
mod take_field;
Expand All @@ -33,6 +34,7 @@ pub use map_field::*;
pub use map_fields::*;
pub use map_type::*;
pub use map_type_ref::*;
pub use option_as_ref::*;
pub use partial_data::*;
pub use static_string::*;
pub use take_field::*;
Expand Down
19 changes: 19 additions & 0 deletions crates/core/cgp-field/src/traits/option_as_ref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// Borrows the value inside an `Option` as a `&T`.
///
/// The blanket impl covers any `Option<U>` whose `U` implements `AsRef<T>`, so an
/// `#[implicit] nickname: Option<&str>` argument reads an `Option<String>`, an
/// `Option<Cow<str>>`, or an `Option<Box<str>>` field without needing an allocator
/// of its own.
pub trait OptionAsRef<T: ?Sized> {
/// Borrows the contained value, or `None` when the option is empty.
fn option_as_ref(&self) -> Option<&T>;
}

impl<T: ?Sized, U> OptionAsRef<T> for Option<U>
where
U: AsRef<T>,
{
fn option_as_ref(&self) -> Option<&T> {
self.as_ref().map(|value| value.as_ref())
}
}
1 change: 1 addition & 0 deletions crates/macros/cgp-macro-core/src/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export_constructs! {
HasFields,
HasFieldsRef,
HasFieldMut,
OptionAsRef,
FieldGetter,
MutFieldGetter,
FromFields,
Expand Down
51 changes: 40 additions & 11 deletions crates/macros/cgp-macro-core/src/functions/field/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ use crate::types::getter::FieldMode;
pub fn parse_field_type(
return_type: &Type,
receiver_mut: &Option<Mut>,
) -> syn::Result<(Type, FieldMode, Option<Mut>)> {
parse_field_type_with(return_type, receiver_mut, false)
}

/// Like [`parse_field_type`], but `borrow_str` decides how a `&str` is read. An
/// `#[implicit]` argument sets it, so the field may be any `AsRef<str>` and needs no
/// allocator; a getter leaves it off, because its `HasField` bound is also used by
/// the `WithProvider` adapter, which expects the field type itself.
pub fn parse_field_type_with(
return_type: &Type,
receiver_mut: &Option<Mut>,
borrow_str: bool,
) -> syn::Result<(Type, FieldMode, Option<Mut>)> {
match &return_type {
Type::Reference(type_ref) => {
Expand All @@ -23,11 +35,21 @@ pub fn parse_field_type(
let field_mut = type_ref.mutability;

if type_ref.elem.as_ref() == &parse_internal! { str } {
// Special case to handle &str as String field
if borrow_str {
// A `&str` reads any `AsRef<str>` field with `.as_ref()`, so a
// `String`, a `Cow<str>`, or a `Box<str>` field all work, and
// none of them needs an allocator.
let field_type: Type = parse_internal! { str };

let field_type: Type = parse_internal! { String };
Ok((field_type, FieldMode::Str, field_mut))
} else {
// A getter's `HasField` bound is also used by the `WithProvider`
// adapter, which expects the field type itself, so it keeps the
// `String` field.
let field_type: Type = parse_internal! { String };

Ok((field_type, FieldMode::Str, field_mut))
Ok((field_type, FieldMode::Str, field_mut))
}
} else if let Type::Slice(slice) = type_ref.elem.as_ref() {
// A shared `&[T]` reads any `AsRef<[T]>` field with `.as_ref()`; a
// `&mut [T]` reads any `AsMut<[T]>` field with `.as_mut()`. Both use
Expand Down Expand Up @@ -56,14 +78,21 @@ pub fn parse_field_type(
let field_type = type_ref.elem.as_ref();

if field_type == &parse_internal! { str } {
// `Option<&str>` is backed by an `Option<String>` field and read
// with `.as_deref()` (or `.as_deref_mut()` for `Option<&mut str>`),
// mirroring the `&str`/`String` special case for a plain reference.
Ok((
parse_internal! { Option< String > },
FieldMode::OptionStr,
field_mut,
))
if borrow_str {
// An `#[implicit]` argument reads any `Option<U>` whose `U`
// implements `AsRef<str>` through `OptionAsRef`, so a
// `String`, a `Cow<str>`, or a `Box<str>` field all work and
// none needs an allocator.
Ok((parse_internal! { str }, FieldMode::OptionStr, field_mut))
} else {
// A getter keeps the `Option<String>` field, because its bound
// is also used by the `WithProvider` adapter.
Ok((
parse_internal! { Option< String > },
FieldMode::OptionStr,
field_mut,
))
}
} else {
Ok((
parse_internal! { Option< #field_type > },
Expand Down
5 changes: 3 additions & 2 deletions crates/macros/cgp-macro-core/src/functions/implicits/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use syn::token::Comma;
use syn::visit::{self, Visit};
use syn::{Attribute, FnArg, Meta, Pat, PatIdent, PatType, Receiver};

use crate::functions::parse_field_type;
use crate::functions::parse_field_type_with;
use crate::types::implicits::{ImplicitArgField, ImplicitArgFields};

pub fn extract_and_parse_implicit_args(
Expand Down Expand Up @@ -68,7 +68,8 @@ pub fn parse_implicit_arg(receiver: &Receiver, arg: &PatType) -> syn::Result<Imp
// `&mut` of an `Option<&mut T>` — and rejects a mutable read without a `&mut
// self` receiver. The receiver's own mutability never forces a mutable read of
// an immutably-typed argument.
let (field_type, field_mode, field_mut) = parse_field_type(&arg_type, &receiver.mutability)?;
let (field_type, field_mode, field_mut) =
parse_field_type_with(&arg_type, &receiver.mutability, true)?;

let spec = ImplicitArgField {
field_name: pat_ident.ident.clone(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ pub fn derive_to_fields_match_arms(

let constructor = derive_to_fields_constructor(&variant.fields, |label| match label {
FieldLabel::Named(label) => quote! {
#label .into()
::core::convert::Into::into(#label)
},
FieldLabel::Unnamed(label) => {
let field_name = Ident::new(&format!("field_{label}"), label.span());

quote! {
#field_name .into()
::core::convert::Into::into(#field_name)
}
}
FieldLabel::None => quote! { field },
Expand All @@ -60,7 +60,7 @@ pub fn derive_to_fields_match_arms(
let variant_args = extract_variant_args(&variant.fields)?;

let inject_variant = inject_prefix(quote! {
#Either::Left( #constructor .into() )
#Either::Left( ::core::convert::Into::into(#constructor) )
});

inject_prefix = Box::new(move |inner| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub fn derive_to_fields_ref_for_struct(item_struct: &ItemStruct) -> syn::Result<
&self.0
},
_ => quote! {
( &self #field_name ) .into()
::core::convert::Into::into(&self #field_name)
},
})?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub fn derive_to_fields_for_struct(item_struct: &ItemStruct) -> syn::Result<Item
self.0
},
_ => quote! {
self #field_name .into()
::core::convert::Into::into(self #field_name)
},
})?;

Expand Down
43 changes: 40 additions & 3 deletions crates/macros/cgp-macro-core/src/types/field/has_field_bound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use quote::{ToTokens, quote};
use syn::Type;
use syn::token::Mut;

use crate::exports::{HasField, HasFieldMut};
use crate::exports::{HasField, HasFieldMut, OptionAsRef};
use crate::types::getter::FieldMode;

pub struct HasFieldBound {
Expand All @@ -25,19 +25,56 @@ impl ToTokens for HasFieldBound {
let output = if field_mut.is_none() {
if let FieldMode::Slice = field_mode {
quote! {
#HasField< #tag_type, Value: AsRef< [ #field_type ] > + 'static >
#HasField< #tag_type, Value: ::core::convert::AsRef< [ #field_type ] > + 'static >
}
} else if let FieldMode::OptionStr = field_mode {
if field_type.to_token_stream().to_string() == "str" {
quote! {
#HasField< #tag_type, Value: #OptionAsRef< str > + 'static >
}
} else {
quote! {
#HasField< #tag_type, Value = #field_type >
}
}
} else if let FieldMode::Str = field_mode {
if field_type.to_token_stream().to_string() == "str" {
// An `#[implicit]` argument borrows any `AsRef<str>` field, so a
// `String`, a `Cow<str>`, or a `Box<str>` field all work. The
// `'static` bound mirrors the slice case: the generated impl
// names the associated type in a `where` clause, which needs it
// to outlive the impl.
quote! {
#HasField< #tag_type, Value: ::core::convert::AsRef< str > + 'static >
}
} else {
// A getter keeps the field type itself, because its bound is also
// used by the `WithProvider` adapter.
quote! {
#HasField< #tag_type, Value = #field_type >
}
}
} else {
quote! {
#HasField< #tag_type, Value = #field_type >
}
}
} else if let FieldMode::Str = field_mode {
if field_type.to_token_stream().to_string() == "str" {
quote! {
#HasFieldMut< #tag_type, Value: ::core::convert::AsMut< str > + 'static >
}
} else {
quote! {
#HasFieldMut< #tag_type, Value = #field_type >
}
}
} else if let FieldMode::Slice = field_mode {
// A mutable slice reads through `.as_mut()`, so the field's value must
// implement `AsMut<[T]>` — the mutable mirror of the shared slice's
// `AsRef<[T]>` bound.
quote! {
#HasFieldMut< #tag_type, Value: AsMut< [ #field_type ] > + 'static >
#HasFieldMut< #tag_type, Value: ::core::convert::AsMut< [ #field_type ] > + 'static >
}
} else {
quote! {
Expand Down
24 changes: 14 additions & 10 deletions crates/macros/cgp-macro-core/src/types/getter/field_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::token::Mut;

use crate::exports::MRef;
use crate::exports::{MRef, OptionAsRef};

/// The conversion a getter body applies to a raw field read, chosen from the
/// getter method's return type by [`parse_field_type`](crate::functions::parse_field_type).
Expand All @@ -23,36 +23,40 @@ impl FieldMode {
/// truth shared by the getter-method body and the `#[implicit]` argument
/// binding, so both families convert a given field identically.
pub fn apply(&self, call_expr: TokenStream, field_mut: &Option<Mut>) -> TokenStream {
// Every conversion is a fully qualified trait call, so an inherent method of
// the same name on the field's type cannot capture it.
match self {
FieldMode::Reference => call_expr,
FieldMode::OptionRef => {
// The field is an `Option<T>`, so the read is `Option`'s own inherent
// method, not the `AsRef`/`AsMut` conversion.
if field_mut.is_none() {
quote! { #call_expr .as_ref() }
quote! { ::core::option::Option::as_ref( #call_expr ) }
} else {
quote! { #call_expr .as_mut() }
quote! { ::core::option::Option::as_mut( #call_expr ) }
}
}
FieldMode::OptionStr => {
if field_mut.is_none() {
quote! { #call_expr .as_deref() }
quote! { #OptionAsRef::option_as_ref( #call_expr ) }
} else {
quote! { #call_expr .as_deref_mut() }
quote! { ::core::option::Option::as_deref_mut( #call_expr ) }
}
}
FieldMode::MRef => quote! { #MRef::Ref( #call_expr ) },
FieldMode::Str => {
if field_mut.is_none() {
quote! { #call_expr .as_str() }
quote! { ::core::convert::AsRef::as_ref( #call_expr ) }
} else {
quote! { #call_expr .as_mut_str() }
quote! { ::core::convert::AsMut::as_mut( #call_expr ) }
}
}
FieldMode::Copy => quote! { #call_expr .clone() },
FieldMode::Copy => quote! { ::core::clone::Clone::clone( #call_expr ) },
FieldMode::Slice => {
if field_mut.is_none() {
quote! { #call_expr .as_ref() }
quote! { ::core::convert::AsRef::as_ref( #call_expr ) }
} else {
quote! { #call_expr .as_mut() }
quote! { ::core::convert::AsMut::as_mut( #call_expr ) }
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions crates/macros/cgp-macro-core/src/types/getter/get_field_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use quote::{ToTokens, quote};
use syn::Expr;
use syn::token::Mut;

use crate::exports::{HasField, HasFieldMut};
use crate::types::field::FieldName;

pub struct GetFieldExpr {
Expand All @@ -16,14 +17,17 @@ impl ToTokens for GetFieldExpr {
let receiver = &self.receiver;
let field_name = &self.field_name;

let method = if self.field_mut.is_none() {
quote!(get_field)
// A fully qualified trait call, so an inherent method of the same name on the
// context cannot capture the read.
let (field_trait, method) = if self.field_mut.is_none() {
(quote!(#HasField), quote!(get_field))
} else {
quote!(get_field_mut)
(quote!(#HasFieldMut), quote!(get_field_mut))
};

tokens.extend(quote! {
#receiver.#method(
#field_trait::#method(
#receiver,
::core::marker::PhantomData::<#field_name>
)
})
Expand Down
11 changes: 10 additions & 1 deletion crates/macros/cgp-macro-core/src/types/getter/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::{Ident, ImplItemFn, Type};

use crate::exports::{HasField, HasFieldMut};
use crate::functions::parse_internal;
use crate::types::cgp_getter::GetterField;
use crate::types::getter::ContextArg;
Expand Down Expand Up @@ -71,6 +72,14 @@ impl GetterMethod {
quote! { get_field_mut }
};

// The context read is a fully qualified trait call, so an inherent method of
// the same name on the context cannot capture it.
let get_field_trait = if getter_field.receiver_mut.is_none() {
quote! { #HasField }
} else {
quote! { #HasFieldMut }
};

let context_var = match &context_arg {
ContextArg::SelfArg => {
quote! { self }
Expand All @@ -88,7 +97,7 @@ impl GetterMethod {
}
None => {
quote! {
#context_var . #get_field_method ( ::core::marker::PhantomData::< #tag_type > )
#get_field_trait :: #get_field_method ( #context_var, ::core::marker::PhantomData::< #tag_type > )
}
}
};
Expand Down
4 changes: 2 additions & 2 deletions crates/main/cgp-core/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ pub use cgp_field::impls::{IsMut, IsNothing, IsPresent, IsRef, IsVoid, UseField}
pub use cgp_field::traits::{
BuildField, ExtractField, FieldGetter, FinalizeBuild, FinalizeExtract, FromFields, FromVariant,
HasBuilder, HasExtractor, HasExtractorMut, HasExtractorRef, HasField, HasFieldMut, HasFields,
HasFieldsRef, IntoBuilder, MapType, MapTypeRef, MutFieldGetter, PartialData, ToFields,
ToFieldsRef, UpdateField,
HasFieldsRef, IntoBuilder, MapType, MapTypeRef, MutFieldGetter, OptionAsRef, PartialData,
ToFields, ToFieldsRef, UpdateField,
};
pub use cgp_field::types::{Chars, Cons, Either, Field, Index, Life, MRef, Nil, Symbol, Void};
pub use cgp_macro::{
Expand Down
Loading
Loading