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}