1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use std::{
    path::{Path, PathBuf},
    sync::{atomic, Arc},
};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SourceType {
    Schema,
    Query,
    Document,
    BuiltIn,
}

impl SourceType {
    /// Returns `true` if the source type is [`BuiltIn`].
    ///
    /// [`BuiltIn`]: SourceType::BuiltIn
    #[must_use]
    pub fn is_built_in(&self) -> bool {
        matches!(self, Self::BuiltIn)
    }

    /// Returns `true` if the source type is [`Document`].
    ///
    /// [`Document`]: SourceType::Document
    #[must_use]
    pub fn is_document(&self) -> bool {
        matches!(self, Self::Document)
    }

    /// Returns `true` if the source type is [`Query`].
    ///
    /// [`Query`]: SourceType::Query
    #[must_use]
    pub fn is_query(&self) -> bool {
        matches!(self, Self::Query)
    }

    /// Returns `true` if the source type is [`Schema`].
    ///
    /// [`Schema`]: SourceType::Schema
    #[must_use]
    pub fn is_schema(&self) -> bool {
        matches!(self, Self::Schema)
    }
}

/// Represents a GraphQL source file.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Source {
    ty: SourceType,
    filename: PathBuf,
    text: Arc<str>,
}

impl Source {
    /// Create a GraphQL schema source file.
    pub fn schema(filename: PathBuf, text: impl Into<Arc<str>>) -> Self {
        Self {
            ty: SourceType::Schema,
            filename,
            text: text.into(),
        }
    }

    /// Create a GraphQL executable source file.
    pub fn executable(filename: PathBuf, text: impl Into<Arc<str>>) -> Self {
        Self {
            ty: SourceType::Query,
            filename,
            text: text.into(),
        }
    }

    /// Create a GraphQL document source file.
    ///
    /// A Document can contain type definitions *and* executable definitions. You can also use it
    /// when you don't know the actual source type.
    pub fn document(filename: PathBuf, text: impl Into<Arc<str>>) -> Self {
        Self {
            ty: SourceType::Document,
            filename,
            text: text.into(),
        }
    }
    /// Create a GraphQL type system file with built in types.
    pub(crate) fn built_in(filename: PathBuf, text: impl Into<Arc<str>>) -> Self {
        Self {
            ty: SourceType::BuiltIn,
            filename,
            text: text.into(),
        }
    }

    pub fn filename(&self) -> &Path {
        &self.filename
    }

    pub fn source_type(&self) -> SourceType {
        self.ty
    }

    pub fn text(&self) -> Arc<str> {
        Arc::clone(&self.text)
    }
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct FileId {
    id: u64,
}

impl From<u64> for FileId {
    fn from(val: u64) -> Self {
        Self { id: val }
    }
}

/// The next file ID to use. This is global so file IDs do not conflict between different compiler
/// instances.
static NEXT: atomic::AtomicU64 = atomic::AtomicU64::new(1);

impl FileId {
    // Returning a different value every time does not sound like good `impl Default`
    #[allow(clippy::new_without_default)]
    pub fn new() -> Self {
        Self {
            id: NEXT.fetch_add(1, atomic::Ordering::Relaxed),
        }
    }

    // Exposed for tests, but relying on the test order is probably not a good idea…
    pub(crate) fn as_u64(self) -> u64 {
        self.id
    }

    /// Reset file ID back to 1, used to get consistent results in tests.
    #[allow(unused)]
    pub(crate) fn reset() {
        NEXT.store(1, atomic::Ordering::SeqCst);
    }
}