1use crate::diagnostic::CliReport;
2use crate::diagnostic::ToCliReport;
3use crate::node::ExtensionId;
4use crate::parser::FileId;
5use crate::parser::LineColumn;
6use crate::parser::SourceMap;
7use crate::parser::SourceSpan;
8use crate::parser::TaggedFileId;
9use crate::Node;
10use rowan::TextRange;
11use std::fmt;
12use std::marker::PhantomData;
13use std::mem::size_of;
14use std::mem::ManuallyDrop;
15use std::ops::Range;
16use std::ptr::NonNull;
17use std::sync::Arc;
18
19#[macro_export]
40macro_rules! name {
41 ($value: ident) => {
42 $crate::name!(stringify!($value))
43 };
44 ($value: expr) => {{
45 const _: () = { assert!($crate::Name::is_valid_syntax($value)) };
46 $crate::Name::new_static_unchecked(&$value)
47 }};
48}
49
50pub struct Name {
60 ptr: NonNull<u8>,
63 len: u32,
64 start_offset: u32, tagged_file_id: TaggedFileId, phantom: PhantomData<UnpackedRepr>,
67}
68
69#[allow(dead_code)] enum UnpackedRepr {
71 Heap(Arc<str>),
72 Static(&'static str),
73}
74
75#[derive(Clone, Eq, PartialEq, thiserror::Error)]
78#[error("`{name}` is not a valid GraphQL name")]
79pub struct InvalidNameError {
80 pub name: String,
81 pub location: Option<SourceSpan>,
82}
83
84const TAG_ARC: bool = true;
85const TAG_STATIC: bool = false;
86
87const _: () = {
88 assert!(size_of::<Name>() == 24);
91 assert!(size_of::<Name>() == size_of::<Option<Name>>());
92
93 const fn assert_send_and_sync<T: Send + Sync>() {}
95 assert_send_and_sync::<(UnpackedRepr, u32, TaggedFileId)>();
96};
97
98unsafe impl Send for Name {}
99
100unsafe impl Sync for Name {}
101
102impl Name {
103 pub fn new(value: &str) -> Result<Self, InvalidNameError> {
105 Self::check_valid_syntax(value)?;
106 Ok(Self::new_unchecked(value))
107 }
108
109 pub fn new_static(value: &'static str) -> Result<Self, InvalidNameError> {
111 Self::check_valid_syntax(value)?;
112 Ok(Self::new_static_unchecked(value))
113 }
114
115 pub fn new_unchecked(value: &str) -> Self {
120 Self::from_arc_unchecked(value.into())
121 }
122
123 pub fn from_arc_unchecked(arc: Arc<str>) -> Self {
128 let len = Self::new_len(&arc);
129 let ptr = Arc::into_raw(arc).cast_mut().cast();
130 let ptr = unsafe { NonNull::new_unchecked(ptr) };
132 Self {
133 ptr,
134 len,
135 start_offset: 0,
136 tagged_file_id: TaggedFileId::pack(TAG_ARC, FileId::NONE),
137 phantom: PhantomData,
138 }
139 }
140
141 pub const fn new_static_unchecked(value: &'static str) -> Self {
147 let ptr = value.as_ptr().cast_mut();
148 let ptr = unsafe { NonNull::new_unchecked(ptr) };
150 Self {
151 ptr,
152 len: Self::new_len(value),
153 start_offset: 0,
154 tagged_file_id: TaggedFileId::pack(TAG_STATIC, FileId::NONE),
155 phantom: PhantomData,
156 }
157 }
158
159 pub fn with_location(mut self, location: SourceSpan) -> Self {
161 debug_assert_eq!(location.text_range.len(), self.len.into());
162 self.start_offset = location.text_range.start().into();
163 self.tagged_file_id = TaggedFileId::pack(self.tagged_file_id.tag(), location.file_id);
164 self
165 }
166
167 const fn new_len(value: &str) -> u32 {
168 let len = value.len();
169 if len >= (u32::MAX as usize) {
170 panic!("Name length overflows 4 GiB")
171 }
172 len as _
173 }
174
175 pub fn location(&self) -> Option<SourceSpan> {
178 let file_id = self.tagged_file_id.file_id();
179 if file_id != FileId::NONE {
180 Some(SourceSpan {
181 file_id,
182 text_range: TextRange::at(self.start_offset.into(), self.len.into()),
183 })
184 } else {
185 None
186 }
187 }
188
189 pub fn line_column_range(&self, sources: &SourceMap) -> Option<Range<LineColumn>> {
191 self.location()?.line_column_range(sources)
192 }
193
194 #[allow(clippy::len_without_is_empty)] #[inline]
196 pub fn len(&self) -> usize {
197 self.len as _
198 }
199
200 #[inline]
201 pub fn as_str(&self) -> &str {
202 let slice = NonNull::slice_from_raw_parts(self.ptr, self.len());
203 unsafe { std::str::from_utf8_unchecked(slice.as_ref()) }
206 }
207
208 pub fn as_static_str(&self) -> Option<&'static str> {
213 if self.tagged_file_id.tag() == TAG_STATIC {
214 let raw_slice = NonNull::slice_from_raw_parts(self.ptr, self.len());
215 Some(unsafe { std::str::from_utf8_unchecked(raw_slice.as_ref()) })
218 } else {
219 None
220 }
221 }
222
223 fn as_arc(&self) -> Option<ManuallyDrop<Arc<str>>> {
224 if self.tagged_file_id.tag() == TAG_ARC {
225 let raw_slice = NonNull::slice_from_raw_parts(self.ptr, self.len())
226 .as_ptr()
227 .cast_const();
228
229 Some(ManuallyDrop::new(unsafe {
235 Arc::from_raw(raw_slice as *const str)
236 }))
237 } else {
238 None
239 }
240 }
241
242 pub fn to_cloned_arc(&self) -> Option<Arc<str>> {
246 self.as_arc()
247 .map(|manually_drop| Arc::clone(&manually_drop))
248 }
249
250 pub const fn is_valid_syntax(value: &str) -> bool {
253 let bytes = value.as_bytes();
254 let Some(&first) = bytes.first() else {
255 return false;
256 };
257 if !Self::is_name_start(first) {
258 return false;
259 }
260 let mut i = 1;
262 while i < bytes.len() {
263 if !Self::is_name_continue(bytes[i]) {
264 return false;
265 }
266 i += 1
267 }
268 true
269 }
270
271 fn check_valid_syntax(value: &str) -> Result<(), InvalidNameError> {
272 if Self::is_valid_syntax(value) {
273 Ok(())
274 } else {
275 Err(InvalidNameError {
276 name: value.to_owned(),
277 location: None,
278 })
279 }
280 }
281
282 const fn is_name_start(byte: u8) -> bool {
284 byte.is_ascii_alphabetic() || byte == b'_'
285 }
286
287 const fn is_name_continue(byte: u8) -> bool {
289 byte.is_ascii_alphanumeric() || byte == b'_'
290 }
291
292 pub fn to_node(&self, extension_id: Option<ExtensionId>) -> Node<Name> {
295 let mut node = Node::new_opt_location(self.clone(), self.location());
296 if let Some(id) = extension_id {
297 node.set_extension_id(id);
298 }
299 node
300 }
301}
302
303impl Clone for Name {
304 fn clone(&self) -> Self {
305 if let Some(arc) = self.as_arc() {
306 let _ptr = Arc::into_raw(Arc::clone(&arc));
307 }
310 Self { ..*self }
311 }
312}
313
314impl Drop for Name {
315 fn drop(&mut self) {
316 if let Some(arc) = &mut self.as_arc() {
317 unsafe { ManuallyDrop::drop(arc) }
319 }
320 }
321}
322
323impl std::hash::Hash for Name {
324 #[inline]
325 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
326 self.as_str().hash(state) }
328}
329
330impl std::ops::Deref for Name {
331 type Target = str;
332
333 #[inline]
334 fn deref(&self) -> &Self::Target {
335 self.as_str()
336 }
337}
338
339impl AsRef<str> for Name {
340 #[inline]
341 fn as_ref(&self) -> &str {
342 self.as_str()
343 }
344}
345
346impl std::borrow::Borrow<str> for Name {
347 fn borrow(&self) -> &str {
348 self.as_str()
349 }
350}
351
352impl std::fmt::Debug for Name {
353 #[inline]
354 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355 self.as_str().fmt(f)
356 }
357}
358
359impl std::fmt::Display for Name {
360 #[inline]
361 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
362 self.as_str().fmt(f)
363 }
364}
365
366impl Eq for Name {}
367
368impl PartialEq for Name {
369 #[inline]
370 fn eq(&self, other: &Self) -> bool {
371 self.as_str() == other.as_str() }
373}
374
375impl Ord for Name {
376 #[inline]
377 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
378 self.as_str().cmp(other.as_str())
379 }
380}
381
382impl PartialOrd for Name {
383 #[inline]
384 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
385 Some(self.cmp(other))
386 }
387}
388
389impl std::borrow::Borrow<str> for Node<Name> {
390 fn borrow(&self) -> &str {
391 self.as_str()
392 }
393}
394
395impl PartialEq<str> for Node<Name> {
396 fn eq(&self, other: &str) -> bool {
397 self.as_str() == other
398 }
399}
400
401impl<T: AsRef<str>> PartialEq<T> for Node<Name> {
402 fn eq(&self, other: &T) -> bool {
403 self.as_str() == other.as_ref()
404 }
405}
406
407impl PartialEq<str> for Name {
408 #[inline]
409 fn eq(&self, other: &str) -> bool {
410 self.as_str() == other
411 }
412}
413
414impl PartialOrd<str> for Name {
415 #[inline]
416 fn partial_cmp(&self, other: &str) -> Option<std::cmp::Ordering> {
417 self.as_str().partial_cmp(other)
418 }
419}
420
421impl PartialEq<&'_ str> for Name {
422 #[inline]
423 fn eq(&self, other: &&'_ str) -> bool {
424 self.as_str() == *other
425 }
426}
427
428impl PartialOrd<&'_ str> for Name {
429 #[inline]
430 fn partial_cmp(&self, other: &&'_ str) -> Option<std::cmp::Ordering> {
431 self.as_str().partial_cmp(*other)
432 }
433}
434
435impl From<&'_ Self> for Name {
436 #[inline]
437 fn from(value: &'_ Self) -> Self {
438 value.clone()
439 }
440}
441
442impl From<Name> for Arc<str> {
443 fn from(value: Name) -> Self {
444 match value.to_cloned_arc() {
445 Some(arc) => arc,
446 None => value.as_str().into(),
447 }
448 }
449}
450
451impl TryFrom<Arc<str>> for Name {
452 type Error = InvalidNameError;
453
454 fn try_from(value: Arc<str>) -> Result<Self, Self::Error> {
455 Self::check_valid_syntax(&value)?;
456 Ok(Self::from_arc_unchecked(value))
457 }
458}
459
460impl serde::Serialize for Name {
461 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
462 where
463 S: serde::Serializer,
464 {
465 serializer.serialize_str(self.as_str())
466 }
467}
468
469impl<'de> serde::Deserialize<'de> for Name {
470 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
471 where
472 D: serde::Deserializer<'de>,
473 {
474 const EXPECTING: &str = "a string in GraphQL Name syntax";
475 struct Visitor;
476 impl serde::de::Visitor<'_> for Visitor {
477 type Value = Name;
478
479 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
480 formatter.write_str(EXPECTING)
481 }
482
483 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
484 where
485 E: serde::de::Error,
486 {
487 Name::new(v)
488 .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &EXPECTING))
489 }
490 }
491 deserializer.deserialize_str(Visitor)
492 }
493}
494
495impl TryFrom<&str> for Name {
496 type Error = InvalidNameError;
497
498 fn try_from(value: &str) -> Result<Self, Self::Error> {
499 Self::new(value)
500 }
501}
502
503impl TryFrom<String> for Name {
504 type Error = InvalidNameError;
505
506 fn try_from(value: String) -> Result<Self, Self::Error> {
507 Self::new(&value)
508 }
509}
510
511impl TryFrom<&'_ String> for Name {
512 type Error = InvalidNameError;
513
514 fn try_from(value: &'_ String) -> Result<Self, Self::Error> {
515 Self::new(value)
516 }
517}
518
519impl AsRef<Name> for Name {
520 fn as_ref(&self) -> &Name {
521 self
522 }
523}
524
525impl ToCliReport for InvalidNameError {
526 fn location(&self) -> Option<SourceSpan> {
527 self.location
528 }
529 fn report(&self, report: &mut CliReport) {
530 report.with_label_opt(self.location, "cannot be parsed as a GraphQL Name");
531 }
532}
533
534impl fmt::Debug for InvalidNameError {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 fmt::Display::fmt(self, f)
537 }
538}