1use 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
17impl<'tcx> TranslateCtx<'tcx> {
19 fn register_file(&mut self, filename: FileName, span: rustc_span::Span) -> FileId {
22 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 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 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 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 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 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 _ => 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 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 meta::SpanData { file_id, beg, end }
165 }
166
167 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 let data = self.translate_span_data(source_info.span);
175
176 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
202impl<'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 let disambiguator = Disambiguator::new(path_elem.disambiguator as usize);
226 let path_elem = match path_elem.data {
228 DefPathItem::CrateRoot { name, .. } => {
229 Some(PathElem::Ident(name.to_string(), disambiguator))
230 }
231 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 let impl_elem = match full_def.kind() {
244 hax::FullDefKind::InherentImpl { ty, .. } => {
246 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 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 DefPathItem::OpaqueTy => None,
282 DefPathItem::Closure => {
286 Some(PathElem::Builtin(BuiltinPathElem::Closure, disambiguator))
287 }
288 DefPathItem::ForeignMod => None,
291 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 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 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 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(); }
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 pub fn translate_name(&mut self, src: &TransItemSource) -> Result<Name, Error> {
462 let mut name = self.name_for_src(src)?;
463 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 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
493impl<'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 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 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 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 fn parse_attr_from_raw(
641 &mut self,
642 def_id: &hax::DefId,
643 raw_attr: RawAttribute,
644 ) -> Result<Attribute, String> {
645 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 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 "opaque" if args.is_none() => Attribute::Opaque,
673 "exclude" if args.is_none() => Attribute::Exclude,
675 "transparent" if args.is_none() => Attribute::Transparent,
677 "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 "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 "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 "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 "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 "test" if args.is_none() => Attribute::Unknown(raw_attr.clone()),
815 _ => return Ok(None),
816 };
817 Ok(Some(parsed))
818 }
819
820 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 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
911impl<'tcx> TranslateCtx<'tcx> {
913 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 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 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 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}