charon_lib/ast/
meta_utils.rs

1//! This file groups everything which is linked to implementations about [crate::meta]
2use crate::meta::*;
3use crate::names::{Disambiguator, Name, PathElem};
4use itertools::Itertools;
5use std::borrow::Cow;
6use std::cmp::Ordering;
7use std::iter::Iterator;
8use std::ops::Range;
9
10/// Given a line number within a source file, get the byte of the start of the line. Obviously not
11/// efficient to do many times, but this is used is diagnostic paths only. The line numer is
12/// expected to be 1-based.
13fn line_to_start_byte(source: &str, line_nbr: usize) -> usize {
14    let mut cur_byte = 0;
15    for (i, line) in source.split_inclusive('\n').enumerate() {
16        if line_nbr == i + 1 {
17            break;
18        }
19        cur_byte += line.len();
20    }
21    cur_byte
22}
23
24impl Loc {
25    fn dummy() -> Self {
26        Loc { line: 0, col: 0 }
27    }
28
29    fn min(l0: &Loc, l1: &Loc) -> Loc {
30        match l0.line.cmp(&l1.line) {
31            Ordering::Equal => Loc {
32                line: l0.line,
33                col: std::cmp::min(l0.col, l1.col),
34            },
35            Ordering::Less => *l0,
36            Ordering::Greater => *l1,
37        }
38    }
39
40    fn max(l0: &Loc, l1: &Loc) -> Loc {
41        match l0.line.cmp(&l1.line) {
42            Ordering::Equal => Loc {
43                line: l0.line,
44                col: std::cmp::max(l0.col, l1.col),
45            },
46            Ordering::Greater => *l0,
47            Ordering::Less => *l1,
48        }
49    }
50
51    pub fn to_byte(self, source: &str) -> usize {
52        line_to_start_byte(source, self.line) + self.col
53    }
54}
55
56impl RawSpan {
57    pub fn dummy() -> Self {
58        RawSpan {
59            file_id: FileId::from_raw(0),
60            beg: Loc::dummy(),
61            end: Loc::dummy(),
62        }
63    }
64
65    /// Value with which we order `RawSpans`s.
66    fn sort_key(&self) -> impl Ord {
67        (self.file_id, self.beg, self.end)
68    }
69
70    pub fn to_byte_range(self, source: &str) -> Range<usize> {
71        self.beg.to_byte(source)..self.end.to_byte(source)
72    }
73}
74
75/// Manual impls because `SpanData` is not orderable.
76impl PartialOrd for RawSpan {
77    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
78        Some(self.cmp(other))
79    }
80}
81impl Ord for RawSpan {
82    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
83        self.sort_key().cmp(&other.sort_key())
84    }
85}
86
87impl Span {
88    pub fn dummy() -> Self {
89        Span {
90            span: RawSpan::dummy(),
91            generated_from_span: None,
92        }
93    }
94}
95
96/// Combine some span information (useful when we need to compute the
97/// span-information of, say, a sequence).
98pub fn combine_span(m0: &Span, m1: &Span) -> Span {
99    // Merge the spans
100    if m0.span.file_id == m1.span.file_id {
101        let span = RawSpan {
102            file_id: m0.span.file_id,
103            beg: Loc::min(&m0.span.beg, &m1.span.beg),
104            end: Loc::max(&m0.span.end, &m1.span.end),
105        };
106
107        // We don't attempt to merge the "generated from" spans: they might
108        // come from different files, and even if they come from the same files
109        // they might come from different macros, etc.
110        Span {
111            span,
112            generated_from_span: None,
113        }
114    } else {
115        // It happens that the spans don't come from the same file. In this
116        // situation, we just return the first span. TODO: improve this.
117        *m0
118    }
119}
120
121/// Combine all the span information in a slice.
122pub fn combine_span_iter<'a, T: Iterator<Item = &'a Span>>(mut ms: T) -> Span {
123    // The iterator should have a next element
124    let mut mc: Span = *ms.next().unwrap();
125    for m in ms {
126        mc = combine_span(&mc, m);
127    }
128
129    mc
130}
131
132impl FileName {
133    pub fn to_string(&self) -> Cow<'_, str> {
134        match self {
135            FileName::Virtual(path_buf) | FileName::Local(path_buf) => path_buf.to_string_lossy(),
136            FileName::NotReal(path) => Cow::Borrowed(path),
137        }
138    }
139}
140
141impl Attribute {
142    /// Parse a raw attribute to recognize our special `charon::*` and `aeneas::*` attributes.
143    pub fn parse_from_raw(raw_attr: RawAttribute) -> Result<Self, String> {
144        // If the attribute path has two components, the first of which is `charon` or `aeneas`, we
145        // try to parse it. Otherwise we return `Unknown`.
146        let path = raw_attr.path.split("::").collect_vec();
147        let attr_name = if let &[path_start, attr_name] = path.as_slice()
148            && (path_start == "charon" || path_start == "aeneas")
149        {
150            attr_name
151        } else {
152            return Ok(Self::Unknown(raw_attr));
153        };
154
155        match Self::parse_special_attr(attr_name, raw_attr.args.as_deref())? {
156            Some(parsed) => Ok(parsed),
157            None => Err(format!(
158                "Unrecognized attribute: `{}`",
159                raw_attr.to_string()
160            )),
161        }
162    }
163
164    /// Parse a `charon::*` or `aeneas::*` attribute.
165    fn parse_special_attr(attr_name: &str, args: Option<&str>) -> Result<Option<Self>, String> {
166        let parsed = match attr_name {
167            // `#[charon::opaque]`
168            "opaque" if args.is_none() => Self::Opaque,
169            // `#[charon::rename("new_name")]`
170            "rename" if let Some(attr) = args => {
171                let Some(attr) = attr
172                    .strip_prefix("\"")
173                    .and_then(|attr| attr.strip_suffix("\""))
174                else {
175                    return Err(format!(
176                        "the new name should be between quotes: `rename(\"{attr}\")`."
177                    ));
178                };
179
180                if attr.is_empty() {
181                    return Err(format!("attribute `rename` should not be empty"));
182                }
183
184                let first_char = attr.chars().nth(0).unwrap();
185                let is_identifier = (first_char.is_alphabetic() || first_char == '_')
186                    && attr.chars().all(|c| c.is_alphanumeric() || c == '_');
187                if !is_identifier {
188                    return Err(format!(
189                        "attribute `rename` should contain a valid identifier"
190                    ));
191                }
192
193                Self::Rename(attr.to_string())
194            }
195            // `#[charon::variants_prefix("T")]`
196            "variants_prefix" if let Some(attr) = args => {
197                let Some(attr) = attr
198                    .strip_prefix("\"")
199                    .and_then(|attr| attr.strip_suffix("\""))
200                else {
201                    return Err(format!(
202                        "the name should be between quotes: `variants_prefix(\"{attr}\")`."
203                    ));
204                };
205
206                Self::VariantsPrefix(attr.to_string())
207            }
208            // `#[charon::variants_suffix("T")]`
209            "variants_suffix" if let Some(attr) = args => {
210                let Some(attr) = attr
211                    .strip_prefix("\"")
212                    .and_then(|attr| attr.strip_suffix("\""))
213                else {
214                    return Err(format!(
215                        "the name should be between quotes: `variants_suffix(\"{attr}\")`."
216                    ));
217                };
218
219                Self::VariantsSuffix(attr.to_string())
220            }
221            _ => return Ok(None),
222        };
223        Ok(Some(parsed))
224    }
225}
226
227impl ItemOpacity {
228    pub fn with_content_visibility(self, contents_are_public: bool) -> Self {
229        use ItemOpacity::*;
230        match self {
231            Invisible => Invisible,
232            Transparent => Transparent,
233            Foreign if contents_are_public => Transparent,
234            Foreign => Opaque,
235            Opaque => Opaque,
236        }
237    }
238
239    pub fn with_private_contents(self) -> Self {
240        self.with_content_visibility(false)
241    }
242}
243
244impl ItemMeta {
245    pub fn renamed_name(&self) -> Name {
246        let mut name = self.name.clone();
247        if let Some(rename) = self.attr_info.rename.clone() {
248            *name.name.last_mut().unwrap() = PathElem::Ident(rename, Disambiguator::new(0));
249        }
250        name
251    }
252}
253
254impl Default for Span {
255    fn default() -> Self {
256        Self::dummy()
257    }
258}