ad_astra/runtime/
origin.rs

1////////////////////////////////////////////////////////////////////////////////
2// This file is part of "Ad Astra", an embeddable scripting programming       //
3// language platform.                                                         //
4//                                                                            //
5// This work is proprietary software with source-available code.              //
6//                                                                            //
7// To copy, use, distribute, or contribute to this work, you must agree to    //
8// the terms of the General License Agreement:                                //
9//                                                                            //
10// https://github.com/Eliah-Lakhin/ad-astra/blob/master/EULA.md               //
11//                                                                            //
12// The agreement grants a Basic Commercial License, allowing you to use       //
13// this work in non-commercial and limited commercial products with a total   //
14// gross revenue cap. To remove this commercial limit for one of your         //
15// products, you must acquire a Full Commercial License.                      //
16//                                                                            //
17// If you contribute to the source code, documentation, or related materials, //
18// you must grant me an exclusive license to these contributions.             //
19// Contributions are governed by the "Contributions" section of the General   //
20// License Agreement.                                                         //
21//                                                                            //
22// Copying the work in parts is strictly forbidden, except as permitted       //
23// under the General License Agreement.                                       //
24//                                                                            //
25// If you do not or cannot agree to the terms of this Agreement,              //
26// do not use this work.                                                      //
27//                                                                            //
28// This work is provided "as is", without any warranties, express or implied, //
29// except where such disclaimers are legally invalid.                         //
30//                                                                            //
31// Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин).                 //
32// All rights reserved.                                                       //
33////////////////////////////////////////////////////////////////////////////////
34
35use std::{
36    cmp::Ordering,
37    fmt::{Debug, Display, Formatter},
38    hash::{Hash, Hasher},
39    ops::{Bound, Range, RangeFrom, RangeInclusive},
40};
41
42use compact_str::CompactString;
43use lady_deirdre::{
44    arena::{Entry, Id, Identifiable},
45    lexis::{
46        Column,
47        Line,
48        Position,
49        PositionSpan,
50        SiteRef,
51        SiteRefSpan,
52        SiteSpan,
53        SourceCode,
54        ToSpan,
55        TokenRef,
56    },
57    syntax::PolyRef,
58};
59
60use crate::runtime::{Ident, PackageMeta, ScriptIdent};
61
62/// A representation of a Rust or Script source code range.
63///
64/// The primary purpose of this object is to track data flow points (both
65/// Rust and Script points) during script evaluation.
66///
67/// Typically, you don't need to create this object manually. You receive
68/// instances of Origin in runtime trait functions and pass them to the
69/// corresponding runtime API functions.
70///
71/// This Origin object helps the Script Engine generate descriptive
72/// [runtime errors](crate::runtime::RuntimeError) if an error occurs during
73/// script evaluation. Additionally, its [ScriptOrigin] variant is widely used
74/// in the static script code analysis API to represent script source code
75/// spans.
76///
77/// For debugging purposes, you can instantiate the Origin object as
78/// [Origin::nil], which intentionally does not point to any source code.
79#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
80pub enum Origin {
81    /// A range representing a portion of Rust source code.
82    Rust(&'static RustOrigin),
83
84    /// A range representing a portion of Script source code.
85    Script(ScriptOrigin),
86}
87
88impl Default for Origin {
89    #[inline(always)]
90    fn default() -> Self {
91        Self::nil()
92    }
93}
94
95impl Debug for Origin {
96    #[inline(always)]
97    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
98        match self {
99            Self::Rust(origin) => Debug::fmt(origin, formatter),
100            Self::Script(origin) => Debug::fmt(origin, formatter),
101        }
102    }
103}
104
105impl From<&'static RustOrigin> for Origin {
106    #[inline(always)]
107    fn from(value: &'static RustOrigin) -> Self {
108        Self::Rust(value)
109    }
110}
111
112impl From<ScriptOrigin> for Origin {
113    #[inline(always)]
114    fn from(value: ScriptOrigin) -> Self {
115        Self::Script(value)
116    }
117}
118
119impl From<SiteRefSpan> for Origin {
120    #[inline(always)]
121    fn from(value: SiteRefSpan) -> Self {
122        Self::Script(ScriptOrigin::from(value))
123    }
124}
125
126impl<'a> From<&'a SiteRefSpan> for Origin {
127    #[inline(always)]
128    fn from(value: &'a SiteRefSpan) -> Self {
129        Self::Script(ScriptOrigin::from(value))
130    }
131}
132
133impl From<SiteRef> for Origin {
134    #[inline(always)]
135    fn from(value: SiteRef) -> Self {
136        Self::Script(ScriptOrigin::from(value))
137    }
138}
139
140impl<'a> From<&'a SiteRef> for Origin {
141    #[inline(always)]
142    fn from(value: &'a SiteRef) -> Self {
143        Self::Script(ScriptOrigin::from(value))
144    }
145}
146
147impl From<TokenRef> for Origin {
148    #[inline(always)]
149    fn from(value: TokenRef) -> Self {
150        Self::Script(ScriptOrigin::from(value))
151    }
152}
153
154impl<'a> From<&'a TokenRef> for Origin {
155    fn from(value: &'a TokenRef) -> Self {
156        Self::Script(ScriptOrigin::from(value))
157    }
158}
159
160impl From<Range<TokenRef>> for Origin {
161    #[inline(always)]
162    fn from(value: Range<TokenRef>) -> Self {
163        Self::Script(ScriptOrigin::from(value))
164    }
165}
166
167impl<'a> From<Range<&'a TokenRef>> for Origin {
168    #[inline(always)]
169    fn from(value: Range<&'a TokenRef>) -> Self {
170        Self::Script(ScriptOrigin::from(value))
171    }
172}
173
174impl From<RangeInclusive<TokenRef>> for Origin {
175    #[inline(always)]
176    fn from(value: RangeInclusive<TokenRef>) -> Self {
177        Self::Script(ScriptOrigin::from(value))
178    }
179}
180
181impl<'a> From<RangeInclusive<&'a TokenRef>> for Origin {
182    #[inline(always)]
183    fn from(value: RangeInclusive<&'a TokenRef>) -> Self {
184        Self::Script(ScriptOrigin::from(value))
185    }
186}
187
188impl From<RangeFrom<TokenRef>> for Origin {
189    #[inline(always)]
190    fn from(value: RangeFrom<TokenRef>) -> Self {
191        Self::Script(ScriptOrigin::from(value))
192    }
193}
194
195impl<'a> From<RangeFrom<&'a TokenRef>> for Origin {
196    #[inline(always)]
197    fn from(value: RangeFrom<&'a TokenRef>) -> Self {
198        Self::Script(ScriptOrigin::from(value))
199    }
200}
201
202impl From<RangeFrom<SiteRef>> for Origin {
203    #[inline(always)]
204    fn from(value: RangeFrom<SiteRef>) -> Self {
205        Self::Script(ScriptOrigin::from(value))
206    }
207}
208
209impl<'a> From<RangeFrom<&'a SiteRef>> for Origin {
210    #[inline(always)]
211    fn from(value: RangeFrom<&'a SiteRef>) -> Self {
212        Self::Script(ScriptOrigin::from(value))
213    }
214}
215
216impl Origin {
217    /// Creates an instance of Origin that intentionally does not point
218    /// to any source code. This serves as the Default constructor for this
219    /// object.
220    ///
221    /// Use this function for API debugging purposes, or when the corresponding
222    /// Script runtime operation cannot be associated with any Rust or Script
223    /// source code.
224    #[inline(always)]
225    pub fn nil() -> Self {
226        Self::Rust(&RustOrigin::nil())
227    }
228
229    /// Returns true if this instance is the [Nil Origin](Origin::nil).
230    #[inline(always)]
231    pub fn is_nil(&self) -> bool {
232        match self {
233            Self::Rust(origin) => origin.is_nil(),
234            Self::Script(origin) => origin.is_nil(),
235        }
236    }
237
238    /// Attempts to return the [script package](PackageMeta) to which this
239    /// source code belongs.
240    ///
241    /// This function returns `None` if the package cannot be recognized (e.g.,
242    /// in the case of the [Nil Origin](Origin::nil)).
243    #[inline(always)]
244    pub fn package(&self) -> Option<&'static PackageMeta> {
245        match self {
246            Self::Rust(origin) => origin.package(),
247            Self::Script(origin) => origin.package(),
248        }
249    }
250
251    #[inline(always)]
252    pub(crate) fn into_ident(self, string: impl Into<CompactString>) -> Ident {
253        match self {
254            Self::Rust(..) => Ident::Script(ScriptIdent::from_string(TokenRef::nil(), string)),
255
256            Self::Script(origin) => {
257                Ident::Script(ScriptIdent::from_string(origin.into_token_ref(), string))
258            }
259        }
260    }
261}
262
263static NIL_RUST_ORIGIN: RustOrigin = RustOrigin {
264    package: None,
265    code: None,
266};
267
268/// A pointer to a specific location in the Rust source code.
269///
270/// This object points to a particular place in a Rust file and represents the
271/// origin of an exported Rust construct or a part of it.
272///
273/// Typically, you don't need to create this object manually. The
274/// [export](crate::export) macro generates static instances of RustOrigin
275/// during the introspection of Rust items. These instances are created in
276/// static memory by the export system. Therefore, you would generally use
277/// `&'static RustOrigin` references wrapped in [Origin].
278#[derive(Clone, Copy, PartialOrd, Ord, Hash)]
279pub struct RustOrigin {
280    /// The name and version of the crate to which the Rust file belongs.
281    pub package: Option<(&'static str, &'static str)>,
282
283    /// The actual reference to the Rust file within the crate.
284    pub code: Option<RustCode>,
285}
286
287impl Default for RustOrigin {
288    #[inline(always)]
289    fn default() -> Self {
290        NIL_RUST_ORIGIN
291    }
292}
293
294impl PartialEq for RustOrigin {
295    #[inline]
296    fn eq(&self, other: &Self) -> bool {
297        match (&self.package, &other.package) {
298            (Some(this), Some(other)) => {
299                if this.ne(other) {
300                    return false;
301                }
302            }
303
304            _ => return false,
305        }
306
307        if let (Some(this), Some(other)) = (&self.code, &other.code) {
308            if this.ne(other) {
309                return false;
310            }
311        }
312
313        true
314    }
315}
316
317impl Eq for RustOrigin {}
318
319impl Debug for RustOrigin {
320    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
321        if self.package.is_none() && self.code.is_none() {
322            return formatter.write_str("RustOrigin(invalid)");
323        }
324
325        let mut debug_struct = formatter.debug_struct("RustOrigin");
326
327        if let Some((name, version)) = &self.package {
328            debug_struct.field("package", &format_args!("{name}@{version}"));
329        }
330
331        if let Some(code) = &self.code {
332            debug_struct.field("code", &code);
333        }
334
335        debug_struct.finish()
336    }
337}
338
339impl Display for RustOrigin {
340    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
341        if let Some(code) = &self.code {
342            return Display::fmt(code, formatter);
343        }
344
345        if let Some((name, _)) = self.package {
346            return formatter.write_str(name);
347        }
348
349        formatter.write_str("[?]")
350    }
351}
352
353impl RustOrigin {
354    /// Returns a RustOrigin that intentionally does not point to any Rust code.
355    /// This is the [Default] value of this object.
356    #[inline(always)]
357    pub fn nil() -> &'static Self {
358        &NIL_RUST_ORIGIN
359    }
360
361    /// Returns true if this instance is the [Nil RustOrigin](Self::nil).
362    pub fn is_nil(&self) -> bool {
363        self == &NIL_RUST_ORIGIN
364    }
365
366    /// Returns the [script package metadata](PackageMeta) of the crate that
367    /// this RustOrigin points to.
368    ///
369    /// Returns None if the package is not specified or if the crate is not
370    /// a script package.
371    #[inline(always)]
372    pub fn package(&self) -> Option<&'static PackageMeta> {
373        if let Some((name, version)) = self.package {
374            return PackageMeta::of(name, &format!("={}", version));
375        }
376
377        None
378    }
379
380    /// This function is guaranteed to panic with the provided `message`.
381    ///
382    /// Unlike a normal `panic!`, the stack trace for this panic will typically
383    /// start from the Rust code that this RustOrigin points to.
384    #[inline(never)]
385    pub fn blame<T>(&self, message: &str) -> T {
386        if let Some(code) = self.code {
387            (code.blame_fn)(message);
388        }
389
390        match self.package {
391            Some((name, _)) => panic!("{}: {}", name, message),
392            None => panic!("{}", message),
393        }
394    }
395}
396
397/// A representation of a range within Script source code.
398///
399/// This object points to specific span within the
400/// [ScriptModule](crate::analysis::ScriptModule)'s source code text, often
401/// highlighting identifiers and similar syntactic constructs. Generally,
402/// it can span arbitrary range of text tokens.
403///
404/// Unlike absolute ranges (such as `10..20` or
405/// `Position::new(1, 4)..Position::new(3, 8)`), the ScriptOrigin range is
406/// relative. If you edit the script's text before, after, or inside the
407/// referenced range, this range will realign accordingly. However, if the
408/// text edits affect the ScriptOrigin bounds directly, the object will
409/// become obsolete.
410///
411/// In general, ScriptOrigin may represent an invalid range or become
412/// invalid over time if the source code is edited. You can check the range's
413/// validity using the [is_valid_span](ToSpan::is_valid_span) method, passing
414/// an instance of [ModuleText](crate::analysis::ModuleText):
415/// `script_origin.is_valid_span(&module_text)`.
416///
417/// Note that ScriptOrigin belongs to a specific ScriptModule instance
418/// and is valid only for that module. The [id](Id::id) function of ScriptOrigin
419/// returns a globally unique identifier for the script module to which this
420/// ScriptOrigin object belongs.
421#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
422pub struct ScriptOrigin {
423    id: Id,
424    start: Option<Entry>,
425    end: Bound<Entry>,
426}
427
428impl Default for ScriptOrigin {
429    #[inline(always)]
430    fn default() -> Self {
431        Self::nil()
432    }
433}
434
435impl PartialOrd for ScriptOrigin {
436    #[inline(always)]
437    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
438        Some(self.cmp(other))
439    }
440}
441
442impl Ord for ScriptOrigin {
443    #[inline(always)]
444    fn cmp(&self, other: &Self) -> Ordering {
445        match self.id.cmp(&other.id) {
446            Ordering::Equal => match self.start.cmp(&other.start) {
447                Ordering::Equal => match (&self.end, &other.end) {
448                    (Bound::Included(this), Bound::Included(other)) => this.cmp(other),
449                    (Bound::Included(..), Bound::Excluded(..)) => Ordering::Less,
450                    (Bound::Included(..), Bound::Unbounded) => Ordering::Less,
451
452                    (Bound::Excluded(..), Bound::Included(..)) => Ordering::Greater,
453                    (Bound::Excluded(this), Bound::Excluded(other)) => this.cmp(other),
454                    (Bound::Excluded(..), Bound::Unbounded) => Ordering::Less,
455
456                    (Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
457                    (Bound::Unbounded, _) => Ordering::Greater,
458                },
459
460                other => other,
461            },
462
463            other => other,
464        }
465    }
466}
467
468impl Identifiable for ScriptOrigin {
469    #[inline(always)]
470    fn id(&self) -> Id {
471        self.id
472    }
473}
474
475impl From<SiteRefSpan> for ScriptOrigin {
476    #[inline(always)]
477    fn from(value: SiteRefSpan) -> Self {
478        Self::from(&value)
479    }
480}
481
482impl<'a> From<&'a SiteRefSpan> for ScriptOrigin {
483    #[inline(always)]
484    fn from(value: &'a SiteRefSpan) -> Self {
485        let id = value.start.id();
486
487        if id.is_nil() || id != value.end.id() {
488            return Self::default();
489        }
490
491        Self {
492            id,
493
494            start: {
495                let bound = value.start.token_ref();
496
497                match bound.is_nil() {
498                    true => None,
499                    false => Some(bound.entry),
500                }
501            },
502
503            end: {
504                let bound = value.end.token_ref();
505
506                match bound.is_nil() {
507                    true => Bound::Unbounded,
508                    false => Bound::Excluded(bound.entry),
509                }
510            },
511        }
512    }
513}
514
515impl From<SiteRef> for ScriptOrigin {
516    #[inline(always)]
517    fn from(value: SiteRef) -> Self {
518        Self::from(&value)
519    }
520}
521
522impl<'a> From<&'a SiteRef> for ScriptOrigin {
523    #[inline(always)]
524    fn from(value: &'a SiteRef) -> Self {
525        let id = value.id();
526
527        if id.is_nil() {
528            return Self::default();
529        }
530
531        let bound = value.token_ref();
532
533        match bound.id.is_nil() {
534            true => Self {
535                id,
536                start: None,
537                end: Bound::Unbounded,
538            },
539
540            false => Self {
541                id,
542                start: Some(bound.entry),
543                end: Bound::Excluded(bound.entry),
544            },
545        }
546    }
547}
548
549impl From<TokenRef> for ScriptOrigin {
550    #[inline(always)]
551    fn from(value: TokenRef) -> Self {
552        Self::from(&value)
553    }
554}
555
556impl<'a> From<&'a TokenRef> for ScriptOrigin {
557    #[inline(always)]
558    fn from(value: &'a TokenRef) -> Self {
559        Self {
560            id: value.id,
561            start: Some(value.entry),
562            end: Bound::Included(value.entry),
563        }
564    }
565}
566
567impl From<Range<TokenRef>> for ScriptOrigin {
568    #[inline(always)]
569    fn from(value: Range<TokenRef>) -> Self {
570        Self::from(&value.start..&value.end)
571    }
572}
573
574impl<'a> From<Range<&'a TokenRef>> for ScriptOrigin {
575    #[inline(always)]
576    fn from(value: Range<&'a TokenRef>) -> Self {
577        let id = value.start.id;
578
579        if id.is_nil() || id != value.end.id {
580            return Self::default();
581        }
582
583        Self {
584            id,
585            start: Some(value.start.entry),
586            end: Bound::Excluded(value.end.entry),
587        }
588    }
589}
590
591impl From<RangeInclusive<TokenRef>> for ScriptOrigin {
592    #[inline(always)]
593    fn from(value: RangeInclusive<TokenRef>) -> Self {
594        Self::from(value.start()..=value.end())
595    }
596}
597
598impl<'a> From<RangeInclusive<&'a TokenRef>> for ScriptOrigin {
599    #[inline(always)]
600    fn from(value: RangeInclusive<&'a TokenRef>) -> Self {
601        let id = value.start().id;
602
603        if id.is_nil() || id != value.end().id {
604            return Self::default();
605        }
606
607        Self {
608            id,
609            start: Some(value.start().entry),
610            end: Bound::Included(value.end().entry),
611        }
612    }
613}
614
615impl From<RangeFrom<TokenRef>> for ScriptOrigin {
616    #[inline(always)]
617    fn from(value: RangeFrom<TokenRef>) -> Self {
618        Self::from(&value.start..)
619    }
620}
621
622impl<'a> From<RangeFrom<&'a TokenRef>> for ScriptOrigin {
623    #[inline(always)]
624    fn from(value: RangeFrom<&'a TokenRef>) -> Self {
625        let id = value.start.id();
626
627        if id.is_nil() {
628            return Self::default();
629        }
630
631        Self {
632            id: value.start.id,
633            start: Some(value.start.entry),
634            end: Bound::Unbounded,
635        }
636    }
637}
638
639impl From<RangeFrom<SiteRef>> for ScriptOrigin {
640    #[inline(always)]
641    fn from(value: RangeFrom<SiteRef>) -> Self {
642        Self::from(&value.start..)
643    }
644}
645
646impl<'a> From<RangeFrom<&'a SiteRef>> for ScriptOrigin {
647    #[inline(always)]
648    fn from(value: RangeFrom<&'a SiteRef>) -> Self {
649        let id = value.start.id();
650
651        if id.is_nil() {
652            return Self::default();
653        }
654
655        let bound = value.start.token_ref();
656
657        match bound.id.is_nil() {
658            true => Self {
659                id,
660                start: None,
661                end: Bound::Unbounded,
662            },
663
664            false => Self {
665                id,
666                start: Some(bound.entry),
667                end: Bound::Unbounded,
668            },
669        }
670    }
671}
672
673// Safety: `is_valid_span` falls back to `to_site_span`.
674unsafe impl ToSpan for ScriptOrigin {
675    #[inline(always)]
676    fn to_site_span(&self, code: &impl SourceCode) -> Option<SiteSpan> {
677        if self.id != code.id() {
678            return None;
679        }
680
681        let length = code.length();
682
683        let start = match &self.start {
684            Some(entry) => code.get_site(entry)?.min(length),
685            None => length,
686        };
687
688        let end = match &self.end {
689            Bound::Included(entry) => {
690                let chunk_length = code.get_length(entry)?;
691                let bound = (code.get_site(entry)? + chunk_length).min(length);
692
693                if bound < start {
694                    return None;
695                }
696
697                bound
698            }
699
700            Bound::Excluded(entry) => {
701                let bound = code.get_site(entry)?;
702
703                if bound < start {
704                    return None;
705                }
706
707                bound
708            }
709
710            Bound::Unbounded => length,
711        };
712
713        Some(start..end)
714    }
715
716    #[inline(always)]
717    fn is_valid_span(&self, code: &impl SourceCode) -> bool {
718        self.to_site_span(code).is_some()
719    }
720}
721
722impl ScriptOrigin {
723    /// Creates an instance of ScriptOrigin that intentionally does not point
724    /// to any Script code. This serves as the [Default] constructor for this
725    /// object.
726    #[inline(always)]
727    pub const fn nil() -> Self {
728        Self {
729            id: Id::nil(),
730            start: None,
731            end: Bound::Unbounded,
732        }
733    }
734
735    #[inline(always)]
736    pub(crate) fn invalid(id: Id) -> Self {
737        Self {
738            id,
739            start: Some(Entry::nil()),
740            end: Bound::Unbounded,
741        }
742    }
743
744    #[inline(always)]
745    pub(crate) fn eoi(id: Id) -> Self {
746        Self {
747            id,
748            start: None,
749            end: Bound::Unbounded,
750        }
751    }
752
753    /// Returns the [script package metadata](PackageMeta) of the
754    /// [ScriptModule](crate::analysis::ScriptModule) that this ScriptOrigin
755    /// points to.
756    ///
757    /// Returns None if the package cannot be found, for instance, if the
758    /// corresponding ScriptModule has been dropped.
759    #[inline(always)]
760    pub fn package(&self) -> Option<&'static PackageMeta> {
761        PackageMeta::by_id(self.id)
762    }
763
764    /// Returns true if this instance is the [Nil ScriptOrigin](Self::nil).
765    #[inline(always)]
766    pub const fn is_nil(&self) -> bool {
767        if self.id.is_nil() {
768            return true;
769        }
770
771        if let Some(entry) = &self.start {
772            if entry.is_nil() {
773                return true;
774            }
775        }
776
777        match &self.end {
778            Bound::Included(entry) | Bound::Excluded(entry) => {
779                if entry.is_nil() {
780                    return true;
781                }
782            }
783            _ => (),
784        }
785
786        false
787    }
788
789    #[inline(always)]
790    pub(crate) fn union(&mut self, other: &Self) {
791        self.end = other.end;
792    }
793
794    #[inline(always)]
795    pub(crate) fn unbound(&mut self) {
796        self.end = Bound::Unbounded;
797    }
798
799    fn into_token_ref(self) -> TokenRef {
800        let Some(entry) = self.start else {
801            return TokenRef::nil();
802        };
803
804        TokenRef { id: self.id, entry }
805    }
806}
807
808/// A component of [RustOrigin] that indicates a specific location in the Rust
809/// source code.
810#[derive(Clone, Copy, PartialOrd, Ord)]
811pub struct RustCode {
812    /// The name of the Rust module.
813    pub module: &'static str,
814
815    /// A one-based line number within the module file.
816    pub line: u32,
817
818    /// A one-based column number within a line of the module file.
819    pub column: u32,
820
821    /// A function that panics at the location of the pointed Rust construct
822    /// with the specified error message.
823    pub blame_fn: fn(&str),
824}
825
826impl Hash for RustCode {
827    #[inline]
828    fn hash<H: Hasher>(&self, state: &mut H) {
829        self.module.hash(state);
830        self.line.hash(state);
831        self.column.hash(state);
832    }
833}
834
835impl PartialEq for RustCode {
836    #[inline]
837    fn eq(&self, other: &Self) -> bool {
838        if self.module.ne(other.module) {
839            return false;
840        }
841
842        if self.line.ne(&other.line) {
843            return false;
844        }
845
846        if self.column.ne(&other.column) {
847            return false;
848        }
849
850        true
851    }
852}
853
854impl Eq for RustCode {}
855
856impl Debug for RustCode {
857    #[inline(always)]
858    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
859        formatter
860            .debug_struct("RustCode")
861            .field("module", &self.module)
862            .field("position", &format_args!("{}", self.position()))
863            .finish()
864    }
865}
866
867impl Display for RustCode {
868    #[inline(always)]
869    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
870        formatter.write_fmt(format_args!("{} [{}]", self.module, self.span_string()))
871    }
872}
873
874impl RustCode {
875    /// Similar to [RustOrigin::blame]. Refer to its documentation for details.
876    #[inline(never)]
877    pub fn blame(&self, message: &str) {
878        (self.blame_fn)(message);
879        panic!("{}: {}", self, message);
880    }
881
882    /// A helper function that returns a [Position] representation of the line
883    /// and column of this RustCode.
884    #[inline(always)]
885    pub fn position(&self) -> Position {
886        Position::new(self.line as Line, self.column as Column)
887    }
888
889    /// A helper function that returns a zero-span [Range] of
890    /// [positions](Self::position): `code.position()..code.position()`.
891    #[inline(always)]
892    pub fn span(&self) -> PositionSpan {
893        let bound = self.position();
894
895        bound..bound
896    }
897
898    /// A helper function that formats the line and column in the canonical
899    /// end-user-facing form of `[<line>]:[<column>]`.
900    pub fn span_string(&self) -> String {
901        format!("{}:{}", self.line, self.column)
902    }
903}