1 // Copyright 2014 The Servo Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
10 #![allow(non_upper_case_globals)]
12 #[cfg(feature = "heapsize")]
13 use heapsize
::HeapSizeOf
;
16 use serde
::{Deserialize, Deserializer, Serialize, Serializer}
;
18 use std
::ascii
::AsciiExt
;
20 use std
::cmp
::Ordering
::{self, Equal}
;
22 use std
::hash
::{Hash, Hasher}
;
23 use std
::marker
::PhantomData
;
29 use std
::sync
::atomic
::AtomicIsize
;
30 use std
::sync
::atomic
::Ordering
::SeqCst
;
32 use shared
::{STATIC_TAG
, INLINE_TAG
, DYNAMIC_TAG
, TAG_MASK
, MAX_INLINE_LEN
, STATIC_SHIFT_BITS
,
33 ENTRY_ALIGNMENT
, pack_static
};
34 use self::UnpackedAtom
::{Dynamic, Inline, Static}
;
36 #[cfg(feature = "log-events")]
39 #[cfg(not(feature = "log-events"))]
40 macro_rules
! log (($e
:expr
) => (()));
42 const NB_BUCKETS
: usize = 1 << 12; // 4096
43 const BUCKET_MASK
: u64 = (1 << 12) - 1;
46 buckets
: [Option
<Box
<StringCacheEntry
>>; NB_BUCKETS
],
49 #[cfg(feature = "heapsize")]
50 impl HeapSizeOf
for StringCache
{
51 fn heap_size_of_children(&self) -> usize {
52 self.buckets
.iter().fold(0, |size
, bucket
| size
+ bucket
.heap_size_of_children())
57 static ref STRING_CACHE
: Mutex
<StringCache
> = Mutex
::new(StringCache
::new());
60 /// A token that represents the heap used by the dynamic string cache.
61 #[cfg(feature = "heapsize")]
62 pub struct StringCacheHeap
;
64 #[cfg(feature = "heapsize")]
65 impl HeapSizeOf
for StringCacheHeap
{
66 fn heap_size_of_children(&self) -> usize {
67 STRING_CACHE
.lock().unwrap().heap_size_of_children()
71 struct StringCacheEntry
{
72 next_in_bucket
: Option
<Box
<StringCacheEntry
>>,
74 ref_count
: AtomicIsize
,
78 #[cfg(feature = "heapsize")]
79 impl HeapSizeOf
for StringCacheEntry
{
80 fn heap_size_of_children(&self) -> usize {
81 self.next_in_bucket
.heap_size_of_children() +
82 self.string
.heap_size_of_children()
86 impl StringCacheEntry
{
87 fn new(next
: Option
<Box
<StringCacheEntry
>>, hash
: u64, string
: String
)
92 ref_count
: AtomicIsize
::new(1),
93 string
: string
.into_boxed_str(),
99 fn new() -> StringCache
{
101 buckets
: unsafe { mem::zeroed() }
,
105 fn add(&mut self, string
: Cow
<str>, hash
: u64) -> *mut StringCacheEntry
{
106 let bucket_index
= (hash
& BUCKET_MASK
) as usize;
108 let mut ptr
: Option
<&mut Box
<StringCacheEntry
>> =
109 self.buckets
[bucket_index
].as_mut();
111 while let Some(entry
) = ptr
.take() {
112 if entry
.hash
== hash
&& &*entry
.string
== &*string
{
113 if entry
.ref_count
.fetch_add(1, SeqCst
) > 0 {
116 // Uh-oh. The pointer's reference count was zero, which means someone may try
117 // to free it. (Naive attempts to defend against this, for example having the
118 // destructor check to see whether the reference count is indeed zero, don't
119 // work due to ABA.) Thus we need to temporarily add a duplicate string to the
121 entry
.ref_count
.fetch_sub(1, SeqCst
);
124 ptr
= entry
.next_in_bucket
.as_mut();
127 debug_assert
!(mem
::align_of
::<StringCacheEntry
>() >= ENTRY_ALIGNMENT
);
128 let string
= string
.into_owned();
129 let _string_clone
= if cfg
!(feature
= "log-events") {
134 let mut entry
= Box
::new(StringCacheEntry
::new(
135 self.buckets
[bucket_index
].take(), hash
, string
));
136 let ptr
: *mut StringCacheEntry
= &mut *entry
;
137 self.buckets
[bucket_index
] = Some(entry
);
138 log
!(Event
::Insert(ptr
as u64, _string_clone
));
143 fn remove(&mut self, key
: u64) {
144 let ptr
= key
as *mut StringCacheEntry
;
146 let value
: &StringCacheEntry
= unsafe { &*ptr }
;
147 debug_assert
!(value
.ref_count
.load(SeqCst
) == 0);
148 (value
.hash
& BUCKET_MASK
) as usize
152 let mut current
: &mut Option
<Box
<StringCacheEntry
>> = &mut self.buckets
[bucket_index
];
155 let entry_ptr
: *mut StringCacheEntry
= match current
.as_mut() {
156 Some(entry
) => &mut **entry
,
159 if entry_ptr
== ptr
{
160 mem
::drop(mem
::replace(current
, unsafe { (*entry_ptr).next_in_bucket.take() }
));
163 current
= unsafe { &mut (*entry_ptr).next_in_bucket }
;
166 log
!(Event
::Remove(key
));
170 pub trait StaticAtomSet
{
171 fn get() -> &'
static PhfStrSet
;
172 fn empty_string_index() -> u32;
175 pub struct PhfStrSet
{
177 pub disps
: &'
static [(u32, u32)],
178 pub atoms
: &'
static [&'
static str],
179 pub hashes
: &'
static [u32],
182 pub struct EmptyStaticAtomSet
;
184 impl StaticAtomSet
for EmptyStaticAtomSet
{
185 fn get() -> &'
static PhfStrSet
{
186 // The name is a lie: this set is not empty (it contains the empty string)
187 // but that’s only to avoid divisions by zero in rust-phf.
188 static SET
: PhfStrSet
= PhfStrSet
{
192 // "" SipHash'd, and xored with u64_hash_to_u32.
193 hashes
: &[0x3ddddef3],
198 fn empty_string_index() -> u32 {
203 /// Use this if you don’t care about static atoms.
204 pub type DefaultAtom
= Atom
<EmptyStaticAtomSet
>;
206 pub struct Atom
<Static
: StaticAtomSet
> {
207 /// This field is public so that the `atom!()` macros can use it.
208 /// You should not otherwise access this field.
210 pub unsafe_data
: u64,
213 pub phantom
: PhantomData
<Static
>,
216 #[cfg(feature = "heapsize")]
217 impl<Static
: StaticAtomSet
> HeapSizeOf
for Atom
<Static
> {
219 fn heap_size_of_children(&self) -> usize {
224 impl<Static
: StaticAtomSet
> ::precomputed_hash
::PrecomputedHash
for Atom
<Static
> {
225 fn precomputed_hash(&self) -> u32 {
230 fn u64_hash_as_u32(h
: u64) -> u32 {
231 // This may or may not be great...
232 ((h
>> 32) ^ h
) as u32
235 impl<Static
: StaticAtomSet
> Atom
<Static
> {
237 unsafe fn unpack(&self) -> UnpackedAtom
{
238 UnpackedAtom
::from_packed(self.unsafe_data
)
241 pub fn get_hash(&self) -> u32 {
242 match unsafe { self.unpack() }
{
244 let static_set
= Static
::get();
245 static_set
.hashes
[index
as usize]
248 let entry
= entry
as *mut StringCacheEntry
;
249 u64_hash_as_u32(unsafe { (*entry).hash }
)
252 u64_hash_as_u32(self.unsafe_data
)
258 impl<Static
: StaticAtomSet
> Default
for Atom
<Static
> {
260 fn default() -> Self {
262 unsafe_data
: pack_static(Static
::empty_string_index()),
268 impl<Static
: StaticAtomSet
> Hash
for Atom
<Static
> {
270 fn hash
<H
>(&self, state
: &mut H
) where H
: Hasher
{
271 state
.write_u32(self.get_hash())
275 impl<Static
: StaticAtomSet
> Eq
for Atom
<Static
> {}
277 // NOTE: This impl requires that a given string must always be interned the same way.
278 impl<Static
: StaticAtomSet
> PartialEq
for Atom
<Static
> {
280 fn eq(&self, other
: &Self) -> bool
{
281 self.unsafe_data
== other
.unsafe_data
285 impl<Static
: StaticAtomSet
> PartialEq
<str> for Atom
<Static
> {
286 fn eq(&self, other
: &str) -> bool
{
291 impl<Static
: StaticAtomSet
> PartialEq
<Atom
<Static
>> for str {
292 fn eq(&self, other
: &Atom
<Static
>) -> bool
{
297 impl<Static
: StaticAtomSet
> PartialEq
<String
> for Atom
<Static
> {
298 fn eq(&self, other
: &String
) -> bool
{
299 &self[..] == &other
[..]
303 impl<'a
, Static
: StaticAtomSet
> From
<Cow
<'a
, str>> for Atom
<Static
> {
305 fn from(string_to_add
: Cow
<'a
, str>) -> Self {
306 let static_set
= Static
::get();
307 let hash
= phf_shared
::hash(&*string_to_add
, static_set
.key
);
308 let index
= phf_shared
::get_index(hash
, static_set
.disps
, static_set
.atoms
.len());
310 let unpacked
= if static_set
.atoms
[index
as usize] == string_to_add
{
313 let len
= string_to_add
.len();
314 if len
<= MAX_INLINE_LEN
{
315 let mut buf
: [u8; 7] = [0; 7];
316 buf
[..len
].copy_from_slice(string_to_add
.as_bytes());
317 Inline(len
as u8, buf
)
319 Dynamic(STRING_CACHE
.lock().unwrap().add(string_to_add
, hash
) as *mut ())
323 let data
= unsafe { unpacked.pack() }
;
324 log
!(Event
::Intern(data
));
325 Atom { unsafe_data: data, phantom: PhantomData }
329 impl<'a
, Static
: StaticAtomSet
> From
<&'a
str> for Atom
<Static
> {
331 fn from(string_to_add
: &str) -> Self {
332 Atom
::from(Cow
::Borrowed(string_to_add
))
336 impl<Static
: StaticAtomSet
> From
<String
> for Atom
<Static
> {
338 fn from(string_to_add
: String
) -> Self {
339 Atom
::from(Cow
::Owned(string_to_add
))
343 impl<Static
: StaticAtomSet
> Clone
for Atom
<Static
> {
345 fn clone(&self) -> Self {
347 match from_packed_dynamic(self.unsafe_data
) {
349 let entry
= entry
as *mut StringCacheEntry
;
350 (*entry
).ref_count
.fetch_add(1, SeqCst
);
356 unsafe_data
: self.unsafe_data
,
357 phantom
: PhantomData
,
362 impl<Static
: StaticAtomSet
> Drop
for Atom
<Static
> {
365 // Out of line to guide inlining.
366 fn drop_slow
<Static
: StaticAtomSet
>(this
: &mut Atom
<Static
>) {
367 STRING_CACHE
.lock().unwrap().remove(this
.unsafe_data
);
371 match from_packed_dynamic(self.unsafe_data
) {
373 let entry
= entry
as *mut StringCacheEntry
;
374 if (*entry
).ref_count
.fetch_sub(1, SeqCst
) == 1 {
384 impl<Static
: StaticAtomSet
> ops
::Deref
for Atom
<Static
> {
388 fn deref(&self) -> &str {
390 match self.unpack() {
392 let buf
= inline_orig_bytes(&self.unsafe_data
);
393 str::from_utf8_unchecked(buf
)
395 Static(idx
) => Static
::get().atoms
.get(idx
as usize).expect("bad static atom"),
397 let entry
= entry
as *mut StringCacheEntry
;
405 impl<Static
: StaticAtomSet
> fmt
::Display
for Atom
<Static
> {
407 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
408 <str as fmt
::Display
>::fmt(self, f
)
412 impl<Static
: StaticAtomSet
> fmt
::Debug
for Atom
<Static
> {
414 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
415 let ty_str
= unsafe {
416 match self.unpack() {
417 Dynamic(..) => "dynamic",
418 Inline(..) => "inline",
419 Static(..) => "static",
423 write
!(f
, "Atom('{}' type={})", &*self, ty_str
)
427 impl<Static
: StaticAtomSet
> PartialOrd
for Atom
<Static
> {
429 fn partial_cmp(&self, other
: &Self) -> Option
<Ordering
> {
430 if self.unsafe_data
== other
.unsafe_data
{
433 self.as_ref().partial_cmp(other
.as_ref())
437 impl<Static
: StaticAtomSet
> Ord
for Atom
<Static
> {
439 fn cmp(&self, other
: &Self) -> Ordering
{
440 if self.unsafe_data
== other
.unsafe_data
{
443 self.as_ref().cmp(other
.as_ref())
447 impl<Static
: StaticAtomSet
> AsRef
<str> for Atom
<Static
> {
448 fn as_ref(&self) -> &str {
453 impl<Static
: StaticAtomSet
> Serialize
for Atom
<Static
> {
454 fn serialize
<S
>(&self, serializer
: S
) -> Result
<S
::Ok
, S
::Error
> where S
: Serializer
{
455 let string
: &str = self.as_ref();
456 string
.serialize(serializer
)
460 impl<'a
, Static
: StaticAtomSet
> Deserialize
<'a
> for Atom
<Static
> {
461 fn deserialize
<D
>(deserializer
: D
) -> Result
<Self, D
::Error
> where D
: Deserializer
<'a
> {
462 let string
: String
= try
!(Deserialize
::deserialize(deserializer
));
463 Ok(Atom
::from(string
))
467 // AsciiExt requires mutating methods, so we just implement the non-mutating ones.
468 // We don't need to implement is_ascii because there's no performance improvement
469 // over the one from &str.
470 impl<Static
: StaticAtomSet
> Atom
<Static
> {
471 fn from_mutated_str
<F
: FnOnce(&mut str)>(s
: &str, f
: F
) -> Self {
472 let mut buffer
: [u8; 64] = unsafe { mem::uninitialized() }
;
473 if let Some(buffer_prefix
) = buffer
.get_mut(..s
.len()) {
474 buffer_prefix
.copy_from_slice(s
.as_bytes());
475 // FIXME: use from std::str when stable https://github.com/rust-lang/rust/issues/41119
476 pub unsafe fn from_utf8_unchecked_mut(v
: &mut [u8]) -> &mut str {
479 let as_str
= unsafe { from_utf8_unchecked_mut(buffer_prefix) }
;
483 let mut string
= s
.to_owned();
489 pub fn to_ascii_uppercase(&self) -> Self {
490 for (i
, b
) in self.bytes().enumerate() {
491 if let b'a'
... b'z'
= b
{
492 return Atom
::from_mutated_str(self, |s
| s
[i
..].make_ascii_uppercase())
498 pub fn to_ascii_lowercase(&self) -> Self {
499 for (i
, b
) in self.bytes().enumerate() {
500 if let b'A'
... b'Z'
= b
{
501 return Atom
::from_mutated_str(self, |s
| s
[i
..].make_ascii_lowercase())
507 pub fn eq_ignore_ascii_case(&self, other
: &Self) -> bool
{
508 (self == other
) || self.eq_str_ignore_ascii_case(&**other
)
511 pub fn eq_str_ignore_ascii_case(&self, other
: &str) -> bool
{
512 (&**self).eq_ignore_ascii_case(other
)
516 // Atoms use a compact representation which fits this enum in a single u64.
517 // Inlining avoids actually constructing the unpacked representation in memory.
518 #[allow(missing_copy_implementations)]
520 /// Pointer to a dynamic table entry. Must be 16-byte aligned!
523 /// Length + bytes of string.
526 /// Index in static interning table.
531 fn inline_atom_slice(x
: &u64) -> &[u8] {
533 let x
: *const u64 = x
;
534 let mut data
= x
as *const u8;
535 // All except the lowest byte, which is first in little-endian, last in big-endian.
536 if cfg
!(target_endian
= "little") {
537 data
= data
.offset(1);
540 slice
::from_raw_parts(data
, len
)
545 fn inline_atom_slice_mut(x
: &mut u64) -> &mut [u8] {
548 let mut data
= x
as *mut u8;
549 // All except the lowest byte, which is first in little-endian, last in big-endian.
550 if cfg
!(target_endian
= "little") {
551 data
= data
.offset(1);
554 slice
::from_raw_parts_mut(data
, len
)
560 unsafe fn pack(self) -> u64 {
562 Static(n
) => pack_static(n
),
565 debug_assert
!(0 == n
& TAG_MASK
);
568 Inline(len
, buf
) => {
569 debug_assert
!((len
as usize) <= MAX_INLINE_LEN
);
570 let mut data
: u64 = (INLINE_TAG
as u64) | ((len
as u64) << 4);
572 let dest
= inline_atom_slice_mut(&mut data
);
573 dest
.copy_from_slice(&buf
)
581 unsafe fn from_packed(data
: u64) -> UnpackedAtom
{
582 debug_assert
!(DYNAMIC_TAG
== 0); // Dynamic is untagged
584 match (data
& TAG_MASK
) as u8 {
585 DYNAMIC_TAG
=> Dynamic(data
as *mut ()),
586 STATIC_TAG
=> Static((data
>> STATIC_SHIFT_BITS
) as u32),
588 let len
= ((data
& 0xf0) >> 4) as usize;
589 debug_assert
!(len
<= MAX_INLINE_LEN
);
590 let mut buf
: [u8; 7] = [0; 7];
591 let src
= inline_atom_slice(&data
);
592 buf
.copy_from_slice(src
);
593 Inline(len
as u8, buf
)
595 _
=> debug_unreachable
!(),
600 /// Used for a fast path in Clone and Drop.
602 unsafe fn from_packed_dynamic(data
: u64) -> Option
<*mut ()> {
603 if (DYNAMIC_TAG
as u64) == (data
& TAG_MASK
) {
604 Some(data
as *mut ())
610 /// For as_slice on inline atoms, we need a pointer into the original
613 /// It's undefined behavior to call this on a non-inline atom!!
615 unsafe fn inline_orig_bytes
<'a
>(data
: &'a
u64) -> &'a
[u8] {
616 match UnpackedAtom
::from_packed(*data
) {
618 let src
= inline_atom_slice(&data
);
619 &src
[..(len
as usize)]
621 _
=> debug_unreachable
!(),
630 use super::{StaticAtomSet, StringCacheEntry}
;
631 use super::UnpackedAtom
::{Dynamic, Inline, Static}
;
632 use shared
::ENTRY_ALIGNMENT
;
634 include
!(concat
!(env
!("OUT_DIR"), "/test_atom.rs"));
635 pub type Atom
= TestAtom
;
639 let s0
= Atom
::from("");
640 assert
!(s0
.as_ref() == "");
642 let s1
= Atom
::from("class");
643 assert
!(s1
.as_ref() == "class");
645 let i0
= Atom
::from("blah");
646 assert
!(i0
.as_ref() == "blah");
648 let s0
= Atom
::from("BLAH");
649 assert
!(s0
.as_ref() == "BLAH");
651 let d0
= Atom
::from("zzzzzzzzzz");
652 assert
!(d0
.as_ref() == "zzzzzzzzzz");
654 let d1
= Atom
::from("ZZZZZZZZZZ");
655 assert
!(d1
.as_ref() == "ZZZZZZZZZZ");
658 macro_rules
! unpacks_to (($e
:expr
, $t
:pat
) => (
659 match unsafe { Atom::from($e).unpack() }
{
661 _
=> panic
!("atom has wrong type"),
667 unpacks_to
!("", Static(..));
668 unpacks_to
!("id", Static(..));
669 unpacks_to
!("body", Static(..));
670 unpacks_to
!("c", Inline(..)); // "z" is a static atom
671 unpacks_to
!("zz", Inline(..));
672 unpacks_to
!("zzz", Inline(..));
673 unpacks_to
!("zzzz", Inline(..));
674 unpacks_to
!("zzzzz", Inline(..));
675 unpacks_to
!("zzzzzz", Inline(..));
676 unpacks_to
!("zzzzzzz", Inline(..));
677 unpacks_to
!("zzzzzzzz", Dynamic(..));
678 unpacks_to
!("zzzzzzzzzzzzz", Dynamic(..));
683 let s0
= Atom
::from("fn");
684 let s1
= Atom
::from("fn");
685 let s2
= Atom
::from("loop");
687 let i0
= Atom
::from("blah");
688 let i1
= Atom
::from("blah");
689 let i2
= Atom
::from("blah2");
691 let d0
= Atom
::from("zzzzzzzz");
692 let d1
= Atom
::from("zzzzzzzz");
693 let d2
= Atom
::from("zzzzzzzzz");
711 assert_eq
!(TestAtom
::default(), test_atom
!(""));
712 assert_eq
!(&*TestAtom
::default(), "");
717 fn check(x
: &str, y
: &str) {
718 assert_eq
!(x
< y
, Atom
::from(x
) < Atom
::from(y
));
719 assert_eq
!(x
.cmp(y
), Atom
::from(x
).cmp(&Atom
::from(y
)));
720 assert_eq
!(x
.partial_cmp(y
), Atom
::from(x
).partial_cmp(&Atom
::from(y
)));
724 check("asdf", "body");
725 check("zasdf", "body");
729 check("asdf", "bbbbb");
730 check("zasdf", "bbbbb");
736 let s0
= Atom
::from("fn");
738 let s2
= Atom
::from("loop");
740 let i0
= Atom
::from("blah");
742 let i2
= Atom
::from("blah2");
744 let d0
= Atom
::from("zzzzzzzz");
746 let d2
= Atom
::from("zzzzzzzzz");
762 macro_rules
! assert_eq_fmt (($fmt
:expr
, $x
:expr
, $y
:expr
) => ({
766 panic
!("assertion failed: {} != {}",
767 format_args
!($fmt
, x
),
768 format_args
!($fmt
, y
));
774 fn check(s
: &str, data
: u64) {
775 assert_eq_fmt
!("0x{:016X}", Atom
::from(s
).unsafe_data
, data
);
778 fn check_static(s
: &str, x
: Atom
) {
779 assert_eq_fmt
!("0x{:016X}", x
.unsafe_data
, Atom
::from(s
).unsafe_data
);
780 assert_eq
!(0x2, x
.unsafe_data
& 0xFFFF_FFFF);
781 // The index is unspecified by phf.
782 assert
!((x
.unsafe_data
>> 32) <= TestAtomStaticSet
::get().atoms
.len() as u64);
785 // This test is here to make sure we don't change atom representation
786 // by accident. It may need adjusting if there are changes to the
787 // static atom table, the tag values, etc.
790 check_static("a", test_atom
!("a"));
791 check_static("address", test_atom
!("address"));
792 check_static("area", test_atom
!("area"));
795 check("e", 0x0000_0000_0000_6511);
796 check("xyzzy", 0x0000_797A_7A79_7851);
797 check("xyzzy01", 0x3130_797A_7A79_7871);
799 // Dynamic atoms. This is a pointer so we can't verify every bit.
800 assert_eq
!(0x00, Atom
::from("a dynamic string").unsafe_data
& 0xf);
806 struct EmptyWithDrop
;
807 impl Drop
for EmptyWithDrop
{
808 fn drop(&mut self) {}
810 let compiler_uses_inline_drop_flags
= mem
::size_of
::<EmptyWithDrop
>() > 0;
812 // Guard against accidental changes to the sizes of things.
813 assert_eq
!(mem
::size_of
::<Atom
>(),
814 if compiler_uses_inline_drop_flags { 16 }
else { 8 }
);
815 assert_eq
!(mem
::size_of
::<super::StringCacheEntry
>(),
816 8 + 4 * mem
::size_of
::<usize>());
821 for _
in 0_u32..100 {
822 thread
::spawn(move || {
823 let _
= Atom
::from("a dynamic string");
824 let _
= Atom
::from("another string");
831 assert_eq
!(test_atom
!("body"), Atom
::from("body"));
832 assert_eq
!(test_atom
!("font-weight"), Atom
::from("font-weight"));
837 assert_eq
!(2, match Atom
::from("head") {
838 test_atom
!("br") => 1,
839 test_atom
!("html") | test_atom
!("head") => 2,
843 assert_eq
!(3, match Atom
::from("body") {
844 test_atom
!("br") => 1,
845 test_atom
!("html") | test_atom
!("head") => 2,
849 assert_eq
!(3, match Atom
::from("zzzzzz") {
850 test_atom
!("br") => 1,
851 test_atom
!("html") | test_atom
!("head") => 2,
858 // Ensure we can Deref to a &str
859 let atom
= Atom
::from("foobar");
865 // Ensure we can as_ref to a &str
866 let atom
= Atom
::from("foobar");
867 let _
: &str = atom
.as_ref();
871 fn string_cache_entry_alignment_is_sufficient() {
872 assert
!(mem
::align_of
::<StringCacheEntry
>() >= ENTRY_ALIGNMENT
);
876 fn test_ascii_lowercase() {
877 assert_eq
!(Atom
::from("").to_ascii_lowercase(), Atom
::from(""));
878 assert_eq
!(Atom
::from("aZ9").to_ascii_lowercase(), Atom
::from("az9"));
879 assert_eq
!(Atom
::from("The Quick Brown Fox!").to_ascii_lowercase(), Atom
::from("the quick brown fox!"));
880 assert_eq
!(Atom
::from("JE VAIS À PARIS").to_ascii_lowercase(), Atom
::from("je vais À paris"));
884 fn test_ascii_uppercase() {
885 assert_eq
!(Atom
::from("").to_ascii_uppercase(), Atom
::from(""));
886 assert_eq
!(Atom
::from("aZ9").to_ascii_uppercase(), Atom
::from("AZ9"));
887 assert_eq
!(Atom
::from("The Quick Brown Fox!").to_ascii_uppercase(), Atom
::from("THE QUICK BROWN FOX!"));
888 assert_eq
!(Atom
::from("Je vais à Paris").to_ascii_uppercase(), Atom
::from("JE VAIS à PARIS"));
892 fn test_eq_ignore_ascii_case() {
893 assert
!(Atom
::from("").eq_ignore_ascii_case(&Atom
::from("")));
894 assert
!(Atom
::from("aZ9").eq_ignore_ascii_case(&Atom
::from("aZ9")));
895 assert
!(Atom
::from("aZ9").eq_ignore_ascii_case(&Atom
::from("Az9")));
896 assert
!(Atom
::from("The Quick Brown Fox!").eq_ignore_ascii_case(&Atom
::from("THE quick BROWN fox!")));
897 assert
!(Atom
::from("Je vais à Paris").eq_ignore_ascii_case(&Atom
::from("je VAIS à PARIS")));
898 assert
!(!Atom
::from("").eq_ignore_ascii_case(&Atom
::from("az9")));
899 assert
!(!Atom
::from("aZ9").eq_ignore_ascii_case(&Atom
::from("")));
900 assert
!(!Atom
::from("aZ9").eq_ignore_ascii_case(&Atom
::from("9Za")));
901 assert
!(!Atom
::from("The Quick Brown Fox!").eq_ignore_ascii_case(&Atom
::from("THE quick BROWN fox!!")));
902 assert
!(!Atom
::from("Je vais à Paris").eq_ignore_ascii_case(&Atom
::from("JE vais À paris")));
906 fn test_from_string() {
907 assert
!(Atom
::from("camembert".to_owned()) == Atom
::from("camembert"));
911 #[cfg(all(test, feature = "unstable"))]