marker_api/
span.rs

1use std::marker::PhantomData;
2
3use crate::{
4    common::{ExpnId, MacroId, SpanId, SpanSrcId, SymbolId},
5    context::with_cx,
6    diagnostic::Applicability,
7    ffi,
8    private::Sealed,
9};
10
11/// A byte position used for the start and end position of [`Span`]s.
12///
13/// This position can map to a source file or a virtual space, when it comes
14/// from the expansion of a macro. It's expected that a [`SpanPos`] always
15/// points to the start of a character. Indexing to the middle of a multi byte
16/// character can result in panics.
17///
18/// The start [`SpanPos`] doesn't have to start at zero, the order can be decided
19/// by the driver. A [`Span`] should always use [`SpanPos`] from the same span source.
20///
21/// **Stability notice**:
22/// * The position may not be stable between different sessions.
23/// * [`SpanPos`] should never be stored by lint crates, as drivers might change [`SpanPos`] between
24///   different `check_*` function calls.
25/// * The layout and size of this type might change. The type will continue to provide the current
26///   trait implementations.
27#[repr(C)]
28#[derive(Debug, Copy, Clone)]
29pub struct SpanPos(
30    /// Rustc only uses u32, therefore it should be safe to do the same. This
31    /// allows crates to have a total span size of ~4 GB (with expanded macros).
32    /// That sounds reasonable :D
33    u32,
34);
35
36#[cfg(feature = "driver-api")]
37impl SpanPos {
38    pub fn new(index: u32) -> Self {
39        Self(index)
40    }
41
42    pub fn index(self) -> u32 {
43        self.0
44    }
45}
46
47/// Information about a specific expansion.
48///
49/// [`Span`]s in Rust are structured in layers. The root layer is the source code
50/// written in a source file. Macros can be expanded to AST nodes with [`Span`]s.
51/// These are then added as a new layer, on top of the root. This struct provides
52/// the information about one expansion layer.
53///
54/// ### Simple Macro Rules
55///
56/// ```
57/// macro_rules! ex1 {
58///     () => {
59///         1 + 1
60///     };
61/// }
62///
63/// ex1!();
64/// ```
65///
66/// In this example `ex1!()` expands into the expression `1 + 1`. The [`Span`]
67/// of the binary expression and numbers will all be from an expansion. Snipping the
68/// [`Span`] of the binary expression would return `1 + 1` from inside the macro rules.
69///
70/// ### Macro Rules with Parameters
71///
72/// ```
73/// macro_rules! ex2 {
74///     ($a:literal, $b:literal) => {
75///         $a + $b
76///     };
77/// }
78///
79/// ex2!(1, 2);
80/// ```
81///
82/// In this example `ex2!(1, 2)` expands into the expression `1 + 2`. The [`Span`]
83/// of the binary expression is marked to come from the expansion of a macro.
84/// The `1` and `2` literals are marked as coming from the root layer, since
85/// they're actually written in the source file.
86///
87/// ### Macros Invoking Macros
88///
89/// ```
90/// macro_rules! ex3a {
91///     ($a:literal, $b:literal) => {
92///         $a + $b
93///     };
94/// }
95/// macro_rules! ex3b {
96///     ($a:literal) => {
97///         ex3a!($a, 3)
98///     };
99/// }
100///
101/// ex3b!(2);
102/// ```
103///
104/// In this example `ex3b!(2)` expands to `ex3a!(2, 3)` which in turn expands to
105/// `2 + 3`. This expansion has three layers, first the root, which contains the
106/// `ex3b!(2)` call. The next layer is the `ex3a!(2, 3)` call. The binary expression
107/// comes from the third layer, the number 3 from the second, and the number 2 from
108/// the root layer, since this one was actually written by the user.
109///
110/// ### Macros Creating Macros
111///
112/// ```
113/// macro_rules! ex4a {
114///     () => {
115///         macro_rules! ex4b {
116///             () => {
117///                 4 + 4
118///             };
119///         }
120///     };
121/// }
122///
123/// ex4a!();
124/// ex4b!();
125/// ```
126///
127/// This example expands `ex4a` into a new `ex4b` macro, which in turn expands
128/// into a `4 + 4` expression. The [`Span`] of the binary expression has three
129/// layers. First the root layer calling the `ex4b` macro, which calls the `ex4a`
130/// macro, which inturn expands into the `4 + 4` expression.
131///
132/// ### Proc Macros
133///
134/// Proc macros and some built-in macros are different from `macro_rules!` macros as
135/// they are opaque to the driver. It's just known that some tokens are provided as an
136/// input and somehow expanded. The [`Span`]s of the expanded tokens are marked as
137/// coming from an expansion by default. However, macro crates can sometimes override
138/// this with some trickery. (Please use this forbidden knowledge carefully.)
139#[repr(C)]
140#[derive(Debug)]
141pub struct ExpnInfo<'ast> {
142    _lifetime: PhantomData<&'ast ()>,
143    parent: ExpnId,
144    call_site: SpanId,
145    macro_id: MacroId,
146}
147
148impl<'ast> ExpnInfo<'ast> {
149    /// This returns [`Some`] if this expansion comes from another expansion.
150    #[must_use]
151    pub fn parent(&self) -> Option<&ExpnInfo<'ast>> {
152        with_cx(self, |cx| cx.span_expn_info(self.parent))
153    }
154
155    /// The [`Span`] that invoked the macro, that this expansion belongs to.
156    #[must_use]
157    pub fn call_site(&self) -> &Span<'ast> {
158        with_cx(self, |cx| cx.span(self.call_site))
159    }
160
161    pub fn macro_id(&self) -> MacroId {
162        self.macro_id
163    }
164}
165
166#[cfg(feature = "driver-api")]
167impl<'ast> ExpnInfo<'ast> {
168    #[must_use]
169    pub fn new(parent: ExpnId, call_site: SpanId, macro_id: MacroId) -> Self {
170        Self {
171            _lifetime: PhantomData,
172            parent,
173            call_site,
174            macro_id,
175        }
176    }
177}
178
179/// A region of code, used for snipping, lint emission, and the retrieval of
180/// context information.
181///
182/// [`Span`]s provide context information, like the file location or macro expansion
183/// that created this span. [`SpanPos`] values from different sources or files should
184/// not be mixed. Check out the documentation of [`SpanPos`] for more information.
185///
186/// [`Span`]s don't provide any way to map back to the AST nodes, that they
187/// belonged to. If you require this information, consider passing the nodes
188/// instead or alongside the [`Span`].
189///
190/// [`Span`]s with invalid positions in the `start` or `end` value can cause panics
191/// in the driver. Please handle them with care, and also consider that UTF-8 allows
192/// multiple bytes per character. Instances provided by the API or driver directly,
193/// are always valid.
194///
195/// Handling macros during linting can be difficult, generally it's advised to
196/// abort, if the code originates from a macro. The API provides an automatic way
197/// by setting the [`MacroReport`][crate::common::MacroReport] value during lint
198/// creation. If your lint is targeting code from macro expansions, please
199/// consider that users might not be able to influence the generated code. It's
200/// also worth checking that all linted nodes originate from the same macro expansion.
201/// Check out the documentation of [`ExpnInfo`].
202#[repr(C)]
203#[derive(Clone)]
204pub struct Span<'ast> {
205    _lifetime: PhantomData<&'ast ()>,
206    /// The source of this [`Span`]. The id space and position distribution is
207    /// decided by the driver. To get the full source information it might be
208    /// necessary to also pass the start and end position to the driver.
209    source_id: SpanSrcId,
210    /// This information could also be retrieved, by requesting the [`ExpnInfo`]
211    /// of this span. However, from looking at Clippy and rustc lints, it looks
212    /// like the main interest is, if this comes from a macro expansion, not from
213    /// which one. Having this boolean flag will be sufficient to answer this simple
214    /// question and will save on extra [`SpanSrcId`] mappings.
215    from_expansion: bool,
216    start: SpanPos,
217    end: SpanPos,
218}
219
220impl<'ast> std::fmt::Debug for Span<'ast> {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        fn fmt_pos(pos: Option<FilePos<'_>>) -> String {
223            match pos {
224                Some(pos) => format!("{}:{}", pos.line(), pos.column()),
225                None => "[invalid]".to_string(),
226            }
227        }
228
229        let src = self.source();
230        let name = match src {
231            SpanSource::File(file) => format!(
232                "{}:{} - {}",
233                file.file(),
234                fmt_pos(file.try_to_file_pos(self.start)),
235                fmt_pos(file.try_to_file_pos(self.end))
236            ),
237            SpanSource::Macro(expn) => format!("[Inside Macro] {:#?}", expn.call_site()),
238            SpanSource::Builtin(_) => "[Builtin]".to_string(),
239        };
240        f.debug_struct(&name).finish()
241    }
242}
243
244impl<'ast> Span<'ast> {
245    /// Returns `true`, if this [`Span`] comes from a macro expansion.
246    pub fn is_from_expansion(&self) -> bool {
247        self.from_expansion
248    }
249
250    /// Returns the code snippet that this [`Span`] refers to or [`None`] if the
251    /// snippet is unavailable.
252    ///
253    /// ```ignore
254    /// let variable = 15_000;
255    /// //             ^^^^^^
256    /// //             lit_span
257    ///
258    /// lit_span.snippet(); // -> Some("15_000")
259    /// ```
260    ///
261    /// There are several reasons, why a snippet might be unavailable. Also
262    /// depend on the used driver. You can also checkout the other snippet
263    /// methods to better deal with these cases:
264    /// * [`snippet_or`](Self::snippet_or)
265    /// * [`snippet_with_applicability`](Self::snippet_with_applicability)
266    #[must_use]
267    pub fn snippet(&self) -> Option<&'ast str> {
268        with_cx(self, |cx| cx.span_snipped(self))
269    }
270
271    /// Returns the code snippet that this [`Span`] refers to or the given default
272    /// if the snippet is unavailable.
273    ///
274    /// For placeholders, it's recommended to use angle brackets with information
275    /// should be filled out. For example, if you want to snip an expression, you
276    /// should use `<expr>` as the default value.
277    ///
278    /// If you're planning to use this snippet in a suggestion, consider using
279    /// [`snippet_with_applicability`](Self::snippet_with_applicability) instead.
280    pub fn snippet_or<'a, 'b>(&self, default: &'a str) -> &'b str
281    where
282        'a: 'b,
283        'ast: 'b,
284    {
285        self.snippet().unwrap_or(default)
286    }
287
288    /// Adjusts the given [`Applicability`] according to the context and returns the
289    /// code snippet that this [`Span`] refers to or the given default if the
290    /// snippet is unavailable.
291    ///
292    /// For the placeholder, it's recommended to use angle brackets with information
293    /// should be filled out. A placeholder for an expression should look like
294    /// this: `<expr>`
295    ///
296    /// The applicability will never be upgraded by this method. When you draft
297    /// suggestions, you'll generally start with the highest [`Applicability`]
298    /// your suggestion should have, and then use it with this snippet function
299    /// to adjust it accordingly. The applicability is then used to submit the
300    /// suggestion to the driver.
301    ///
302    /// Here is an example, for constructing a string with two expressions `a` and `b`:
303    ///
304    /// ```rust,ignore
305    /// let mut app = Applicability::MachineApplicable;
306    /// let sugg = format!(
307    ///     "{}..{}",
308    ///     a.span().snippet_with_applicability("<expr-a>", &mut app),
309    ///     b.span().snippet_with_applicability("<expr-b>", &mut app),
310    /// );
311    /// ```
312    pub fn snippet_with_applicability<'a, 'b>(&self, placeholder: &'a str, applicability: &mut Applicability) -> &'b str
313    where
314        'a: 'b,
315        'ast: 'b,
316    {
317        if *applicability != Applicability::Unspecified && self.is_from_expansion() {
318            *applicability = Applicability::MaybeIncorrect;
319        }
320        self.snippet().unwrap_or_else(|| {
321            if matches!(
322                *applicability,
323                Applicability::MachineApplicable | Applicability::MaybeIncorrect
324            ) {
325                *applicability = Applicability::HasPlaceholders;
326            }
327            placeholder
328        })
329    }
330
331    /// Returns the length of the this [`Span`] in bytes.
332    pub fn len(&self) -> usize {
333        (self.end.0 - self.start.0)
334            .try_into()
335            .expect("Marker is not compiled for usize::BITs < 32")
336    }
337
338    /// Returns `true` if the span has a length of 0. This means that no bytes are
339    /// inside the span.
340    pub fn is_empty(&self) -> bool {
341        self.len() == 0
342    }
343
344    /// Returns the start position of this [`Span`].
345    pub fn start(&self) -> SpanPos {
346        self.start
347    }
348
349    /// Sets the start position of this [`Span`].
350    pub fn set_start(&mut self, start: SpanPos) {
351        assert!(
352            start.0 <= self.end.0,
353            "the start position should always be <= of the end position"
354        );
355        self.start = start;
356    }
357
358    /// Returns a new [`Span`] with the given start position.
359    #[must_use]
360    pub fn with_start(&self, start: SpanPos) -> Span<'ast> {
361        let mut new_span = self.clone();
362        new_span.set_start(start);
363        new_span
364    }
365
366    /// Returns the end position of this [`Span`].
367    pub fn end(&self) -> SpanPos {
368        self.end
369    }
370
371    /// Sets the end position of this [`Span`].
372    pub fn set_end(&mut self, end: SpanPos) {
373        assert!(
374            self.start.0 <= end.0,
375            "the start position should always be >= of the end position"
376        );
377        self.end = end;
378    }
379
380    /// Returns a new [`Span`] with the given end position.
381    #[must_use]
382    pub fn with_end(&self, end: SpanPos) -> Span<'ast> {
383        let mut new_span = self.clone();
384        new_span.set_end(end);
385        new_span
386    }
387
388    #[must_use]
389    pub fn source(&self) -> SpanSource<'ast> {
390        with_cx(self, |cx| cx.span_source(self))
391    }
392}
393
394impl<'ast> HasSpan<'ast> for Span<'ast> {
395    fn span(&self) -> &Span<'ast> {
396        self
397    }
398}
399
400#[cfg(feature = "driver-api")]
401impl<'ast> Span<'ast> {
402    #[must_use]
403    pub fn new(source_id: SpanSrcId, from_expansion: bool, start: SpanPos, end: SpanPos) -> Self {
404        Self {
405            _lifetime: PhantomData,
406            source_id,
407            from_expansion,
408            start,
409            end,
410        }
411    }
412
413    pub fn source_id(&self) -> SpanSrcId {
414        self.source_id
415    }
416}
417
418#[repr(C)]
419#[derive(Debug)]
420#[non_exhaustive]
421pub enum SpanSource<'ast> {
422    File(&'ast FileInfo<'ast>),
423    Macro(&'ast ExpnInfo<'ast>),
424    Builtin(&'ast BuiltinInfo<'ast>),
425}
426
427#[repr(C)]
428#[derive(Debug)]
429pub struct FileInfo<'ast> {
430    file: ffi::FfiStr<'ast>,
431    span_src: SpanSrcId,
432}
433
434impl<'ast> FileInfo<'ast> {
435    pub fn file(&self) -> &str {
436        self.file.get()
437    }
438
439    /// Tries to map the given [`SpanPos`] to a [`FilePos`]. It will return [`None`]
440    /// if the given [`FilePos`] belongs to a different [`FileInfo`].
441    pub fn try_to_file_pos(&self, span_pos: SpanPos) -> Option<FilePos> {
442        with_cx(self, |cx| cx.span_pos_to_file_loc(self, span_pos))
443    }
444
445    /// Map the given [`SpanPos`] to a [`FilePos`]. This will panic, if the
446    /// [`SpanPos`] doesn't belong to this [`FileInfo`]
447    pub fn to_file_pos(&self, span_pos: SpanPos) -> FilePos {
448        self.try_to_file_pos(span_pos).unwrap_or_else(|| {
449            panic!(
450                "the given span position `{span_pos:#?}` is out of range of the file `{}`",
451                self.file.get()
452            )
453        })
454    }
455}
456
457#[cfg(feature = "driver-api")]
458impl<'ast> FileInfo<'ast> {
459    #[must_use]
460    pub fn new(file: &'ast str, span_src: SpanSrcId) -> Self {
461        Self {
462            file: file.into(),
463            span_src,
464        }
465    }
466
467    pub fn span_src(&self) -> SpanSrcId {
468        self.span_src
469    }
470}
471
472/// A location inside a file.
473///
474/// [`SpanPos`] instances belonging to files can be mapped to [`FilePos`] with
475/// the [`FileInfo`] from the [`SpanSource`] of the [`Span`] they belong to. See:
476/// [`FileInfo::to_file_pos`].
477#[repr(C)]
478#[derive(Debug, Clone, Copy)]
479pub struct FilePos<'ast> {
480    /// The lifetime is not needed right now, but I want to have it, to potentualy
481    /// add more behavior to this struct.
482    _lifetime: PhantomData<&'ast ()>,
483    /// The 1-indexed line in bytes
484    line: usize,
485    /// The 1-indexed column in bytes
486    column: usize,
487}
488
489impl<'ast> FilePos<'ast> {
490    /// Returns the 1-indexed line location in bytes
491    pub fn line(&self) -> usize {
492        self.line
493    }
494
495    /// Returns the 1-indexed column location in bytes
496    pub fn column(&self) -> usize {
497        self.column
498    }
499}
500
501#[cfg(feature = "driver-api")]
502impl<'ast> FilePos<'ast> {
503    pub fn new(line: usize, column: usize) -> Self {
504        Self {
505            _lifetime: PhantomData,
506            line,
507            column,
508        }
509    }
510}
511
512/// The [`Span`] belongs to something, which was generated by the Compiler. This
513/// could be the imports from the prelude or the testing harness.
514#[repr(C)]
515#[derive(Debug, Clone, Copy)]
516#[cfg_attr(feature = "driver-api", derive(Default))]
517pub struct BuiltinInfo<'ast> {
518    /// The lifetime is not needed right now, but I want to have it, to potentualy
519    /// add more behavior to this struct.
520    _lifetime: PhantomData<&'ast ()>,
521    /// `#[repr(C)]` requires a field, to make this a proper type. This is just
522    /// the smallest one.
523    _data: u8,
524}
525
526#[repr(C)]
527#[cfg_attr(feature = "driver-api", derive(Clone))]
528pub struct Ident<'ast> {
529    _lifetime: PhantomData<&'ast ()>,
530    sym: SymbolId,
531    span: SpanId,
532}
533
534impl<'ast> Ident<'ast> {
535    pub fn name(&self) -> &str {
536        with_cx(self, |cx| cx.symbol_str(self.sym))
537    }
538}
539
540impl<'ast> HasSpan<'ast> for Ident<'ast> {
541    fn span(&self) -> &Span<'ast> {
542        with_cx(self, |cx| cx.span(self.span))
543    }
544}
545
546#[cfg(feature = "driver-api")]
547impl<'ast> Ident<'ast> {
548    pub fn new(sym: SymbolId, span: SpanId) -> Self {
549        Self {
550            _lifetime: PhantomData,
551            sym,
552            span,
553        }
554    }
555}
556
557impl<'ast> std::fmt::Debug for Ident<'ast> {
558    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
559        f.debug_struct("Ident")
560            .field("name", &self.name())
561            .field("span", &self.span())
562            .finish()
563    }
564}
565
566impl<'ast> std::fmt::Display for Ident<'ast> {
567    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
568        write!(f, "{}", self.name())
569    }
570}
571
572macro_rules! impl_ident_eq_for {
573    ($ty:ty) => {
574        impl<'ast> PartialEq<$ty> for Ident<'ast> {
575            fn eq(&self, other: &$ty) -> bool {
576                self.name().eq(other)
577            }
578        }
579        impl<'ast> PartialEq<Ident<'ast>> for $ty {
580            fn eq(&self, other: &Ident<'ast>) -> bool {
581                other.name().eq(self)
582            }
583        }
584    };
585    ($($ty:ty),+) => {
586        $(
587            impl_ident_eq_for!($ty);
588        )+
589    };
590}
591
592use impl_ident_eq_for;
593
594impl_ident_eq_for!(
595    str,
596    String,
597    std::ffi::OsStr,
598    std::ffi::OsString,
599    std::borrow::Cow<'_, str>
600);
601
602/// A trait for types, that provide a [`Span`]. It is implemented for all
603/// AST nodes, [`Span`] itself, and for references to them as well.
604///
605/// This gives you the ability to invoke functions that take `impl HasSpan`
606/// in many different ways. Just choose the one that fits your use case the best.
607///
608/// ```
609/// # use marker_api::prelude::*;
610///
611/// fn takes_span<'ast>(span: impl HasSpan<'ast>) {
612///     let span: &Span<'ast> = span.span();
613///     // ...
614/// }
615///
616/// fn visit_expr(expr: ExprKind<'_>) {
617///     takes_span(expr);
618///     takes_span(&expr);
619///     takes_span(expr.span());
620///     takes_span(&expr.span());
621/// }
622/// ```
623pub trait HasSpan<'ast>: Sealed {
624    /// This returns the [`Span`] of the implementing AST node.
625    fn span(&self) -> &Span<'ast>;
626}
627
628macro_rules! impl_has_span_via_field {
629    ($ty:ty) => {
630        $crate::span::impl_has_span_via_field!($ty, span);
631    };
632    ($ty:ty, $($field_access:ident).*) => {
633        impl<'ast> $crate::span::HasSpan<'ast> for $ty {
634            fn span(&self) -> &$crate::span::Span<'ast> {
635                $crate::context::with_cx(self, |cx| cx.span(self.$($field_access).*))
636            }
637        }
638    }
639}
640pub(crate) use impl_has_span_via_field;
641
642/// This macro implements the [`HasSpan`] trait for data types, that provide a
643/// `span()` method.
644macro_rules! impl_spanned_for {
645    ($ty:ty) => {
646        impl<'ast> $crate::span::HasSpan<'ast> for $ty {
647            fn span(&self) -> &$crate::span::Span<'ast> {
648                self.span()
649            }
650        }
651    };
652}
653pub(crate) use impl_spanned_for;
654
655impl<'ast, N: HasSpan<'ast>> HasSpan<'ast> for &N {
656    fn span(&self) -> &Span<'ast> {
657        (*self).span()
658    }
659}