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
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub fn derive_extract_field_impls(
if is_ref {
parse_internal! { <__R__ as #MapTypeRef>::Map<'__a__, #value_type> }
} else {
value_type.clone()
value_type
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use syn::{Arm, Ident, ItemEnum, ItemImpl};
use crate::exports::{HasExtractor, HasExtractorMut, HasExtractorRef, IsMut, IsPresent, IsRef};
use crate::functions::override_item_span;
use crate::parse_internal;
use crate::types::cgp_data::to_generic_args;
use crate::types::cgp_data::{get_variant_payload, to_generic_args};

/// Emit the owned `HasExtractor` impl: `to_extractor`/`from_extractor` map each
/// variant to and from the all-`IsPresent` configuration of the partial enum.
Expand All @@ -28,16 +28,21 @@ pub fn derive_has_extractor_impl(
});

let variant_ident = &variant.ident;
let payload = get_variant_payload(variant)?;
let pattern = payload.match_pattern(variant);
let value = payload.owned_value();
let constructor = payload.constructor(variant);
let binding = payload.binding();

to_match_arms.push(parse_internal! {
Self :: #variant_ident ( value ) => {
#extractor_ident:: #variant_ident ( value )
#pattern => {
#extractor_ident:: #variant_ident ( #value )
}
});

from_match_arms.push(parse_internal! {
#extractor_ident:: #variant_ident ( value ) => {
Self :: #variant_ident ( value )
#extractor_ident:: #variant_ident ( #binding ) => {
#constructor
}
});
}
Expand Down Expand Up @@ -111,10 +116,13 @@ pub fn derive_has_extractor_ref_impl(
});

let variant_ident = &variant.ident;
let payload = get_variant_payload(variant)?;
let pattern = payload.match_pattern(variant);
let value = payload.ref_value();

match_arms.push(parse_internal! {
Self :: #variant_ident ( value ) => {
#extractor_ident:: #variant_ident ( value )
#pattern => {
#extractor_ident:: #variant_ident ( #value )
}
});
}
Expand Down Expand Up @@ -185,10 +193,13 @@ pub fn derive_has_extractor_mut_impl(
});

let variant_ident = &variant.ident;
let payload = get_variant_payload(variant)?;
let pattern = payload.match_pattern(variant);
let value = payload.mut_value(variant);

match_arms.push(parse_internal! {
Self :: #variant_ident ( value ) => {
#extractor_ident:: #variant_ident ( value )
#pattern => {
#extractor_ident:: #variant_ident ( #value )
}
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,133 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Field, FieldMutability, Fields, FieldsUnnamed, Type, Variant, Visibility};

