diff --git a/derive/src/into_owned.rs b/derive/src/into_owned.rs index 4da1bc10..313117bd 100644 --- a/derive/src/into_owned.rs +++ b/derive/src/into_owned.rs @@ -2,15 +2,15 @@ use proc_macro::{self, TokenStream}; use proc_macro2::Span; use quote::quote; use syn::{ - parse_macro_input, Data, DataEnum, DeriveInput, Field, Fields, GenericArgument, Ident, Member, PathArguments, - Type, + parse_macro_input, parse_quote, Data, DataEnum, DeriveInput, Field, Fields, GenericArgument, Ident, Member, + PathArguments, Type, }; pub(crate) fn derive_into_owned(input: TokenStream) -> TokenStream { let DeriveInput { ident: self_name, data, - generics, + mut generics, .. } = parse_macro_input!(input); @@ -105,16 +105,61 @@ pub(crate) fn derive_into_owned(input: TokenStream) -> TokenStream { } }; - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let orig_generics = generics.clone(); - let into_owned = if generics.lifetimes().next().is_none() { - panic!("can't derive IntoOwned on a type without any lifetimes") + // Add generic bounds for all type parameters. + let mut type_param_names = vec![]; + + for ty in generics.type_params() { + type_param_names.push(ty.ident.clone()); + } + + for type_param in type_param_names { + generics.make_where_clause().predicates.push_value(parse_quote! { + #type_param: 'static + for<'aa> crate::traits::IntoOwned<'aa> + }) + } + + let has_lifetime = generics + .params + .first() + .map_or(false, |v| matches!(v, syn::GenericParam::Lifetime(..))); + + // Prepend `'any` to generics + let any = syn::GenericParam::Lifetime(syn::LifetimeDef { + attrs: Default::default(), + lifetime: syn::Lifetime { + apostrophe: Span::call_site(), + ident: Ident::new("any", Span::call_site()), + }, + colon_token: None, + bounds: Default::default(), + }); + generics.params.insert(0, any.clone()); + + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = orig_generics.split_for_impl(); + + let into_owned = if !has_lifetime { + quote! { + impl #impl_generics crate::traits::IntoOwned<'any> for #self_name #ty_generics #where_clause { + type Owned = Self; + + #[inline] + fn into_owned(self) -> Self { + self + } + } + } } else { let params = generics.type_params(); quote! { - impl #impl_generics #self_name #ty_generics #where_clause { + impl #impl_generics crate::traits::IntoOwned<'any> for #self_name #ty_generics #where_clause { + type Owned = #self_name<'any, #(#params),*>; /// Consumes the value and returns an owned clone. - pub fn into_owned<'x>(self) -> #self_name<'x, #(#params),*> { + fn into_owned(self) -> Self::Owned { + use crate::traits::IntoOwned; + #res } } diff --git a/node/src/transformer.rs b/node/src/transformer.rs index 3480d2dd..6873b502 100644 --- a/node/src/transformer.rs +++ b/node/src/transformer.rs @@ -3,6 +3,7 @@ use std::{ ops::{Index, IndexMut}, }; +use lightningcss::traits::IntoOwned; use lightningcss::{ media_query::MediaFeatureValue, properties::{ diff --git a/src/error.rs b/src/error.rs index 274a5cae..c142be00 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,8 @@ use crate::properties::custom::Token; use crate::rules::Location; +#[cfg(feature = "into_owned")] +use crate::traits::IntoOwned; use crate::values::string::CowArcStr; use cssparser::{BasicParseErrorKind, ParseError, ParseErrorKind}; use parcel_selectors::parser::SelectorParseErrorKind; diff --git a/src/media_query.rs b/src/media_query.rs index f6921c61..7497a58f 100644 --- a/src/media_query.rs +++ b/src/media_query.rs @@ -1,5 +1,4 @@ //! Media queries. - use crate::error::{ErrorWithLocation, MinifyError, MinifyErrorKind, ParserError, PrinterError}; use crate::macros::enum_property; use crate::parser::starts_with_ignore_ascii_case; @@ -11,6 +10,8 @@ use crate::rules::custom_media::CustomMediaRule; use crate::rules::Location; use crate::stylesheet::ParserOptions; use crate::targets::{should_compile, Targets}; +#[cfg(feature = "into_owned")] +use crate::traits::IntoOwned; use crate::traits::{Parse, ToCss}; use crate::values::ident::{DashedIdent, Ident}; use crate::values::number::{CSSInteger, CSSNumber}; @@ -1257,6 +1258,7 @@ macro_rules! define_query_features { pub(crate) use define_query_features; define_query_features! { + #[cfg_attr(feature = "into_owned", derive(lightningcss_derive::IntoOwned))] /// A media query feature identifier. pub enum MediaFeatureId { /// The [width](https://w3c.github.io/csswg-drafts/mediaqueries-5/#width) media feature. diff --git a/src/properties/custom.rs b/src/properties/custom.rs index 42a2f3b9..891c4831 100644 --- a/src/properties/custom.rs +++ b/src/properties/custom.rs @@ -180,6 +180,7 @@ impl<'i> UnparsedProperty<'i> { ) -> Result, ()> { use super::Property; use crate::stylesheet::PrinterOptions; + use crate::traits::IntoOwned; // Substitute variables in the token list. self.value.substitute_variables(vars); diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 9a060f32..d5d9027d 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -128,6 +128,8 @@ use crate::parser::ParserOptions; use crate::prefixes::Feature; use crate::printer::{Printer, PrinterOptions}; use crate::targets::Targets; +#[cfg(feature = "into_owned")] +use crate::traits::IntoOwned; use crate::traits::{Parse, ParseWithOptions, Shorthand, ToCss}; use crate::values::number::{CSSInteger, CSSNumber}; use crate::values::string::CowArcStr; diff --git a/src/rules/container.rs b/src/rules/container.rs index 7529930e..9af13d99 100644 --- a/src/rules/container.rs +++ b/src/rules/container.rs @@ -74,6 +74,7 @@ pub type ContainerSizeFeature<'i> = QueryFeature<'i, ContainerSizeFeatureId>; define_query_features! { /// A container query size feature identifier. + #[cfg_attr(feature = "into_owned", derive(lightningcss_derive::IntoOwned))] pub enum ContainerSizeFeatureId { /// The [width](https://w3c.github.io/csswg-drafts/css-contain-3/#width) size container feature. "width": Width = Length, diff --git a/src/traits.rs b/src/traits.rs index 81f62760..d3cac881 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -10,6 +10,38 @@ use crate::targets::{Browsers, Targets}; use crate::vendor_prefix::VendorPrefix; use cssparser::*; +/// A trait for things that can be cloned with a new lifetime. +/// +/// `'any` lifeitme means the output should have `'static` lifetime. +#[cfg(feature = "into_owned")] +pub trait IntoOwned<'any> { + /// A variant of `Self` with a new lifetime. + type Owned: 'any; + + /// Make lifetime of `self` `'static`. + fn into_owned(self) -> Self::Owned; +} + +#[cfg(feature = "into_owned")] +macro_rules! impl_into_owned { + ($t: ty) => { + impl<'a> IntoOwned<'a> for $t { + type Owned = Self; + + #[inline] + fn into_owned(self) -> Self { + self + } + } + }; + ($($t:ty),*) => { + $(impl_into_owned!($t);)* + }; +} + +#[cfg(feature = "into_owned")] +impl_into_owned!(bool, f32, f64, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize); + /// Trait for things that can be parsed from CSS syntax. pub trait Parse<'i>: Sized { /// Parse a value of this type using an existing parser. diff --git a/src/values/string.rs b/src/values/string.rs index b0cb5de3..a067adaa 100644 --- a/src/values/string.rs +++ b/src/values/string.rs @@ -129,9 +129,14 @@ impl<'a> CowArcStr<'a> { } } } +} + +#[cfg(feature = "into_owned")] +impl<'any> crate::traits::IntoOwned<'any> for CowArcStr<'_> { + type Owned = CowArcStr<'any>; /// Consumes the value and returns an owned clone. - pub fn into_owned<'x>(self) -> CowArcStr<'x> { + fn into_owned(self) -> Self::Owned { if self.borrowed_len_or_max != usize::MAX { CowArcStr::from(self.as_ref().to_owned()) } else {