Skip to main content

charon_driver/translate/
translate_meta.rs

1//! Translate information about items: name, attributes, etc.
2use itertools::Itertools;
3use rustc_middle::mir;
4use rustc_span::RemapPathScopeComponents;
5use std::{
6    cmp::Ord,
7    path::{Component, PathBuf},
8    sync::LazyLock,
9};
10
11use super::translate_crate::RustcItem;
12use super::translate_ctx::*;
13use crate::hax;
14use crate::hax::{DefPathItem, SInto};
15use charon_lib::{ast::*, name_matcher::NamePattern};
16
17// Spans
18impl<'tcx> TranslateCtx<'tcx> {
19    /// Register a file if it is a "real" file and was not already registered
20    /// `span` must be a span from which we obtained that filename.
21    fn register_file(&mut self, filename: FileName, span: rustc_span::Span) -> FileId {
22        // Lookup the file if it was already registered
23        match self.file_to_id.get(&filename) {
24            Some(id) => *id,
25            None => {
26                let source_file = self.tcx.sess.source_map().lookup_source_file(span.lo());
27                let crate_name = self.tcx.crate_name(source_file.cnum).to_string();
28                let id = self.translated.files.push_with(|id| File {
29                    id,
30                    name: filename.clone(),
31                    crate_name,
32                    contents: source_file.src.as_deref().cloned(),
33                });
34                self.file_to_id.insert(filename, id);
35                id
36            }
37        }
38    }
39
40    pub fn translate_filename(&mut self, name: rustc_span::FileName) -> meta::FileName {
41        match name {
42            rustc_span::FileName::Real(name) => {
43                match name.local_path() {
44                    Some(path) => {
45                        // Normalize path separators: cross-compiling to Windows uses `\` as path
46                        // separators.
47                        let path: PathBuf = {
48                            let mut normalized = PathBuf::new();
49                            for comp in path.components() {
50                                for segment in comp.as_os_str().to_string_lossy().split("\\") {
51                                    normalized.push(segment);
52                                }
53                            }
54                            normalized
55                        };
56                        // Find the cargo home directory: according to cargo docs and having a
57                        // look at the cargo source, it's either the `$CARGO_HOME` var or
58                        // `$HOME/.cargo`
59                        static CARGO_HOME: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
60                            std::env::var("CARGO_HOME")
61                                .map(PathBuf::from)
62                                .ok()
63                                .or_else(|| std::env::home_dir().map(|p| p.join(".cargo")))
64                        });
65                        // The path to files in the standard library may be full paths to
66                        // somewhere in the sysroot or in the original toolchain source tree. This
67                        // may depend on how the toolchain is installed (rustup vs nix), so we
68                        // normalize the paths here to avoid inconsistencies in the translation.
69                        let path = if let Some(rust_src) = path
70                            .ancestors()
71                            .find(|ancestor| ancestor.ends_with("lib/rustlib/src/rust"))
72                            && let Ok(path) = path.strip_prefix(rust_src)
73                        {
74                            let mut rewritten_path: PathBuf = "/rustc".into();
75                            rewritten_path.extend(path);
76                            rewritten_path
77                        } else if let Ok(path) = path.strip_prefix(&self.sysroot) {
78                            // Unclear if this can happen, but just in case.
79                            let mut rewritten_path: PathBuf = "/toolchain".into();
80                            rewritten_path.extend(path);
81                            rewritten_path
82                        } else if let Some(cargo_home) = &*CARGO_HOME
83                            && let Ok(path) = path.strip_prefix(cargo_home)
84                        {
85                            let mut rewritten_path: PathBuf = "/cargo".into();
86                            rewritten_path.extend(path);
87                            rewritten_path
88                        } else if let Ok(current_dir) = std::env::current_dir()
89                            && let Ok(path) = path.strip_prefix(current_dir)
90                        {
91                            path.to_path_buf()
92                        } else {
93                            path
94                        };
95                        FileName::Local(path)
96                    }
97                    None => {
98                        // We use the virtual name because it is always available.
99                        // That name normally starts with `/rustc/<hash>/`. For our purposes we hide
100                        // the hash.
101                        let virtual_name = name.path(RemapPathScopeComponents::MACRO);
102                        let mut components_iter = virtual_name.components();
103                        if let Some(
104                            [
105                                Component::RootDir,
106                                Component::Normal(rustc),
107                                Component::Normal(hash),
108                            ],
109                        ) = components_iter.by_ref().array_chunks().next()
110                            && rustc.to_str() == Some("rustc")
111                            && hash.len() == 40
112                        {
113                            let path_without_hash = [Component::RootDir, Component::Normal(rustc)]
114                                .into_iter()
115                                .chain(components_iter)
116                                .collect();
117                            FileName::Virtual(path_without_hash)
118                        } else {
119                            FileName::Virtual(virtual_name.into())
120                        }
121                    }
122                }
123            }
124            // We use the debug formatter to generate a filename.
125            // This is not ideal, but filenames are for debugging anyway.
126            _ => FileName::NotReal(format!("{name:?}")),
127        }
128    }
129
130    pub fn translate_span_data(&mut self, span: rustc_span::Span) -> meta::SpanData {
131        let smap: &rustc_span::source_map::SourceMap = self.tcx.sess.psess.source_map();
132        let filename = smap.span_to_filename(span);
133        let filename = self.translate_filename(filename);
134        let file_id = match &filename {
135            FileName::NotReal(_) => {
136                // For now we forbid not real filenames
137                unimplemented!();
138            }
139            FileName::Virtual(_) | FileName::Local(_) => self.register_file(filename, span),
140        };
141
142        let convert_loc = |pos: rustc_span::BytePos| -> Loc {
143            let loc = smap.lookup_char_pos(pos);
144            Loc {
145                line: loc.line,
146                col: loc.col_display,
147            }
148        };
149        let beg = convert_loc(span.lo());
150        let end = convert_loc(span.hi());
151
152        // Put together
153        meta::SpanData { file_id, beg, end }
154    }
155
156    /// Compute span data from a Rust source scope
157    pub fn translate_span_from_source_info(
158        &mut self,
159        source_scopes: &rustc_index::IndexVec<mir::SourceScope, mir::SourceScopeData>,
160        source_info: &mir::SourceInfo,
161    ) -> Span {
162        // Translate the span
163        let data = self.translate_span_data(source_info.span);
164
165        // Lookup the top-most inlined parent scope.
166        let mut parent_span = None;
167        let mut scope_data = &source_scopes[source_info.scope];
168        while let Some(parent_scope) = scope_data.inlined_parent_scope {
169            scope_data = &source_scopes[parent_scope];
170            parent_span = Some(scope_data.span);
171        }
172
173        if let Some(parent_span) = parent_span {
174            let parent_span = self.translate_span_data(parent_span);
175            Span {
176                data: parent_span,
177                generated_from_span: Some(data),
178            }
179        } else {
180            Span {
181                data,
182                generated_from_span: None,
183            }
184        }
185    }
186
187    pub(crate) fn translate_span(&mut self, span: &rustc_span::Span) -> Span {
188        Span {
189            data: self.translate_span_data(*span),
190            generated_from_span: None,
191        }
192    }
193
194    pub(crate) fn def_span(&mut self, def_id: &hax::DefId) -> Span {
195        let span = def_id.def_span(&self.hax_state);
196        self.translate_span(&span)
197    }
198}
199
200// Names
201impl<'tcx> TranslateCtx<'tcx> {
202    fn path_elem_for_def(
203        &mut self,
204        span: Span,
205        item: &RustcItem,
206    ) -> Result<Option<PathElem>, Error> {
207        let def_id = item.def_id();
208        let path_elem = def_id.path_item(&self.hax_state);
209        // Disambiguator disambiguates identically-named (but distinct) identifiers. This happens
210        // notably with macros and inherent impl blocks.
211        let disambiguator = Disambiguator::new(path_elem.disambiguator as usize);
212        // Match over the key data
213        let path_elem = match path_elem.data {
214            DefPathItem::CrateRoot { name, .. } => {
215                Some(PathElem::Ident(name.to_string(), disambiguator))
216            }
217            // We map the three namespaces onto a single one. We can always disambiguate by looking
218            // at the definition.
219            DefPathItem::TypeNs(symbol)
220            | DefPathItem::ValueNs(symbol)
221            | DefPathItem::MacroNs(symbol) => {
222                Some(PathElem::Ident(symbol.to_string(), disambiguator))
223            }
224            DefPathItem::Impl => {
225                let full_def = self.hax_def_for_item(item)?;
226                // Two cases, depending on whether the impl block is
227                // a "regular" impl block (`impl Foo { ... }`) or a trait
228                // implementation (`impl Bar for Foo { ... }`).
229                let impl_elem = match full_def.kind() {
230                    // Inherent impl ("regular" impl)
231                    hax::FullDefKind::InherentImpl { ty, .. } => {
232                        // We need to convert the type, which may contain quantified
233                        // substs and bounds. In order to properly do so, we introduce
234                        // a body translation context.
235                        let item_src =
236                            TransItemSource::new(item.clone(), TransItemSourceKind::InherentImpl);
237                        let mut bt_ctx = ItemTransCtx::new(item_src, None, self);
238                        bt_ctx.translate_item_generics(
239                            span,
240                            &full_def,
241                            &TransItemSourceKind::InherentImpl,
242                        )?;
243                        let ty = bt_ctx.translate_ty(span, ty)?;
244                        ImplElem::Ty(Box::new(Binder {
245                            kind: BinderKind::InherentImplBlock,
246                            params: bt_ctx.into_generics(),
247                            skip_binder: ty,
248                        }))
249                    }
250                    // Trait implementation
251                    hax::FullDefKind::TraitImpl { .. } => {
252                        let impl_id = {
253                            let item_src = TransItemSource::new(
254                                item.clone(),
255                                TransItemSourceKind::TraitImpl(TransImplSource::Normal),
256                            );
257                            self.register_and_enqueue(&None, item_src).unwrap()
258                        };
259                        ImplElem::Trait(impl_id)
260                    }
261                    _ => unreachable!(),
262                };
263
264                Some(PathElem::Impl(impl_elem))
265            }
266            // TODO: do nothing for now
267            DefPathItem::OpaqueTy => None,
268            // TODO: this is not very satisfactory, but on the other hand
269            // we should be able to extract closures in local let-bindings
270            // (i.e., we shouldn't have to introduce top-level let-bindings).
271            DefPathItem::Closure => Some(PathElem::Ident("closure".to_string(), disambiguator)),
272            // Do nothing, functions in `extern` blocks are in the same namespace as the
273            // block.
274            DefPathItem::ForeignMod => None,
275            // Do nothing, the constructor of a struct/variant has the same name as the
276            // struct/variant.
277            DefPathItem::Ctor => None,
278            DefPathItem::Use => Some(PathElem::Ident("{use}".to_string(), disambiguator)),
279            DefPathItem::AnonConst => Some(PathElem::Ident("{const}".to_string(), disambiguator)),
280            DefPathItem::PromotedConst => Some(PathElem::Ident(
281                "{promoted_const}".to_string(),
282                disambiguator,
283            )),
284            _ => {
285                raise_error!(
286                    self,
287                    span,
288                    "Unexpected DefPathItem for `{def_id:?}`: {path_elem:?}"
289                );
290            }
291        };
292        Ok(path_elem)
293    }
294
295    /// Retrieve the name for this [`hax::DefId`]. Because a given `DefId` may give rise to several
296    /// charon items, prefer to use `translate_name` when possible.
297    ///
298    /// We lookup the path associated to an id, and convert it to a name.
299    /// Paths very precisely identify where an item is. There are important
300    /// subcases, like the items in an `Impl` block:
301    /// ```ignore
302    /// impl<T> List<T> {
303    ///   fn new() ...
304    /// }
305    /// ```
306    ///
307    /// One issue here is that "List" *doesn't appear* in the path, which would
308    /// look like the following:
309    ///
310    ///   `TypeNS("Crate") :: Impl :: ValueNs("new")`
311    ///                       ^^^
312    ///           This is where "List" should be
313    ///
314    /// For this reason, whenever we find an `Impl` path element, we actually
315    /// lookup the type of the sub-path, from which we can derive a name.
316    ///
317    /// Besides, as there may be several "impl" blocks for one type, each impl
318    /// block is identified by a unique number (rustc calls this a
319    /// "disambiguator"), which we grab.
320    ///
321    /// Example:
322    /// ========
323    /// For instance, if we write the following code in crate `test` and module
324    /// `bla`:
325    /// ```ignore
326    /// impl<T> Foo<T> {
327    ///   fn foo() { ... }
328    /// }
329    ///
330    /// impl<T> Foo<T> {
331    ///   fn bar() { ... }
332    /// }
333    /// ```
334    ///
335    /// The names we will generate for `foo` and `bar` are:
336    /// `[Ident("test"), Ident("bla"), Ident("Foo"), Impl(impl<T> Ty<T>, Disambiguator(0)), Ident("foo")]`
337    /// `[Ident("test"), Ident("bla"), Ident("Foo"), Impl(impl<T> Ty<T>, Disambiguator(1)), Ident("bar")]`
338    fn name_for_item(&mut self, item: &RustcItem) -> Result<Name, Error> {
339        if let Some(name) = self.cached_names.get(item) {
340            return Ok(name.clone());
341        }
342        let def_id = item.def_id();
343        trace!("Computing name for `{def_id:?}`");
344
345        let parent_name = if let Some(parent_id) = def_id.parent(&self.hax_state) {
346            let def = self.hax_def_for_item(item)?;
347            if matches!(item, RustcItem::Mono(..))
348                && let Some(parent_item) = def.typing_parent(&self.hax_state)
349            {
350                self.name_for_item(&RustcItem::Mono(parent_item.clone()))?
351            } else {
352                self.name_for_item(&RustcItem::Poly(parent_id.clone()))?
353            }
354        } else {
355            Name { name: Vec::new() }
356        };
357        let span = self.def_span(def_id);
358        let mut name = parent_name;
359        if let Some(path_elem) = self.path_elem_for_def(span, item)? {
360            name.name.push(path_elem);
361        }
362
363        trace!("Computed name for `{def_id:?}`: `{name:?}`");
364        self.cached_names.insert(item.clone(), name.clone());
365        Ok(name)
366    }
367
368    /// Compute the name for an item.
369    /// Internal function, use `translate_name`.
370    pub fn name_for_src(&mut self, src: &TransItemSource) -> Result<Name, Error> {
371        let mut name = if let Some(parent) = src.parent() {
372            self.name_for_src(&parent)?
373        } else {
374            self.name_for_item(&src.item)?
375        };
376        match &src.kind {
377            // Nothing to do for the real items.
378            TransItemSourceKind::Type
379            | TransItemSourceKind::Fun
380            | TransItemSourceKind::Global
381            | TransItemSourceKind::TraitImpl(TransImplSource::Normal)
382            | TransItemSourceKind::TraitDecl
383            | TransItemSourceKind::InherentImpl
384            | TransItemSourceKind::Module => {}
385
386            TransItemSourceKind::TraitImpl(
387                kind @ (TransImplSource::Callable(..)
388                | TransImplSource::ImplicitDestruct
389                | TransImplSource::TraitAlias),
390            ) => {
391                if let TransImplSource::Callable(..) = kind {
392                    let _ = name.name.pop(); // Pop the `{closure}`
393                }
394                let impl_id = self.register_and_enqueue(&None, src.clone()).unwrap();
395                name.name.push(PathElem::Impl(ImplElem::Trait(impl_id)));
396            }
397            TransItemSourceKind::CallableMethod(kind) => {
398                let fn_name = kind.method_name().to_string();
399                name.name
400                    .push(PathElem::Ident(fn_name, Disambiguator::ZERO));
401            }
402            TransItemSourceKind::DropGlueMethod(..) => {
403                name.name.push(PathElem::Ident(
404                    "drop_glue".to_string(),
405                    Disambiguator::ZERO,
406                ));
407            }
408            TransItemSourceKind::ClosureAsFnCast => {
409                name.name
410                    .push(PathElem::Ident("as_fn".into(), Disambiguator::ZERO));
411            }
412            TransItemSourceKind::VTable
413            | TransItemSourceKind::VTableInstance(..)
414            | TransItemSourceKind::VTableInstanceInitializer(..) => {
415                name.name
416                    .push(PathElem::Ident("{vtable}".into(), Disambiguator::ZERO));
417            }
418            TransItemSourceKind::VTableMethod => {
419                name.name.push(PathElem::Ident(
420                    "{vtable_method}".into(),
421                    Disambiguator::ZERO,
422                ));
423            }
424            TransItemSourceKind::VTableDropShim => {
425                name.name.push(PathElem::Ident(
426                    "{vtable_drop_shim}".into(),
427                    Disambiguator::ZERO,
428                ));
429            }
430        }
431        Ok(name)
432    }
433
434    /// Retrieve the name for an item.
435    pub fn translate_name(&mut self, src: &TransItemSource) -> Result<Name, Error> {
436        let mut name = self.name_for_src(src)?;
437        // Push the generics used for monomorphization, if any. We skip mono trait impls as the
438        // generics are already included in the `PathElem::Impl` reference.
439        if let RustcItem::Mono(item_ref) = &src.item
440            && !item_ref.generic_args.is_empty()
441            && !matches!(src.kind, TransItemSourceKind::TraitImpl(..))
442        {
443            let trans_id = self.register_no_enqueue(&None, src).unwrap();
444            let span = self.def_span(&item_ref.def_id);
445            let mut bt_ctx = ItemTransCtx::new(src.clone(), trans_id, self);
446            let binder = bt_ctx.inside_binder(BinderKind::Other, |bt_ctx| {
447                // We skip the clauses: the args are enough to uniquely identify an ite.
448                bt_ctx.translate_generic_args(span, &item_ref.generic_args, &[])
449            })?;
450            if !binder.skip_binder.is_empty() {
451                name.name.push(PathElem::Instantiated(Box::new(binder)));
452            }
453        }
454        Ok(name)
455    }
456
457    pub(crate) fn opacity_for_name(&self, name: &Name) -> ItemOpacity {
458        self.options.opacity_for_name(&self.translated, name)
459    }
460}
461
462enum ContractTarget {
463    Parent,
464    Path(String),
465}
466
467// Attributes
468impl<'tcx> TranslateCtx<'tcx> {
469    fn resolve_contract_target(
470        &mut self,
471        def_id: &hax::DefId,
472        target: ContractTarget,
473    ) -> Result<MaybeAssocItemId, String> {
474        if !matches!(
475            def_id.kind,
476            hax::DefKind::Fn | hax::DefKind::AssocFn | hax::DefKind::Closure
477        ) {
478            return Err("contract attributes can only be applied to functions".to_string());
479        }
480
481        let parent_id = def_id.parent(&self.hax_state);
482        let target_def_id = match &target {
483            ContractTarget::Parent => parent_id.ok_or_else(|| {
484                "#[charon::contract(..., parent)] is invalid at the crate root".to_string()
485            })?,
486            ContractTarget::Path(target_name) => {
487                let mut siblings = Vec::new();
488                if let Some(parent_id) = &parent_id {
489                    let parent_def = self.poly_hax_def(parent_id).map_err(|err| err.msg)?;
490                    siblings.extend(
491                        parent_def
492                            .nameable_children(&self.hax_state)
493                            .into_iter()
494                            .map(|(_, id)| id),
495                    );
496
497                    // Free items inside a function body aren't in the `nameable_children`.
498                    if matches!(
499                        parent_def.kind(),
500                        hax::FullDefKind::Fn { .. }
501                            | hax::FullDefKind::AssocFn { .. }
502                            | hax::FullDefKind::Closure { .. }
503                    ) && let Some(parent_local_id) =
504                        parent_id.as_real_def_id().and_then(|id| id.as_local())
505                        && let Some(body_id) =
506                            self.tcx.hir_node_by_def_id(parent_local_id).body_id()
507                    {
508                        use rustc_hir::intravisit;
509
510                        struct NestedItems(Vec<rustc_hir::def_id::LocalDefId>);
511                        impl<'tcx> intravisit::Visitor<'tcx> for NestedItems {
512                            fn visit_nested_item(&mut self, id: rustc_hir::ItemId) {
513                                self.0.push(id.owner_id.def_id);
514                            }
515                        }
516
517                        let mut nested_items = NestedItems(Vec::new());
518                        intravisit::walk_body(&mut nested_items, self.tcx.hir_body(body_id));
519                        siblings.extend(
520                            nested_items
521                                .0
522                                .into_iter()
523                                .map(|id| id.to_def_id().sinto(&self.hax_state)),
524                        );
525                    }
526                }
527                let siblings = siblings
528                    .into_iter()
529                    .filter(|sibling| sibling != def_id)
530                    .filter(|sibling| match sibling.path_item(&self.hax_state).data {
531                        DefPathItem::ValueNs(name)
532                        | DefPathItem::TypeNs(name)
533                        | DefPathItem::MacroNs(name) => name.as_str() == target_name.as_str(),
534                        _ => false,
535                    })
536                    .collect_vec();
537                match siblings.as_slice() {
538                    [sibling] => sibling.clone(),
539                    [] => {
540                        // Fall back to full path search.
541                        let path = NamePattern::parse(target_name)
542                            .map_err(|err| format!("invalid item path `{target_name}`: {err}"))?;
543                        let targets =
544                            super::resolve_path::def_path_def_ids(&self.hax_state, &path, true)
545                                .map_err(|err| {
546                                    format!("failed to resolve item path `{target_name}`: {err}")
547                                })?;
548                        let [target] = targets.as_slice() else {
549                            return Err(format!(
550                                "item path `{target_name}` resolved to {} items; expected exactly one",
551                                targets.len()
552                            ));
553                        };
554                        target.sinto(&self.hax_state)
555                    }
556                    _ => {
557                        return Err(format!(
558                            "found several sibling items named `{target_name}`; expected exactly one"
559                        ));
560                    }
561                }
562            }
563        };
564
565        let target_def = self.poly_hax_def(&target_def_id).map_err(|err| err.msg)?;
566        if self.options.monomorphize_with_hax && target_def.this().has_non_lt_param {
567            return Err("contracts on generic items are not supported \
568                with `--monomorphize`"
569                .to_string());
570        }
571
572        if let ContractTarget::Path(_) = target
573            && let hax::FullDefKind::AssocFn {
574                associated_item, ..
575            }
576            | hax::FullDefKind::AssocConst {
577                associated_item, ..
578            }
579            | hax::FullDefKind::AssocTy {
580                associated_item, ..
581            } = target_def.kind()
582            && let hax::AssocItemContainer::TraitContainer { trait_ref } =
583                &associated_item.container
584        {
585            let kind = TransItemSourceKind::TraitDecl;
586            let trait_src =
587                TransItemSource::from_item(trait_ref, kind, self.options.monomorphize_with_hax);
588            let trait_id = self.register_and_enqueue(&None, trait_src).unwrap();
589            let item_id = self
590                .translate_assoc_item_id(trait_id, &target_def_id)
591                .map_err(|err| err.msg)?;
592            Ok(MaybeAssocItemId::Assoc(trait_id, item_id))
593        } else {
594            let kind = match target_def.kind() {
595                // Point at the method that contains the closure code.
596                hax::FullDefKind::Closure { args, .. } => TransItemSourceKind::CallableMethod(
597                    super::translate_closures::translate_closure_kind(&args.kind),
598                ),
599                _ => self
600                    .base_kind_for_item(&target_def_id)
601                    .ok_or_else(|| format!("`{target_def_id:?}` is not a translatable item"))?,
602            };
603            let target_src = TransItemSource::from_item(
604                target_def.this(),
605                kind,
606                self.options.monomorphize_with_hax,
607            );
608            let item_id: ItemId = self.register_and_enqueue(&None, target_src).unwrap();
609            Ok(MaybeAssocItemId::Free(item_id))
610        }
611    }
612
613    /// Parse a raw attribute to recognize our special `charon::*`, `aeneas::*` and `verify::*` attributes.
614    fn parse_attr_from_raw(
615        &mut self,
616        def_id: &hax::DefId,
617        raw_attr: RawAttribute,
618    ) -> Result<Attribute, String> {
619        // If the attribute path has two components, the first of which is `charon` or `aeneas`, we
620        // try to parse it. Otherwise we return `Unknown`.
621        let path = raw_attr.path.split("::").collect_vec();
622        let attr_name = if let &[path_start, attr_name] = path.as_slice()
623            && (path_start == "charon" || path_start == "aeneas" || path_start == "verify")
624        {
625            attr_name
626        } else {
627            return Ok(Attribute::Unknown(raw_attr));
628        };
629
630        match self.parse_special_attr(def_id, attr_name, &raw_attr)? {
631            Some(parsed) => Ok(parsed),
632            None => Err(format!("Unrecognized attribute: `{}`", raw_attr)),
633        }
634    }
635
636    /// Parse a `charon::*`, `aeneas::*` or `verify::*` attribute.
637    fn parse_special_attr(
638        &mut self,
639        def_id: &hax::DefId,
640        attr_name: &str,
641        raw_attr: &RawAttribute,
642    ) -> Result<Option<Attribute>, String> {
643        let args = raw_attr.args.as_deref();
644        let parsed = match attr_name {
645            // `#[charon::opaque]`
646            "opaque" if args.is_none() => Attribute::Opaque,
647            // `#[charon::opaque]`
648            "exclude" if args.is_none() => Attribute::Exclude,
649            // `#[charon::transparent]`
650            "transparent" if args.is_none() => Attribute::Transparent,
651            // `#[charon::contract(kind = "...", parent)]` or
652            // `#[charon::contract(kind = "...", for = "path")]`
653            "contract" if let Some(args) = args => {
654                use syn::{ext::IdentExt, parse::Parser};
655
656                let parser = |input: syn::parse::ParseStream<'_>| {
657                    let mut kind = None;
658                    let mut target = None;
659                    while !input.is_empty() {
660                        let key = input.call(syn::Ident::parse_any)?;
661                        let key_name = key.to_string();
662                        if key_name == "parent" && !input.peek(syn::Token![=]) {
663                            if target.is_some() {
664                                return Err(syn::Error::new(
665                                    key.span(),
666                                    "duplicate contract target",
667                                ));
668                            }
669                            target = Some(ContractTarget::Parent);
670                        } else {
671                            input.parse::<syn::Token![=]>()?;
672                            let value = input.parse::<syn::LitStr>()?.value();
673                            match key_name.as_str() {
674                                "kind" => {
675                                    if kind.is_some() {
676                                        return Err(syn::Error::new(
677                                            key.span(),
678                                            "duplicate contract argument `kind`",
679                                        ));
680                                    }
681                                    kind = Some(value);
682                                }
683                                "for" => {
684                                    if target.is_some() {
685                                        return Err(syn::Error::new(
686                                            key.span(),
687                                            "duplicate contract target",
688                                        ));
689                                    }
690                                    target = Some(ContractTarget::Path(value));
691                                }
692                                "parent" => {
693                                    return Err(syn::Error::new(
694                                        key.span(),
695                                        "contract argument `parent` does not take a value",
696                                    ));
697                                }
698                                _ => {
699                                    return Err(syn::Error::new(
700                                        key.span(),
701                                        format!("unknown contract argument `{key}`"),
702                                    ));
703                                }
704                            }
705                        }
706                        if !input.is_empty() {
707                            input.parse::<syn::Token![,]>()?;
708                        }
709                    }
710                    let kind =
711                        kind.ok_or_else(|| input.error("missing contract argument `kind`"))?;
712                    let target = target
713                        .ok_or_else(|| input.error("missing contract target `parent` or `for`"))?;
714                    Ok((kind, target))
715                };
716                let (kind, target) = parser.parse_str(args).map_err(|err| {
717                    format!(
718                        "invalid contract syntax: {err}; expected \
719                         `#[charon::contract(kind = \"...\", parent)]` or \
720                         `#[charon::contract(kind = \"...\", for = \"item path\")]`"
721                    )
722                })?;
723                Attribute::IsContract {
724                    kind,
725                    target: self.resolve_contract_target(def_id, target)?,
726                }
727            }
728            // `#[charon::rename("new_name")]`
729            "rename" if let Some(attr) = args => {
730                let Some(attr) = attr
731                    .strip_prefix("\"")
732                    .and_then(|attr| attr.strip_suffix("\""))
733                else {
734                    return Err(format!(
735                        "the new name should be between quotes: `rename(\"{attr}\")`."
736                    ));
737                };
738
739                if attr.is_empty() {
740                    return Err(format!("attribute `rename` should not be empty"));
741                }
742
743                let first_char = attr.chars().nth(0).unwrap();
744                let is_identifier = (first_char.is_alphabetic() || first_char == '_')
745                    && attr.chars().all(|c| c.is_alphanumeric() || c == '_');
746                if !is_identifier {
747                    return Err(format!(
748                        "attribute `rename` should contain a valid identifier"
749                    ));
750                }
751
752                Attribute::Rename(attr.to_string())
753            }
754            // `#[charon::variants_prefix("T")]`
755            "variants_prefix" if let Some(attr) = args => {
756                let Some(attr) = attr
757                    .strip_prefix("\"")
758                    .and_then(|attr| attr.strip_suffix("\""))
759                else {
760                    return Err(format!(
761                        "the name should be between quotes: `variants_prefix(\"{attr}\")`."
762                    ));
763                };
764
765                Attribute::VariantsPrefix(attr.to_string())
766            }
767            // `#[charon::variants_suffix("T")]`
768            "variants_suffix" if let Some(attr) = args => {
769                let Some(attr) = attr
770                    .strip_prefix("\"")
771                    .and_then(|attr| attr.strip_suffix("\""))
772                else {
773                    return Err(format!(
774                        "the name should be between quotes: `variants_suffix(\"{attr}\")`."
775                    ));
776                };
777
778                Attribute::VariantsSuffix(attr.to_string())
779            }
780            // `#[verify::start_from]`
781            "start_from" => {
782                if matches!(def_id.kind, hax::DefKind::Mod) {
783                    return Err("`start_from` on modules has no effect".to_string());
784                }
785                Attribute::Unknown(raw_attr.clone())
786            }
787            // `#[verify::test]`: mark a function for test extraction
788            "test" if args.is_none() => Attribute::Unknown(raw_attr.clone()),
789            _ => return Ok(None),
790        };
791        Ok(Some(parsed))
792    }
793
794    /// Translates a rust attribute. Returns `None` if the attribute is a doc comment (rustc
795    /// encodes them as attributes). For now we use `String`s for `Attributes`.
796    pub(crate) fn translate_attribute(
797        &mut self,
798        def_id: &hax::DefId,
799        attr: &rustc_hir::Attribute,
800    ) -> Option<Attribute> {
801        use rustc_hir as hir;
802        use rustc_hir::attrs as hir_attrs;
803        match attr {
804            hir::Attribute::Parsed(hir_attrs::AttributeKind::DocComment { comment, .. }) => {
805                Some(Attribute::DocComment(comment.to_string()))
806            }
807            hir::Attribute::Parsed(attr) => self
808                .translate_rustc_attribute_kind(attr)
809                .ok()
810                .map(Attribute::Builtin),
811            hir::Attribute::Unparsed(attr) => {
812                let raw_attr = RawAttribute {
813                    path: attr.path.to_string(),
814                    args: match &attr.args {
815                        hir::AttrArgs::Empty => None,
816                        hir::AttrArgs::Delimited(args) => {
817                            Some(rustc_ast_pretty::pprust::tts_to_string(&args.tokens))
818                        }
819                        hir::AttrArgs::Eq { expr, .. } => {
820                            self.tcx.sess.source_map().span_to_snippet(expr.span).ok()
821                        }
822                    },
823                };
824                match self.parse_attr_from_raw(def_id, raw_attr) {
825                    Ok(a) => Some(a),
826                    Err(msg) => {
827                        let span = self.translate_span(&attr.span.sinto(&self.hax_state));
828                        register_error!(self, span, "Error parsing attribute: {msg}");
829                        None
830                    }
831                }
832            }
833        }
834    }
835
836    pub(crate) fn translate_inline(&self, def: &hax::FullDef<'tcx>) -> Option<InlineAttr> {
837        match def.kind() {
838            hax::FullDefKind::Fn { inline, .. }
839            | hax::FullDefKind::AssocFn { inline, .. }
840            | hax::FullDefKind::Closure { inline, .. } => match inline {
841                hax::InlineAttr::None => None,
842                hax::InlineAttr::Hint => Some(InlineAttr::Hint),
843                hax::InlineAttr::Never => Some(InlineAttr::Never),
844                hax::InlineAttr::Always => Some(InlineAttr::Always),
845                hax::InlineAttr::Force { .. } => Some(InlineAttr::Always),
846            },
847            _ => None,
848        }
849    }
850
851    pub(crate) fn translate_attr_info(&mut self, def: &hax::FullDef<'tcx>) -> AttrInfo {
852        // Default to `false` for impl blocks and closures.
853        let public = def.visibility.unwrap_or(false);
854        let inline = self.translate_inline(def);
855        let attributes = def
856            .attributes
857            .iter()
858            .filter_map(|attr| self.translate_attribute(def.def_id(), attr))
859            .collect_vec();
860
861        let rename = {
862            let mut renames = attributes.iter().filter_map(|a| a.as_rename()).cloned();
863            let rename = renames.next();
864            if renames.next().is_some() {
865                let span = self.translate_span(&def.span);
866                register_error!(
867                    self,
868                    span,
869                    "There should be at most one `charon::rename(\"...\")` \
870                    or `aeneas::rename(\"...\")` attribute per declaration",
871                );
872            }
873            rename
874        };
875
876        AttrInfo {
877            attributes,
878            inline,
879            public,
880            rename,
881        }
882    }
883}
884
885// `ItemMeta`
886impl<'tcx> TranslateCtx<'tcx> {
887    /// Whether this item is in an `extern { .. }` block, in which case it has no body.
888    pub(crate) fn is_extern_item(&mut self, def: &hax::FullDef<'tcx>) -> bool {
889        def.def_id()
890            .parent(&self.hax_state)
891            .is_some_and(|parent| matches!(parent.kind, hax::DefKind::ForeignMod))
892    }
893
894    /// If this is an item declared in an `extern { .. }` block, return its symbol name.
895    pub(crate) fn extern_item_symbol_name(&mut self, def: &hax::FullDef<'tcx>) -> Option<String> {
896        if !self.is_extern_item(def) {
897            return None;
898        }
899        let path_item = def.def_id().path_item(&self.hax_state);
900        match path_item.data {
901            hax::DefPathItem::ValueNs(name) | hax::DefPathItem::TypeNs(name) => {
902                Some(name.to_string())
903            }
904            _ => None,
905        }
906    }
907
908    /// Compute the meta information for a Rust item.
909    pub(crate) fn translate_item_meta(
910        &mut self,
911        def: &hax::FullDef<'tcx>,
912        item_src: &TransItemSource,
913        name: Name,
914        name_opacity: ItemOpacity,
915    ) -> ItemMeta {
916        if let Some(item_meta) = self.cached_item_metas.get(item_src) {
917            return item_meta.clone();
918        }
919        let span = def.source_span.as_ref().unwrap_or(&def.span);
920        let span = self.translate_span(span);
921        let is_local = def.def_id().is_local();
922        let (attr_info, lang_item, diagnostic_item) = if !item_src.is_derived_item()
923            || matches!(item_src.kind, TransItemSourceKind::CallableMethod(..))
924        {
925            let attr_info = self.translate_attr_info(def);
926            let lang_item = def
927                .def_id()
928                .as_real_def_id()
929                .and_then(|id| self.tcx.as_lang_item(id))
930                .map(|lang_item| {
931                    self.translate_rustc_lang_item(&lang_item)
932                        .expect("all rustc LangItem variants should be translated")
933                });
934            let diagnostic_item = def.diagnostic_item.map(|s| s.to_string());
935            (attr_info, lang_item, diagnostic_item)
936        } else {
937            (AttrInfo::default(), None, None)
938        };
939
940        let opacity = if attr_info.attributes.iter().any(|attr| attr.is_exclude()) {
941            ItemOpacity::Invisible.max(name_opacity)
942        } else if self.is_extern_item(def)
943            || attr_info.attributes.iter().any(|attr| attr.is_opaque())
944        {
945            // Force opaque in these cases.
946            ItemOpacity::Opaque.max(name_opacity)
947        } else {
948            name_opacity
949        };
950
951        let item_meta = ItemMeta {
952            name,
953            span,
954            source_text: def.source_text.clone(),
955            attr_info,
956            is_local,
957            opacity,
958            lang_item,
959            diagnostic_item,
960        };
961        self.cached_item_metas
962            .insert(item_src.clone(), item_meta.clone());
963        item_meta
964    }
965}