Skip to main content

dada_ir_ast/
span.rs

1use dada_util::FromImpls;
2use salsa::Update;
3use serde::Serialize;
4
5use crate::{
6    ast::{AstAggregate, AstFunction},
7    inputs::SourceFile,
8};
9
10#[derive(
11    Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Update, FromImpls, Serialize,
12)]
13pub enum Anchor<'db> {
14    SourceFile(SourceFile),
15    Class(AstAggregate<'db>),
16    Function(AstFunction<'db>),
17}
18
19impl<'db> Anchor<'db> {
20    pub fn span(&self, db: &'db dyn crate::Db) -> Span<'db> {
21        match self {
22            Anchor::SourceFile(source_file) => Span {
23                anchor: *self,
24                start: Offset::ZERO,
25                end: Offset::from(source_file.contents_if_ok(db).len()),
26            },
27            Anchor::Class(data) => data.span(db),
28            Anchor::Function(data) => data.span(db),
29        }
30    }
31
32    /// Compute the absolute span of this anchor's contents.
33    pub fn absolute_span_of_contents(&self, db: &'db dyn crate::Db) -> AbsoluteSpan {
34        match self {
35            Anchor::SourceFile(source_file) => source_file.absolute_span(db),
36
37            // For most anchors, we have to skip past the `{}` or `()` in the delimiters by invoking `narrow`.
38            Anchor::Class(data) => data.span(db).absolute_span(db).narrow(),
39            Anchor::Function(data) => data.span(db).absolute_span(db).narrow(),
40        }
41    }
42
43    pub fn source_file(&self, db: &dyn crate::Db) -> SourceFile {
44        match self {
45            Anchor::SourceFile(source_file) => *source_file,
46            Anchor::Class(ast_class_item) => ast_class_item.name_span(db).source_file(db),
47            Anchor::Function(ast_function) => ast_function.name(db).span.source_file(db),
48        }
49    }
50}
51
52/// A span within the input.
53///
54/// The offsets are stored relative to the start of the **anchor**,
55/// which is some item (e.g., a class, function, etc). The use of relative offsets avoids
56/// incremental churn if lines or content is added before/after the definition.
57#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Update)]
58pub struct Span<'db> {
59    pub anchor: Anchor<'db>,
60    pub start: Offset,
61    pub end: Offset,
62}
63
64impl serde::Serialize for Span<'_> {
65    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
66    where
67        S: serde::Serializer,
68    {
69        salsa::with_attached_database(|db| {
70            let db: &dyn crate::Db = db.as_view();
71            serde::Serialize::serialize(&self.absolute_span(db), serializer)
72        })
73        .unwrap_or_else(|| panic!("cannot serialize without attached database"))
74    }
75}
76
77impl std::fmt::Debug for Span<'_> {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.debug_struct("Span")
80            .field("start", &self.start)
81            .field("end", &self.end)
82            .field("anchor", &"...")
83            .finish()
84    }
85}
86
87/// An absolute span within the input. The offsets are stored as absolute offsets
88/// within a given source file. These are used for diagnostics or outputs but not
89/// internally during compilation.
90#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize)]
91pub struct AbsoluteSpan {
92    pub source_file: SourceFile,
93    pub start: AbsoluteOffset,
94    pub end: AbsoluteOffset,
95}
96
97impl AbsoluteSpan {
98    /// Skip one character at the start/end of the span.
99    /// Used to skip past delimiters when computing absolute spans.
100    pub fn narrow(mut self) -> Self {
101        self.start = self.start + Offset::ONE;
102        self.end = self.end - Offset::ONE;
103        assert!(self.start <= self.end);
104        self
105    }
106
107    /// Convert into a span anchored at the source file.
108    pub fn into_span(self, _db: &dyn crate::Db) -> Span<'_> {
109        Span {
110            anchor: self.source_file.into(),
111            start: Offset::from(self.start),
112            end: Offset::from(self.end),
113        }
114    }
115
116    /// True if `self` contains all of `other`
117    pub fn contains(self, other: AbsoluteSpan) -> bool {
118        self.source_file == other.source_file && self.start <= other.start && self.end >= other.end
119    }
120}
121
122impl<'db> Span<'db> {
123    pub fn to(self, db: &'db dyn crate::Db, end: impl IntoOptionSpan<'db>) -> Span<'db> {
124        match end.into_opt_span() {
125            Some(end) => {
126                if self.anchor == end.anchor {
127                    Span {
128                        anchor: self.anchor,
129                        start: self.start,
130                        end: end.end,
131                    }
132                } else {
133                    // this invariant can fail when errors occur etc.
134                    // for now just convert to absolute spans, though we
135                    // could probably be more precise.
136                    self.absolute_span(db)
137                        .into_span(db)
138                        .to(db, end.absolute_span(db).into_span(db))
139                }
140            }
141            None => self,
142        }
143    }
144
145    pub fn start_from(self, start: impl IntoOptionSpan<'db>) -> Span<'db> {
146        match start.into_opt_span() {
147            Some(start) => {
148                assert!(self.anchor == start.anchor);
149                Span {
150                    anchor: self.anchor,
151                    start: start.start,
152                    end: self.end,
153                }
154            }
155            None => self,
156        }
157    }
158
159    /// Span pointing at the start of `self`
160    pub fn at_start(self) -> Span<'db> {
161        Span {
162            anchor: self.anchor,
163            start: self.end,
164            end: self.end,
165        }
166    }
167
168    /// Span pointing at the end of `self`
169    pub fn at_end(self) -> Span<'db> {
170        Span {
171            anchor: self.anchor,
172            start: self.end,
173            end: self.end,
174        }
175    }
176
177    /// Convert this span into an absolute span for reporting errors.
178    pub fn absolute_span(&self, db: &'db dyn crate::Db) -> AbsoluteSpan {
179        let anchor_span = self.anchor.absolute_span_of_contents(db);
180        AbsoluteSpan {
181            source_file: anchor_span.source_file,
182            start: anchor_span.start + self.start,
183            end: anchor_span.start + self.end,
184        }
185    }
186
187    pub fn source_file(&self, db: &dyn crate::Db) -> SourceFile {
188        self.anchor.source_file(db)
189    }
190}
191
192impl<'db> Spanned<'db> for Span<'db> {
193    fn span(&self, _db: &'db dyn crate::Db) -> Span<'db> {
194        *self
195    }
196}
197
198/// Implemented by all things that have a span (and span itself).
199///
200/// For AST nodes, yields the entire encompassing span.
201///
202/// For symbols, yields a span intended for use in error reporting.
203pub trait Spanned<'db> {
204    fn span(&self, db: &'db dyn crate::Db) -> Span<'db>;
205}
206
207/// Returns the span of this item in the source.
208///
209/// This is distinct from the [`Spanned`] impl, which returns
210/// the span best used in error reporting.
211pub trait SourceSpanned<'db> {
212    fn source_span(&self, db: &'db dyn crate::Db) -> Span<'db>;
213}
214
215/// Either `Span` or `Option<Span>`.
216pub trait IntoOptionSpan<'db> {
217    fn into_opt_span(self) -> Option<Span<'db>>;
218}
219
220impl<'db> IntoOptionSpan<'db> for Span<'db> {
221    fn into_opt_span(self) -> Option<Span<'db>> {
222        Some(self)
223    }
224}
225
226impl<'db> IntoOptionSpan<'db> for Option<Span<'db>> {
227    fn into_opt_span(self) -> Option<Span<'db>> {
228        self
229    }
230}
231
232#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize)]
233pub struct Offset(u32);
234
235impl From<usize> for Offset {
236    fn from(offset: usize) -> Self {
237        assert!(offset < u32::MAX as usize);
238        Offset(offset as u32)
239    }
240}
241
242impl From<u32> for Offset {
243    fn from(offset: u32) -> Self {
244        Offset(offset)
245    }
246}
247
248impl From<AbsoluteOffset> for Offset {
249    fn from(offset: AbsoluteOffset) -> Self {
250        Offset(offset.0)
251    }
252}
253
254impl Offset {
255    pub const ZERO: Offset = Offset(0);
256    pub const ONE: Offset = Offset(1);
257
258    pub fn as_u32(&self) -> u32 {
259        self.0
260    }
261
262    pub fn as_usize(&self) -> usize {
263        self.0 as usize
264    }
265}
266
267impl std::ops::Add<usize> for Offset {
268    type Output = Offset;
269
270    fn add(self, rhs: usize) -> Self::Output {
271        assert!(rhs < u32::MAX as usize);
272        Offset(self.0.checked_add(rhs as u32).unwrap())
273    }
274}
275
276impl std::ops::Add<Offset> for Offset {
277    type Output = Offset;
278
279    fn add(self, rhs: Offset) -> Self::Output {
280        Offset(self.0.checked_add(rhs.0).unwrap())
281    }
282}
283
284impl std::ops::Sub<Offset> for Offset {
285    type Output = Offset;
286
287    fn sub(self, rhs: Offset) -> Self::Output {
288        Offset(self.0.checked_sub(rhs.0).unwrap())
289    }
290}
291
292#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize)]
293pub struct AbsoluteOffset(u32);
294
295impl AbsoluteOffset {
296    pub const ZERO: AbsoluteOffset = AbsoluteOffset(0);
297
298    pub fn as_usize(self) -> usize {
299        self.0 as usize
300    }
301}
302
303impl From<usize> for AbsoluteOffset {
304    fn from(offset: usize) -> Self {
305        assert!(offset < u32::MAX as usize);
306        AbsoluteOffset(offset as u32)
307    }
308}
309
310impl From<u32> for AbsoluteOffset {
311    fn from(offset: u32) -> Self {
312        AbsoluteOffset(offset)
313    }
314}
315
316impl std::ops::Add<Offset> for AbsoluteOffset {
317    type Output = AbsoluteOffset;
318
319    fn add(self, rhs: Offset) -> Self::Output {
320        AbsoluteOffset(self.0.checked_add(rhs.0).unwrap())
321    }
322}
323
324impl std::ops::Sub<Offset> for AbsoluteOffset {
325    type Output = AbsoluteOffset;
326
327    fn sub(self, rhs: Offset) -> Self::Output {
328        AbsoluteOffset(self.0.checked_sub(rhs.0).unwrap())
329    }
330}
331
332impl std::ops::Sub<AbsoluteOffset> for AbsoluteOffset {
333    type Output = u32;
334
335    fn sub(self, rhs: AbsoluteOffset) -> Self::Output {
336        self.0.checked_sub(rhs.0).unwrap()
337    }
338}
339
340impl PartialOrd for AbsoluteSpan {
341    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
342        Some(Self::cmp(self, other))
343    }
344}
345
346/// Span A < Span B if:
347///
348/// * A is enclosed in B
349/// * A ends before B ends
350/// * A starts before B starts
351impl Ord for AbsoluteSpan {
352    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
353        let AbsoluteSpan {
354            source_file,
355            start,
356            end,
357        } = self;
358        let AbsoluteSpan {
359            source_file: other_source_file,
360            start: other_start,
361            end: other_end,
362        } = other;
363
364        // Same starting point...
365
366        //      ^^^^^^^^^^^^^
367        //           ==
368        //      ^^^^^^^^^^^^^
369
370        //      ^^^^^^^^^^^^^^^^
371        //           >
372        //      ^^^^^^^^^^^^^
373
374        //      ^^^^^^^^
375        //           <
376        //      ^^^^^^^^^^^^^
377
378        // Less starting point...
379
380        //      ^^^^^^^^^^^^^
381        //           >
382        //    ^^^^^^^^^^^^
383
384        //      ^^^^^^^^^^^^^
385        //           <
386        //    ^^^^^^^^^^^^^^^
387
388        //      ^^^^^^^^^^^^^
389        //           <
390        //    ^^^^^^^^^^^^^^^^^^^
391
392        // Greater starting point
393
394        //      ^^^^^^^^^^^^^
395        //            <
396        //    ^^^^^^^^^^^^^^^^^^
397
398        //      ^^^^^^^^^^^^^
399        //            <
400        //            ^^^^^^^^^^
401
402        //      ^^^^^^^^^^^^^
403        //            <
404        //    ^^^^^^^^^^^^^^^
405
406        //      ^^^^^^^^^^^^^
407        //           ==
408        //      ^^^^^^^^^^^^^
409
410        //      ^^^^^^^^^^^^^
411        //           >
412        //         ^^^^^^^^^^
413
414        (source_file, end, other_start).cmp(&(other_source_file, other_end, start))
415    }
416}
417
418/// A zero-based line number
419#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize)]
420pub struct ZeroLine(u32);
421
422/// A one-based line number
423#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize)]
424pub struct OneLine(u32);
425
426/// A zero-based column number
427#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize)]
428pub struct ZeroColumn(u32);
429
430/// A one-based column number
431#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize)]
432pub struct OneColumn(u32);
433
434macro_rules! methods {
435    ($t:ident) => {
436        impl $t {
437            pub fn as_u32(self) -> u32 {
438                self.0
439            }
440
441            pub fn as_usize(self) -> usize {
442                self.0 as usize
443            }
444        }
445
446        impl From<usize> for $t {
447            fn from(offset: usize) -> Self {
448                assert!(offset < u32::MAX as usize);
449                $t(offset as u32)
450            }
451        }
452
453        impl From<u32> for $t {
454            fn from(offset: u32) -> Self {
455                $t(offset)
456            }
457        }
458
459        impl std::ops::Add<$t> for $t {
460            type Output = $t;
461
462            fn add(self, rhs: $t) -> Self::Output {
463                $t(self.0.checked_add(rhs.0).unwrap())
464            }
465        }
466
467        impl std::ops::Sub<$t> for $t {
468            type Output = $t;
469
470            fn sub(self, rhs: $t) -> Self::Output {
471                $t(self.0.checked_sub(rhs.0).unwrap())
472            }
473        }
474
475        impl std::ops::Add<u32> for $t {
476            type Output = $t;
477
478            fn add(self, rhs: u32) -> Self::Output {
479                $t(self.0.checked_add(rhs).unwrap())
480            }
481        }
482
483        impl std::ops::Sub<u32> for $t {
484            type Output = $t;
485
486            fn sub(self, rhs: u32) -> Self::Output {
487                $t(self.0.checked_sub(rhs).unwrap())
488            }
489        }
490    };
491}
492
493methods!(ZeroColumn);
494methods!(ZeroLine);
495methods!(OneColumn);
496methods!(OneLine);