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