lightningcss/values/
string.rs

1//! Types used to represent strings.
2
3use crate::traits::{Parse, ToCss};
4#[cfg(feature = "visitor")]
5use crate::visitor::{Visit, VisitTypes, Visitor};
6use cssparser::{serialize_string, CowRcStr};
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Deserializer};
9#[cfg(any(feature = "serde", feature = "nodejs"))]
10use serde::{Serialize, Serializer};
11use std::borrow::{Borrow, Cow};
12use std::cmp;
13use std::fmt;
14use std::hash;
15use std::marker::PhantomData;
16use std::ops::Deref;
17use std::rc::Rc;
18use std::slice;
19use std::str;
20use std::sync::Arc;
21
22// We cannot store CowRcStr from cssparser directly because it is not threadsafe (due to Rc).
23// CowArcStr is exactly the same, but uses Arc instead of Rc. We could use Cow<str> instead,
24// but real-world benchmarks show a performance regression, likely due to the larger memory
25// footprint.
26//
27// In order to convert between CowRcStr and CowArcStr without cloning, we use some unsafe
28// tricks to access the internal fields. LocalCowRcStr must be exactly the same as CowRcStr
29// so we can transmutate between them.
30struct LocalCowRcStr<'a> {
31  ptr: &'static (),
32  borrowed_len_or_max: usize,
33  phantom: PhantomData<Result<&'a str, Rc<String>>>,
34}
35
36/// A string that is either shared (heap-allocated and atomically reference-counted)
37/// or borrowed from the input CSS source code.
38pub struct CowArcStr<'a> {
39  ptr: &'static (),
40  borrowed_len_or_max: usize,
41  phantom: PhantomData<Result<&'a str, Arc<String>>>,
42}
43
44impl<'a> From<CowRcStr<'a>> for CowArcStr<'a> {
45  #[inline]
46  fn from(s: CowRcStr<'a>) -> Self {
47    (&s).into()
48  }
49}
50
51impl<'a> From<&CowRcStr<'a>> for CowArcStr<'a> {
52  #[inline]
53  fn from(s: &CowRcStr<'a>) -> Self {
54    let local = unsafe { std::mem::transmute::<&CowRcStr<'a>, &LocalCowRcStr<'a>>(&s) };
55    if local.borrowed_len_or_max == usize::MAX {
56      // If the string is owned and not borrowed, we do need to clone.
57      // We could possibly use std::mem::take here, but that would mutate the
58      // original CowRcStr which we borrowed, so might be unexpected. Owned
59      // CowRcStr are very rare in practice though, since most strings are
60      // borrowed directly from the input.
61      let ptr = local.ptr as *const () as *mut String;
62      CowArcStr::from(unsafe { (*ptr).clone() })
63    } else {
64      let s = unsafe {
65        str::from_utf8_unchecked(slice::from_raw_parts(
66          local.ptr as *const () as *const u8,
67          local.borrowed_len_or_max,
68        ))
69      };
70
71      CowArcStr::from(s)
72    }
73  }
74}
75
76// The below implementation is copied and modified from cssparser.
77
78impl<'a> From<&'a str> for CowArcStr<'a> {
79  #[inline]
80  fn from(s: &'a str) -> Self {
81    let len = s.len();
82    assert!(len < usize::MAX);
83    CowArcStr {
84      ptr: unsafe { &*(s.as_ptr() as *const ()) },
85      borrowed_len_or_max: len,
86      phantom: PhantomData,
87    }
88  }
89}
90
91impl<'a> From<String> for CowArcStr<'a> {
92  #[inline]
93  fn from(s: String) -> Self {
94    CowArcStr::from_arc(Arc::new(s))
95  }
96}
97
98impl<'a> From<Cow<'a, str>> for CowArcStr<'a> {
99  #[inline]
100  fn from(s: Cow<'a, str>) -> Self {
101    match s {
102      Cow::Borrowed(s) => s.into(),
103      Cow::Owned(s) => s.into(),
104    }
105  }
106}
107
108impl<'a> CowArcStr<'a> {
109  #[inline]
110  fn from_arc(s: Arc<String>) -> Self {
111    let ptr = unsafe { &*(Arc::into_raw(s) as *const ()) };
112    CowArcStr {
113      ptr,
114      borrowed_len_or_max: usize::MAX,
115      phantom: PhantomData,
116    }
117  }
118
119  #[inline]
120  fn unpack(&self) -> Result<&'a str, *const String> {
121    if self.borrowed_len_or_max == usize::MAX {
122      Err(self.ptr as *const () as *const String)
123    } else {
124      unsafe {
125        Ok(str::from_utf8_unchecked(slice::from_raw_parts(
126          self.ptr as *const () as *const u8,
127          self.borrowed_len_or_max,
128        )))
129      }
130    }
131  }
132}
133
134#[cfg(feature = "into_owned")]
135impl<'any> static_self::IntoOwned<'any> for CowArcStr<'_> {
136  type Owned = CowArcStr<'any>;
137
138  /// Consumes the value and returns an owned clone.
139  fn into_owned(self) -> Self::Owned {
140    if self.borrowed_len_or_max != usize::MAX {
141      CowArcStr::from(self.as_ref().to_owned())
142    } else {
143      unsafe { std::mem::transmute(self) }
144    }
145  }
146}
147
148impl<'a> Clone for CowArcStr<'a> {
149  #[inline]
150  fn clone(&self) -> Self {
151    match self.unpack() {
152      Err(ptr) => {
153        let rc = unsafe { Arc::from_raw(ptr) };
154        let new_rc = rc.clone();
155        std::mem::forget(rc); // Don’t actually take ownership of this strong reference
156        CowArcStr::from_arc(new_rc)
157      }
158      Ok(_) => CowArcStr { ..*self },
159    }
160  }
161}
162
163impl<'a> Drop for CowArcStr<'a> {
164  #[inline]
165  fn drop(&mut self) {
166    if let Err(ptr) = self.unpack() {
167      std::mem::drop(unsafe { Arc::from_raw(ptr) })
168    }
169  }
170}
171
172impl<'a> Deref for CowArcStr<'a> {
173  type Target = str;
174
175  #[inline]
176  fn deref(&self) -> &str {
177    self.unpack().unwrap_or_else(|ptr| unsafe { &**ptr })
178  }
179}
180
181// Boilerplate / trivial impls below.
182
183impl<'a> AsRef<str> for CowArcStr<'a> {
184  #[inline]
185  fn as_ref(&self) -> &str {
186    self
187  }
188}
189
190impl<'a> Borrow<str> for CowArcStr<'a> {
191  #[inline]
192  fn borrow(&self) -> &str {
193    self
194  }
195}
196
197impl<'a> Default for CowArcStr<'a> {
198  #[inline]
199  fn default() -> Self {
200    Self::from("")
201  }
202}
203
204impl<'a> hash::Hash for CowArcStr<'a> {
205  #[inline]
206  fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
207    str::hash(self, hasher)
208  }
209}
210
211impl<'a, T: AsRef<str>> PartialEq<T> for CowArcStr<'a> {
212  #[inline]
213  fn eq(&self, other: &T) -> bool {
214    str::eq(self, other.as_ref())
215  }
216}
217
218impl<'a, T: AsRef<str>> PartialOrd<T> for CowArcStr<'a> {
219  #[inline]
220  fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
221    str::partial_cmp(self, other.as_ref())
222  }
223}
224
225impl<'a> Eq for CowArcStr<'a> {}
226
227impl<'a> Ord for CowArcStr<'a> {
228  #[inline]
229  fn cmp(&self, other: &Self) -> cmp::Ordering {
230    str::cmp(self, other)
231  }
232}
233
234impl<'a> fmt::Display for CowArcStr<'a> {
235  #[inline]
236  fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
237    str::fmt(self, formatter)
238  }
239}
240
241impl<'a> fmt::Debug for CowArcStr<'a> {
242  #[inline]
243  fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
244    str::fmt(self, formatter)
245  }
246}
247
248#[cfg(any(feature = "nodejs", feature = "serde"))]
249impl<'a> Serialize for CowArcStr<'a> {
250  fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
251    self.as_ref().serialize(serializer)
252  }
253}
254
255#[cfg(feature = "serde")]
256#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
257impl<'a, 'de: 'a> Deserialize<'de> for CowArcStr<'a> {
258  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
259  where
260    D: Deserializer<'de>,
261  {
262    deserializer.deserialize_str(CowArcStrVisitor)
263  }
264}
265
266#[cfg(feature = "jsonschema")]
267#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
268impl<'a> schemars::JsonSchema for CowArcStr<'a> {
269  fn is_referenceable() -> bool {
270    true
271  }
272
273  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
274    String::json_schema(gen)
275  }
276
277  fn schema_name() -> String {
278    "String".into()
279  }
280}
281
282#[cfg(feature = "serde")]
283struct CowArcStrVisitor;
284
285#[cfg(feature = "serde")]
286impl<'de> serde::de::Visitor<'de> for CowArcStrVisitor {
287  type Value = CowArcStr<'de>;
288
289  fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
290    formatter.write_str("a CowArcStr")
291  }
292
293  fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
294  where
295    E: serde::de::Error,
296  {
297    Ok(v.into())
298  }
299
300  fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
301  where
302    E: serde::de::Error,
303  {
304    Ok(v.to_owned().into())
305  }
306
307  fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
308  where
309    E: serde::de::Error,
310  {
311    Ok(v.into())
312  }
313}
314
315#[cfg(feature = "visitor")]
316impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for CowArcStr<'i> {
317  const CHILD_TYPES: VisitTypes = VisitTypes::empty();
318  fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {
319    Ok(())
320  }
321}
322
323/// A quoted CSS string.
324#[derive(Clone, Eq, Ord, Hash, Debug)]
325#[cfg_attr(feature = "visitor", derive(Visit))]
326#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
327#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
328#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
329pub struct CSSString<'i>(#[cfg_attr(feature = "serde", serde(borrow))] pub CowArcStr<'i>);
330
331impl<'i> Parse<'i> for CSSString<'i> {
332  fn parse<'t>(
333    input: &mut cssparser::Parser<'i, 't>,
334  ) -> Result<Self, cssparser::ParseError<'i, crate::error::ParserError<'i>>> {
335    let s = input.expect_string()?;
336    Ok(CSSString(s.into()))
337  }
338}
339
340impl<'i> ToCss for CSSString<'i> {
341  fn to_css<W>(&self, dest: &mut crate::printer::Printer<W>) -> Result<(), crate::error::PrinterError>
342  where
343    W: std::fmt::Write,
344  {
345    serialize_string(&self.0, dest)?;
346    Ok(())
347  }
348}
349
350impl<'i> cssparser::ToCss for CSSString<'i> {
351  fn to_css<W>(&self, dest: &mut W) -> fmt::Result
352  where
353    W: fmt::Write,
354  {
355    serialize_string(&self.0, dest)
356  }
357}
358
359macro_rules! impl_string_type {
360  ($t: ident) => {
361    impl<'i> From<CowRcStr<'i>> for $t<'i> {
362      fn from(s: CowRcStr<'i>) -> Self {
363        $t(s.into())
364      }
365    }
366
367    impl<'a> From<&CowRcStr<'a>> for $t<'a> {
368      fn from(s: &CowRcStr<'a>) -> Self {
369        $t(s.into())
370      }
371    }
372
373    impl<'i> From<String> for $t<'i> {
374      fn from(s: String) -> Self {
375        $t(s.into())
376      }
377    }
378
379    impl<'i> From<&'i str> for $t<'i> {
380      fn from(s: &'i str) -> Self {
381        $t(s.into())
382      }
383    }
384
385    impl<'a> From<std::borrow::Cow<'a, str>> for $t<'a> {
386      #[inline]
387      fn from(s: std::borrow::Cow<'a, str>) -> Self {
388        match s {
389          std::borrow::Cow::Borrowed(s) => s.into(),
390          std::borrow::Cow::Owned(s) => s.into(),
391        }
392      }
393    }
394
395    impl<'a> Deref for $t<'a> {
396      type Target = str;
397
398      #[inline]
399      fn deref(&self) -> &str {
400        self.0.deref()
401      }
402    }
403
404    impl<'a> AsRef<str> for $t<'a> {
405      #[inline]
406      fn as_ref(&self) -> &str {
407        self
408      }
409    }
410
411    impl<'a> Borrow<str> for $t<'a> {
412      #[inline]
413      fn borrow(&self) -> &str {
414        self
415      }
416    }
417
418    impl<'a> std::fmt::Display for $t<'a> {
419      #[inline]
420      fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
421        str::fmt(self, formatter)
422      }
423    }
424
425    impl<'a, T: AsRef<str>> PartialEq<T> for $t<'a> {
426      #[inline]
427      fn eq(&self, other: &T) -> bool {
428        str::eq(self, other.as_ref())
429      }
430    }
431
432    impl<'a, T: AsRef<str>> PartialOrd<T> for $t<'a> {
433      #[inline]
434      fn partial_cmp(&self, other: &T) -> Option<std::cmp::Ordering> {
435        str::partial_cmp(self, other.as_ref())
436      }
437    }
438  };
439}
440
441impl_string_type!(CSSString);
442
443pub(crate) use impl_string_type;