/// Return a newtype variant's single payload type, or a spanned error if the
/// variant is not exactly one unnamed field — the requirement the variant
/// derives enforce.
pub fn get_variant_type(variant: &Variant) -> syn::Result<&Type> {
use crate::exports::Nil;
use crate::parse_internal;

/// The payload of a variant the variant derives accept: either the single
/// unnamed field of a newtype variant, or `Nil` for a variant with no fields.
pub enum VariantPayload<'a> {
/// A newtype variant `V(T)`, whose payload is `T`.
Single(&'a Type),
/// A variant with no fields, written `V`, `V()`, or `V {}`, whose payload is
/// `Nil`, the empty product `#[derive(HasFields)]` already gives it.
Empty,
}

impl VariantPayload<'_> {
/// The payload type: the field's type, or `Nil`.
pub fn payload_type(&self) -> syn::Result<Type> {
match self {
Self::Single(field_type) => Ok((*field_type).clone()),
Self::Empty => Ok(parse_internal! { #Nil }),
}
}

/// A pattern matching the variant on the original enum, binding the payload
/// to `value`. An empty variant is matched by `{ .. }`, which matches all
/// three of its forms.
pub fn match_pattern(&self, variant: &Variant) -> TokenStream {
let variant_ident = &variant.ident;

match self {
Self::Single(_) => quote! { Self :: #variant_ident ( value ) },
Self::Empty => quote! { Self :: #variant_ident { .. } },
}
}

/// An expression building the variant on the original enum from a payload
/// bound to `value`. An empty variant is built by `{}`, which builds all
/// three of its forms and ignores the payload.
pub fn constructor(&self, variant: &Variant) -> TokenStream {
let variant_ident = &variant.ident;

match self {
Self::Single(_) => quote! { Self :: #variant_ident ( value ) },
Self::Empty => quote! { Self :: #variant_ident {} },
}
}

/// The binding for a payload the variant is built from: `value` for a newtype
/// variant, and `_` for an empty one, which ignores its `Nil`.
pub fn binding(&self) -> TokenStream {
match self {
Self::Single(_) => quote! { value },
Self::Empty => quote! { _ },
}
}

/// The payload the owned extractor holds for a matched variant.
pub fn owned_value(&self) -> TokenStream {
match self {
Self::Single(_) => quote! { value },
Self::Empty => quote! { #Nil },
}
}

/// The payload the shared borrowed extractor holds: a promoted `&Nil` for an
/// empty variant, since it has no field to borrow.
pub fn ref_value(&self) -> TokenStream {
match self {
Self::Single(_) => quote! { value },
Self::Empty => quote! { &#Nil },
}
}

/// The payload the mutable borrowed extractor holds. An empty variant has no
/// field to borrow and Rust does not promote `&mut Nil`, so the payload is a
/// leaked `Box<Nil>`, which never allocates because `Nil` is zero-sized.
///
/// `Box` is written bare and resolved where the derive is used, the one name
/// a CGP expansion takes from the caller's scope rather than from `cgp`,
/// which links no `alloc`. It is spanned at the variant, so a `no_std` crate
/// without `Box` in scope sees the error on the empty variant.
pub fn mut_value(&self, variant: &Variant) -> TokenStream {
match self {
Self::Single(_) => quote! { value },
Self::Empty => {
let span = variant.ident.span();
let boxed = quote_spanned! { span => Box };

quote! { #boxed::leak(#boxed::new(#Nil)) }
}
}
}
}

/// Return a variant's payload, or a spanned error if the variant has several
/// fields or named fields, which the variant derives cannot carry as one payload.
pub fn get_variant_payload(variant: &Variant) -> syn::Result<VariantPayload<'_>> {
match &variant.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
if let Some(field) = fields.unnamed.first() {
return Ok(&field.ty);
return Ok(VariantPayload::Single(&field.ty));
}
}
Fields::Unnamed(fields) if fields.unnamed.is_empty() => {
return Ok(VariantPayload::Empty);
}
Fields::Named(fields) if fields.named.is_empty() => {
return Ok(VariantPayload::Empty);
}
Fields::Unit => return Ok(VariantPayload::Empty),
_ => {}
}

Err(syn::Error::new(
variant.span(),
"Expected variant to contain exactly one unnamed field",
"Expected variant to contain exactly one unnamed field, or no fields",
))
}

/// Return a variant's payload type: the newtype field's type, or `Nil` for a
/// variant with no fields.
pub fn get_variant_type(variant: &Variant) -> syn::Result<Type> {
get_variant_payload(variant)?.payload_type()
}

/// Wrap a type as a single-unnamed-field variant body, used to re-shape a
/// partial enum's variants around their `MapType`-wrapped payloads.
pub fn type_to_variant_fields(type_: &Type) -> Fields {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use syn::{ItemEnum, ItemImpl};
use crate::exports::FromVariant;
use crate::functions::override_item_span;
use crate::parse_internal;
use crate::types::cgp_data::get_variant_type;
use crate::types::cgp_data::get_variant_payload;
use crate::types::field::Symbol;

/// Emit one `FromVariant` impl per variant, each keyed by the variant name's
Expand All @@ -19,16 +19,19 @@ pub fn derive_from_variant_from_enum(item_enum: &ItemEnum) -> syn::Result<Vec<It
for variant in item_enum.variants.iter() {
let variant_ident = &variant.ident;
let variant_tag = Symbol::from_ident(variant_ident.clone());
let variant_type = get_variant_type(variant)?;
let payload = get_variant_payload(variant)?;
let variant_type = payload.payload_type()?;
let constructor = payload.constructor(variant);
let binding = payload.binding();

let item_impl: ItemImpl = parse_internal! {
impl #impl_generics #FromVariant<#variant_tag> for #enum_ident #ty_generics
#where_clause
{
type Value = #variant_type;

fn from_variant(_tag: ::core::marker::PhantomData<#variant_tag>, value: Self::Value) -> Self {
Self::#variant_ident(value)
fn from_variant(_tag: ::core::marker::PhantomData<#variant_tag>, #binding: Self::Value) -> Self {
#constructor
}
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! `#[derive(CgpData)]` — the umbrella derive — rejects the inputs its shape
//! dispatch and variant codegen cannot lower: an item that is neither a struct
//! nor an enum, and (on the enum path) a variant that is not a single unnamed
//! field. The bad-variant rejection is inherited from the shared variant codegen
//! nor an enum, and (on the enum path) a variant with several fields or with
//! named fields. The bad-variant rejection is inherited from the shared variant codegen
//! that `#[derive(CgpVariant)]`/`#[derive(FromVariant)]` also use, so this only
//! confirms the umbrella propagates it; the non-struct-or-enum rejection is
//! unique to `ItemCgpData`'s parser.
Expand All @@ -28,8 +28,8 @@ fn rejects_non_struct_or_enum() {

#[test]
fn rejects_struct_style_variant_on_enum() {
// On the enum path the umbrella runs the same single-unnamed-field variant
// codegen as `#[derive(CgpVariant)]`, so a struct-style variant is refused.
// On the enum path the umbrella runs the same variant codegen as
// `#[derive(CgpVariant)]`, so a struct-style variant with fields is refused.
assert_macro_rejects("derive(CgpData) on a struct-style variant", || {
cgp_macro_lib::derive_cgp_data(quote!(
pub enum Shape {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,68 @@
//! `#[derive(FromVariant)]` and `#[derive(CgpVariant)]` reject the enum shapes
//! their codegen cannot lower: a variant that does not carry exactly one unnamed
//! field, and (for `CgpVariant`) a non-enum item.
//! their codegen cannot lower: a variant with several fields or with named
//! fields, which has no single payload, and (for `CgpVariant`) a non-enum item.
//! A variant with no fields is accepted, with the payload `Nil`.
//!
//! See cgp-knowledge-base/cgp/implementation/entrypoints/derive_from_variant.md (Known issues,
//! Tests) and cgp-knowledge-base/cgp/implementation/entrypoints/derive_cgp_variant.md for the
//! user-facing semantics.

use quote::quote;

use super::assert_macro_rejects;
use super::{assert_macro_rejects, assert_macro_rejects_with};

#[test]
fn rejects_fieldless_variant() {
// A unit variant carries no payload, so there is no value type for the
// constructor to wrap.
assert_macro_rejects("derive(FromVariant) on a fieldless variant", || {
cgp_macro_lib::derive_from_variant(quote!(
pub enum Shape {
Circle(Circle),
Empty,
}
))
});
}
const VARIANT_SHAPE_MESSAGE: &str =
"Expected variant to contain exactly one unnamed field, or no fields";

#[test]
fn rejects_multi_field_variant() {
// A multi-field tuple variant has more than one payload; the constructor
// keys a single `Value` per variant, so the shape is refused.
assert_macro_rejects("derive(FromVariant) on a multi-field variant", || {
cgp_macro_lib::derive_from_variant(quote!(
pub enum Shape {
Pair(u32, u32),
}
))
});
assert_macro_rejects_with(
"derive(FromVariant) on a multi-field variant",
VARIANT_SHAPE_MESSAGE,
|| {
cgp_macro_lib::derive_from_variant(quote!(
pub enum Shape {
Pair(u32, u32),
}
))
},
);
}

#[test]
fn rejects_struct_style_variant() {
// A struct-style variant names its fields; the constructor expects a single
// unnamed field.
assert_macro_rejects("derive(FromVariant) on a struct-style variant", || {
cgp_macro_lib::derive_from_variant(quote!(
pub enum Shape {
Named { x: u32 },
}
))
});
// unnamed field. An empty struct-style variant, `Named {}`, is accepted.
assert_macro_rejects_with(
"derive(FromVariant) on a struct-style variant",
VARIANT_SHAPE_MESSAGE,
|| {
cgp_macro_lib::derive_from_variant(quote!(
pub enum Shape {
Named { x: u32 },
}
))
},
);
}

#[test]
fn extract_field_rejects_struct_style_variant() {
// The extractor derive shares the constructor's variant-shape check.
assert_macro_rejects_with(
"derive(ExtractField) on a struct-style variant",
VARIANT_SHAPE_MESSAGE,
|| {
cgp_macro_lib::derive_extract_field(quote!(
pub enum Shape {
Circle(Circle),
Named { x: u32 },
}
))
},
);
}

#[test]
Expand Down
Loading
Loading