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