pavex_reflection/
lib.rs

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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
/// A set of coordinates to identify a precise spot in a source file.
///
/// # Implementation Notes
///
/// `Location` is an owned version of [`std::panic::Location`].
/// You can build a `Location` instance starting from a [`std::panic::Location`]:
///
/// ```rust
/// use pavex_reflection::Location;
///
/// let location: Location = std::panic::Location::caller().into();
/// ```
pub struct Location {
    /// The line number.
    ///
    /// Lines are 1-indexed (i.e. the first line is numbered as 1, not 0).
    pub line: u32,
    /// The column number.
    ///
    /// Columns are 1-indexed (i.e. the first column is numbered as 1, not 0).
    pub column: u32,
    /// The name of the source file.
    ///
    /// Check out [`std::panic::Location::file`] for more details.
    pub file: String,
}

impl<'a> From<&'a std::panic::Location<'a>> for Location {
    fn from(l: &'a std::panic::Location<'a>) -> Self {
        Self {
            line: l.line(),
            column: l.column(),
            file: l.file().into(),
        }
    }
}

impl Location {
    #[track_caller]
    pub fn caller() -> Self {
        std::panic::Location::caller().into()
    }
}

#[derive(Debug, Hash, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
/// All the information required to identify a component registered against a [`Blueprint`].
///
/// It is an implementation detail of the builder.
///
/// [`Blueprint`]: crate::blueprint::Blueprint
pub struct RawIdentifiers {
    /// Information on the callsite where the component was registered with the [`Blueprint`].
    pub registered_at: RegisteredAt,
    /// An unambiguous path to the type/callable.
    pub import_path: String,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
/// Information on the callsite where the component was registered.
pub struct RegisteredAt {
    /// The name of the crate that registered the component against the blueprint builder,
    /// as it appears in the `package` section of its `Cargo.toml`.  
    /// In particular,
    /// it has *not* been normalised—e.g. hyphens are not replaced with underscores.  
    ///
    /// This information is needed to resolve the import path unambiguously.
    ///
    /// E.g. `my_crate::module_1::type_2`—which crate is `my_crate`?
    /// This is not obvious due to the possibility of [renaming dependencies in `Cargo.toml`](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html?highlight=rename,depende#renaming-dependencies-in-cargotoml):
    ///
    /// ```toml
    /// [package]
    /// name = "mypackage"
    /// version = "0.0.1"
    ///
    /// [dependencies]
    /// my_crate = { version = "0.1", registry = "custom", package = "their_crate" }
    /// ```
    pub crate_name: String,
    /// The path to the module where the component was registered, obtained via [`module_path!`].
    pub module_path: String,
}

impl RawIdentifiers {
    #[track_caller]
    pub fn from_raw_parts(import_path: String, registered_at: RegisteredAt) -> Self {
        Self {
            registered_at,
            import_path,
        }
    }

    /// Return an unambiguous fully-qualified path pointing at the callable.
    ///
    /// The returned path can be used to import the callable.
    pub fn fully_qualified_path(&self) -> Vec<String> {
        let mut segments: Vec<_> = self
            .import_path
            .split("::")
            .map(|s| s.trim())
            .map(ToOwned::to_owned)
            .collect();
        // Replace the relative portion of the path (`crate`) with the actual crate name.
        if segments[0] == "crate" {
            // Hyphens are allowed in crate names, but the Rust compiler doesn't
            // allow them in actual import paths.
            // They are "transparently" replaced with underscores.
            segments[0] = self.registered_at.crate_name.replace('-', "_");
            segments
        } else if segments[0] == "self" {
            // The path is relative to the current module.
            // We "rebase" it to get an absolute path.
            let mut new_segments: Vec<_> = self
                .registered_at
                .module_path
                .split("::")
                .map(|s| s.trim())
                .map(ToOwned::to_owned)
                .collect();
            new_segments.extend(segments.into_iter().skip(1));
            new_segments
        } else if segments[0] == "super" {
            let n_super: usize = {
                let mut n_super = 0;
                let iter = segments.iter();
                for p in iter {
                    if p == "super" {
                        n_super += 1;
                    } else {
                        break;
                    }
                }
                n_super
            };
            // The path is relative to the current module.
            // We "rebase" it to get an absolute path.
            let module_segments: Vec<_> = self
                .registered_at
                .module_path
                .split("::")
                .map(|s| s.trim())
                .map(ToOwned::to_owned)
                .collect();
            let n_module_segments = module_segments.len();
            
            module_segments
                .into_iter()
                .take(n_module_segments - n_super)
                .chain(segments.into_iter().skip(n_super))
                .collect()
        } else {
            segments
        }
    }

    /// The path provided by the user, unaltered.
    pub fn raw_path(&self) -> &str {
        &self.import_path
    }

    pub fn registered_at(&self) -> &RegisteredAt {
        &self.registered_at
    }
}