diff --git a/rr_frontend/.cargo/config.toml b/rr_frontend/.cargo/config.toml
index eb40ad9d1483d47410153fee839dd6193952f539..916cbb3dbec008ee56b0fd4df62f70ac70e7a33c 100644
--- a/rr_frontend/.cargo/config.toml
+++ b/rr_frontend/.cargo/config.toml
@@ -31,6 +31,7 @@ rustflags = [
     "-Aclippy::panic_in_result_fn",
     "-Aclippy::unwrap_in_result",
     "-Aclippy::separated_literal_suffix",
+    "-Aclippy::pub_use",
 
     # clippy::style
     "-Aclippy::new_without_default",
diff --git a/rr_frontend/translation/src/attrs.rs b/rr_frontend/translation/src/attrs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..43040e5a1fbac70f1a01612f49015cf4b79f68c0
--- /dev/null
+++ b/rr_frontend/translation/src/attrs.rs
@@ -0,0 +1,82 @@
+// © 2023, The RefinedRust Develcpers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Utility functions for obtaining relevant attributes.
+
+use std::mem;
+
+use log::{info, trace};
+use rr_rustc_interface::ast::ast;
+use rr_rustc_interface::data_structures::fx::FxHashSet;
+use rr_rustc_interface::hir::def_id::{DefId, CRATE_DEF_INDEX};
+use rr_rustc_interface::middle::mir;
+use rr_rustc_interface::middle::ty::{self, TyCtxt};
+use rr_rustc_interface::{hir, middle, span};
+use serde::{Deserialize, Serialize};
+
+use crate::spec_parsers::get_export_as_attr;
+use crate::{force_matches, types, Environment};
+
+/// Check if `<tool>::<name>` is among the attributes, where `tool` is determined by the
+/// `spec_hotword` config.
+/// Any arguments of the attribute are ignored.
+pub fn has_tool_attr(attrs: &[ast::Attribute], name: &str) -> bool {
+    get_tool_attr(attrs, name).is_some()
+}
+
+/// Get the arguments for a tool attribute, if it exists.
+pub fn get_tool_attr<'a>(attrs: &'a [ast::Attribute], name: &str) -> Option<&'a ast::AttrArgs> {
+    attrs.iter().find_map(|attr| match &attr.kind {
+        ast::AttrKind::Normal(na) => {
+            let segments = &na.item.path.segments;
+            let args = &na.item.args;
+            (segments.len() == 2
+                && segments[0].ident.as_str() == rrconfig::spec_hotword().as_str()
+                && segments[1].ident.as_str() == name)
+                .then_some(args)
+        },
+        _ => None,
+    })
+}
+
+/// Check if `<tool>::name` is among the filtered attributes.
+pub fn has_tool_attr_filtered(attrs: &[&ast::AttrItem], name: &str) -> bool {
+    attrs.iter().any(|item| {
+        let segments = &item.path.segments;
+        segments.len() == 2
+            && segments[0].ident.as_str() == rrconfig::spec_hotword().as_str()
+            && segments[1].ident.as_str() == name
+    })
+}
+
+/// Check if any attribute starting with `<tool>` is among the attributes.
+pub fn has_any_tool_attr(attrs: &[ast::Attribute]) -> bool {
+    attrs.iter().any(|attr| match &attr.kind {
+        ast::AttrKind::Normal(na) => {
+            let segments = &na.item.path.segments;
+            segments[0].ident.as_str() == rrconfig::spec_hotword().as_str()
+        },
+        _ => false,
+    })
+}
+
+/// Get all tool attributes, i.e. attributes of the shape `<tool>::attr`, where `tool` is
+/// determined by the `spec_hotword` config.
+pub fn filter_for_tool(attrs: &[ast::Attribute]) -> Vec<&ast::AttrItem> {
+    attrs
+        .iter()
+        .filter_map(|attr| match &attr.kind {
+            ast::AttrKind::Normal(na) => {
+                let item = &na.item;
+
+                let seg = item.path.segments.get(0)?;
+
+                (seg.ident.name.as_str() == rrconfig::spec_hotword()).then_some(item)
+            },
+            _ => None,
+        })
+        .collect()
+}
diff --git a/rr_frontend/translation/src/base.rs b/rr_frontend/translation/src/base.rs
index b23f4fb3abd3fc1de1dc35573907457830272ff7..2d6197749cf0a65ba04512e552a26502c7333fff 100644
--- a/rr_frontend/translation/src/base.rs
+++ b/rr_frontend/translation/src/base.rs
@@ -11,7 +11,16 @@ use rr_rustc_interface::middle::ty;
 use rr_rustc_interface::polonius_engine::FactTypes;
 
 use crate::environment::borrowck::facts;
-use crate::trait_registry::Error;
+use crate::traits;
+
+/// Strip symbols from an identifier to be compatible with Coq.
+/// In particular things like ' or ::.
+pub fn strip_coq_ident(s: &str) -> String {
+    String::from(s)
+        .replace('\'', "")
+        .replace("::", "_")
+        .replace(|c: char| !(c.is_alphanumeric() || c == '_'), "")
+}
 
 pub type Region = <RustcFacts as FactTypes>::Origin;
 pub type Loan = <RustcFacts as FactTypes>::Loan;
@@ -62,15 +71,15 @@ pub enum TranslationError<'tcx> {
     #[display("Trait could not be resolved: {}", _0)]
     TraitResolution(String),
     #[display("Trait translation failed: {}", _0)]
-    TraitTranslation(Error<'tcx>),
+    TraitTranslation(traits::Error<'tcx>),
     #[display("Procedure could not be registered: {}", _0)]
     ProcedureRegistry(String),
     #[display("Lookup in a dummy scope: {}", _0)]
     DummyScope(String),
 }
 
-impl<'tcx> From<Error<'tcx>> for TranslationError<'tcx> {
-    fn from(x: Error<'tcx>) -> Self {
+impl<'tcx> From<traits::Error<'tcx>> for TranslationError<'tcx> {
+    fn from(x: traits::Error<'tcx>) -> Self {
         Self::TraitTranslation(x)
     }
 }
diff --git a/rr_frontend/translation/src/checked_op_analysis.rs b/rr_frontend/translation/src/body/checked_op_analysis.rs
similarity index 100%
rename from rr_frontend/translation/src/checked_op_analysis.rs
rename to rr_frontend/translation/src/body/checked_op_analysis.rs
diff --git a/rr_frontend/translation/src/body/mod.rs b/rr_frontend/translation/src/body/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..52c25f7db2d73b54f9593c7bc2ab4226044fc7e1
--- /dev/null
+++ b/rr_frontend/translation/src/body/mod.rs
@@ -0,0 +1,129 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+mod checked_op_analysis;
+
+use std::collections::{btree_map, BTreeMap, HashMap, HashSet};
+
+use log::{info, trace, warn};
+use radium::coq;
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{
+    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
+    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
+    TerminatorKind, UnOp, VarDebugInfoContents,
+};
+use rr_rustc_interface::middle::ty::fold::TypeFolder;
+use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind};
+use rr_rustc_interface::middle::{mir, ty};
+use rr_rustc_interface::{abi, ast, middle};
+use typed_arena::Arena;
+
+use crate::base::*;
+use crate::environment::borrowck::facts;
+use crate::environment::polonius_info::PoloniusInfo;
+use crate::environment::procedure::Procedure;
+use crate::environment::{dump_borrowck_info, polonius_info, Environment};
+use crate::spec_parsers::parse_utils::ParamLookup;
+use crate::spec_parsers::verbose_function_spec_parser::{
+    ClosureMetaInfo, FunctionRequirements, FunctionSpecParser, VerboseFunctionSpecParser,
+};
+use crate::traits::{registry, resolution};
+use crate::{base, consts, procedures, regions, search, traits, types};
+
+pub mod signature;
+mod translation;
+
+/// Get the syntypes of function arguments for a procedure call.
+pub fn get_arg_syntypes_for_procedure_call<'tcx, 'def>(
+    tcx: ty::TyCtxt<'tcx>,
+    ty_translator: &types::LocalTX<'def, 'tcx>,
+    callee_did: DefId,
+    ty_params: &[ty::GenericArg<'tcx>],
+) -> Result<Vec<radium::SynType>, TranslationError<'tcx>> {
+    let caller_did = ty_translator.get_proc_did();
+
+    // Get the type of the callee, fully instantiated
+    let full_ty: ty::EarlyBinder<Ty<'tcx>> = tcx.type_of(callee_did);
+    let full_ty = full_ty.instantiate(tcx, ty_params);
+
+    // We create a dummy scope in order to make the lifetime lookups succeed, since we only want
+    // syntactic types here.
+    // Since we do the substitution of the generics above, we should translate generics and
+    // traits in the caller's scope.
+    let scope = ty_translator.scope.borrow();
+    let param_env: ty::ParamEnv<'tcx> = tcx.param_env(scope.did);
+    let callee_state = types::CalleeState::new(&param_env, &scope.generic_scope);
+    let mut dummy_state = types::STInner::CalleeTranslation(callee_state);
+
+    let mut syntypes = Vec::new();
+    match full_ty.kind() {
+        ty::TyKind::FnDef(_, _) => {
+            let sig = full_ty.fn_sig(tcx);
+            for ty in sig.inputs().skip_binder() {
+                let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?;
+                syntypes.push(st);
+            }
+        },
+        ty::TyKind::Closure(_, args) => {
+            let clos_args = args.as_closure();
+            let sig = clos_args.sig();
+            let pre_sig = sig.skip_binder();
+            // we also need to add the closure argument here
+
+            let tuple_ty = clos_args.tupled_upvars_ty();
+            match clos_args.kind() {
+                ty::ClosureKind::Fn | ty::ClosureKind::FnMut => {
+                    syntypes.push(radium::SynType::Ptr);
+                },
+                ty::ClosureKind::FnOnce => {
+                    let st =
+                        ty_translator.translator.translate_type_to_syn_type(tuple_ty, &mut dummy_state)?;
+                    syntypes.push(st);
+                },
+            }
+            for ty in pre_sig.inputs() {
+                let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?;
+                syntypes.push(st);
+            }
+        },
+        _ => unimplemented!(),
+    }
+
+    Ok(syntypes)
+}
+
+/// A scope of trait attributes mapping to Coq names to be used in a function's spec
+struct TraitSpecNameScope {
+    attrs: HashMap<String, String>,
+}
+
+/// When translating a function spec where attributes of a trait are in scope,
+/// we create a wrapper to lookup references to the trait's attributes when parsing function specs.
+struct FunctionSpecScope<'a, T> {
+    generics: &'a T,
+    attrs: &'a TraitSpecNameScope,
+}
+impl<'a, T: ParamLookup> ParamLookup for FunctionSpecScope<'a, T> {
+    fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> {
+        self.generics.lookup_ty_param(path)
+    }
+
+    fn lookup_lft(&self, lft: &str) -> Option<&radium::Lft> {
+        self.generics.lookup_lft(lft)
+    }
+
+    fn lookup_literal(&self, path: &[&str]) -> Option<&str> {
+        if path.len() == 1 {
+            if let Some(lit) = self.attrs.attrs.get(path[0]) {
+                return Some(lit);
+            }
+        }
+        self.generics.lookup_literal(path)
+    }
+}
diff --git a/rr_frontend/translation/src/body/signature.rs b/rr_frontend/translation/src/body/signature.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a00d22a8b60416c38cb61d21b7bb2ee1770d62cc
--- /dev/null
+++ b/rr_frontend/translation/src/body/signature.rs
@@ -0,0 +1,870 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Part of the function translation responsible for translating the signature and specification.
+
+use std::collections::{btree_map, BTreeMap, HashMap, HashSet};
+
+use log::{info, trace, warn};
+use radium::coq;
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{
+    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
+    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
+    TerminatorKind, UnOp, VarDebugInfoContents,
+};
+use rr_rustc_interface::middle::ty::fold::TypeFolder;
+use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind};
+use rr_rustc_interface::middle::{mir, ty};
+use rr_rustc_interface::{abi, ast, middle};
+use typed_arena::Arena;
+
+use crate::base::*;
+use crate::body::checked_op_analysis::CheckedOpLocalAnalysis;
+use crate::body::translation;
+use crate::environment::borrowck::facts;
+use crate::environment::polonius_info::PoloniusInfo;
+use crate::environment::procedure::Procedure;
+use crate::environment::{dump_borrowck_info, polonius_info, Environment};
+use crate::regions::inclusion_tracker::InclusionTracker;
+use crate::spec_parsers::parse_utils::ParamLookup;
+use crate::spec_parsers::verbose_function_spec_parser::{
+    ClosureMetaInfo, FunctionRequirements, FunctionSpecParser, VerboseFunctionSpecParser,
+};
+use crate::traits::{registry, resolution};
+use crate::{base, consts, procedures, regions, search, traits, types};
+
+/// A scope of trait attributes mapping to Coq names to be used in a function's spec
+struct TraitSpecNameScope {
+    attrs: HashMap<String, String>,
+}
+
+/// When translating a function spec where attributes of a trait are in scope,
+/// we create a wrapper to lookup references to the trait's attributes when parsing function specs.
+struct FunctionSpecScope<'a, T> {
+    generics: &'a T,
+    attrs: &'a TraitSpecNameScope,
+}
+impl<'a, T: ParamLookup> ParamLookup for FunctionSpecScope<'a, T> {
+    fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> {
+        self.generics.lookup_ty_param(path)
+    }
+
+    fn lookup_lft(&self, lft: &str) -> Option<&radium::Lft> {
+        self.generics.lookup_lft(lft)
+    }
+
+    fn lookup_literal(&self, path: &[&str]) -> Option<&str> {
+        if path.len() == 1 {
+            if let Some(lit) = self.attrs.attrs.get(path[0]) {
+                return Some(lit);
+            }
+        }
+        self.generics.lookup_literal(path)
+    }
+}
+
+pub struct TX<'a, 'def, 'tcx> {
+    env: &'def Environment<'tcx>,
+    /// this needs to be annotated with the right borrowck things
+    proc: &'def Procedure<'tcx>,
+    /// the Caesium function buildder
+    translated_fn: radium::FunctionBuilder<'def>,
+    /// tracking lifetime inclusions for the generation of lifetime inclusions
+    inclusion_tracker: InclusionTracker<'a, 'tcx>,
+
+    /// registry of other procedures
+    procedure_registry: &'a procedures::Scope<'def>,
+    /// registry of consts
+    const_registry: &'a consts::Scope<'def>,
+    /// attributes on this function
+    attrs: &'a [&'a ast::ast::AttrItem],
+    /// polonius info for this function
+    info: &'a PoloniusInfo<'a, 'tcx>,
+    /// translator for types
+    ty_translator: types::LocalTX<'def, 'tcx>,
+    /// trait registry in the current scope
+    trait_registry: &'def registry::TR<'tcx, 'def>,
+    /// argument types (from the signature, with generics substituted)
+    inputs: Vec<Ty<'tcx>>,
+}
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    /// Generate a spec for a trait method.
+    pub fn spec_for_trait_method(
+        env: &'def Environment<'tcx>,
+        proc_did: DefId,
+        name: &str,
+        spec_name: &str,
+        attrs: &'a [&'a ast::ast::AttrItem],
+        ty_translator: &'def types::TX<'def, 'tcx>,
+        trait_registry: &'def registry::TR<'tcx, 'def>,
+    ) -> Result<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>, TranslationError<'tcx>> {
+        let mut translated_fn = radium::FunctionBuilder::new(name, spec_name);
+
+        let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc_did);
+        let ty = ty.instantiate_identity();
+        // substs are the generic args of this function (including lifetimes)
+        // sig is the function signature
+        let sig = match ty.kind() {
+            TyKind::FnDef(_def, _args) => {
+                assert!(ty.is_fn());
+                let sig = ty.fn_sig(env.tcx());
+                sig
+            },
+            _ => panic!("can not handle non-fns"),
+        };
+        info!("Function signature: {:?}", sig);
+
+        let params = Self::get_proc_ty_params(env.tcx(), proc_did);
+        info!("Function generic args: {:?}", params);
+
+        let (inputs, output, region_substitution) =
+            regions::init::replace_fnsig_args_with_polonius_vars(env, params, sig);
+        info!("inputs: {:?}, output: {:?}", inputs, output);
+
+        let (type_scope, trait_attrs) = Self::setup_local_scope(
+            env,
+            ty_translator,
+            trait_registry,
+            proc_did,
+            params.as_slice(),
+            &mut translated_fn,
+            region_substitution,
+            false,
+            None,
+        )?;
+        let type_translator = types::LocalTX::new(ty_translator, type_scope);
+
+        // TODO: add universal constraints (ideally in setup_local_scope)
+
+        let spec_builder = Self::process_attrs(
+            attrs,
+            &type_translator,
+            &mut translated_fn,
+            &trait_attrs,
+            inputs.as_slice(),
+            output,
+        )?;
+        translated_fn.add_function_spec_from_builder(spec_builder);
+
+        translated_fn.try_into().map_err(TranslationError::AttributeError)
+    }
+
+    /// Create a translation instance for a closure.
+    pub fn new_closure(
+        env: &'def Environment<'tcx>,
+        meta: &procedures::Meta,
+        proc: Procedure<'tcx>,
+        attrs: &'a [&'a ast::ast::AttrItem],
+        ty_translator: &'def types::TX<'def, 'tcx>,
+        trait_registry: &'def registry::TR<'tcx, 'def>,
+        proc_registry: &'a procedures::Scope<'def>,
+        const_registry: &'a consts::Scope<'def>,
+    ) -> Result<Self, TranslationError<'tcx>> {
+        let mut translated_fn = radium::FunctionBuilder::new(meta.get_name(), meta.get_spec_name());
+
+        // TODO can we avoid the leak
+        let proc: &'def Procedure = &*Box::leak(Box::new(proc));
+        let body = proc.get_mir();
+        Self::dump_body(body);
+
+        let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc.get_id());
+        let ty = ty.instantiate_identity();
+        let closure_kind = match ty.kind() {
+            TyKind::Closure(_def, closure_args) => {
+                assert!(ty.is_closure());
+                let clos = closure_args.as_closure();
+                clos.kind()
+            },
+            _ => panic!("can not handle non-closures"),
+        };
+
+        let local_decls = &body.local_decls;
+        let closure_arg = local_decls.get(Local::from_usize(1)).unwrap();
+        let closure_ty;
+
+        match closure_kind {
+            ty::ClosureKind::Fn => {
+                if let ty::TyKind::Ref(_, ty, _) = closure_arg.ty.kind() {
+                    closure_ty = ty;
+                } else {
+                    unreachable!();
+                }
+            },
+            ty::ClosureKind::FnMut => {
+                if let ty::TyKind::Ref(_, ty, _) = closure_arg.ty.kind() {
+                    closure_ty = ty;
+                } else {
+                    unreachable!("unexpected type {:?}", closure_arg.ty);
+                }
+            },
+            ty::ClosureKind::FnOnce => {
+                closure_ty = &closure_arg.ty;
+            },
+        }
+
+        let parent_args;
+        let mut capture_regions = Vec::new();
+        let sig;
+        let captures;
+        let upvars_tys;
+        if let ty::TyKind::Closure(did, closure_args) = closure_ty.kind() {
+            let clos = closure_args.as_closure();
+
+            let tupled_upvars_tys = clos.tupled_upvars_ty();
+            upvars_tys = clos.upvar_tys();
+            parent_args = clos.parent_args();
+            let unnormalized_sig = clos.sig();
+            sig = unnormalized_sig;
+            info!("closure sig: {:?}", sig);
+
+            captures = env.tcx().closure_captures(did.as_local().unwrap());
+            info!("Closure has captures: {:?}", captures);
+
+            // find additional lifetime parameters
+            for (place, ty) in captures.iter().zip(clos.upvar_tys().iter()) {
+                if place.region.is_some() {
+                    // find region from ty
+                    if let ty::TyKind::Ref(region, _, _) = ty.kind() {
+                        capture_regions.push(*region);
+                    }
+                }
+            }
+            info!("Closure capture regions: {:?}", capture_regions);
+
+            info!("Closure arg upvar_tys: {:?}", tupled_upvars_tys);
+            info!("Function signature: {:?}", sig);
+            info!("Closure generic args: {:?}", parent_args);
+        } else {
+            unreachable!();
+        }
+
+        match PoloniusInfo::new(env, proc) {
+            Ok(info) => {
+                // TODO: avoid leak
+                let info: &'def PoloniusInfo = &*Box::leak(Box::new(info));
+
+                // For closures, we only handle the parent's args here!
+                // TODO: do we need to do something special for the parent's late-bound region
+                // parameters?
+                // TODO: should we always take the lifetime parameters?
+                let params = parent_args;
+                //proc.get_type_params();
+                info!("Function generic args: {:?}", params);
+
+                // dump graphviz files
+                // color code: red: dying loan, pink: becoming a zombie; green: is zombie
+                if rrconfig::dump_borrowck_info() {
+                    dump_borrowck_info(env, proc.get_id(), info);
+                }
+
+                let (tupled_inputs, output, mut region_substitution) =
+                    regions::init::replace_fnsig_args_with_polonius_vars(env, params, sig);
+
+                // Process the lifetime parameters that come from the captures
+                for r in capture_regions {
+                    // TODO: problem: we're introducing inconsistent names here.
+                    if let ty::RegionKind::ReVar(r) = r.kind() {
+                        let lft = info.mk_atomic_region(r);
+                        let name = regions::format_atomic_region_direct(&lft, None);
+                        region_substitution.region_names.insert(r, name);
+                        // TODO: add to region_substitution?
+                    } else {
+                        unreachable!();
+                    }
+                }
+                // also add the lifetime for the outer reference
+                let mut maybe_outer_lifetime = None;
+                if let ty::TyKind::Ref(r, _, _) = closure_arg.ty.kind() {
+                    if let ty::RegionKind::ReVar(r) = r.kind() {
+                        // We need to do some hacks here to find the right Polonius region:
+                        // `r` is the non-placeholder region that the variable gets, but we are
+                        // looking for the corresponding placeholder region
+                        let r2 = regions::init::find_placeholder_region_for(r, info).unwrap();
+
+                        info!("using lifetime {:?} for closure universal", r2);
+                        let lft = info.mk_atomic_region(r2);
+                        let name = regions::format_atomic_region_direct(&lft, None);
+                        region_substitution.region_names.insert(r2, name);
+
+                        maybe_outer_lifetime = Some(r2);
+                    } else {
+                        unreachable!();
+                    }
+                }
+
+                // detuple the inputs
+                assert!(tupled_inputs.len() == 1);
+                let input_tuple_ty = tupled_inputs[0];
+                let mut inputs = Vec::new();
+
+                // push the closure as the first argument
+                /*
+                if let Some(r2) = maybe_outer_lifetime {
+                    // in this case, we need to patch the region first
+                    if let ty::TyKind::Ref(_, ty, m) = closure_arg.ty.kind() {
+                        let new_region = ty::Region::new_var(env.tcx(), r2);
+                        inputs.push(env.tcx().mk_ty_from_kind(ty::TyKind::Ref(new_region, *ty, *m)));
+                    }
+                }
+                else {
+                    inputs.push(closure_arg.ty);
+                }
+                */
+
+                if let ty::TyKind::Tuple(args) = input_tuple_ty.kind() {
+                    inputs.extend(args.iter());
+                }
+
+                info!("inputs({}): {:?}, output: {:?}", inputs.len(), inputs, output);
+
+                let mut inclusion_tracker = InclusionTracker::new(info);
+                // add placeholder subsets
+                let initial_point: facts::Point = facts::Point {
+                    location: BasicBlock::from_u32(0).start_location(),
+                    typ: facts::PointType::Start,
+                };
+                for (r1, r2) in &info.borrowck_in_facts.known_placeholder_subset {
+                    inclusion_tracker.add_static_inclusion(
+                        *r1,
+                        *r2,
+                        info.interner.get_point_index(&initial_point),
+                    );
+                }
+
+                let (type_scope, trait_attrs) = Self::setup_local_scope(
+                    env,
+                    ty_translator,
+                    trait_registry,
+                    proc.get_id(),
+                    params,
+                    &mut translated_fn,
+                    region_substitution,
+                    true,
+                    Some(info),
+                )?;
+                let type_translator = types::LocalTX::new(ty_translator, type_scope);
+
+                let mut t = Self {
+                    env,
+                    proc,
+                    info,
+                    translated_fn,
+                    inclusion_tracker,
+                    procedure_registry: proc_registry,
+                    attrs,
+                    ty_translator: type_translator,
+                    trait_registry,
+                    const_registry,
+                    inputs: inputs.clone(),
+                };
+
+                // compute meta information needed to generate the spec
+                let mut translated_upvars_types = Vec::new();
+                for ty in upvars_tys {
+                    let translated_ty = t.ty_translator.translate_type(ty)?;
+                    translated_upvars_types.push(translated_ty);
+                }
+                let meta;
+                {
+                    let scope = t.ty_translator.scope.borrow();
+                    meta = ClosureMetaInfo {
+                        kind: closure_kind,
+                        captures,
+                        capture_tys: &translated_upvars_types,
+                        closure_lifetime: maybe_outer_lifetime
+                            .map(|x| scope.lifetime_scope.lookup_region(x).unwrap().to_owned()),
+                    };
+                }
+
+                // process attributes
+                t.process_closure_attrs(&trait_attrs, &inputs, output, meta)?;
+                Ok(t)
+            },
+            Err(err) => Err(TranslationError::UnknownError(format!("{:?}", err))),
+        }
+    }
+
+    /// Translate the body of a function.
+    pub fn new(
+        env: &'def Environment<'tcx>,
+        meta: &procedures::Meta,
+        proc: Procedure<'tcx>,
+        attrs: &'a [&'a ast::ast::AttrItem],
+        ty_translator: &'def types::TX<'def, 'tcx>,
+        trait_registry: &'def registry::TR<'tcx, 'def>,
+        proc_registry: &'a procedures::Scope<'def>,
+        const_registry: &'a consts::Scope<'def>,
+    ) -> Result<Self, TranslationError<'tcx>> {
+        let mut translated_fn = radium::FunctionBuilder::new(meta.get_name(), meta.get_spec_name());
+
+        // TODO can we avoid the leak
+        let proc: &'def Procedure = &*Box::leak(Box::new(proc));
+
+        let body = proc.get_mir();
+        Self::dump_body(body);
+
+        let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc.get_id());
+        let ty = ty.instantiate_identity();
+        // substs are the generic args of this function (including lifetimes)
+        // sig is the function signature
+        let sig = match ty.kind() {
+            TyKind::FnDef(_def, _args) => {
+                assert!(ty.is_fn());
+                let sig = ty.fn_sig(env.tcx());
+                sig
+            },
+            _ => panic!("can not handle non-fns"),
+        };
+
+        info!("Function signature: {:?}", sig);
+
+        match PoloniusInfo::new(env, proc) {
+            Ok(info) => {
+                // TODO: avoid leak
+                let info: &'def PoloniusInfo = &*Box::leak(Box::new(info));
+
+                let params = Self::get_proc_ty_params(env.tcx(), proc.get_id());
+                info!("Function generic args: {:?}", params);
+
+                // dump graphviz files
+                // color code: red: dying loan, pink: becoming a zombie; green: is zombie
+                if rrconfig::dump_borrowck_info() {
+                    dump_borrowck_info(env, proc.get_id(), info);
+                }
+
+                let (inputs, output, region_substitution) =
+                    regions::init::replace_fnsig_args_with_polonius_vars(env, params, sig);
+                info!("inputs: {:?}, output: {:?}", inputs, output);
+
+                let mut inclusion_tracker = InclusionTracker::new(info);
+                // add placeholder subsets
+                let initial_point: facts::Point = facts::Point {
+                    location: BasicBlock::from_u32(0).start_location(),
+                    typ: facts::PointType::Start,
+                };
+                for (r1, r2) in &info.borrowck_in_facts.known_placeholder_subset {
+                    inclusion_tracker.add_static_inclusion(
+                        *r1,
+                        *r2,
+                        info.interner.get_point_index(&initial_point),
+                    );
+                }
+
+                let (type_scope, trait_attrs) = Self::setup_local_scope(
+                    env,
+                    ty_translator,
+                    trait_registry,
+                    proc.get_id(),
+                    params.as_slice(),
+                    &mut translated_fn,
+                    region_substitution,
+                    true,
+                    Some(info),
+                )?;
+                let type_translator = types::LocalTX::new(ty_translator, type_scope);
+
+                // process attributes
+                let mut spec_builder = Self::process_attrs(
+                    attrs,
+                    &type_translator,
+                    &mut translated_fn,
+                    &trait_attrs,
+                    inputs.as_slice(),
+                    output,
+                )?;
+
+                let mut t = Self {
+                    env,
+                    proc,
+                    info,
+                    translated_fn,
+                    inclusion_tracker,
+                    procedure_registry: proc_registry,
+                    attrs,
+                    ty_translator: type_translator,
+                    trait_registry,
+                    const_registry,
+                    inputs: inputs.clone(),
+                };
+
+                if spec_builder.has_spec() {
+                    // add universal constraints
+                    {
+                        let scope = t.ty_translator.scope.borrow();
+                        let universal_constraints = regions::init::get_relevant_universal_constraints(
+                            &scope.lifetime_scope,
+                            &mut t.inclusion_tracker,
+                            t.info,
+                        );
+                        info!("univeral constraints: {:?}", universal_constraints);
+                        for (lft1, lft2) in universal_constraints {
+                            spec_builder.add_lifetime_constraint(lft1, lft2);
+                        }
+                    }
+
+                    t.translated_fn.add_function_spec_from_builder(spec_builder);
+                } else {
+                    let spec = t.make_trait_instance_spec()?;
+                    if let Some(spec) = spec {
+                        t.translated_fn.add_trait_function_spec(spec);
+                    } else {
+                        return Err(TranslationError::AttributeError(
+                            "No valid specification provided".to_owned(),
+                        ));
+                    }
+                }
+
+                Ok(t)
+            },
+            Err(err) => Err(TranslationError::UnknownError(format!("{:?}", err))),
+        }
+    }
+
+    /// Translate the body of the function.
+    pub fn translate(
+        mut self,
+        spec_arena: &'def Arena<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>,
+    ) -> Result<radium::Function<'def>, TranslationError<'tcx>> {
+        let translator = translation::TX::new(
+            self.env,
+            self.procedure_registry,
+            self.const_registry,
+            self.trait_registry,
+            self.ty_translator,
+            self.proc,
+            self.attrs,
+            self.info,
+            &self.inputs,
+            self.inclusion_tracker,
+            self.translated_fn,
+        )?;
+        translator.translate(spec_arena)
+    }
+
+    /// Translation that only generates a specification.
+    pub fn generate_spec(
+        self,
+    ) -> Result<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>, TranslationError<'tcx>> {
+        self.translated_fn.try_into().map_err(TranslationError::AttributeError)
+    }
+}
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    /// Get type parameters of the given procedure.
+    fn get_proc_ty_params(tcx: ty::TyCtxt<'tcx>, did: DefId) -> ty::GenericArgsRef<'tcx> {
+        let ty = tcx.type_of(did).instantiate_identity();
+        match ty.kind() {
+            ty::TyKind::FnDef(_, params) => params,
+            ty::TyKind::Closure(_, closure_args) => {
+                assert!(ty.is_closure());
+                let clos = closure_args.as_closure();
+                let parent_args = clos.parent_args();
+
+                // TODO: this doesn't include lifetime parameters specific to this closure...
+                tcx.mk_args(parent_args)
+            },
+            _ => panic!("Procedure::new called on a procedure whose type is not TyKind::FnDef!"),
+        }
+    }
+
+    /// Set up the local generic scope of the function, including type parameters, lifetime
+    /// parameters, and trait constraints.
+    fn setup_local_scope(
+        env: &Environment<'tcx>,
+        ty_translator: &'def types::TX<'def, 'tcx>,
+        trait_registry: &'def registry::TR<'tcx, 'def>,
+        proc_did: DefId,
+        params: &[ty::GenericArg<'tcx>],
+        translated_fn: &mut radium::FunctionBuilder<'def>,
+        region_substitution: regions::EarlyLateRegionMap,
+        add_trait_specs: bool,
+        info: Option<&'def PoloniusInfo<'def, 'tcx>>,
+    ) -> Result<(types::FunctionState<'tcx, 'def>, TraitSpecNameScope), TranslationError<'tcx>> {
+        // add universals to the function
+        // important: these need to be in the right order!
+        for (vid, name) in &region_substitution.region_names {
+            translated_fn.add_universal_lifetime(name.to_owned());
+        }
+
+        // enter the procedure
+        let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(proc_did);
+        let type_scope = types::FunctionState::new_with_traits(
+            proc_did,
+            env,
+            env.tcx().mk_args(params),
+            region_substitution,
+            param_env,
+            ty_translator,
+            trait_registry,
+            info,
+        )?;
+
+        // add generic args to the fn
+        let generics = &type_scope.generic_scope;
+        for t in generics.tyvars() {
+            translated_fn.add_ty_param(t);
+        }
+
+        // add specs for traits of generics
+        let trait_uses = type_scope.generic_scope.trait_scope().get_trait_uses();
+        for ((did, params), trait_ref) in trait_uses {
+            let trait_use_ref = trait_ref.trait_use.borrow();
+            let trait_use = trait_use_ref.as_ref().unwrap();
+            translated_fn.add_trait_requirement(trait_use.clone());
+        }
+
+        // check if we are in the implementation of a trait or trait impl
+        let mut trait_attr_map = HashMap::new();
+        if let Some(trait_did) = env.tcx().trait_of_item(proc_did) {
+            // we are in a trait declaration
+            if let Some(trait_ref) = trait_registry.lookup_trait(trait_did) {
+                // make the parameter for the attrs that the function is parametric over
+                if let Some(trait_use_ref) = type_scope.generic_scope.trait_scope().get_self_trait_use() {
+                    let trait_use_ref = trait_use_ref.trait_use.borrow();
+                    let trait_use = trait_use_ref.as_ref().unwrap();
+                    let param_name = trait_use.make_spec_attrs_param_name();
+                    // add the corresponding record entries to the map
+                    for attr in &trait_ref.declared_attrs {
+                        let record_item = trait_ref.make_spec_attr_name(attr);
+                        trait_attr_map.insert(attr.to_owned(), format!("{param_name}.({record_item})"));
+                    }
+                }
+            }
+        }
+        if let Some(impl_did) = env.tcx().impl_of_method(proc_did) {
+            if let Some(trait_did) = env.tcx().trait_id_of_impl(impl_did) {
+                // we are in a trait impl
+                if let Some(trait_ref) = trait_registry.lookup_trait(trait_did) {
+                    let attrs = trait_registry.get_impl_attrs_term(impl_did)?;
+                    // add the corresponding record entries to the map
+                    for attr in &trait_ref.declared_attrs {
+                        let record_item = trait_ref.make_spec_attr_name(attr);
+                        trait_attr_map.insert(attr.to_owned(), format!("({attrs}).({record_item})"));
+                    }
+                }
+            }
+        }
+        let trait_scope = TraitSpecNameScope {
+            attrs: trait_attr_map,
+        };
+
+        // TODO: can we also setup the lifetime constraints here?
+        // TODO: understand better how these clauses relate to Polonius
+        // Note: these constraints do not seem to include implied bounds.
+        /*
+        let clauses = param_env.caller_bounds();
+        info!("looking for outlives clauses");
+        for cl in clauses.iter() {
+            let cl_kind = cl.kind();
+            let cl_kind = cl_kind.skip_binder();
+
+            // only look for trait predicates for now
+            if let ty::ClauseKind::RegionOutlives(region_pred) = cl_kind {
+                info!("region outlives: {:?} {:?}", region_pred.0, region_pred.1);
+            }
+            if let ty::ClauseKind::TypeOutlives(outlives_pred) = cl_kind {
+                info!("type outlives: {:?} {:?}", outlives_pred.0, outlives_pred.1);
+            }
+        }
+        */
+
+        Ok((type_scope, trait_scope))
+    }
+
+    /// Process extra requirements annotated on a function spec.
+    fn process_function_requirements(
+        fn_builder: &mut radium::FunctionBuilder<'def>,
+        requirements: FunctionRequirements,
+    ) {
+        for e in requirements.early_coq_params {
+            fn_builder.add_early_coq_param(e);
+        }
+        for e in requirements.late_coq_params {
+            fn_builder.add_late_coq_param(e);
+        }
+        for e in requirements.proof_info.linktime_assumptions {
+            fn_builder.add_linktime_assumption(e);
+        }
+        for e in requirements.proof_info.sidecond_tactics {
+            fn_builder.add_manual_tactic(e);
+        }
+    }
+
+    /// Parse and process attributes of this closure.
+    fn process_closure_attrs<'b>(
+        &mut self,
+        trait_attrs: &TraitSpecNameScope,
+        inputs: &[Ty<'tcx>],
+        output: Ty<'tcx>,
+        meta: ClosureMetaInfo<'b, 'tcx, 'def>,
+    ) -> Result<(), TranslationError<'tcx>> {
+        trace!("entering process_closure_attrs");
+        let v = self.attrs;
+
+        // Translate signature
+        info!("inputs: {:?}, output: {:?}", inputs, output);
+        let mut translated_arg_types: Vec<radium::Type<'def>> = Vec::new();
+        for arg in inputs {
+            let translated: radium::Type<'def> = self.ty_translator.translate_type(*arg)?;
+            translated_arg_types.push(translated);
+        }
+        let translated_ret_type: radium::Type<'def> = self.ty_translator.translate_type(output)?;
+        info!("translated function type: {:?} → {}", translated_arg_types, translated_ret_type);
+
+        // Determine parser
+        let parser = rrconfig::attribute_parser();
+        if parser.as_str() != "verbose" {
+            trace!("leaving process_closure_attrs");
+            return Err(TranslationError::UnknownAttributeParser(parser));
+        }
+
+        let mut spec_builder = radium::LiteralFunctionSpecBuilder::new();
+
+        // add universal constraints
+        {
+            let scope = self.ty_translator.scope.borrow();
+            let universal_constraints = regions::init::get_relevant_universal_constraints(
+                &scope.lifetime_scope,
+                &mut self.inclusion_tracker,
+                self.info,
+            );
+            info!("universal constraints: {:?}", universal_constraints);
+            for (lft1, lft2) in universal_constraints {
+                spec_builder.add_lifetime_constraint(lft1, lft2);
+            }
+        }
+
+        let ty_translator = &self.ty_translator;
+        // Hack: create indirection by tracking the tuple uses we create in here.
+        // (We need a read reference to the scope, so we can't write to it at the same time)
+        let mut tuple_uses = HashMap::new();
+        {
+            let scope = ty_translator.scope.borrow();
+            let scope = FunctionSpecScope {
+                generics: &*scope,
+                attrs: trait_attrs,
+            };
+            let mut parser =
+                VerboseFunctionSpecParser::new(&translated_arg_types, &translated_ret_type, &scope, |lit| {
+                    ty_translator.translator.intern_literal(lit)
+                });
+
+            parser
+                .parse_closure_spec(v, &mut spec_builder, meta, |x| {
+                    ty_translator.make_tuple_use(x, Some(&mut tuple_uses))
+                })
+                .map_err(TranslationError::AttributeError)?;
+
+            Self::process_function_requirements(&mut self.translated_fn, parser.into());
+        }
+        let mut scope = ty_translator.scope.borrow_mut();
+        scope.tuple_uses.extend(tuple_uses);
+        self.translated_fn.add_function_spec_from_builder(spec_builder);
+
+        trace!("leaving process_closure_attrs");
+        Ok(())
+    }
+
+    /// Parse and process attributes of this function.
+    fn process_attrs(
+        attrs: &[&ast::ast::AttrItem],
+        ty_translator: &types::LocalTX<'def, 'tcx>,
+        translator: &mut radium::FunctionBuilder<'def>,
+        trait_attrs: &TraitSpecNameScope,
+        inputs: &[Ty<'tcx>],
+        output: Ty<'tcx>,
+    ) -> Result<radium::LiteralFunctionSpecBuilder<'def>, TranslationError<'tcx>> {
+        info!("inputs: {:?}, output: {:?}", inputs, output);
+
+        let mut translated_arg_types: Vec<radium::Type<'def>> = Vec::new();
+        for arg in inputs {
+            let translated: radium::Type<'def> = ty_translator.translate_type(*arg)?;
+            translated_arg_types.push(translated);
+        }
+        let translated_ret_type: radium::Type<'def> = ty_translator.translate_type(output)?;
+        info!("translated function type: {:?} → {}", translated_arg_types, translated_ret_type);
+
+        let mut spec_builder = radium::LiteralFunctionSpecBuilder::new();
+
+        let parser = rrconfig::attribute_parser();
+        match parser.as_str() {
+            "verbose" => {
+                {
+                    let scope = ty_translator.scope.borrow();
+                    let scope = FunctionSpecScope {
+                        generics: &*scope,
+                        attrs: trait_attrs,
+                    };
+                    let mut parser: VerboseFunctionSpecParser<'_, 'def, _, _> =
+                        VerboseFunctionSpecParser::new(
+                            &translated_arg_types,
+                            &translated_ret_type,
+                            &scope,
+                            |lit| ty_translator.translator.intern_literal(lit),
+                        );
+
+                    parser
+                        .parse_function_spec(attrs, &mut spec_builder)
+                        .map_err(TranslationError::AttributeError)?;
+                    Self::process_function_requirements(translator, parser.into());
+                }
+
+                Ok(spec_builder)
+            },
+            _ => Err(TranslationError::UnknownAttributeParser(parser)),
+        }
+    }
+
+    /// Make a specification for a method of a trait instance derived from the trait's default spec.
+    fn make_trait_instance_spec(
+        &self,
+    ) -> Result<Option<radium::InstantiatedTraitFunctionSpec<'def>>, TranslationError<'tcx>> {
+        let did = self.proc.get_id();
+        if let Some(impl_did) = self.env.tcx().impl_of_method(did) {
+            if let Some(trait_did) = self.env.tcx().trait_id_of_impl(impl_did) {
+                let trait_ref = self
+                    .trait_registry
+                    .lookup_trait(trait_did)
+                    .ok_or_else(|| TranslationError::TraitResolution(format!("{trait_did:?}")))?;
+                let fn_name = base::strip_coq_ident(self.env.tcx().item_name(self.proc.get_id()).as_str());
+
+                let trait_info = self.trait_registry.get_trait_impl_info(impl_did)?;
+                //self.trait_registry.lookup_impl(impl_did)?;
+                let attr_term = self.trait_registry.get_impl_attrs_term(impl_did)?;
+                return Ok(Some(radium::InstantiatedTraitFunctionSpec::new(trait_info, fn_name, attr_term)));
+            }
+        }
+
+        Ok(None)
+    }
+
+    fn dump_body(body: &Body) {
+        // TODO: print to file
+        //for dec in &body.local_decls {
+        //info!("Local decl: {:?}", dec);
+        //}
+
+        let basic_blocks = &body.basic_blocks;
+        for (bb_idx, bb) in basic_blocks.iter_enumerated() {
+            Self::dump_basic_block(bb_idx, bb);
+        }
+    }
+
+    /// Dump a basic block as info debug output.
+    fn dump_basic_block(bb_idx: BasicBlock, bb: &BasicBlockData) {
+        info!("Basic block {:?}:", bb_idx);
+        let mut i = 0;
+        for s in &bb.statements {
+            info!("{}\t{:?}", i, s);
+            i += 1;
+        }
+        info!("{}\t{:?}", i, bb.terminator());
+    }
+}
diff --git a/rr_frontend/translation/src/body/translation/basicblock.rs b/rr_frontend/translation/src/body/translation/basicblock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2f74d8776976a90e159ccc8dd50febab8af132f0
--- /dev/null
+++ b/rr_frontend/translation/src/body/translation/basicblock.rs
@@ -0,0 +1,245 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+use std::collections::{btree_map, BTreeMap, HashMap, HashSet};
+
+use log::info;
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{
+    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
+    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
+    TerminatorKind, UnOp, VarDebugInfoContents,
+};
+use rr_rustc_interface::middle::ty::fold::TypeFolder;
+use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind};
+use rr_rustc_interface::middle::{mir, ty};
+use rr_rustc_interface::{abi, ast, middle};
+
+use super::TX;
+use crate::base::*;
+use crate::traits::{registry, resolution};
+use crate::{base, consts, procedures, regions, search, traits, types};
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    /// Translate a single basic block.
+    pub(super) fn translate_basic_block(
+        &mut self,
+        bb_idx: BasicBlock,
+        bb: &BasicBlockData<'tcx>,
+    ) -> Result<radium::Stmt, TranslationError<'tcx>> {
+        // we translate from back to front, starting with the terminator, since Caesium statements
+        // have a continuation (the next statement to execute)
+
+        // first do the endlfts for the things right before the terminator
+        let mut idx = bb.statements.len();
+        let loc = Location {
+            block: bb_idx,
+            statement_index: idx,
+        };
+        let dying = self.info.get_dying_loans(loc);
+        // TODO zombie?
+        let _dying_zombie = self.info.get_dying_zombie_loans(loc);
+        let mut cont_stmt: radium::Stmt = self.translate_terminator(bb.terminator(), loc, dying)?;
+
+        //cont_stmt = self.prepend_endlfts(cont_stmt, loc, dying);
+        //cont_stmt = self.prepend_endlfts(cont_stmt, loc, dying_zombie);
+
+        for stmt in bb.statements.iter().rev() {
+            idx -= 1;
+            let loc = Location {
+                block: bb_idx,
+                statement_index: idx,
+            };
+
+            // get all dying loans, and emit endlfts for these.
+            // We loop over all predecessor locations, since some loans may end at the start of a
+            // basic block (in particular related to NLL stuff)
+            let pred = self.get_loc_predecessors(loc);
+            let mut dying_loans = HashSet::new();
+            for p in pred {
+                let dying_between = self.info.get_loans_dying_between(p, loc, false);
+                for l in &dying_between {
+                    dying_loans.insert(*l);
+                }
+                // also include zombies
+                let dying_between = self.info.get_loans_dying_between(p, loc, true);
+                for l in &dying_between {
+                    dying_loans.insert(*l);
+                }
+            }
+            // we prepend them before the current statement
+
+            match &stmt.kind {
+                StatementKind::Assign(b) => {
+                    let (plc, val) = b.as_ref();
+
+                    if (self.is_spec_closure_local(plc.local)?).is_some() {
+                        info!("skipping assignment to spec closure local: {:?}", plc);
+                    } else if let Some(rewritten_ty) = self.checked_op_temporaries.get(&plc.local) {
+                        // if this is a checked op, be sure to remember it
+                        info!("rewriting assignment to checked op: {:?}", plc);
+
+                        let synty = self.ty_translator.translate_type_to_syn_type(*rewritten_ty)?;
+
+                        let translated_val = self.translate_rvalue(loc, val)?;
+                        let translated_place = self.translate_place(plc)?;
+
+                        // this should be a temporary
+                        assert!(plc.projection.is_empty());
+
+                        let ot = synty.into();
+                        cont_stmt = radium::Stmt::Assign {
+                            ot,
+                            e1: translated_place,
+                            e2: translated_val,
+                            s: Box::new(cont_stmt),
+                        };
+                    } else {
+                        let plc_ty = self.get_type_of_place(plc);
+                        let rhs_ty = val.ty(&self.proc.get_mir().local_decls, self.env.tcx());
+
+                        let borrow_annots = regions::assignment::get_assignment_loan_annots(
+                            &mut self.inclusion_tracker, &self.ty_translator,
+                            loc, val);
+
+                        let plc_ty = self.get_type_of_place(plc);
+                        let plc_strongly_writeable = !self.check_place_below_reference(plc);
+                        let (expr_annot, pre_stmt_annots, post_stmt_annots) =
+                            regions::assignment::get_assignment_annots(
+                                self.env, &mut self.inclusion_tracker, &self.ty_translator,
+                                loc, plc_strongly_writeable, plc_ty, rhs_ty);
+
+                        // TODO; maybe move this to rvalue
+                        let composite_annots = regions::composite::get_composite_rvalue_creation_annots(
+                            self.env, &mut self.inclusion_tracker, &self.ty_translator, loc, rhs_ty);
+
+                        cont_stmt = radium::Stmt::with_annotations(
+                            cont_stmt,
+                            post_stmt_annots,
+                            &Some("post-assignment".to_owned()),
+                        );
+
+                        let translated_val = radium::Expr::with_optional_annotation(
+                            self.translate_rvalue(loc, val)?,
+                            expr_annot,
+                            Some("assignment".to_owned()),
+                        );
+                        let translated_place = self.translate_place(plc)?;
+                        let synty = self.ty_translator.translate_type_to_syn_type(plc_ty.ty)?;
+                        cont_stmt = radium::Stmt::Assign {
+                            ot: synty.into(),
+                            e1: translated_place,
+                            e2: translated_val,
+                            s: Box::new(cont_stmt),
+                        };
+                        cont_stmt = radium::Stmt::with_annotations(
+                            cont_stmt,
+                            pre_stmt_annots,
+                            &Some("assignment".to_owned()),
+                        );
+                        cont_stmt = radium::Stmt::with_annotations(
+                            cont_stmt,
+                            borrow_annots,
+                            &Some("borrow".to_owned()),
+                        );
+                        cont_stmt = radium::Stmt::with_annotations(
+                            cont_stmt,
+                            composite_annots,
+                            &Some("composite".to_owned()),
+                        );
+                    }
+                },
+
+                StatementKind::Deinit(_) => {
+                    // TODO: find out where this is emitted
+                    return Err(TranslationError::UnsupportedFeature {
+                        description: "RefinedRust does currently not support Deinit".to_owned(),
+                    });
+                },
+
+                StatementKind::FakeRead(b) => {
+                    // we can probably ignore this, but I'm not sure
+                    info!("Ignoring FakeRead: {:?}", b);
+                },
+
+                StatementKind::Intrinsic(intrinsic) => {
+                    match intrinsic.as_ref() {
+                        NonDivergingIntrinsic::Assume(_) => {
+                            // ignore
+                            info!("Ignoring Assume: {:?}", intrinsic);
+                        },
+                        NonDivergingIntrinsic::CopyNonOverlapping(_) => {
+                            return Err(TranslationError::UnsupportedFeature {
+                                description: "RefinedRust does currently not support the CopyNonOverlapping Intrinsic".to_owned(),
+                            });
+                        },
+                    }
+                },
+
+                StatementKind::PlaceMention(place) => {
+                    // TODO: this is missed UB
+                    info!("Ignoring PlaceMention: {:?}", place);
+                },
+
+                StatementKind::SetDiscriminant {
+                    place: _place,
+                    variant_index: _variant_index,
+                } => {
+                    // TODO
+                    return Err(TranslationError::UnsupportedFeature {
+                        description: "RefinedRust does currently not support SetDiscriminant".to_owned(),
+                    });
+                },
+
+                // don't need that info
+                | StatementKind::AscribeUserType(_, _)
+                // don't need that
+                | StatementKind::Coverage(_)
+                // no-op
+                | StatementKind::ConstEvalCounter
+                // ignore
+                | StatementKind::Nop
+                // just ignore
+                | StatementKind::StorageLive(_)
+                // just ignore
+                | StatementKind::StorageDead(_)
+                // just ignore retags
+                | StatementKind::Retag(_, _) => (),
+            }
+
+            cont_stmt = self.prepend_endlfts(cont_stmt, dying_loans.into_iter());
+        }
+
+        Ok(cont_stmt)
+    }
+
+    /// Get predecessors in the CFG.
+    fn get_loc_predecessors(&self, loc: Location) -> Vec<Location> {
+        if loc.statement_index > 0 {
+            let pred = Location {
+                block: loc.block,
+                statement_index: loc.statement_index - 1,
+            };
+            vec![pred]
+        } else {
+            // check for gotos that go to this basic block
+            let pred_bbs = self.proc.predecessors(loc.block);
+            let basic_blocks = &self.proc.get_mir().basic_blocks;
+            pred_bbs
+                .iter()
+                .map(|bb| {
+                    let data = &basic_blocks[*bb];
+                    Location {
+                        block: *bb,
+                        statement_index: data.statements.len(),
+                    }
+                })
+                .collect()
+        }
+    }
+}
diff --git a/rr_frontend/translation/src/body/translation/calls.rs b/rr_frontend/translation/src/body/translation/calls.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5229e096f89e83c286d0df33400b403f10fdfdf8
--- /dev/null
+++ b/rr_frontend/translation/src/body/translation/calls.rs
@@ -0,0 +1,613 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+use log::{info, trace};
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{
+    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
+    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
+    TerminatorKind, UnOp, VarDebugInfoContents,
+};
+use rr_rustc_interface::middle::ty::fold::TypeFolder;
+use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind};
+use rr_rustc_interface::middle::{mir, ty};
+use rr_rustc_interface::{abi, ast, middle};
+
+use super::TX;
+use crate::base::*;
+use crate::environment::borrowck::facts;
+use crate::traits::resolution;
+use crate::{regions, types};
+
+/// Get the syntypes of function arguments for a procedure call.
+fn get_arg_syntypes_for_procedure_call<'tcx, 'def>(
+    tcx: ty::TyCtxt<'tcx>,
+    ty_translator: &types::LocalTX<'def, 'tcx>,
+    callee_did: DefId,
+    ty_params: &[ty::GenericArg<'tcx>],
+) -> Result<Vec<radium::SynType>, TranslationError<'tcx>> {
+    let caller_did = ty_translator.get_proc_did();
+
+    // Get the type of the callee, fully instantiated
+    let full_ty: ty::EarlyBinder<Ty<'tcx>> = tcx.type_of(callee_did);
+    let full_ty = full_ty.instantiate(tcx, ty_params);
+
+    // We create a dummy scope in order to make the lifetime lookups succeed, since we only want
+    // syntactic types here.
+    // Since we do the substitution of the generics above, we should translate generics and
+    // traits in the caller's scope.
+    let scope = ty_translator.scope.borrow();
+    let param_env: ty::ParamEnv<'tcx> = tcx.param_env(scope.did);
+    let callee_state = types::CalleeState::new(&param_env, &scope.generic_scope);
+    let mut dummy_state = types::STInner::CalleeTranslation(callee_state);
+
+    let mut syntypes = Vec::new();
+    match full_ty.kind() {
+        ty::TyKind::FnDef(_, _) => {
+            let sig = full_ty.fn_sig(tcx);
+            for ty in sig.inputs().skip_binder() {
+                let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?;
+                syntypes.push(st);
+            }
+        },
+        ty::TyKind::Closure(_, args) => {
+            let clos_args = args.as_closure();
+            let sig = clos_args.sig();
+            let pre_sig = sig.skip_binder();
+            // we also need to add the closure argument here
+
+            let tuple_ty = clos_args.tupled_upvars_ty();
+            match clos_args.kind() {
+                ty::ClosureKind::Fn | ty::ClosureKind::FnMut => {
+                    syntypes.push(radium::SynType::Ptr);
+                },
+                ty::ClosureKind::FnOnce => {
+                    let st =
+                        ty_translator.translator.translate_type_to_syn_type(tuple_ty, &mut dummy_state)?;
+                    syntypes.push(st);
+                },
+            }
+            for ty in pre_sig.inputs() {
+                let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?;
+                syntypes.push(st);
+            }
+        },
+        _ => unimplemented!(),
+    }
+
+    Ok(syntypes)
+}
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    /// Internally register that we have used a procedure with a particular instantiation of generics, and
+    /// return the code parameter name.
+    /// Arguments:
+    /// - `callee_did`: the `DefId` of the callee
+    /// - `ty_params`: the instantiation for the callee's type parameters
+    /// - `trait_spec_terms`: if the callee has any trait assumptions, these are specification parameter terms
+    ///   for these traits
+    /// - `trait_assoc_tys`: if the callee has any trait assumptions, these are the instantiations for all
+    ///   associated types
+    fn register_use_procedure(
+        &mut self,
+        callee_did: DefId,
+        extra_spec_args: Vec<String>,
+        ty_params: ty::GenericArgsRef<'tcx>,
+        trait_specs: Vec<radium::TraitReqInst<'def, ty::Ty<'tcx>>>,
+    ) -> Result<(String, Vec<radium::Type<'def>>, Vec<radium::Lft>), TranslationError<'tcx>> {
+        trace!("enter register_use_procedure callee_did={callee_did:?} ty_params={ty_params:?}");
+        // The key does not include the associated types, as the resolution of the associated types
+        // should always be unique for one combination of type parameters, as long as we remain in
+        // the same environment (which is the case within this procedure).
+        // Therefore, already the type parameters are sufficient to distinguish different
+        // instantiations.
+        let key = types::generate_args_inst_key(self.env.tcx(), ty_params)?;
+
+        // re-quantify
+        let quantified_args = self.ty_translator.get_generic_abstraction_for_procedure(
+            callee_did,
+            ty_params,
+            &trait_specs,
+            true,
+        )?;
+
+        let tup = (callee_did, key);
+        let res;
+        if let Some(proc_use) = self.collected_procedures.get(&tup) {
+            res = proc_use.loc_name.clone();
+        } else {
+            let meta = self
+                .procedure_registry
+                .lookup_function(callee_did)
+                .ok_or_else(|| TranslationError::UnknownProcedure(format!("{:?}", callee_did)))?;
+            // explicit instantiation is needed sometimes
+            let spec_name = format!("{} (RRGS:=RRGS)", meta.get_spec_name());
+            let code_name = meta.get_name();
+            let loc_name = format!("{}_loc", types::mangle_name_with_tys(code_name, tup.1.as_slice()));
+
+            let syntypes = get_arg_syntypes_for_procedure_call(
+                self.env.tcx(),
+                &self.ty_translator,
+                callee_did,
+                ty_params.as_slice(),
+            )?;
+
+            let mut translated_params = quantified_args.fn_ty_param_inst;
+
+            info!(
+                "Registered procedure instance {} of {:?} with {:?} and layouts {:?}",
+                loc_name, callee_did, translated_params, syntypes
+            );
+
+            let specs = trait_specs.into_iter().map(|x| x.try_into().unwrap()).collect();
+
+            let proc_use = radium::UsedProcedure::new(
+                loc_name,
+                spec_name,
+                extra_spec_args,
+                quantified_args.scope,
+                specs,
+                translated_params,
+                quantified_args.fn_lft_param_inst,
+                syntypes,
+            );
+
+            res = proc_use.loc_name.clone();
+            self.collected_procedures.insert(tup, proc_use);
+        }
+        trace!("leave register_use_procedure");
+        Ok((res, quantified_args.callee_ty_param_inst, quantified_args.callee_lft_param_inst))
+    }
+
+    /// Internally register that we have used a trait method with a particular instantiation of
+    /// generics, and return the code parameter name.
+    fn register_use_trait_method<'c>(
+        &'c mut self,
+        callee_did: DefId,
+        ty_params: ty::GenericArgsRef<'tcx>,
+        trait_specs: Vec<radium::TraitReqInst<'def, ty::Ty<'tcx>>>,
+    ) -> Result<(String, Vec<radium::Type<'def>>, Vec<radium::Lft>), TranslationError<'tcx>> {
+        trace!("enter register_use_trait_method did={:?} ty_params={:?}", callee_did, ty_params);
+        // Does not include the associated types in the key; see `register_use_procedure` for an
+        // explanation.
+        let key = types::generate_args_inst_key(self.env.tcx(), ty_params)?;
+
+        let (method_loc_name, method_spec_term, method_params) =
+            self.ty_translator.register_use_trait_procedure(self.env, callee_did, ty_params)?;
+        // re-quantify
+        let quantified_args = self.ty_translator.get_generic_abstraction_for_procedure(
+            callee_did,
+            method_params,
+            &trait_specs,
+            false,
+        )?;
+
+        let tup = (callee_did, key);
+        let res;
+        if let Some(proc_use) = self.collected_procedures.get(&tup) {
+            res = proc_use.loc_name.clone();
+        } else {
+            // TODO: should we use ty_params or method_params?
+            let syntypes = get_arg_syntypes_for_procedure_call(
+                self.env.tcx(),
+                &self.ty_translator,
+                callee_did,
+                ty_params.as_slice(),
+            )?;
+
+            let mut translated_params = quantified_args.fn_ty_param_inst;
+
+            info!(
+                "Registered procedure instance {} of {:?} with {:?} and layouts {:?}",
+                method_loc_name, callee_did, translated_params, syntypes
+            );
+
+            let specs = trait_specs.into_iter().map(|x| x.try_into().unwrap()).collect();
+            let proc_use = radium::UsedProcedure::new(
+                method_loc_name,
+                method_spec_term,
+                vec![],
+                quantified_args.scope,
+                specs,
+                translated_params,
+                quantified_args.fn_lft_param_inst,
+                syntypes,
+            );
+
+            res = proc_use.loc_name.clone();
+            self.collected_procedures.insert(tup, proc_use);
+        }
+        trace!("leave register_use_procedure");
+        Ok((res, quantified_args.callee_ty_param_inst, quantified_args.callee_lft_param_inst))
+    }
+
+    /// Resolve the trait requirements of a function call.
+    /// The target of the call, [did], should have been resolved as much as possible,
+    /// as the requirements of a call can be different depending on which impl we consider.
+    fn resolve_trait_requirements_of_call(
+        &self,
+        did: DefId,
+        params: ty::GenericArgsRef<'tcx>,
+    ) -> Result<Vec<radium::TraitReqInst<'def, Ty<'tcx>>>, TranslationError<'tcx>> {
+        let mut scope = self.ty_translator.scope.borrow_mut();
+        let mut state = types::STInner::InFunction(&mut scope);
+        self.trait_registry.resolve_trait_requirements_in_state(&mut state, did, params)
+    }
+
+    /// Translate the use of an `FnDef`, registering that the current function needs to link against
+    /// a particular monomorphization of the used function.
+    /// Is guaranteed to return a `radium::Expr::CallTarget` with the parameter instantiation of
+    /// this function annotated.
+    pub(super) fn translate_fn_def_use(
+        &mut self,
+        ty: Ty<'tcx>,
+    ) -> Result<radium::Expr, TranslationError<'tcx>> {
+        let TyKind::FnDef(defid, params) = ty.kind() else {
+            return Err(TranslationError::UnknownError("not a FnDef type".to_owned()));
+        };
+
+        let current_param_env: ty::ParamEnv<'tcx> = self.env.tcx().param_env(self.proc.get_id());
+
+        // Check whether we are calling into a trait method.
+        // This works since we did not resolve concrete instances, so this is always an abstract
+        // reference to the trait.
+        let calling_trait = self.env.tcx().trait_of_item(*defid);
+
+        // Check whether we are calling a plain function or a trait method
+        let Some(calling_trait) = calling_trait else {
+            // resolve the trait requirements
+            let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?;
+
+            // track that we are using this function and generate the Coq location name
+            let (code_param_name, ty_hint, lft_hint) =
+                self.register_use_procedure(*defid, vec![], params, trait_spec_terms)?;
+
+            let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect();
+
+            return Ok(radium::Expr::CallTarget(code_param_name, ty_hint, lft_hint));
+        };
+
+        // Otherwise, we are calling a trait method
+        // Resolve the trait instance using trait selection
+        let Some((resolved_did, resolved_params, kind)) =
+            resolution::resolve_assoc_item(self.env.tcx(), current_param_env, *defid, params)
+        else {
+            return Err(TranslationError::TraitResolution(format!("Could not resolve trait {:?}", defid)));
+        };
+
+        info!(
+            "Resolved trait method {:?} as {:?} with substs {:?} and kind {:?}",
+            defid, resolved_did, resolved_params, kind
+        );
+
+        match kind {
+            resolution::TraitResolutionKind::UserDefined => {
+                // We can statically resolve the particular trait instance,
+                // but need to apply the spec to the instance's spec attributes
+
+                // resolve the trait requirements
+                let trait_spec_terms =
+                    self.resolve_trait_requirements_of_call(resolved_did, resolved_params)?;
+
+                let (param_name, ty_hint, lft_hint) =
+                    self.register_use_procedure(resolved_did, vec![], resolved_params, trait_spec_terms)?;
+                let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect();
+
+                Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint))
+            },
+
+            resolution::TraitResolutionKind::Param => {
+                // In this case, we have already applied it to the spec attribute
+
+                // resolve the trait requirements
+                let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?;
+
+                let (param_name, ty_hint, lft_hint) =
+                    self.register_use_trait_method(resolved_did, resolved_params, trait_spec_terms)?;
+                let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect();
+
+                Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint))
+            },
+
+            resolution::TraitResolutionKind::Closure => {
+                // TODO: here, we should first generate an instance of the trait
+                //let body = self.env.tcx().instance_mir(middle::ty::InstanceDef::Item(resolved_did));
+                //let body = self.env.tcx().instance_mir(middle::ty::InstanceDef::FnPtrShim(*defid, ty));
+                //info!("closure body: {:?}", body);
+
+                //FunctionTranslator::dump_body(body);
+
+                //let res_result = ty::Instance::resolve(self.env.tcx(), callee_param_env, *defid, params);
+                //info!("Resolution {:?}", res_result);
+
+                // the args are just the closure args. We can ignore them.
+                let _clos_args = resolved_params.as_closure();
+
+                // resolve the trait requirements
+                let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?;
+
+                let (param_name, ty_hint, lft_hint) =
+                    self.register_use_procedure(resolved_did, vec![], ty::List::empty(), trait_spec_terms)?;
+                let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect();
+
+                Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint))
+            },
+        }
+    }
+
+    /// Split the type of a function operand of a call expression to a base type and an instantiation for
+    /// generics.
+    fn call_expr_op_split_inst(
+        &self,
+        constant: &Constant<'tcx>,
+    ) -> Result<
+        (DefId, ty::PolyFnSig<'tcx>, ty::GenericArgsRef<'tcx>, ty::PolyFnSig<'tcx>),
+        TranslationError<'tcx>,
+    > {
+        match constant.literal {
+            ConstantKind::Ty(c) => {
+                match c.ty().kind() {
+                    TyKind::FnDef(def, args) => {
+                        let ty: ty::EarlyBinder<Ty<'tcx>> = self.env.tcx().type_of(def);
+                        let ty_ident = ty.instantiate_identity();
+                        assert!(ty_ident.is_fn());
+                        let ident_sig = ty_ident.fn_sig(self.env.tcx());
+
+                        let ty_instantiated = ty.instantiate(self.env.tcx(), args.as_slice());
+                        let instantiated_sig = ty_instantiated.fn_sig(self.env.tcx());
+
+                        Ok((*def, ident_sig, args, instantiated_sig))
+                    },
+                    // TODO handle FnPtr, closure
+                    _ => Err(TranslationError::Unimplemented {
+                        description: "implement function pointers".to_owned(),
+                    }),
+                }
+            },
+            ConstantKind::Val(_, ty) => {
+                match ty.kind() {
+                    TyKind::FnDef(def, args) => {
+                        let ty: ty::EarlyBinder<Ty<'tcx>> = self.env.tcx().type_of(def);
+
+                        let ty_ident = ty.instantiate_identity();
+                        assert!(ty_ident.is_fn());
+                        let ident_sig = ty_ident.fn_sig(self.env.tcx());
+
+                        let ty_instantiated = ty.instantiate(self.env.tcx(), args.as_slice());
+                        let instantiated_sig = ty_instantiated.fn_sig(self.env.tcx());
+
+                        Ok((*def, ident_sig, args, instantiated_sig))
+                    },
+                    // TODO handle FnPtr, closure
+                    _ => Err(TranslationError::Unimplemented {
+                        description: "implement function pointers".to_owned(),
+                    }),
+                }
+            },
+            ConstantKind::Unevaluated(_, _) => Err(TranslationError::Unimplemented {
+                description: "implement ConstantKind::Unevaluated".to_owned(),
+            }),
+        }
+    }
+
+    pub(super) fn translate_function_call(
+        &mut self,
+        func: &Operand<'tcx>,
+        args: &[Operand<'tcx>],
+        destination: &Place<'tcx>,
+        target: Option<middle::mir::BasicBlock>,
+        loc: Location,
+        dying_loans: &[facts::Loan],
+    ) -> Result<radium::Stmt, TranslationError<'tcx>> {
+        let startpoint = self.info.interner.get_point_index(&facts::Point {
+            location: loc,
+            typ: facts::PointType::Start,
+        });
+
+        let Operand::Constant(box func_constant) = func else {
+            return Err(TranslationError::UnsupportedFeature {
+                description: format!(
+                    "RefinedRust does currently not support this kind of call operand (got: {:?})",
+                    func
+                ),
+            });
+        };
+
+        // Get the type of the return value from the function
+        let (target_did, sig, generic_args, inst_sig) = self.call_expr_op_split_inst(func_constant)?;
+        info!("calling function {:?}", target_did);
+        info!("call substs: {:?} = {:?}, {:?}", func, sig, generic_args);
+
+        // for lifetime annotations:
+        // 1. get the regions involved here. for that, get the instantiation of the function.
+        //    + if it's a FnDef type, that should be easy.
+        //    + for a function pointer: ?
+        //    + for a closure: ?
+        //   (Polonius does not seem to distinguish early/late bound in any way, except
+        //   that they are numbered in different passes)
+        // 2. find the constraints here involving the regions.
+        // 3. solve for the regions.
+        //    + transitively propagate the constraints
+        //    + check for equalities
+        //    + otherwise compute intersection. singleton intersection just becomes an equality def.
+        // 4. add annotations accordingly
+        //    + either a startlft
+        //    + or a copy name
+        // 5. add shortenlft annotations to line up arguments.
+        //    + for that, we need the type of the LHS, and what the argument types (with
+        //    substituted regions) should be.
+        // 6. annotate the return value on assignment and establish constraints.
+
+        let classification =
+            regions::calls::compute_call_regions(self.env, &self.inclusion_tracker, generic_args, loc);
+
+        // update the inclusion tracker with the new regions we have introduced
+        // We just add the inclusions and ignore that we resolve it in a "tight" way.
+        // the cases where we need the reverse inclusion should be really rare.
+        for (r, c) in &classification.classification {
+            match c {
+                regions::calls::CallRegionKind::EqR(r2) => {
+                    // put it at the start point, because the inclusions come into effect
+                    // at the point right before.
+                    self.inclusion_tracker.add_static_inclusion(*r, *r2, startpoint);
+                    self.inclusion_tracker.add_static_inclusion(*r2, *r, startpoint);
+                },
+                regions::calls::CallRegionKind::Intersection(lfts) => {
+                    // all the regions represented by lfts need to be included in r
+                    for r2 in lfts {
+                        self.inclusion_tracker.add_static_inclusion(*r2, *r, startpoint);
+                    }
+                },
+            }
+        }
+
+        // translate the function expression.
+        let func_expr = self.translate_operand(func, false)?;
+        // We expect this to be an Expr::CallTarget, being annotated with the type parameters we
+        // instantiate it with.
+        let radium::Expr::CallTarget(func_lit, ty_param_annots, mut lft_param_annots) = func_expr else {
+            unreachable!("Logic error in call target translation");
+        };
+        let func_expr = radium::Expr::MetaParam(func_lit);
+
+        // translate the arguments
+        let mut translated_args = Vec::new();
+        for arg in args {
+            // to_ty is the type the function expects
+
+            //let ty = arg.ty(&self.proc.get_mir().local_decls, self.env.tcx());
+            let translated_arg = self.translate_operand(arg, true)?;
+            translated_args.push(translated_arg);
+        }
+
+        // We have to add the late regions, since we do not requantify over them.
+        for late in &classification.late_regions {
+            let lft = self.format_region(*late);
+            lft_param_annots.push(lft);
+        }
+        info!("Call lifetime instantiation (early): {:?}", classification.early_regions);
+        info!("Call lifetime instantiation (late): {:?}", classification.late_regions);
+
+        // TODO: do we need to do something with late bounds?
+        let output_ty = inst_sig.output().skip_binder();
+        info!("call has instantiated type {:?}", inst_sig);
+
+        // compute the resulting annotations
+        let lhs_ty = self.get_type_of_place(destination);
+        let lhs_strongly_writeable = !self.check_place_below_reference(destination);
+        let (rhs_annots, pre_stmt_annots, post_stmt_annots) = regions::assignment::get_assignment_annots(
+            self.env,
+            &mut self.inclusion_tracker,
+            &self.ty_translator,
+            loc,
+            lhs_strongly_writeable,
+            lhs_ty,
+            output_ty,
+        );
+        info!(
+            "assignment annots after call: expr: {:?}, pre-stmt: {:?}, post-stmt: {:?}",
+            rhs_annots, pre_stmt_annots, post_stmt_annots
+        );
+
+        // TODO: add annotations for the assignment
+        // for that:
+        // - get the type of the place
+        // - enforce constraints as necessary. this might spawn dyninclusions with some of the new regions =>
+        //   In Coq, also the aliases should get proper endlft events to resolve the dyninclusions.
+        // - update the name map
+        let call_expr = radium::Expr::Call {
+            f: Box::new(func_expr),
+            lfts: lft_param_annots,
+            tys: ty_param_annots,
+            args: translated_args,
+        };
+        let stmt = match target {
+            Some(target) => {
+                let mut cont_stmt = self.translate_goto_like(&loc, target)?;
+                // end loans before the goto, but after the call.
+                // TODO: may cause duplications?
+                cont_stmt = self.prepend_endlfts(cont_stmt, dying_loans.iter().copied());
+
+                let cont_stmt = radium::Stmt::with_annotations(
+                    cont_stmt,
+                    post_stmt_annots,
+                    &Some("post_function_call".to_owned()),
+                );
+
+                // assign stmt with call; then jump to bb
+                let place_ty = self.get_type_of_place(destination);
+                let place_st = self.ty_translator.translate_type_to_syn_type(place_ty.ty)?;
+                let place_expr = self.translate_place(destination)?;
+                let ot = place_st.into();
+
+                let annotated_rhs = radium::Expr::with_optional_annotation(
+                    call_expr,
+                    rhs_annots,
+                    Some("function_call".to_owned()),
+                );
+                let assign_stmt = radium::Stmt::Assign {
+                    ot,
+                    e1: place_expr,
+                    e2: annotated_rhs,
+                    s: Box::new(cont_stmt),
+                };
+                radium::Stmt::with_annotations(
+                    assign_stmt,
+                    pre_stmt_annots,
+                    &Some("function_call".to_owned()),
+                )
+            },
+            None => {
+                // expr stmt with call; then stuck (we have not provided a continuation, after all)
+                radium::Stmt::ExprS {
+                    e: call_expr,
+                    s: Box::new(radium::Stmt::Stuck),
+                }
+            },
+        };
+
+        let mut stmt_annots = Vec::new();
+
+        // add annotations to initialize the regions for the call (before the call)
+        for (r, class) in &classification.classification {
+            let lft = self.format_region(*r);
+            match class {
+                regions::calls::CallRegionKind::EqR(r2) => {
+                    let lft2 = self.format_region(*r2);
+                    stmt_annots.push(radium::Annotation::CopyLftName(lft2, lft));
+                },
+
+                regions::calls::CallRegionKind::Intersection(rs) => {
+                    match rs.len() {
+                        0 => {
+                            return Err(TranslationError::UnsupportedFeature {
+                                description: "RefinedRust does currently not support unconstrained lifetime"
+                                    .to_owned(),
+                            });
+                        },
+                        1 => {
+                            // this is really just an equality constraint
+                            if let Some(r2) = rs.iter().next() {
+                                let lft2 = self.format_region(*r2);
+                                stmt_annots.push(radium::Annotation::CopyLftName(lft2, lft));
+                            }
+                        },
+                        _ => {
+                            // a proper intersection
+                            let lfts: Vec<_> = rs.iter().map(|r| self.format_region(*r)).collect();
+                            stmt_annots.push(radium::Annotation::AliasLftIntersection(lft, lfts));
+                        },
+                    };
+                },
+            }
+        }
+
+        let stmt = radium::Stmt::with_annotations(stmt, stmt_annots, &Some("function_call".to_owned()));
+        Ok(stmt)
+    }
+}
diff --git a/rr_frontend/translation/src/body/translation/constants.rs b/rr_frontend/translation/src/body/translation/constants.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dd9a5206d002e7f5ea1395aa50e58363722c7382
--- /dev/null
+++ b/rr_frontend/translation/src/body/translation/constants.rs
@@ -0,0 +1,195 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+use log::info;
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{
+    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
+    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
+    TerminatorKind, UnOp, VarDebugInfoContents,
+};
+use rr_rustc_interface::middle::ty::fold::TypeFolder;
+use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind};
+use rr_rustc_interface::middle::{mir, ty};
+use rr_rustc_interface::{abi, ast, middle};
+
+use super::TX;
+use crate::base::*;
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    /// Translate a scalar at a specific type to a `radium::Expr`.
+    // TODO: Use `TryFrom` instead
+    fn translate_scalar(
+        &mut self,
+        sc: &Scalar,
+        ty: Ty<'tcx>,
+    ) -> Result<radium::Expr, TranslationError<'tcx>> {
+        // TODO: Use `TryFrom` instead
+        fn translate_literal<'tcx, T, U>(
+            sc: Result<T, U>,
+            fptr: fn(T) -> radium::Literal,
+        ) -> Result<radium::Expr, TranslationError<'tcx>> {
+            sc.map_or(Err(TranslationError::InvalidLayout), |lit| Ok(radium::Expr::Literal(fptr(lit))))
+        }
+
+        match ty.kind() {
+            TyKind::Bool => translate_literal(sc.to_bool(), radium::Literal::Bool),
+
+            TyKind::Int(it) => match it {
+                ty::IntTy::I8 => translate_literal(sc.to_i8(), radium::Literal::I8),
+                ty::IntTy::I16 => translate_literal(sc.to_i16(), radium::Literal::I16),
+                ty::IntTy::I32 => translate_literal(sc.to_i32(), radium::Literal::I32),
+                ty::IntTy::I128 => translate_literal(sc.to_i128(), radium::Literal::I128),
+
+                // For Radium, the pointer size is 8 bytes
+                ty::IntTy::I64 | ty::IntTy::Isize => translate_literal(sc.to_i64(), radium::Literal::I64),
+            },
+
+            TyKind::Uint(it) => match it {
+                ty::UintTy::U8 => translate_literal(sc.to_u8(), radium::Literal::U8),
+                ty::UintTy::U16 => translate_literal(sc.to_u16(), radium::Literal::U16),
+                ty::UintTy::U32 => translate_literal(sc.to_u32(), radium::Literal::U32),
+                ty::UintTy::U128 => translate_literal(sc.to_u128(), radium::Literal::U128),
+
+                // For Radium, the pointer is 8 bytes
+                ty::UintTy::U64 | ty::UintTy::Usize => translate_literal(sc.to_u64(), radium::Literal::U64),
+            },
+
+            TyKind::Char => translate_literal(sc.to_char(), radium::Literal::Char),
+
+            TyKind::FnDef(_, _) => self.translate_fn_def_use(ty),
+
+            TyKind::Tuple(tys) => {
+                if tys.is_empty() {
+                    return Ok(radium::Expr::Literal(radium::Literal::ZST));
+                }
+
+                Err(TranslationError::UnsupportedFeature {
+                    description: format!(
+                        "RefinedRust does currently not support compound construction of tuples using literals (got: {:?})",
+                        ty
+                    ),
+                })
+            },
+
+            TyKind::Ref(_, _, _) => match sc {
+                Scalar::Int(_) => unreachable!(),
+
+                Scalar::Ptr(pointer, _) => {
+                    let glob_alloc = self.env.tcx().global_alloc(pointer.provenance);
+                    match glob_alloc {
+                        middle::mir::interpret::GlobalAlloc::Static(did) => {
+                            info!(
+                                "Found static GlobalAlloc {:?} for Ref scalar {:?} at type {:?}",
+                                did, sc, ty
+                            );
+
+                            let s = self.const_registry.get_static(did)?;
+                            self.collected_statics.insert(did);
+                            Ok(radium::Expr::Literal(radium::Literal::Loc(s.loc_name.clone())))
+                        },
+                        middle::mir::interpret::GlobalAlloc::Memory(alloc) => {
+                            // TODO: this is needed
+                            Err(TranslationError::UnsupportedFeature {
+                                description: format!(
+                                    "RefinedRust does currently not support GlobalAlloc {:?} for scalar {:?} at type {:?}",
+                                    glob_alloc, sc, ty
+                                ),
+                            })
+                        },
+                        _ => Err(TranslationError::UnsupportedFeature {
+                            description: format!(
+                                "RefinedRust does currently not support GlobalAlloc {:?} for scalar {:?} at type {:?}",
+                                glob_alloc, sc, ty
+                            ),
+                        }),
+                    }
+                },
+            },
+
+            _ => Err(TranslationError::UnsupportedFeature {
+                description: format!(
+                    "RefinedRust does currently not support layout for const value: (got: {:?})",
+                    ty
+                ),
+            }),
+        }
+    }
+
+    /// Translate a constant value from const evaluation.
+    fn translate_constant_value(
+        &mut self,
+        v: mir::interpret::ConstValue<'tcx>,
+        ty: Ty<'tcx>,
+    ) -> Result<radium::Expr, TranslationError<'tcx>> {
+        match v {
+            ConstValue::Scalar(sc) => self.translate_scalar(&sc, ty),
+            ConstValue::ZeroSized => {
+                // TODO are there more special cases we need to handle somehow?
+                match ty.kind() {
+                    TyKind::FnDef(_, _) => {
+                        info!("Translating ZST val for function call target: {:?}", ty);
+                        self.translate_fn_def_use(ty)
+                    },
+                    _ => Ok(radium::Expr::Literal(radium::Literal::ZST)),
+                }
+            },
+            _ => {
+                // TODO: do we actually care about this case or is this just something that can
+                // appear as part of CTFE/MIRI?
+                Err(TranslationError::UnsupportedFeature {
+                    description: format!("Unsupported Constant: ConstValue; {:?}", v),
+                })
+            },
+        }
+    }
+
+    /// Translate a Constant to a `radium::Expr`.
+    pub(super) fn translate_constant(
+        &mut self,
+        constant: &Constant<'tcx>,
+    ) -> Result<radium::Expr, TranslationError<'tcx>> {
+        match constant.literal {
+            ConstantKind::Ty(v) => {
+                let const_ty = v.ty();
+
+                match v.kind() {
+                    ConstKind::Value(v) => {
+                        // this doesn't contain the necessary structure anymore. Need to reconstruct using the
+                        // type.
+                        match v.try_to_scalar() {
+                            Some(sc) => self.translate_scalar(&sc, const_ty),
+                            _ => Err(TranslationError::UnsupportedFeature {
+                                description: format!("const value not supported: {:?}", v),
+                            }),
+                        }
+                    },
+                    _ => Err(TranslationError::UnsupportedFeature {
+                        description: "Unsupported ConstKind".to_owned(),
+                    }),
+                }
+            },
+            ConstantKind::Val(val, ty) => self.translate_constant_value(val, ty),
+            ConstantKind::Unevaluated(c, ty) => {
+                // call const evaluation
+                let param_env: ty::ParamEnv<'tcx> = self.env.tcx().param_env(self.proc.get_id());
+                match self.env.tcx().const_eval_resolve(param_env, c, None) {
+                    Ok(res) => self.translate_constant_value(res, ty),
+                    Err(e) => match e {
+                        ErrorHandled::Reported(_) => Err(TranslationError::UnsupportedFeature {
+                            description: "Cannot interpret constant".to_owned(),
+                        }),
+                        ErrorHandled::TooGeneric => Err(TranslationError::UnsupportedFeature {
+                            description: "Const use is too generic".to_owned(),
+                        }),
+                    },
+                }
+            },
+        }
+    }
+}
diff --git a/rr_frontend/translation/src/body/translation/loops.rs b/rr_frontend/translation/src/body/translation/loops.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e6bcc1f0ce8c55da512f81e32e77c4653a4a9f4d
--- /dev/null
+++ b/rr_frontend/translation/src/body/translation/loops.rs
@@ -0,0 +1,122 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+use log::info;
+use radium::coq;
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{
+    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
+    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
+    TerminatorKind, UnOp, VarDebugInfoContents,
+};
+use rr_rustc_interface::middle::ty::fold::TypeFolder;
+use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind};
+use rr_rustc_interface::middle::{mir, ty};
+use rr_rustc_interface::{abi, ast, middle};
+
+use super::TX;
+use crate::base::*;
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    /// Parse the attributes on spec closure `did` as loop annotations and add it as an invariant
+    /// to the generated code.
+    pub(super) fn parse_attributes_on_loop_spec_closure(
+        &self,
+        loop_head: BasicBlock,
+        did: Option<DefId>,
+    ) -> radium::LoopSpec {
+        // for now: just make invariants True.
+
+        // need to do:
+        // - find out the locals in the right order, make parameter names for them. based on their type and
+        //   initialization status, get the refinement type.
+        // - output/pretty-print this map when generating the typing proof of each function. [done]
+        //  + should not be a separate definition, but rather a "set (.. := ...)" with a marker type so
+        //    automation can find it.
+
+        // representation of loop invariants:
+        // - introduce parameters for them.
+
+        let mut rfn_binders = Vec::new();
+        let prop_body = radium::IProp::True;
+
+        // determine invariant on initialization:
+        // - we need this both for the refinement invariant (though this could be removed if we make uninit
+        //   generic over the refinement)
+        // - in order to establish the initialization invariant in each loop iteration, since we don't have
+        //   proper subtyping for uninit => maybe we could fix this too by making uninit variant in the
+        //   refinement type? then we could have proper subtyping lemmas.
+        //  + to bring it to the right refinement type initially, maybe have some automation /
+        //  annotation
+        // TODO: consider changing it like that.
+        //
+        // Note that StorageDead will not help us for determining initialization/ making it invariant, since
+        // it only applies to full stack slots, not individual paths. one thing that makes it more
+        // complicated in the frontend: initialization may in practice also be path-dependent.
+        //  - this does not cause issues with posing a too strong loop invariant,
+        //  - but this poses an issue for annotations
+        //
+        //
+
+        // get locals
+        for (_, name, ty) in &self.fn_locals {
+            // get the refinement type
+            let mut rfn_ty = ty.get_rfn_type();
+            // wrap it in place_rfn, since we reason about places
+            rfn_ty = coq::term::Type::PlaceRfn(Box::new(rfn_ty));
+
+            // determine their initialization status
+            //let initialized = true; // TODO
+            // determine the actual refinement type for the current initialization status.
+
+            let rfn_name = format!("r_{}", name);
+            rfn_binders.push(coq::binder::Binder::new(Some(rfn_name), rfn_ty));
+        }
+
+        // TODO what do we do about stuff connecting borrows?
+        if let Some(did) = did {
+            let attrs = self.env.get_attributes(did);
+            info!("attrs for loop {:?}: {:?}", loop_head, attrs);
+        } else {
+            info!("no attrs for loop {:?}", loop_head);
+        }
+
+        let pred = radium::IPropPredicate::new(rfn_binders, prop_body);
+        radium::LoopSpec {
+            func_predicate: pred,
+        }
+    }
+
+    /// Find the optional `DefId` of the closure giving the invariant for the loop with head `head_bb`.
+    pub(super) fn find_loop_spec_closure(
+        &self,
+        head_bb: BasicBlock,
+    ) -> Result<Option<DefId>, TranslationError<'tcx>> {
+        let bodies = self.proc.loop_info().get_loop_body(head_bb);
+        let basic_blocks = &self.proc.get_mir().basic_blocks;
+
+        // we go in order through the bodies in order to not stumble upon an annotation for a
+        // nested loop!
+        for body in bodies {
+            // check that we did not go below a nested loop
+            if self.proc.loop_info().get_loop_head(*body) == Some(head_bb) {
+                // check the statements for an assignment
+                let data = basic_blocks.get(*body).unwrap();
+                for stmt in &data.statements {
+                    if let StatementKind::Assign(box (pl, _)) = stmt.kind {
+                        if let Some(did) = self.is_spec_closure_local(pl.local)? {
+                            return Ok(Some(did));
+                        }
+                    }
+                }
+            }
+        }
+
+        Ok(None)
+    }
+}
diff --git a/rr_frontend/translation/src/body/translation/mod.rs b/rr_frontend/translation/src/body/translation/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8372bba3b2e7b6d4db714128358aa8e33b5c2105
--- /dev/null
+++ b/rr_frontend/translation/src/body/translation/mod.rs
@@ -0,0 +1,493 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+mod basicblock;
+mod calls;
+mod constants;
+mod loops;
+mod place;
+mod rvalue;
+mod terminator;
+
+use std::collections::{HashMap, HashSet};
+
+use log::info;
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{
+    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
+    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
+    TerminatorKind, UnOp, VarDebugInfoContents,
+};
+use rr_rustc_interface::middle::ty::fold::TypeFolder;
+use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind};
+use rr_rustc_interface::middle::{mir, ty};
+use rr_rustc_interface::{abi, ast, middle};
+use typed_arena::Arena;
+
+use crate::base::*;
+use crate::body::checked_op_analysis::CheckedOpLocalAnalysis;
+use crate::environment::borrowck::facts;
+use crate::environment::polonius_info::PoloniusInfo;
+use crate::environment::procedure::Procedure;
+use crate::environment::{polonius_info, Environment};
+use crate::regions::inclusion_tracker::InclusionTracker;
+use crate::traits::registry;
+use crate::{base, consts, procedures, regions, types};
+
+/// Struct that keeps track of all information necessary to translate a MIR Body to a `radium::Function`.
+/// `'a` is the lifetime of the translator and ends after translation has finished.
+/// `'def` is the lifetime of the generated code (the code may refer to struct defs).
+/// `'tcx` is the lifetime of the rustc tctx.
+pub struct TX<'a, 'def, 'tcx> {
+    env: &'def Environment<'tcx>,
+    /// registry of other procedures
+    procedure_registry: &'a procedures::Scope<'def>,
+    /// scope of used consts
+    const_registry: &'a consts::Scope<'def>,
+    /// trait registry
+    trait_registry: &'def registry::TR<'tcx, 'def>,
+    /// translator for types
+    ty_translator: types::LocalTX<'def, 'tcx>,
+
+    /// this needs to be annotated with the right borrowck things
+    proc: &'def Procedure<'tcx>,
+    /// attributes on this function
+    attrs: &'a [&'a ast::ast::AttrItem],
+    /// polonius info for this function
+    info: &'a PoloniusInfo<'a, 'tcx>,
+
+    /// maps locals to variable names
+    variable_map: HashMap<Local, String>,
+
+    /// name of the return variable
+    return_name: String,
+    /// syntactic type of the thing to return
+    return_synty: radium::SynType,
+    /// all the other procedures used by this function, and:
+    /// (code_loc_parameter_name, spec_name, type_inst, syntype_of_all_args)
+    collected_procedures: HashMap<(DefId, types::GenericsKey<'tcx>), radium::UsedProcedure<'def>>,
+    /// used statics
+    collected_statics: HashSet<DefId>,
+
+    /// tracking lifetime inclusions for the generation of lifetime inclusions
+    inclusion_tracker: InclusionTracker<'a, 'tcx>,
+    /// initial Polonius constraints that hold at the start of the function
+    initial_constraints: Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)>,
+
+    /// local lifetimes: the LHS is the lifetime name, the RHS are the super lifetimes
+    local_lifetimes: Vec<(radium::specs::Lft, Vec<radium::specs::Lft>)>,
+    /// data structures for tracking which basic blocks still need to be translated
+    /// (we only translate the basic blocks which are actually reachable, in particular when
+    /// skipping unwinding)
+    bb_queue: Vec<BasicBlock>,
+    /// set of already processed blocks
+    processed_bbs: HashSet<BasicBlock>,
+
+    /// map of loop heads to their optional spec closure defid
+    loop_specs: HashMap<BasicBlock, Option<DefId>>,
+
+    /// relevant locals: (local, name, type)
+    fn_locals: Vec<(Local, String, radium::Type<'def>)>,
+
+    /// result temporaries of checked ops that we rewrite
+    /// we assume that this place is typed at (result_type, bool)
+    /// and rewrite accesses to the first component to directly use the place,
+    /// while rewriting accesses to the second component to true.
+    /// TODO: once we handle panics properly, we should use a different translation.
+    /// NOTE: we only rewrite for uses, as these are the only places these are used.
+    checked_op_temporaries: HashMap<Local, Ty<'tcx>>,
+
+    /// the Caesium function buildder
+    translated_fn: radium::FunctionBuilder<'def>,
+}
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    pub fn new(
+        env: &'def Environment<'tcx>,
+        procedure_registry: &'a procedures::Scope<'def>,
+        const_registry: &'a consts::Scope<'def>,
+        trait_registry: &'def registry::TR<'tcx, 'def>,
+        ty_translator: types::LocalTX<'def, 'tcx>,
+
+        proc: &'def Procedure<'tcx>,
+        attrs: &'a [&'a ast::ast::AttrItem],
+        info: &'a PoloniusInfo<'a, 'tcx>,
+        inputs: &[ty::Ty<'tcx>],
+
+        mut inclusion_tracker: InclusionTracker<'a, 'tcx>,
+        mut translated_fn: radium::FunctionBuilder<'def>,
+    ) -> Result<Self, TranslationError<'tcx>> {
+        let body = proc.get_mir();
+
+        // analyze which locals are used for the result of checked-ops, because we will
+        // override their types (eliminating the tuples)
+        let mut checked_op_analyzer = CheckedOpLocalAnalysis::new(env.tcx(), body);
+        checked_op_analyzer.analyze();
+        let checked_op_temporaries = checked_op_analyzer.results();
+
+        // map to translate between locals and the string names we use in radium::
+        let mut variable_map: HashMap<Local, String> = HashMap::new();
+
+        let local_decls = &body.local_decls;
+        info!("Have {} local decls\n", local_decls.len());
+
+        let debug_info = &body.var_debug_info;
+        info!("using debug info: {:?}", debug_info);
+
+        let mut return_synty = radium::SynType::Unit; // default
+        let mut fn_locals = Vec::new();
+        let mut opt_return_name =
+            Err(TranslationError::UnknownError("could not find local for return value".to_owned()));
+        let mut used_names = HashSet::new();
+        let mut arg_tys = Vec::new();
+
+        // go over local_decls and create the right radium:: stack layout
+        for (local, local_decl) in local_decls.iter_enumerated() {
+            let kind = body.local_kind(local);
+            let ty: Ty<'tcx>;
+            if let Some(rewritten_ty) = checked_op_temporaries.get(&local) {
+                ty = *rewritten_ty;
+            } else {
+                ty = local_decl.ty;
+            }
+
+            // check if the type is of a spec fn -- in this case, we can skip this temporary
+            if let TyKind::Closure(id, _) = ty.kind() {
+                if procedure_registry.lookup_function_mode(*id).map_or(false, procedures::Mode::is_ignore) {
+                    // this is a spec fn
+                    info!("skipping local which has specfn closure type: {:?}", local);
+                    continue;
+                }
+            }
+
+            // type:
+            info!("Trying to translate type of local {local:?}: {:?}", ty);
+            let tr_ty = ty_translator.translate_type(ty)?;
+            let st = (&tr_ty).into();
+
+            let name = Self::make_local_name(body, local, &mut used_names);
+            variable_map.insert(local, name.clone());
+
+            fn_locals.push((local, name.clone(), tr_ty));
+
+            match kind {
+                LocalKind::Arg => {
+                    translated_fn.code.add_argument(&name, st);
+                    arg_tys.push(ty);
+                },
+                //LocalKind::Var => translated_fn.code.add_local(&name, st),
+                LocalKind::Temp => translated_fn.code.add_local(&name, st),
+                LocalKind::ReturnPointer => {
+                    return_synty = st.clone();
+                    translated_fn.code.add_local(&name, st);
+                    opt_return_name = Ok(name);
+                },
+            }
+        }
+        info!("radium name map: {:?}", variable_map);
+        // create the function
+        let return_name = opt_return_name?;
+
+        // add lifetime parameters to the map
+        let initial_constraints = regions::init::get_initial_universal_arg_constraints(
+            info,
+            &mut inclusion_tracker,
+            inputs,
+            arg_tys.as_slice(),
+        );
+        info!("initial constraints: {:?}", initial_constraints);
+
+        Ok(Self {
+            env,
+            proc,
+            info,
+            variable_map,
+            translated_fn,
+            return_name,
+            return_synty,
+            inclusion_tracker,
+            collected_procedures: HashMap::new(),
+            procedure_registry,
+            attrs,
+            local_lifetimes: Vec::new(),
+            bb_queue: Vec::new(),
+            processed_bbs: HashSet::new(),
+            ty_translator,
+            loop_specs: HashMap::new(),
+            fn_locals,
+            checked_op_temporaries,
+            initial_constraints,
+            const_registry,
+            trait_registry,
+            collected_statics: HashSet::new(),
+        })
+    }
+
+    /// Main translation function that actually does the translation and returns a `radium::Function`
+    /// if successful.
+    pub fn translate(
+        mut self,
+        spec_arena: &'def Arena<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>,
+    ) -> Result<radium::Function<'def>, TranslationError<'tcx>> {
+        // add loop info
+        let loop_info = self.proc.loop_info();
+        loop_info.dump_body_head();
+
+        // translate the function's basic blocks
+        let basic_blocks = &self.proc.get_mir().basic_blocks;
+
+        // first translate the initial basic block; we add some additional annotations to the front
+        let initial_bb_idx = BasicBlock::from_u32(0);
+        if let Some(bb) = basic_blocks.get(initial_bb_idx) {
+            let mut translated_bb = self.translate_basic_block(initial_bb_idx, bb)?;
+            // push annotation for initial constraints that relate argument's place regions to universals
+            for (r1, r2) in &self.initial_constraints {
+                translated_bb = radium::Stmt::Annot {
+                    a: radium::Annotation::CopyLftName(
+                        self.ty_translator.format_atomic_region(r1),
+                        self.ty_translator.format_atomic_region(r2),
+                    ),
+                    s: Box::new(translated_bb),
+                    why: Some("initialization".to_owned()),
+                };
+            }
+            self.translated_fn.code.add_basic_block(initial_bb_idx.as_usize(), translated_bb);
+        } else {
+            info!("No basic blocks");
+        }
+
+        while let Some(bb_idx) = self.bb_queue.pop() {
+            let bb = &basic_blocks[bb_idx];
+            let translated_bb = self.translate_basic_block(bb_idx, bb)?;
+            self.translated_fn.code.add_basic_block(bb_idx.as_usize(), translated_bb);
+        }
+
+        // assume that all generics are layoutable
+        {
+            let scope = self.ty_translator.scope.borrow();
+            for ty in scope.generic_scope.tyvars() {
+                self.translated_fn.assume_synty_layoutable(radium::SynType::Literal(ty.syn_type));
+            }
+        }
+        // assume that all used literals are layoutable
+        for g in self.ty_translator.scope.borrow().shim_uses.values() {
+            self.translated_fn.assume_synty_layoutable(g.generate_syn_type_term());
+        }
+        // assume that all used tuples are layoutable
+        for g in self.ty_translator.scope.borrow().tuple_uses.values() {
+            self.translated_fn.assume_synty_layoutable(g.generate_syn_type_term());
+        }
+
+        // TODO: process self.loop_specs
+        // - collect the relevant bb -> def_id mappings for this function (so we can eventually generate the
+        //   definition)
+        // - have a function that takes the def_id and then parses the attributes into a loop invariant
+        for (head, did) in &self.loop_specs {
+            let spec = self.parse_attributes_on_loop_spec_closure(*head, *did);
+            self.translated_fn.register_loop_invariant(head.as_usize(), spec);
+        }
+
+        // generate dependencies on other procedures.
+        for used_proc in self.collected_procedures.values() {
+            self.translated_fn.require_function(used_proc.clone());
+        }
+
+        // generate dependencies on statics
+        for did in &self.collected_statics {
+            let s = self.const_registry.get_static(*did)?;
+            self.translated_fn.require_static(s.to_owned());
+        }
+
+        Ok(self.translated_fn.into_function(spec_arena))
+    }
+}
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    /// Generate a string identifier for a Local.
+    /// Tries to find the Rust source code name of the local, otherwise simply enumerates.
+    /// `used_names` keeps track of the Rust source code names that have already been used.
+    fn make_local_name(mir_body: &Body<'tcx>, local: Local, used_names: &mut HashSet<String>) -> String {
+        if let Some(mir_name) = Self::find_name_for_local(mir_body, local, used_names) {
+            let name = base::strip_coq_ident(&mir_name);
+            used_names.insert(mir_name);
+            name
+        } else {
+            let mut name = "__".to_owned();
+            name.push_str(&local.index().to_string());
+            name
+        }
+    }
+
+    /// Find a source name for a local of a MIR body, if possible.
+    fn find_name_for_local(
+        body: &mir::Body<'tcx>,
+        local: mir::Local,
+        used_names: &HashSet<String>,
+    ) -> Option<String> {
+        let debug_info = &body.var_debug_info;
+
+        for dbg in debug_info {
+            let name = &dbg.name;
+            let val = &dbg.value;
+            if let VarDebugInfoContents::Place(l) = *val {
+                // make sure that the place projection is empty -- otherwise this might just
+                // refer to the capture of a closure
+                if let Some(this_local) = l.as_local() {
+                    if this_local == local {
+                        // around closures, multiple symbols may be mapped to the same name.
+                        // To prevent this from happening, we check that the name hasn't been
+                        // used already
+                        // TODO: find better solution
+                        if !used_names.contains(name.as_str()) {
+                            return Some(name.as_str().to_owned());
+                        }
+                    }
+                }
+            } else {
+                // is this case used when constant propagation happens during MIR construction?
+            }
+        }
+
+        None
+    }
+
+    fn format_region(&self, r: facts::Region) -> String {
+        let lft = self.info.mk_atomic_region(r);
+        self.ty_translator.format_atomic_region(&lft)
+    }
+
+    /// Checks whether a place access descends below a reference.
+    fn check_place_below_reference(&self, place: &Place<'tcx>) -> bool {
+        if self.checked_op_temporaries.contains_key(&place.local) {
+            // temporaries are never below references
+            return false;
+        }
+
+        for (pl, _) in place.iter_projections() {
+            // check if the current ty is a reference that we then descend under with proj
+            let cur_ty_kind = pl.ty(&self.proc.get_mir().local_decls, self.env.tcx()).ty.kind();
+            if let TyKind::Ref(_, _, _) = cur_ty_kind {
+                return true;
+            }
+        }
+
+        false
+    }
+
+    /// Registers a drop shim for a particular type for the translation.
+    #[allow(clippy::unused_self)]
+    const fn register_drop_shim_for(&self, _ty: Ty<'tcx>) {
+        // TODO!
+        //let drop_in_place_did: DefId = search::try_resolve_did(self.env.tcx(), &["std", "ptr",
+        // "drop_in_place"]).unwrap();
+
+        //let x: ty::InstanceDef = ty::InstanceDef::DropGlue(drop_in_place_did, Some(ty));
+        //let body: &'tcx mir::Body = self.env.tcx().mir_shims(x);
+
+        //info!("Generated drop shim for {:?}", ty);
+        //Self::dump_body(body);
+    }
+
+    /// Translate a goto-like jump to `target`.
+    fn translate_goto_like(
+        &mut self,
+        _loc: &Location,
+        target: BasicBlock,
+    ) -> Result<radium::Stmt, TranslationError<'tcx>> {
+        self.enqueue_basic_block(target);
+        let res_stmt = radium::Stmt::GotoBlock(target.as_usize());
+
+        let loop_info = self.proc.loop_info();
+        if loop_info.is_loop_head(target) && !self.loop_specs.contains_key(&target) {
+            let spec_defid = self.find_loop_spec_closure(target)?;
+            self.loop_specs.insert(target, spec_defid);
+        }
+
+        Ok(res_stmt)
+    }
+
+    /// Enqueues a basic block for processing, if it has not already been processed,
+    /// and marks it as having been processed.
+    fn enqueue_basic_block(&mut self, bb: BasicBlock) {
+        if !self.processed_bbs.contains(&bb) {
+            self.bb_queue.push(bb);
+            self.processed_bbs.insert(bb);
+        }
+    }
+
+    /// Prepend endlft annotations for dying loans to a statement.
+    fn prepend_endlfts<I>(&self, st: radium::Stmt, dying: I) -> radium::Stmt
+    where
+        I: ExactSizeIterator<Item = facts::Loan>,
+    {
+        let mut cont_stmt = st;
+        if dying.len() > 0 {
+            //info!("Dying at {:?}: {:?}", loc, dying);
+            for d in dying {
+                let lft = self.info.atomic_region_of_loan(d);
+                cont_stmt = radium::Stmt::Annot {
+                    a: radium::Annotation::EndLft(self.ty_translator.format_atomic_region(&lft)),
+                    s: Box::new(cont_stmt),
+                    why: Some("endlft".to_owned()),
+                };
+            }
+        }
+        cont_stmt
+    }
+
+    /// Make a trivial place accessing `local`.
+    fn make_local_place(&self, local: Local) -> Place<'tcx> {
+        Place {
+            local,
+            projection: self.env.tcx().mk_place_elems(&[]),
+        }
+    }
+
+    /// Get the type of a local in a body.
+    fn get_type_of_local(&self, local: Local) -> Result<Ty<'tcx>, TranslationError<'tcx>> {
+        self.proc
+            .get_mir()
+            .local_decls
+            .get(local)
+            .map(|decl| decl.ty)
+            .ok_or_else(|| TranslationError::UnknownVar(String::new()))
+    }
+
+    /// Get the type of a place expression.
+    fn get_type_of_place(&self, pl: &Place<'tcx>) -> PlaceTy<'tcx> {
+        pl.ty(&self.proc.get_mir().local_decls, self.env.tcx())
+    }
+
+    /// Get the type of a const.
+    fn get_type_of_const(cst: &Constant<'tcx>) -> Ty<'tcx> {
+        match cst.literal {
+            ConstantKind::Ty(cst) => cst.ty(),
+            ConstantKind::Val(_, ty) | ConstantKind::Unevaluated(_, ty) => ty,
+        }
+    }
+
+    /// Get the type of an operand.
+    fn get_type_of_operand(&self, op: &Operand<'tcx>) -> Ty<'tcx> {
+        op.ty(&self.proc.get_mir().local_decls, self.env.tcx())
+    }
+
+    /// Check if a local is used for a spec closure.
+    fn is_spec_closure_local(&self, l: Local) -> Result<Option<DefId>, TranslationError<'tcx>> {
+        // check if we should ignore this
+        let local_type = self.get_type_of_local(l)?;
+
+        let TyKind::Closure(did, _) = local_type.kind() else {
+            return Ok(None);
+        };
+
+        Ok(self
+            .procedure_registry
+            .lookup_function_mode(*did)
+            .and_then(|m| m.is_ignore().then_some(*did)))
+    }
+}
diff --git a/rr_frontend/translation/src/body/translation/place.rs b/rr_frontend/translation/src/body/translation/place.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4625823b03f7283d1065e585743350956b7b383a
--- /dev/null
+++ b/rr_frontend/translation/src/body/translation/place.rs
@@ -0,0 +1,124 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+use log::info;
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{
+    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
+    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
+    TerminatorKind, UnOp, VarDebugInfoContents,
+};
+use rr_rustc_interface::middle::ty::fold::TypeFolder;
+use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind};
+use rr_rustc_interface::middle::{mir, ty};
+use rr_rustc_interface::{abi, ast, middle};
+
+use super::TX;
+use crate::base::*;
+use crate::types;
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    /// Translate a place to a Caesium lvalue.
+    pub(super) fn translate_place(
+        &mut self,
+        pl: &Place<'tcx>,
+    ) -> Result<radium::Expr, TranslationError<'tcx>> {
+        // Get the type of the underlying local. We will use this to
+        // get the necessary layout information for dereferencing
+        let mut cur_ty = self.get_type_of_local(pl.local).map(PlaceTy::from_ty)?;
+
+        let local_name = self
+            .variable_map
+            .get(&pl.local)
+            .ok_or_else(|| TranslationError::UnknownVar(format!("{:?}", pl.local)))?;
+
+        let mut acc_expr = radium::Expr::Var(local_name.to_string());
+
+        // iterate in evaluation order
+        for it in pl.projection {
+            match &it {
+                ProjectionElem::Deref => {
+                    // use the type of the dereferencee
+                    let st = self.ty_translator.translate_type_to_syn_type(cur_ty.ty)?;
+                    acc_expr = radium::Expr::Deref {
+                        ot: st.into(),
+                        e: Box::new(acc_expr),
+                    };
+                },
+                ProjectionElem::Field(f, _) => {
+                    // `t` is the type of the field we are accessing!
+                    let lit = self.ty_translator.generate_structlike_use(cur_ty.ty, cur_ty.variant_index)?;
+                    // TODO: does not do the right thing for accesses to fields of zero-sized objects.
+                    let struct_sls = lit.map_or(radium::SynType::Unit, |x| x.generate_raw_syn_type_term());
+                    let name = self.ty_translator.translator.get_field_name_of(
+                        *f,
+                        cur_ty.ty,
+                        cur_ty.variant_index.map(abi::VariantIdx::as_usize),
+                    )?;
+
+                    acc_expr = radium::Expr::FieldOf {
+                        e: Box::new(acc_expr),
+                        name,
+                        sls: struct_sls.to_string(),
+                    };
+                },
+                ProjectionElem::Index(_v) => {
+                    //TODO
+                    return Err(TranslationError::UnsupportedFeature {
+                        description: "places: implement index access".to_owned(),
+                    });
+                },
+                ProjectionElem::ConstantIndex { .. } => {
+                    //TODO
+                    return Err(TranslationError::UnsupportedFeature {
+                        description: "places: implement const index access".to_owned(),
+                    });
+                },
+                ProjectionElem::Subslice { .. } => {
+                    return Err(TranslationError::UnsupportedFeature {
+                        description: "places: implement subslicing".to_owned(),
+                    });
+                },
+                ProjectionElem::Downcast(_, variant_idx) => {
+                    info!("Downcast of ty {:?} to {:?}", cur_ty, variant_idx);
+                    if let ty::TyKind::Adt(def, args) = cur_ty.ty.kind() {
+                        if def.is_enum() {
+                            let enum_use = self.ty_translator.generate_enum_use(*def, args.iter())?;
+                            let els = enum_use.generate_raw_syn_type_term();
+
+                            let variant_name = types::TX::get_variant_name_of(cur_ty.ty, *variant_idx)?;
+
+                            acc_expr = radium::Expr::EnumData {
+                                els: els.to_string(),
+                                variant: variant_name,
+                                e: Box::new(acc_expr),
+                            }
+                        } else {
+                            return Err(TranslationError::UnknownError(
+                                "places: ADT downcasting on non-enum type".to_owned(),
+                            ));
+                        }
+                    } else {
+                        return Err(TranslationError::UnknownError(
+                            "places: ADT downcasting on non-enum type".to_owned(),
+                        ));
+                    }
+                },
+                ProjectionElem::OpaqueCast(_) => {
+                    return Err(TranslationError::UnsupportedFeature {
+                        description: "places: implement opaque casts".to_owned(),
+                    });
+                },
+            };
+            // update cur_ty
+            cur_ty = cur_ty.projection_ty(self.env.tcx(), it);
+        }
+        info!("translating place {:?} to {:?}", pl, acc_expr);
+        Ok(acc_expr)
+    }
+}
diff --git a/rr_frontend/translation/src/body/translation/rvalue.rs b/rr_frontend/translation/src/body/translation/rvalue.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0ea174c498930b39ef7ed484d1ac00da486fa79c
--- /dev/null
+++ b/rr_frontend/translation/src/body/translation/rvalue.rs
@@ -0,0 +1,624 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+use log::{info, trace};
+use radium::coq;
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::index::IndexVec;
+use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{
+    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
+    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
+    TerminatorKind, UnOp, VarDebugInfoContents,
+};
+use rr_rustc_interface::middle::ty::fold::TypeFolder;
+use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind};
+use rr_rustc_interface::middle::{mir, ty};
+use rr_rustc_interface::{abi, ast, middle};
+
+use super::TX;
+use crate::base::*;
+use crate::regions;
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    /// Translate an aggregate expression.
+    fn translate_aggregate(
+        &mut self,
+        loc: Location,
+        kind: &mir::AggregateKind<'tcx>,
+        op: &IndexVec<abi::FieldIdx, mir::Operand<'tcx>>,
+    ) -> Result<radium::Expr, TranslationError<'tcx>> {
+        // translate operands
+        let mut translated_ops: Vec<radium::Expr> = Vec::new();
+        let mut operand_types: Vec<Ty<'tcx>> = Vec::new();
+
+        for o in op {
+            let translated_o = self.translate_operand(o, true)?;
+            let type_of_o = self.get_type_of_operand(o);
+            translated_ops.push(translated_o);
+            operand_types.push(type_of_o);
+        }
+
+        match *kind {
+            mir::AggregateKind::Tuple => {
+                if operand_types.is_empty() {
+                    // translate to unit literal
+                    return Ok(radium::Expr::Literal(radium::Literal::ZST));
+                }
+
+                let struct_use = self.ty_translator.generate_tuple_use(operand_types.iter().copied())?;
+                let sl = struct_use.generate_raw_syn_type_term();
+                let initializers: Vec<_> =
+                    translated_ops.into_iter().enumerate().map(|(i, o)| (i.to_string(), o)).collect();
+
+                Ok(radium::Expr::StructInitE {
+                    sls: coq::term::App::new_lhs(sl.to_string()),
+                    components: initializers,
+                })
+            },
+
+            mir::AggregateKind::Adt(did, variant, args, ..) => {
+                // get the adt def
+                let adt_def: ty::AdtDef<'tcx> = self.env.tcx().adt_def(did);
+
+                if adt_def.is_struct() {
+                    let variant = adt_def.variant(variant);
+                    let struct_use = self.ty_translator.generate_struct_use(variant.def_id, args)?;
+
+                    let Some(struct_use) = struct_use else {
+                        // if not, it's replaced by unit
+                        return Ok(radium::Expr::Literal(radium::Literal::ZST));
+                    };
+
+                    let sl = struct_use.generate_raw_syn_type_term();
+                    let initializers: Vec<_> = translated_ops
+                        .into_iter()
+                        .zip(variant.fields.iter())
+                        .map(|(o, field)| (field.name.to_string(), o))
+                        .collect();
+
+                    return Ok(radium::Expr::StructInitE {
+                        sls: coq::term::App::new_lhs(sl.to_string()),
+                        components: initializers,
+                    });
+                }
+
+                if adt_def.is_enum() {
+                    let variant_def = adt_def.variant(variant);
+
+                    let struct_use =
+                        self.ty_translator.generate_enum_variant_use(variant_def.def_id, args)?;
+                    let sl = struct_use.generate_raw_syn_type_term();
+
+                    let initializers: Vec<_> = translated_ops
+                        .into_iter()
+                        .zip(variant_def.fields.iter())
+                        .map(|(o, field)| (field.name.to_string(), o))
+                        .collect();
+
+                    let variant_e = radium::Expr::StructInitE {
+                        sls: coq::term::App::new_lhs(sl.to_string()),
+                        components: initializers,
+                    };
+
+                    let enum_use = self.ty_translator.generate_enum_use(adt_def, args)?;
+                    let els = enum_use.generate_raw_syn_type_term();
+
+                    info!("generating enum annotation for type {:?}", enum_use);
+                    let ty = radium::RustType::of_type(&radium::Type::Literal(enum_use));
+                    let variant_name = variant_def.name.to_string();
+
+                    return Ok(radium::Expr::EnumInitE {
+                        els: coq::term::App::new_lhs(els.to_string()),
+                        variant: variant_name,
+                        ty,
+                        initializer: Box::new(variant_e),
+                    });
+                }
+
+                // TODO
+                Err(TranslationError::UnsupportedFeature {
+                    description: format!(
+                        "RefinedRust does currently not support aggregate rvalue for other ADTs (got: {kind:?}, {op:?})"
+                    ),
+                })
+            },
+            mir::AggregateKind::Closure(def, _args) => {
+                trace!("Translating Closure aggregate value for {:?}", def);
+
+                // We basically translate this to a tuple
+                if operand_types.is_empty() {
+                    // translate to unit literal
+                    return Ok(radium::Expr::Literal(radium::Literal::ZST));
+                }
+
+                let struct_use = self.ty_translator.generate_tuple_use(operand_types.iter().copied())?;
+                let sl = struct_use.generate_raw_syn_type_term();
+
+                let initializers: Vec<_> =
+                    translated_ops.into_iter().enumerate().map(|(i, o)| (i.to_string(), o)).collect();
+
+                Ok(radium::Expr::StructInitE {
+                    sls: coq::term::App::new_lhs(sl.to_string()),
+                    components: initializers,
+                })
+            },
+
+            _ => {
+                // TODO
+                Err(TranslationError::UnsupportedFeature {
+                    description: format!(
+                        "RefinedRust does currently not support this kind of aggregate rvalue (got: {kind:?}, {op:?})"
+                    ),
+                })
+            },
+        }
+    }
+
+    fn translate_cast(
+        &mut self,
+        kind: mir::CastKind,
+        op: &mir::Operand<'tcx>,
+        to_ty: ty::Ty<'tcx>,
+    ) -> Result<radium::Expr, TranslationError<'tcx>> {
+        let op_ty = self.get_type_of_operand(op);
+        let op_st = self.ty_translator.translate_type_to_syn_type(op_ty)?;
+        let op_ot = op_st.into();
+
+        let translated_op = self.translate_operand(op, true)?;
+
+        let target_st = self.ty_translator.translate_type_to_syn_type(to_ty)?;
+        let target_ot = target_st.into();
+
+        match kind {
+            mir::CastKind::PointerCoercion(x) => {
+                match x {
+                    ty::adjustment::PointerCoercion::MutToConstPointer => {
+                        // this is a NOP in our model
+                        Ok(translated_op)
+                    },
+
+                    ty::adjustment::PointerCoercion::ArrayToPointer
+                    | ty::adjustment::PointerCoercion::ClosureFnPointer(_)
+                    | ty::adjustment::PointerCoercion::ReifyFnPointer
+                    | ty::adjustment::PointerCoercion::UnsafeFnPointer
+                    | ty::adjustment::PointerCoercion::Unsize => Err(TranslationError::UnsupportedFeature {
+                        description: format!(
+                            "RefinedRust does currently not support this kind of pointer coercion (got: {kind:?})"
+                        ),
+                    }),
+                }
+            },
+
+            mir::CastKind::DynStar => Err(TranslationError::UnsupportedFeature {
+                description: "RefinedRust does currently not support dyn* cast".to_owned(),
+            }),
+
+            mir::CastKind::IntToInt => {
+                // Cast integer to integer
+                Ok(radium::Expr::UnOp {
+                    o: radium::Unop::Cast(target_ot),
+                    ot: op_ot,
+                    e: Box::new(translated_op),
+                })
+            },
+
+            mir::CastKind::IntToFloat => Err(TranslationError::UnsupportedFeature {
+                description: "RefinedRust does currently not support int-to-float cast".to_owned(),
+            }),
+
+            mir::CastKind::FloatToInt => Err(TranslationError::UnsupportedFeature {
+                description: "RefinedRust does currently not support float-to-int cast".to_owned(),
+            }),
+
+            mir::CastKind::FloatToFloat => Err(TranslationError::UnsupportedFeature {
+                description: "RefinedRust does currently not support float-to-float cast".to_owned(),
+            }),
+
+            mir::CastKind::PtrToPtr => {
+                match (op_ty.kind(), to_ty.kind()) {
+                    (TyKind::RawPtr(_), TyKind::RawPtr(_)) => {
+                        // Casts between raw pointers are NOPs for us
+                        Ok(translated_op)
+                    },
+
+                    _ => {
+                        // TODO: any other cases we should handle?
+                        Err(TranslationError::UnsupportedFeature {
+                            description: format!(
+                                "RefinedRust does currently not support ptr-to-ptr cast (got: {kind:?}, {op:?}, {to_ty:?})"
+                            ),
+                        })
+                    },
+                }
+            },
+
+            mir::CastKind::FnPtrToPtr => Err(TranslationError::UnsupportedFeature {
+                description: format!(
+                    "RefinedRust does currently not support fnptr-to-ptr cast (got: {kind:?}, {op:?}, {to_ty:?})"
+                ),
+            }),
+
+            mir::CastKind::Transmute => Err(TranslationError::UnsupportedFeature {
+                description: format!(
+                    "RefinedRust does currently not support transmute cast (got: {kind:?}, {op:?}, {to_ty:?})"
+                ),
+            }),
+
+            mir::CastKind::PointerExposeAddress => {
+                // Cast pointer to integer
+                Ok(radium::Expr::UnOp {
+                    o: radium::Unop::Cast(target_ot),
+                    ot: radium::OpType::Ptr,
+                    e: Box::new(translated_op),
+                })
+            },
+
+            mir::CastKind::PointerFromExposedAddress => Err(TranslationError::UnsupportedFeature {
+                description: format!(
+                    "RefinedRust does currently not support this kind of cast (got: {kind:?}, {op:?}, {to_ty:?})"
+                ),
+            }),
+        }
+    }
+
+    /// Translate an operand.
+    /// This will either generate an lvalue (in case of Move or Copy) or an rvalue (in most cases
+    /// of Constant). How this is used depends on the context. (e.g., Use of an integer constant
+    /// does not typecheck, and produces a stuck program).
+    pub(super) fn translate_operand(
+        &mut self,
+        op: &Operand<'tcx>,
+        to_rvalue: bool,
+    ) -> Result<radium::Expr, TranslationError<'tcx>> {
+        match op {
+            // In Caesium: typed_place needs deref (not use) for place accesses.
+            // use is used top-level to convert an lvalue to an rvalue, which is why we use it here.
+            Operand::Copy(place) | Operand::Move(place) => {
+                // check if this goes to a temporary of a checked op
+                let place_kind = if self.checked_op_temporaries.contains_key(&place.local) {
+                    assert!(place.projection.len() == 1);
+
+                    let ProjectionElem::Field(f, _0) = place.projection[0] else {
+                        unreachable!("invariant violation for access to checked op temporary");
+                    };
+
+                    if f.index() != 0 {
+                        // make this a constant false -- our semantics directly checks for overflows
+                        // and otherwise throws UB.
+                        return Ok(radium::Expr::Literal(radium::Literal::Bool(false)));
+                    }
+
+                    // access to the result of the op
+                    self.make_local_place(place.local)
+                } else {
+                    *place
+                };
+
+                let translated_place = self.translate_place(&place_kind)?;
+                let ty = self.get_type_of_place(place);
+
+                let st = self.ty_translator.translate_type_to_syn_type(ty.ty)?;
+
+                if to_rvalue {
+                    Ok(radium::Expr::Use {
+                        ot: st.into(),
+                        e: Box::new(translated_place),
+                    })
+                } else {
+                    Ok(translated_place)
+                }
+            },
+            Operand::Constant(constant) => {
+                // TODO: possibly need different handling of the rvalue flag
+                // when this also handles string literals etc.
+                return self.translate_constant(constant.as_ref());
+            },
+        }
+    }
+
+    /// Translates an Rvalue.
+    pub(super) fn translate_rvalue(
+        &mut self,
+        loc: Location,
+        rval: &Rvalue<'tcx>,
+    ) -> Result<radium::Expr, TranslationError<'tcx>> {
+        match rval {
+            Rvalue::Use(op) => {
+                // converts an lvalue to an rvalue
+                self.translate_operand(op, true)
+            },
+
+            Rvalue::Ref(region, bk, pl) => {
+                let translated_pl = self.translate_place(pl)?;
+                let translated_bk = TX::translate_borrow_kind(*bk)?;
+                let ty_annot = self.get_type_annotation_for_borrow(*bk, pl)?;
+
+                if let Some(loan) = self.info.get_optional_loan_at_location(loc) {
+                    let atomic_region = self.info.atomic_region_of_loan(loan);
+                    let lft = self.ty_translator.format_atomic_region(&atomic_region);
+                    Ok(radium::Expr::Borrow {
+                        lft,
+                        bk: translated_bk,
+                        ty: ty_annot,
+                        e: Box::new(translated_pl),
+                    })
+                } else {
+                    info!("Didn't find loan at {:?}: {:?}; region {:?}", loc, rval, region);
+                    let region = regions::region_to_region_vid(*region);
+                    let lft = self.format_region(region);
+
+                    Ok(radium::Expr::Borrow {
+                        lft,
+                        bk: translated_bk,
+                        ty: ty_annot,
+                        e: Box::new(translated_pl),
+                    })
+                }
+            },
+
+            Rvalue::AddressOf(mt, pl) => {
+                let translated_pl = self.translate_place(pl)?;
+                let translated_mt = TX::translate_mutability(*mt);
+
+                Ok(radium::Expr::AddressOf {
+                    mt: translated_mt,
+                    e: Box::new(translated_pl),
+                })
+            },
+
+            Rvalue::BinaryOp(op, operands) => {
+                let e1 = &operands.as_ref().0;
+                let e2 = &operands.as_ref().1;
+
+                let e1_ty = self.get_type_of_operand(e1);
+                let e2_ty = self.get_type_of_operand(e2);
+                let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?;
+                let e2_st = self.ty_translator.translate_type_to_syn_type(e2_ty)?;
+
+                let translated_e1 = self.translate_operand(e1, true)?;
+                let translated_e2 = self.translate_operand(e2, true)?;
+                let translated_op = self.translate_binop(*op, &operands.as_ref().0, &operands.as_ref().1)?;
+
+                Ok(radium::Expr::BinOp {
+                    o: translated_op,
+                    ot1: e1_st.into(),
+                    ot2: e2_st.into(),
+                    e1: Box::new(translated_e1),
+                    e2: Box::new(translated_e2),
+                })
+            },
+
+            Rvalue::CheckedBinaryOp(op, operands) => {
+                let e1 = &operands.as_ref().0;
+                let e2 = &operands.as_ref().1;
+
+                let e1_ty = self.get_type_of_operand(e1);
+                let e2_ty = self.get_type_of_operand(e2);
+                let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?;
+                let e2_st = self.ty_translator.translate_type_to_syn_type(e2_ty)?;
+
+                let translated_e1 = self.translate_operand(e1, true)?;
+                let translated_e2 = self.translate_operand(e2, true)?;
+                let translated_op = TX::translate_checked_binop(*op)?;
+
+                Ok(radium::Expr::BinOp {
+                    o: translated_op,
+                    ot1: e1_st.into(),
+                    ot2: e2_st.into(),
+                    e1: Box::new(translated_e1),
+                    e2: Box::new(translated_e2),
+                })
+            },
+
+            Rvalue::UnaryOp(op, operand) => {
+                let translated_e1 = self.translate_operand(operand, true)?;
+                let e1_ty = self.get_type_of_operand(operand);
+                let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?;
+                let translated_op = TX::translate_unop(*op, e1_ty)?;
+
+                Ok(radium::Expr::UnOp {
+                    o: translated_op,
+                    ot: e1_st.into(),
+                    e: Box::new(translated_e1),
+                })
+            },
+
+            Rvalue::NullaryOp(op, _ty) => {
+                // TODO: SizeOf
+                Err(TranslationError::UnsupportedFeature {
+                    description: "RefinedRust does currently not support nullary ops (AlignOf, Sizeof)"
+                        .to_owned(),
+                })
+            },
+
+            Rvalue::Discriminant(pl) => {
+                let ty = self.get_type_of_place(pl);
+                let translated_pl = self.translate_place(pl)?;
+                info!("getting discriminant of {:?} at type {:?}", pl, ty);
+
+                let ty::TyKind::Adt(adt_def, args) = ty.ty.kind() else {
+                    return Err(TranslationError::UnsupportedFeature {
+                        description: format!(
+                            "RefinedRust does currently not support discriminant accesses on non-enum types ({:?}, got {:?})",
+                            rval, ty.ty
+                        ),
+                    });
+                };
+
+                let enum_use = self.ty_translator.generate_enum_use(*adt_def, args.iter())?;
+                let els = enum_use.generate_raw_syn_type_term();
+
+                let discriminant_acc = radium::Expr::EnumDiscriminant {
+                    els: els.to_string(),
+                    e: Box::new(translated_pl),
+                };
+
+                // need to do a load from this place
+                let it = ty.ty.discriminant_ty(self.env.tcx());
+                let translated_it = self.ty_translator.translate_type(it)?;
+
+                let radium::Type::Int(translated_it) = translated_it else {
+                    return Err(TranslationError::UnknownError(format!(
+                        "type of discriminant is not an integer type {:?}",
+                        it
+                    )));
+                };
+
+                let ot = radium::OpType::Int(translated_it);
+
+                Ok(radium::Expr::Use {
+                    ot,
+                    e: Box::new(discriminant_acc),
+                })
+            },
+
+            Rvalue::Aggregate(kind, op) => self.translate_aggregate(loc, kind.as_ref(), op),
+
+            Rvalue::Cast(kind, op, to_ty) => self.translate_cast(*kind, op, *to_ty),
+
+            Rvalue::CopyForDeref(_)
+            | Rvalue::Len(..)
+            | Rvalue::Repeat(..)
+            | Rvalue::ThreadLocalRef(..)
+            | Rvalue::ShallowInitBox(_, _) => Err(TranslationError::UnsupportedFeature {
+                description: format!(
+                    "RefinedRust does currently not support this kind of rvalue (got: {:?})",
+                    rval
+                ),
+            }),
+        }
+    }
+
+    /// Get the type to annotate a borrow with.
+    fn get_type_annotation_for_borrow(
+        &self,
+        bk: BorrowKind,
+        pl: &Place<'tcx>,
+    ) -> Result<Option<radium::RustType>, TranslationError<'tcx>> {
+        let BorrowKind::Mut { .. } = bk else {
+            return Ok(None);
+        };
+
+        let ty = self.get_type_of_place(pl);
+
+        // For borrows, we can safely ignore the downcast type -- we cannot borrow a particularly variant
+        let translated_ty = self.ty_translator.translate_type(ty.ty)?;
+        let annot_ty = radium::RustType::of_type(&translated_ty);
+
+        Ok(Some(annot_ty))
+    }
+
+    /// Translate binary operators.
+    /// We need access to the operands, too, to handle the offset operator and get the right
+    /// Caesium layout annotation.
+    fn translate_binop(
+        &self,
+        op: BinOp,
+        e1: &Operand<'tcx>,
+        _e2: &Operand<'tcx>,
+    ) -> Result<radium::Binop, TranslationError<'tcx>> {
+        match op {
+            BinOp::Add | BinOp::AddUnchecked => Ok(radium::Binop::Add),
+            BinOp::Sub | BinOp::SubUnchecked => Ok(radium::Binop::Sub),
+            BinOp::Mul | BinOp::MulUnchecked => Ok(radium::Binop::Mul),
+            BinOp::Div => Ok(radium::Binop::Div),
+            BinOp::Rem => Ok(radium::Binop::Mod),
+
+            BinOp::BitXor => Ok(radium::Binop::BitXor),
+            BinOp::BitAnd => Ok(radium::Binop::BitAnd),
+            BinOp::BitOr => Ok(radium::Binop::BitOr),
+            BinOp::Shl | BinOp::ShlUnchecked => Ok(radium::Binop::Shl),
+            BinOp::Shr | BinOp::ShrUnchecked => Ok(radium::Binop::Shr),
+
+            BinOp::Eq => Ok(radium::Binop::Eq),
+            BinOp::Lt => Ok(radium::Binop::Lt),
+            BinOp::Le => Ok(radium::Binop::Le),
+            BinOp::Ne => Ok(radium::Binop::Ne),
+            BinOp::Ge => Ok(radium::Binop::Ge),
+            BinOp::Gt => Ok(radium::Binop::Gt),
+
+            BinOp::Offset => {
+                // we need to get the layout of the thing we're offsetting
+                // try to get the type of e1.
+                let e1_ty = self.get_type_of_operand(e1);
+                let off_ty = TX::get_offset_ty(e1_ty)?;
+                let st = self.ty_translator.translate_type_to_syn_type(off_ty)?;
+                let ly = st.into();
+                Ok(radium::Binop::PtrOffset(ly))
+            },
+        }
+    }
+
+    /// Get the inner type of a type to which we can apply the offset operator.
+    fn get_offset_ty(ty: Ty<'tcx>) -> Result<Ty<'tcx>, TranslationError<'tcx>> {
+        match ty.kind() {
+            TyKind::Array(t, _) | TyKind::Slice(t) | TyKind::Ref(_, t, _) => Ok(*t),
+            TyKind::RawPtr(tm) => Ok(tm.ty),
+            _ => Err(TranslationError::UnknownError(format!("cannot take offset of {}", ty))),
+        }
+    }
+
+    /// Translate checked binary operators.
+    /// We need access to the operands, too, to handle the offset operator and get the right
+    /// Caesium layout annotation.
+    fn translate_checked_binop(op: BinOp) -> Result<radium::Binop, TranslationError<'tcx>> {
+        match op {
+            BinOp::Add => Ok(radium::Binop::CheckedAdd),
+            BinOp::Sub => Ok(radium::Binop::CheckedSub),
+            BinOp::Mul => Ok(radium::Binop::CheckedMul),
+            BinOp::Shl => Err(TranslationError::UnsupportedFeature {
+                description: "RefinedRust does currently not support checked Shl".to_owned(),
+            }),
+            BinOp::Shr => Err(TranslationError::UnsupportedFeature {
+                description: "RefinedRust does currently not support checked Shr".to_owned(),
+            }),
+            _ => Err(TranslationError::UnknownError(
+                "unexpected checked binop that is not Add, Sub, Mul, Shl, or Shr".to_owned(),
+            )),
+        }
+    }
+
+    /// Translate unary operators.
+    fn translate_unop(op: UnOp, ty: Ty<'tcx>) -> Result<radium::Unop, TranslationError<'tcx>> {
+        match op {
+            UnOp::Not => match ty.kind() {
+                ty::TyKind::Bool => Ok(radium::Unop::NotBool),
+                ty::TyKind::Int(_) | ty::TyKind::Uint(_) => Ok(radium::Unop::NotInt),
+                _ => Err(TranslationError::UnknownError(
+                    "application of UnOp::Not to non-{Int, Bool}".to_owned(),
+                )),
+            },
+            UnOp::Neg => Ok(radium::Unop::Neg),
+        }
+    }
+
+    /// Translate a `BorrowKind`.
+    fn translate_borrow_kind(kind: BorrowKind) -> Result<radium::BorKind, TranslationError<'tcx>> {
+        match kind {
+            BorrowKind::Shared => Ok(radium::BorKind::Shared),
+            BorrowKind::Shallow => {
+                // TODO: figure out what to do with this
+                // arises in match lowering
+                Err(TranslationError::UnsupportedFeature {
+                    description: "RefinedRust does currently not support shallow borrows".to_owned(),
+                })
+            },
+            BorrowKind::Mut { .. } => {
+                // TODO: handle two-phase borrows?
+                Ok(radium::BorKind::Mutable)
+            },
+        }
+    }
+
+    const fn translate_mutability(mt: Mutability) -> radium::Mutability {
+        match mt {
+            Mutability::Mut => radium::Mutability::Mut,
+            Mutability::Not => radium::Mutability::Shared,
+        }
+    }
+}
diff --git a/rr_frontend/translation/src/body/translation/terminator.rs b/rr_frontend/translation/src/body/translation/terminator.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ca749be7a0f50adee360245bb0fc008a2aab3ef2
--- /dev/null
+++ b/rr_frontend/translation/src/body/translation/terminator.rs
@@ -0,0 +1,242 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+use std::collections::HashMap;
+
+use log::{info, trace, warn};
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{
+    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
+    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
+    TerminatorKind, UnOp, VarDebugInfoContents,
+};
+use rr_rustc_interface::middle::ty::fold::TypeFolder;
+use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind};
+use rr_rustc_interface::middle::{mir, ty};
+use rr_rustc_interface::{abi, ast, middle};
+
+use super::TX;
+use crate::base::*;
+use crate::environment::borrowck::facts;
+use crate::search;
+
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
+    /// Check if a call goes to `std::rt::begin_panic`
+    fn is_call_destination_panic(&mut self, func: &Operand) -> bool {
+        let Operand::Constant(box c) = func else {
+            return false;
+        };
+
+        let ConstantKind::Val(_, ty) = c.literal else {
+            return false;
+        };
+
+        let TyKind::FnDef(did, _) = ty.kind() else {
+            return false;
+        };
+
+        if let Some(panic_id_std) =
+            search::try_resolve_did(self.env.tcx(), &["std", "panicking", "begin_panic"])
+        {
+            if panic_id_std == *did {
+                return true;
+            }
+        } else {
+            warn!("Failed to determine DefId of std::panicking::begin_panic");
+        }
+
+        if let Some(panic_id_core) = search::try_resolve_did(self.env.tcx(), &["core", "panicking", "panic"])
+        {
+            if panic_id_core == *did {
+                return true;
+            }
+        } else {
+            warn!("Failed to determine DefId of core::panicking::panic");
+        }
+
+        false
+    }
+
+    /// Translate a terminator.
+    /// We pass the dying loans during this terminator. They need to be added at the right
+    /// intermediate point.
+    pub(super) fn translate_terminator(
+        &mut self,
+        term: &Terminator<'tcx>,
+        loc: Location,
+        dying_loans: Vec<facts::Loan>,
+    ) -> Result<radium::Stmt, TranslationError<'tcx>> {
+        match &term.kind {
+            TerminatorKind::Goto { target } => self.translate_goto_like(&loc, *target),
+
+            TerminatorKind::Call {
+                func,
+                args,
+                destination,
+                target,
+                ..
+            } => {
+                trace!("translating Call {:?}", term);
+                if self.is_call_destination_panic(func) {
+                    info!("Replacing call to std::panicking::begin_panic with Stuck");
+                    return Ok(radium::Stmt::Stuck);
+                }
+
+                self.translate_function_call(func, args, destination, *target, loc, dying_loans.as_slice())
+            },
+
+            TerminatorKind::Return => {
+                // TODO: this requires additional handling for reborrows
+
+                // read from the return place
+                // Is this semantics accurate wrt what the intended MIR semantics is?
+                // Possibly handle this differently by making the first argument of a function a dedicated
+                // return place? See also discussion at https://github.com/rust-lang/rust/issues/71117
+                let stmt = radium::Stmt::Return(radium::Expr::Use {
+                    ot: (&self.return_synty).into(),
+                    e: Box::new(radium::Expr::Var(self.return_name.clone())),
+                });
+
+                // TODO is this right?
+                Ok(self.prepend_endlfts(stmt, dying_loans.into_iter()))
+            },
+
+            //TerminatorKind::Abort => {
+            //res_stmt = radium::Stmt::Stuck;
+            //res_stmt = self.prepend_endlfts(res_stmt, dying_loans.into_iter());
+            //},
+            TerminatorKind::SwitchInt { discr, targets } => {
+                let operand = self.translate_operand(discr, true)?;
+                let all_targets: &[BasicBlock] = targets.all_targets();
+
+                if self.get_type_of_operand(discr).is_bool() {
+                    // we currently special-case this as Caesium has a built-in if and this is more
+                    // convenient to handle for the type-checker
+
+                    // implementation detail: the first index is the `false` branch, the second the
+                    // `true` branch
+                    let true_target = all_targets[1];
+                    let false_target = all_targets[0];
+
+                    let true_branch = self.translate_goto_like(&loc, true_target)?;
+                    let false_branch = self.translate_goto_like(&loc, false_target)?;
+
+                    let stmt = radium::Stmt::If {
+                        e: operand,
+                        ot: radium::OpType::Bool,
+                        s1: Box::new(true_branch),
+                        s2: Box::new(false_branch),
+                    };
+
+                    // TODO: is this right?
+                    return Ok(self.prepend_endlfts(stmt, dying_loans.into_iter()));
+                }
+
+                //info!("switchint: {:?}", term.kind);
+                let operand = self.translate_operand(discr, true)?;
+                let ty = self.get_type_of_operand(discr);
+
+                let mut target_map: HashMap<u128, usize> = HashMap::new();
+                let mut translated_targets: Vec<radium::Stmt> = Vec::new();
+
+                for (idx, (tgt, bb)) in targets.iter().enumerate() {
+                    let bb: BasicBlock = bb;
+                    let translated_target = self.translate_goto_like(&loc, bb)?;
+
+                    target_map.insert(tgt, idx);
+                    translated_targets.push(translated_target);
+                }
+
+                let translated_default = self.translate_goto_like(&loc, targets.otherwise())?;
+                // TODO: need to put endlfts infront of gotos?
+
+                let translated_ty = self.ty_translator.translate_type(ty)?;
+                let radium::Type::Int(it) = translated_ty else {
+                    return Err(TranslationError::UnknownError(
+                        "SwitchInt switching on non-integer type".to_owned(),
+                    ));
+                };
+
+                Ok(radium::Stmt::Switch {
+                    e: operand,
+                    it,
+                    index_map: target_map,
+                    bs: translated_targets,
+                    def: Box::new(translated_default),
+                })
+            },
+
+            TerminatorKind::Assert {
+                cond,
+                expected,
+                target,
+                ..
+            } => {
+                // this translation gets stuck on failure
+                let cond_translated = self.translate_operand(cond, true)?;
+                let comp = radium::Expr::BinOp {
+                    o: radium::Binop::Eq,
+                    ot1: radium::OpType::Bool,
+                    ot2: radium::OpType::Bool,
+                    e1: Box::new(cond_translated),
+                    e2: Box::new(radium::Expr::Literal(radium::Literal::Bool(*expected))),
+                };
+
+                let stmt = self.translate_goto_like(&loc, *target)?;
+
+                // TODO: should we really have this?
+                let stmt = self.prepend_endlfts(stmt, dying_loans.into_iter());
+
+                Ok(radium::Stmt::AssertS {
+                    e: comp,
+                    s: Box::new(stmt),
+                })
+            },
+
+            TerminatorKind::Drop { place, target, .. } => {
+                let ty = self.get_type_of_place(place);
+                self.register_drop_shim_for(ty.ty);
+
+                let place_translated = self.translate_place(place)?;
+                let _drope = radium::Expr::DropE(Box::new(place_translated));
+
+                let stmt = self.translate_goto_like(&loc, *target)?;
+
+                Ok(self.prepend_endlfts(stmt, dying_loans.into_iter()))
+
+                //res_stmt = radium::Stmt::ExprS { e: drope, s: Box::new(res_stmt)};
+            },
+
+            // just a goto for our purposes
+            TerminatorKind::FalseEdge { real_target, .. }
+            // this is just a virtual edge for the borrowchecker, we can translate this to a normal goto
+            | TerminatorKind::FalseUnwind { real_target, .. } => {
+                self.translate_goto_like(&loc, *real_target)
+            },
+
+            TerminatorKind::Unreachable => Ok(radium::Stmt::Stuck),
+
+            TerminatorKind::UnwindResume => Err(TranslationError::Unimplemented {
+                description: "implement UnwindResume".to_owned(),
+            }),
+
+            TerminatorKind::UnwindTerminate(_) => Err(TranslationError::Unimplemented {
+                description: "implement UnwindTerminate".to_owned(),
+            }),
+
+            TerminatorKind::GeneratorDrop
+            | TerminatorKind::InlineAsm { .. }
+            | TerminatorKind::Yield { .. } => Err(TranslationError::UnsupportedFeature {
+                description: format!(
+                    "RefinedRust does currently not support this kind of terminator (got: {:?})",
+                    term
+                ),
+            }),
+        }
+    }
+}
diff --git a/rr_frontend/translation/src/consts.rs b/rr_frontend/translation/src/consts.rs
new file mode 100644
index 0000000000000000000000000000000000000000..42727d726f48ae7e5fce57aa9405cbe43cba0aba
--- /dev/null
+++ b/rr_frontend/translation/src/consts.rs
@@ -0,0 +1,49 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+use std::collections::{btree_map, BTreeMap, HashMap, HashSet};
+
+use rr_rustc_interface::hir::def_id::DefId;
+
+use crate::base::TranslationError;
+
+/// Scope of consts that are available
+pub struct Scope<'def> {
+    // statics are explicitly declared
+    statics: HashMap<DefId, radium::StaticMeta<'def>>,
+    // const places are constants that lie in a static memory segment because they are referred to
+    // by-ref
+    const_places: HashMap<DefId, radium::ConstPlaceMeta<'def>>,
+}
+
+impl<'def> Scope<'def> {
+    /// Create a new const scope.
+    pub fn empty() -> Self {
+        Self {
+            statics: HashMap::new(),
+            const_places: HashMap::new(),
+        }
+    }
+
+    /// Register a static
+    pub fn register_static(&mut self, did: DefId, meta: radium::StaticMeta<'def>) {
+        self.statics.insert(did, meta);
+    }
+
+    /// Register a const place
+    pub fn register_const_place(&mut self, did: DefId, meta: radium::ConstPlaceMeta<'def>) {
+        self.const_places.insert(did, meta);
+    }
+
+    pub fn get_static<'tcx>(&self, did: DefId) -> Result<&radium::StaticMeta<'def>, TranslationError<'tcx>> {
+        self.statics.get(&did).ok_or_else(|| {
+            TranslationError::UnknownError(format!(
+                "Did not find a registered static for did {did:?}; registered: {:?}",
+                self.statics
+            ))
+        })
+    }
+}
diff --git a/rr_frontend/translation/src/environment/mod.rs b/rr_frontend/translation/src/environment/mod.rs
index e46f0606ad3237987e21a8c102b24eebdf101c86..65f89976b13f642d27d13a05e5a97b1575420d03 100644
--- a/rr_frontend/translation/src/environment/mod.rs
+++ b/rr_frontend/translation/src/environment/mod.rs
@@ -32,13 +32,13 @@ use rr_rustc_interface::span::Span;
 use rr_rustc_interface::trait_selection::infer::{InferCtxtExt, TyCtxtInferExt};
 use rr_rustc_interface::{ast, span};
 
+use crate::attrs;
 use crate::data::ProcedureDefId;
 use crate::environment::borrowck::facts;
 use crate::environment::collect_closure_defs_visitor::CollectClosureDefsVisitor;
 use crate::environment::collect_prusti_spec_visitor::CollectPrustiSpecVisitor;
 use crate::environment::loops::{PlaceAccess, PlaceAccessKind, ProcedureLoops};
 use crate::environment::procedure::{BasicBlockIndex, Procedure};
-use crate::utils;
 
 /// Facade to the Rust compiler.
 // #[derive(Copy, Clone)]
@@ -211,7 +211,7 @@ impl<'tcx> Environment<'tcx> {
     pub fn has_tool_attribute(&self, def_id: ProcedureDefId, name: &str) -> bool {
         let tcx = self.tcx();
         // TODO: migrate to get_attrs
-        utils::has_tool_attr(tcx.get_attrs_unchecked(def_id), name)
+        attrs::has_tool_attr(tcx.get_attrs_unchecked(def_id), name)
     }
 
     /// Find whether the procedure has a particular `[tool]::<name>` attribute; if so, return its
@@ -219,14 +219,14 @@ impl<'tcx> Environment<'tcx> {
     pub fn get_tool_attribute<'a>(&'a self, def_id: ProcedureDefId, name: &str) -> Option<&'a ast::AttrArgs> {
         let tcx = self.tcx();
         // TODO: migrate to get_attrs
-        utils::get_tool_attr(tcx.get_attrs_unchecked(def_id), name)
+        attrs::get_tool_attr(tcx.get_attrs_unchecked(def_id), name)
     }
 
     /// Check whether the procedure has any `[tool]` attribute.
     pub fn has_any_tool_attribute(&self, def_id: ProcedureDefId) -> bool {
         let tcx = self.tcx();
         // TODO: migrate to get_attrs
-        utils::has_any_tool_attr(tcx.get_attrs_unchecked(def_id))
+        attrs::has_any_tool_attr(tcx.get_attrs_unchecked(def_id))
     }
 
     /// Get the attributes of an item (e.g. procedures).
@@ -245,11 +245,11 @@ impl<'tcx> Environment<'tcx> {
         F: for<'a> Fn(&'a ast::ast::AttrItem) -> bool,
     {
         let attrs = self.get_attributes(did);
-        let mut filtered_attrs = utils::filter_tool_attrs(attrs);
+        let mut filtered_attrs = attrs::filter_for_tool(attrs);
         // also add selected attributes from the surrounding impl
         if let Some(impl_did) = self.tcx().impl_of_method(did) {
             let impl_attrs = self.get_attributes(impl_did);
-            let filtered_impl_attrs = utils::filter_tool_attrs(impl_attrs);
+            let filtered_impl_attrs = attrs::filter_for_tool(impl_attrs);
             filtered_attrs.extend(filtered_impl_attrs.into_iter().filter(|x| propagate_from_impl(x)));
         }
 
diff --git a/rr_frontend/translation/src/function_body.rs b/rr_frontend/translation/src/function_body.rs
deleted file mode 100644
index ea6749283b24661dc8600f505685fef18eb96865..0000000000000000000000000000000000000000
--- a/rr_frontend/translation/src/function_body.rs
+++ /dev/null
@@ -1,4511 +0,0 @@
-// © 2023, The RefinedRust Developers and Contributors
-//
-// This Source Code Form is subject to the terms of the BSD-3-clause License.
-// If a copy of the BSD-3-clause license was not distributed with this
-// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
-
-use std::collections::{btree_map, BTreeMap, HashMap, HashSet};
-use std::fmt::Write;
-
-use log::{info, trace, warn};
-use radium::coq;
-use rr_rustc_interface::hir::def_id::DefId;
-use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar};
-use rr_rustc_interface::middle::mir::tcx::PlaceTy;
-use rr_rustc_interface::middle::mir::{
-    BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location,
-    Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator,
-    TerminatorKind, UnOp, VarDebugInfoContents,
-};
-use rr_rustc_interface::middle::ty::fold::TypeFolder;
-use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind, TypeFoldable};
-use rr_rustc_interface::middle::{mir, ty};
-use rr_rustc_interface::{abi, ast, middle};
-use typed_arena::Arena;
-
-use crate::arg_folder::*;
-use crate::base::*;
-use crate::checked_op_analysis::CheckedOpLocalAnalysis;
-use crate::environment::borrowck::facts;
-use crate::environment::polonius_info::PoloniusInfo;
-use crate::environment::procedure::Procedure;
-use crate::environment::{dump_borrowck_info, polonius_info, Environment};
-use crate::inclusion_tracker::*;
-use crate::spec_parsers::parse_utils::ParamLookup;
-use crate::spec_parsers::verbose_function_spec_parser::{
-    ClosureMetaInfo, FunctionRequirements, FunctionSpecParser, VerboseFunctionSpecParser,
-};
-use crate::trait_registry::TraitRegistry;
-use crate::type_translator::*;
-use crate::tyvars::*;
-use crate::{traits, utils};
-
-/// Mangle a name by appending type parameters to it.
-pub fn mangle_name_with_tys(method_name: &str, args: &[Ty<'_>]) -> String {
-    // TODO: maybe come up with some better way to generate names
-    let mut mangled_name = method_name.to_owned();
-    for arg in args {
-        mangled_name.push_str(format!("_{}", arg).as_str());
-    }
-    strip_coq_ident(&mangled_name)
-}
-
-/// Mangle a name by appending generic args to it.
-pub fn mangle_name_with_args(name: &str, args: &[ty::GenericArg<'_>]) -> String {
-    let mut mangled_base = name.to_owned();
-    for arg in args {
-        if let ty::GenericArgKind::Type(ty) = arg.unpack() {
-            write!(mangled_base, "_{}", strip_coq_ident(&format!("{ty}"))).unwrap();
-        }
-    }
-    mangled_base
-}
-
-/// Get the syntypes of function arguments for a procedure call.
-pub fn get_arg_syntypes_for_procedure_call<'tcx, 'def>(
-    tcx: ty::TyCtxt<'tcx>,
-    ty_translator: &LocalTypeTranslator<'def, 'tcx>,
-    callee_did: DefId,
-    ty_params: &[ty::GenericArg<'tcx>],
-) -> Result<Vec<radium::SynType>, TranslationError<'tcx>> {
-    let caller_did = ty_translator.get_proc_did();
-
-    // Get the type of the callee, fully instantiated
-    let full_ty: ty::EarlyBinder<Ty<'tcx>> = tcx.type_of(callee_did);
-    let full_ty = full_ty.instantiate(tcx, ty_params);
-
-    // We create a dummy scope in order to make the lifetime lookups succeed, since we only want
-    // syntactic types here.
-    // Since we do the substitution of the generics above, we should translate generics and
-    // traits in the caller's scope.
-    let scope = ty_translator.scope.borrow();
-    let param_env: ty::ParamEnv<'tcx> = tcx.param_env(scope.did);
-    let callee_state = CalleeTranslationState::new(&param_env, &scope.generic_scope);
-    let mut dummy_state = TranslationStateInner::CalleeTranslation(callee_state);
-
-    let mut syntypes = Vec::new();
-    match full_ty.kind() {
-        ty::TyKind::FnDef(_, _) => {
-            let sig = full_ty.fn_sig(tcx);
-            for ty in sig.inputs().skip_binder() {
-                let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?;
-                syntypes.push(st);
-            }
-        },
-        ty::TyKind::Closure(_, args) => {
-            let clos_args = args.as_closure();
-            let sig = clos_args.sig();
-            let pre_sig = sig.skip_binder();
-            // we also need to add the closure argument here
-
-            let tuple_ty = clos_args.tupled_upvars_ty();
-            match clos_args.kind() {
-                ty::ClosureKind::Fn | ty::ClosureKind::FnMut => {
-                    syntypes.push(radium::SynType::Ptr);
-                },
-                ty::ClosureKind::FnOnce => {
-                    let st =
-                        ty_translator.translator.translate_type_to_syn_type(tuple_ty, &mut dummy_state)?;
-                    syntypes.push(st);
-                },
-            }
-            for ty in pre_sig.inputs() {
-                let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?;
-                syntypes.push(st);
-            }
-        },
-        _ => unimplemented!(),
-    }
-
-    Ok(syntypes)
-}
-
-/**
- * Tracks the functions we translated and the Coq names they are available under.
- * To account for dependencies between functions, we may register translated names before we have
- * actually translated the function.
- */
-
-#[derive(Copy, Clone, Eq, PartialEq, Debug)]
-pub enum ProcedureMode {
-    Prove,
-    OnlySpec,
-    TrustMe,
-    Shim,
-    Ignore,
-}
-
-impl ProcedureMode {
-    pub fn is_prove(self) -> bool {
-        self == Self::Prove
-    }
-
-    pub fn is_only_spec(self) -> bool {
-        self == Self::OnlySpec
-    }
-
-    pub fn is_trust_me(self) -> bool {
-        self == Self::TrustMe
-    }
-
-    pub fn is_shim(self) -> bool {
-        self == Self::Shim
-    }
-
-    pub fn is_ignore(self) -> bool {
-        self == Self::Ignore
-    }
-
-    pub fn needs_proof(self) -> bool {
-        self == Self::Prove
-    }
-
-    pub fn needs_def(self) -> bool {
-        self == Self::Prove || self == Self::TrustMe
-    }
-
-    pub fn needs_spec(self) -> bool {
-        self == Self::Prove || self == Self::TrustMe || self == Self::OnlySpec
-    }
-}
-
-#[derive(Clone)]
-pub struct ProcedureMeta {
-    spec_name: String,
-    name: String,
-    mode: ProcedureMode,
-}
-
-impl ProcedureMeta {
-    pub const fn new(spec_name: String, name: String, mode: ProcedureMode) -> Self {
-        Self {
-            spec_name,
-            name,
-            mode,
-        }
-    }
-
-    pub fn get_spec_name(&self) -> &str {
-        &self.spec_name
-    }
-
-    pub fn get_name(&self) -> &str {
-        &self.name
-    }
-
-    pub const fn get_mode(&self) -> ProcedureMode {
-        self.mode
-    }
-}
-
-pub struct ProcedureScope<'def> {
-    /// maps the defid to (code_name, spec_name, name)
-    name_map: BTreeMap<DefId, ProcedureMeta>,
-    /// track the actually translated functions
-    translated_functions: BTreeMap<DefId, radium::Function<'def>>,
-    /// track the functions with just a specification (rr::only_spec)
-    specced_functions: BTreeMap<DefId, &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>,
-}
-
-impl<'def> ProcedureScope<'def> {
-    pub fn new() -> Self {
-        Self {
-            name_map: BTreeMap::new(),
-            translated_functions: BTreeMap::new(),
-            specced_functions: BTreeMap::new(),
-        }
-    }
-
-    /// Lookup the meta information of a function.
-    pub fn lookup_function(&self, did: DefId) -> Option<ProcedureMeta> {
-        self.name_map.get(&did).cloned()
-    }
-
-    /// Lookup a translated function spec
-    pub fn lookup_function_spec(
-        &self,
-        did: DefId,
-    ) -> Option<&'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>> {
-        if let Some(translated_fn) = self.translated_functions.get(&did) {
-            Some(translated_fn.spec)
-        } else if let Some(translated_spec) = self.specced_functions.get(&did) {
-            Some(translated_spec)
-        } else {
-            None
-        }
-    }
-
-    /// Lookup the Coq spec name for a function.
-    pub fn lookup_function_spec_name(&self, did: DefId) -> Option<&str> {
-        self.name_map.get(&did).map(ProcedureMeta::get_spec_name)
-    }
-
-    /// Lookup the name for a function.
-    pub fn lookup_function_mangled_name(&self, did: DefId) -> Option<&str> {
-        self.name_map.get(&did).map(ProcedureMeta::get_name)
-    }
-
-    /// Lookup the mode for a function.
-    pub fn lookup_function_mode(&self, did: DefId) -> Option<ProcedureMode> {
-        self.name_map.get(&did).map(ProcedureMeta::get_mode)
-    }
-
-    /// Register a function.
-    pub fn register_function<'tcx>(
-        &mut self,
-        did: DefId,
-        meta: ProcedureMeta,
-    ) -> Result<(), TranslationError<'tcx>> {
-        if self.name_map.insert(did, meta).is_some() {
-            Err(TranslationError::ProcedureRegistry(format!(
-                "function for defid {:?} has already been registered",
-                did
-            )))
-        } else {
-            Ok(())
-        }
-    }
-
-    /// Provide the code for a translated function.
-    pub fn provide_translated_function(&mut self, did: DefId, trf: radium::Function<'def>) {
-        let meta = &self.name_map[&did];
-        assert!(meta.get_mode().needs_def());
-        assert!(self.translated_functions.insert(did, trf).is_none());
-    }
-
-    /// Provide the specification for an `only_spec` function.
-    pub fn provide_specced_function(
-        &mut self,
-        did: DefId,
-        spec: &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>,
-    ) {
-        let meta = &self.name_map[&did];
-        assert!(meta.get_mode().is_only_spec());
-        assert!(self.specced_functions.insert(did, spec).is_none());
-    }
-
-    /// Iterate over the functions we have generated code for.
-    pub fn iter_code(&self) -> btree_map::Iter<'_, DefId, radium::Function<'def>> {
-        self.translated_functions.iter()
-    }
-
-    /// Iterate over the functions we have generated only specs for.
-    pub fn iter_only_spec(
-        &self,
-    ) -> btree_map::Iter<'_, DefId, &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>> {
-        self.specced_functions.iter()
-    }
-}
-
-/// Scope of consts that are available
-pub struct ConstScope<'def> {
-    // statics are explicitly declared
-    statics: HashMap<DefId, radium::StaticMeta<'def>>,
-    // const places are constants that lie in a static memory segment because they are referred to
-    // by-ref
-    const_places: HashMap<DefId, radium::ConstPlaceMeta<'def>>,
-}
-
-impl<'def> ConstScope<'def> {
-    /// Create a new const scope.
-    pub fn empty() -> Self {
-        Self {
-            statics: HashMap::new(),
-            const_places: HashMap::new(),
-        }
-    }
-
-    /// Register a static
-    pub fn register_static(&mut self, did: DefId, meta: radium::StaticMeta<'def>) {
-        self.statics.insert(did, meta);
-    }
-
-    /// Register a const place
-    pub fn register_const_place(&mut self, did: DefId, meta: radium::ConstPlaceMeta<'def>) {
-        self.const_places.insert(did, meta);
-    }
-}
-
-// solve the constraints for the new_regions
-// we first identify what kinds of constraints these new regions are subject to
-#[derive(Debug)]
-enum CallRegionKind {
-    // this is just an intersection of local regions.
-    Intersection(HashSet<Region>),
-    // this is equal to a specific region
-    EqR(Region),
-}
-
-struct CallRegions {
-    pub early_regions: Vec<Region>,
-    pub late_regions: Vec<Region>,
-    pub classification: HashMap<Region, CallRegionKind>,
-}
-
-/// Information we compute when calling a function from another function.
-/// Determines how to specialize the callee's generics in our spec assumption.
-struct AbstractedGenerics<'def> {
-    /// the scope with new generics to quantify over for the function's specialized spec
-    scope: radium::GenericScope<'def, radium::LiteralTraitSpecUse<'def>>,
-    /// instantiations for the specialized spec hint
-    callee_lft_param_inst: Vec<radium::Lft>,
-    callee_ty_param_inst: Vec<radium::Type<'def>>,
-    /// instantiations for the function use
-    fn_lft_param_inst: Vec<radium::Lft>,
-    fn_ty_param_inst: Vec<radium::Type<'def>>,
-}
-
-/// A scope of trait attributes mapping to Coq names to be used in a function's spec
-struct TraitSpecNameScope {
-    attrs: HashMap<String, String>,
-}
-
-/// When translating a function spec where attributes of a trait are in scope,
-/// we create a wrapper to lookup references to the trait's attributes when parsing function specs.
-struct FunctionSpecScope<'a, T> {
-    generics: &'a T,
-    attrs: &'a TraitSpecNameScope,
-}
-impl<'a, T: ParamLookup> ParamLookup for FunctionSpecScope<'a, T> {
-    fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> {
-        self.generics.lookup_ty_param(path)
-    }
-
-    fn lookup_lft(&self, lft: &str) -> Option<&radium::Lft> {
-        self.generics.lookup_lft(lft)
-    }
-
-    fn lookup_literal(&self, path: &[&str]) -> Option<&str> {
-        if path.len() == 1 {
-            if let Some(lit) = self.attrs.attrs.get(path[0]) {
-                return Some(lit);
-            }
-        }
-        self.generics.lookup_literal(path)
-    }
-}
-
-pub struct FunctionTranslator<'a, 'def, 'tcx> {
-    env: &'def Environment<'tcx>,
-    /// this needs to be annotated with the right borrowck things
-    proc: &'def Procedure<'tcx>,
-    /// the Caesium function buildder
-    translated_fn: radium::FunctionBuilder<'def>,
-    /// tracking lifetime inclusions for the generation of lifetime inclusions
-    inclusion_tracker: InclusionTracker<'a, 'tcx>,
-
-    /// registry of other procedures
-    procedure_registry: &'a ProcedureScope<'def>,
-    /// registry of consts
-    const_registry: &'a ConstScope<'def>,
-    /// attributes on this function
-    attrs: &'a [&'a ast::ast::AttrItem],
-    /// polonius info for this function
-    info: &'a PoloniusInfo<'a, 'tcx>,
-    /// translator for types
-    ty_translator: LocalTypeTranslator<'def, 'tcx>,
-    /// trait registry in the current scope
-    trait_registry: &'def TraitRegistry<'tcx, 'def>,
-    /// argument types (from the signature, with generics substituted)
-    inputs: Vec<Ty<'tcx>>,
-}
-
-impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
-    /// Generate a spec for a trait method.
-    pub fn spec_for_trait_method(
-        env: &'def Environment<'tcx>,
-        proc_did: DefId,
-        name: &str,
-        spec_name: &str,
-        attrs: &'a [&'a ast::ast::AttrItem],
-        ty_translator: &'def TypeTranslator<'def, 'tcx>,
-        trait_registry: &'def TraitRegistry<'tcx, 'def>,
-    ) -> Result<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>, TranslationError<'tcx>> {
-        let mut translated_fn = radium::FunctionBuilder::new(name, spec_name);
-
-        let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc_did);
-        let ty = ty.instantiate_identity();
-        // substs are the generic args of this function (including lifetimes)
-        // sig is the function signature
-        let sig = match ty.kind() {
-            TyKind::FnDef(_def, _args) => {
-                assert!(ty.is_fn());
-                let sig = ty.fn_sig(env.tcx());
-                sig
-            },
-            _ => panic!("can not handle non-fns"),
-        };
-        info!("Function signature: {:?}", sig);
-
-        let params = Self::get_proc_ty_params(env.tcx(), proc_did);
-        info!("Function generic args: {:?}", params);
-
-        let (inputs, output, region_substitution) = Self::process_lifetimes_of_args(env, params, sig);
-        info!("inputs: {:?}, output: {:?}", inputs, output);
-
-        let (type_scope, trait_attrs) = Self::setup_local_scope(
-            env,
-            ty_translator,
-            trait_registry,
-            proc_did,
-            params.as_slice(),
-            &mut translated_fn,
-            region_substitution,
-            false,
-            None,
-        )?;
-        let type_translator = LocalTypeTranslator::new(ty_translator, type_scope);
-
-        // TODO: add universal constraints (ideally in setup_local_scope)
-
-        let spec_builder = Self::process_attrs(
-            attrs,
-            &type_translator,
-            &mut translated_fn,
-            &trait_attrs,
-            inputs.as_slice(),
-            output,
-        )?;
-        translated_fn.add_function_spec_from_builder(spec_builder);
-
-        translated_fn.try_into().map_err(TranslationError::AttributeError)
-    }
-
-    /// Create a translation instance for a closure.
-    pub fn new_closure(
-        env: &'def Environment<'tcx>,
-        meta: &ProcedureMeta,
-        proc: Procedure<'tcx>,
-        attrs: &'a [&'a ast::ast::AttrItem],
-        ty_translator: &'def TypeTranslator<'def, 'tcx>,
-        trait_registry: &'def TraitRegistry<'tcx, 'def>,
-        proc_registry: &'a ProcedureScope<'def>,
-        const_registry: &'a ConstScope<'def>,
-    ) -> Result<Self, TranslationError<'tcx>> {
-        let mut translated_fn = radium::FunctionBuilder::new(&meta.name, &meta.spec_name);
-
-        // TODO can we avoid the leak
-        let proc: &'def Procedure = &*Box::leak(Box::new(proc));
-        let body = proc.get_mir();
-        Self::dump_body(body);
-
-        let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc.get_id());
-        let ty = ty.instantiate_identity();
-        let closure_kind = match ty.kind() {
-            TyKind::Closure(_def, closure_args) => {
-                assert!(ty.is_closure());
-                let clos = closure_args.as_closure();
-                clos.kind()
-            },
-            _ => panic!("can not handle non-closures"),
-        };
-
-        let local_decls = &body.local_decls;
-        let closure_arg = local_decls.get(Local::from_usize(1)).unwrap();
-        let closure_ty;
-
-        match closure_kind {
-            ty::ClosureKind::Fn => {
-                if let ty::TyKind::Ref(_, ty, _) = closure_arg.ty.kind() {
-                    closure_ty = ty;
-                } else {
-                    unreachable!();
-                }
-            },
-            ty::ClosureKind::FnMut => {
-                if let ty::TyKind::Ref(_, ty, _) = closure_arg.ty.kind() {
-                    closure_ty = ty;
-                } else {
-                    unreachable!("unexpected type {:?}", closure_arg.ty);
-                }
-            },
-            ty::ClosureKind::FnOnce => {
-                closure_ty = &closure_arg.ty;
-            },
-        }
-
-        let parent_args;
-        let mut capture_regions = Vec::new();
-        let sig;
-        let captures;
-        let upvars_tys;
-        if let ty::TyKind::Closure(did, closure_args) = closure_ty.kind() {
-            let clos = closure_args.as_closure();
-
-            let tupled_upvars_tys = clos.tupled_upvars_ty();
-            upvars_tys = clos.upvar_tys();
-            parent_args = clos.parent_args();
-            let unnormalized_sig = clos.sig();
-            sig = unnormalized_sig;
-            info!("closure sig: {:?}", sig);
-
-            captures = env.tcx().closure_captures(did.as_local().unwrap());
-            info!("Closure has captures: {:?}", captures);
-
-            // find additional lifetime parameters
-            for (place, ty) in captures.iter().zip(clos.upvar_tys().iter()) {
-                if place.region.is_some() {
-                    // find region from ty
-                    if let ty::TyKind::Ref(region, _, _) = ty.kind() {
-                        capture_regions.push(*region);
-                    }
-                }
-            }
-            info!("Closure capture regions: {:?}", capture_regions);
-
-            info!("Closure arg upvar_tys: {:?}", tupled_upvars_tys);
-            info!("Function signature: {:?}", sig);
-            info!("Closure generic args: {:?}", parent_args);
-        } else {
-            unreachable!();
-        }
-
-        match PoloniusInfo::new(env, proc) {
-            Ok(info) => {
-                // TODO: avoid leak
-                let info: &'def PoloniusInfo = &*Box::leak(Box::new(info));
-
-                // For closures, we only handle the parent's args here!
-                // TODO: do we need to do something special for the parent's late-bound region
-                // parameters?
-                // TODO: should we always take the lifetime parameters?
-                let params = parent_args;
-                //proc.get_type_params();
-                info!("Function generic args: {:?}", params);
-
-                // dump graphviz files
-                // color code: red: dying loan, pink: becoming a zombie; green: is zombie
-                if rrconfig::dump_borrowck_info() {
-                    dump_borrowck_info(env, proc.get_id(), info);
-                }
-
-                let (tupled_inputs, output, mut region_substitution) =
-                    Self::process_lifetimes_of_args(env, params, sig);
-
-                // Process the lifetime parameters that come from the captures
-                for r in capture_regions {
-                    // TODO: problem: we're introducing inconsistent names here.
-                    if let ty::RegionKind::ReVar(r) = r.kind() {
-                        let lft = info.mk_atomic_region(r);
-                        let name = format_atomic_region_direct(&lft, None);
-                        region_substitution.region_names.insert(r, name);
-                        // TODO: add to region_substitution?
-                    } else {
-                        unreachable!();
-                    }
-                }
-                // also add the lifetime for the outer reference
-                let mut maybe_outer_lifetime = None;
-                if let ty::TyKind::Ref(r, _, _) = closure_arg.ty.kind() {
-                    if let ty::RegionKind::ReVar(r) = r.kind() {
-                        // We need to do some hacks here to find the right Polonius region:
-                        // `r` is the non-placeholder region that the variable gets, but we are
-                        // looking for the corresponding placeholder region
-                        let r2 = Self::find_placeholder_region_for(r, info).unwrap();
-
-                        info!("using lifetime {:?} for closure universal", r2);
-                        let lft = info.mk_atomic_region(r2);
-                        let name = format_atomic_region_direct(&lft, None);
-                        region_substitution.region_names.insert(r2, name);
-
-                        maybe_outer_lifetime = Some(r2);
-                    } else {
-                        unreachable!();
-                    }
-                }
-
-                // detuple the inputs
-                assert!(tupled_inputs.len() == 1);
-                let input_tuple_ty = tupled_inputs[0];
-                let mut inputs = Vec::new();
-
-                // push the closure as the first argument
-                /*
-                if let Some(r2) = maybe_outer_lifetime {
-                    // in this case, we need to patch the region first
-                    if let ty::TyKind::Ref(_, ty, m) = closure_arg.ty.kind() {
-                        let new_region = ty::Region::new_var(env.tcx(), r2);
-                        inputs.push(env.tcx().mk_ty_from_kind(ty::TyKind::Ref(new_region, *ty, *m)));
-                    }
-                }
-                else {
-                    inputs.push(closure_arg.ty);
-                }
-                */
-
-                if let ty::TyKind::Tuple(args) = input_tuple_ty.kind() {
-                    inputs.extend(args.iter());
-                }
-
-                info!("inputs({}): {:?}, output: {:?}", inputs.len(), inputs, output);
-
-                let mut inclusion_tracker = InclusionTracker::new(info);
-                // add placeholder subsets
-                let initial_point: facts::Point = facts::Point {
-                    location: BasicBlock::from_u32(0).start_location(),
-                    typ: facts::PointType::Start,
-                };
-                for (r1, r2) in &info.borrowck_in_facts.known_placeholder_subset {
-                    inclusion_tracker.add_static_inclusion(
-                        *r1,
-                        *r2,
-                        info.interner.get_point_index(&initial_point),
-                    );
-                }
-
-                let (type_scope, trait_attrs) = Self::setup_local_scope(
-                    env,
-                    ty_translator,
-                    trait_registry,
-                    proc.get_id(),
-                    params,
-                    &mut translated_fn,
-                    region_substitution,
-                    true,
-                    Some(info),
-                )?;
-                let type_translator = LocalTypeTranslator::new(ty_translator, type_scope);
-
-                let mut t = Self {
-                    env,
-                    proc,
-                    info,
-                    translated_fn,
-                    inclusion_tracker,
-                    procedure_registry: proc_registry,
-                    attrs,
-                    ty_translator: type_translator,
-                    trait_registry,
-                    const_registry,
-                    inputs: inputs.clone(),
-                };
-
-                // compute meta information needed to generate the spec
-                let mut translated_upvars_types = Vec::new();
-                for ty in upvars_tys {
-                    let translated_ty = t.ty_translator.translate_type(ty)?;
-                    translated_upvars_types.push(translated_ty);
-                }
-                let meta;
-                {
-                    let scope = t.ty_translator.scope.borrow();
-                    meta = ClosureMetaInfo {
-                        kind: closure_kind,
-                        captures,
-                        capture_tys: &translated_upvars_types,
-                        closure_lifetime: maybe_outer_lifetime
-                            .map(|x| scope.lifetime_scope.lookup_region(x).unwrap().to_owned()),
-                    };
-                }
-
-                // process attributes
-                t.process_closure_attrs(&trait_attrs, &inputs, output, meta)?;
-                Ok(t)
-            },
-            Err(err) => Err(TranslationError::UnknownError(format!("{:?}", err))),
-        }
-    }
-
-    /// Translate the body of a function.
-    pub fn new(
-        env: &'def Environment<'tcx>,
-        meta: &ProcedureMeta,
-        proc: Procedure<'tcx>,
-        attrs: &'a [&'a ast::ast::AttrItem],
-        ty_translator: &'def TypeTranslator<'def, 'tcx>,
-        trait_registry: &'def TraitRegistry<'tcx, 'def>,
-        proc_registry: &'a ProcedureScope<'def>,
-        const_registry: &'a ConstScope<'def>,
-    ) -> Result<Self, TranslationError<'tcx>> {
-        let mut translated_fn = radium::FunctionBuilder::new(&meta.name, &meta.spec_name);
-
-        // TODO can we avoid the leak
-        let proc: &'def Procedure = &*Box::leak(Box::new(proc));
-
-        let body = proc.get_mir();
-        Self::dump_body(body);
-
-        let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc.get_id());
-        let ty = ty.instantiate_identity();
-        // substs are the generic args of this function (including lifetimes)
-        // sig is the function signature
-        let sig = match ty.kind() {
-            TyKind::FnDef(_def, _args) => {
-                assert!(ty.is_fn());
-                let sig = ty.fn_sig(env.tcx());
-                sig
-            },
-            _ => panic!("can not handle non-fns"),
-        };
-
-        info!("Function signature: {:?}", sig);
-
-        match PoloniusInfo::new(env, proc) {
-            Ok(info) => {
-                // TODO: avoid leak
-                let info: &'def PoloniusInfo = &*Box::leak(Box::new(info));
-
-                let params = Self::get_proc_ty_params(env.tcx(), proc.get_id());
-                info!("Function generic args: {:?}", params);
-
-                // dump graphviz files
-                // color code: red: dying loan, pink: becoming a zombie; green: is zombie
-                if rrconfig::dump_borrowck_info() {
-                    dump_borrowck_info(env, proc.get_id(), info);
-                }
-
-                let (inputs, output, region_substitution) = Self::process_lifetimes_of_args(env, params, sig);
-                info!("inputs: {:?}, output: {:?}", inputs, output);
-
-                let mut inclusion_tracker = InclusionTracker::new(info);
-                // add placeholder subsets
-                let initial_point: facts::Point = facts::Point {
-                    location: BasicBlock::from_u32(0).start_location(),
-                    typ: facts::PointType::Start,
-                };
-                for (r1, r2) in &info.borrowck_in_facts.known_placeholder_subset {
-                    inclusion_tracker.add_static_inclusion(
-                        *r1,
-                        *r2,
-                        info.interner.get_point_index(&initial_point),
-                    );
-                }
-
-                let (type_scope, trait_attrs) = Self::setup_local_scope(
-                    env,
-                    ty_translator,
-                    trait_registry,
-                    proc.get_id(),
-                    params.as_slice(),
-                    &mut translated_fn,
-                    region_substitution,
-                    true,
-                    Some(info),
-                )?;
-                let type_translator = LocalTypeTranslator::new(ty_translator, type_scope);
-
-                // process attributes
-                let mut spec_builder = Self::process_attrs(
-                    attrs,
-                    &type_translator,
-                    &mut translated_fn,
-                    &trait_attrs,
-                    inputs.as_slice(),
-                    output,
-                )?;
-
-                let mut t = Self {
-                    env,
-                    proc,
-                    info,
-                    translated_fn,
-                    inclusion_tracker,
-                    procedure_registry: proc_registry,
-                    attrs,
-                    ty_translator: type_translator,
-                    trait_registry,
-                    const_registry,
-                    inputs: inputs.clone(),
-                };
-
-                if spec_builder.has_spec() {
-                    // add universal constraints
-                    let universal_constraints = t.get_relevant_universal_constraints();
-                    info!("univeral constraints: {:?}", universal_constraints);
-                    for (lft1, lft2) in universal_constraints {
-                        spec_builder.add_lifetime_constraint(lft1, lft2);
-                    }
-
-                    t.translated_fn.add_function_spec_from_builder(spec_builder);
-                } else {
-                    let spec = t.make_trait_instance_spec()?;
-                    if let Some(spec) = spec {
-                        t.translated_fn.add_trait_function_spec(spec);
-                    } else {
-                        return Err(TranslationError::AttributeError(
-                            "No valid specification provided".to_owned(),
-                        ));
-                    }
-                }
-
-                Ok(t)
-            },
-            Err(err) => Err(TranslationError::UnknownError(format!("{:?}", err))),
-        }
-    }
-
-    pub fn translate(
-        mut self,
-        spec_arena: &'def Arena<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>,
-    ) -> Result<radium::Function<'def>, TranslationError<'tcx>> {
-        let body = self.proc.get_mir();
-
-        // analyze which locals are used for the result of checked-ops, because we will
-        // override their types (eliminating the tuples)
-        let mut checked_op_analyzer = CheckedOpLocalAnalysis::new(self.env.tcx(), body);
-        checked_op_analyzer.analyze();
-        let checked_op_locals = checked_op_analyzer.results();
-
-        // map to translate between locals and the string names we use in radium::
-        let mut radium_name_map: HashMap<Local, String> = HashMap::new();
-
-        let local_decls = &body.local_decls;
-        info!("Have {} local decls\n", local_decls.len());
-
-        let debug_info = &body.var_debug_info;
-        info!("using debug info: {:?}", debug_info);
-
-        let mut return_synty = radium::SynType::Unit; // default
-        let mut fn_locals = Vec::new();
-        let mut opt_return_name =
-            Err(TranslationError::UnknownError("could not find local for return value".to_owned()));
-        let mut used_names = HashSet::new();
-        let mut arg_tys = Vec::new();
-
-        // go over local_decls and create the right radium:: stack layout
-        for (local, local_decl) in local_decls.iter_enumerated() {
-            let kind = body.local_kind(local);
-            let ty: Ty<'tcx>;
-            if let Some(rewritten_ty) = checked_op_locals.get(&local) {
-                ty = *rewritten_ty;
-            } else {
-                ty = local_decl.ty;
-            }
-
-            // check if the type is of a spec fn -- in this case, we can skip this temporary
-            if let TyKind::Closure(id, _) = ty.kind() {
-                if self.procedure_registry.lookup_function_mode(*id).map_or(false, ProcedureMode::is_ignore) {
-                    // this is a spec fn
-                    info!("skipping local which has specfn closure type: {:?}", local);
-                    continue;
-                }
-            }
-
-            // type:
-            info!("Trying to translate type of local {local:?}: {:?}", ty);
-            let tr_ty = self.ty_translator.translate_type(ty)?;
-            let st = (&tr_ty).into();
-
-            let name = Self::make_local_name(body, local, &mut used_names);
-            radium_name_map.insert(local, name.clone());
-
-            fn_locals.push((local, name.clone(), tr_ty));
-
-            match kind {
-                LocalKind::Arg => {
-                    self.translated_fn.code.add_argument(&name, st);
-                    arg_tys.push(ty);
-                },
-                //LocalKind::Var => translated_fn.code.add_local(&name, st),
-                LocalKind::Temp => self.translated_fn.code.add_local(&name, st),
-                LocalKind::ReturnPointer => {
-                    return_synty = st.clone();
-                    self.translated_fn.code.add_local(&name, st);
-                    opt_return_name = Ok(name);
-                },
-            }
-        }
-        info!("radium name map: {:?}", radium_name_map);
-        // create the function
-        let return_name = opt_return_name?;
-
-        // add lifetime parameters to the map
-        let inputs2 = self.inputs.clone();
-        let initial_constraints =
-            self.get_initial_universal_arg_constraints(inputs2.as_slice(), arg_tys.as_slice());
-        info!("initial constraints: {:?}", initial_constraints);
-
-        let translator = BodyTranslator {
-            env: self.env,
-            proc: self.proc,
-            info: self.info,
-            variable_map: radium_name_map,
-            translated_fn: self.translated_fn,
-            return_name,
-            return_synty,
-            inclusion_tracker: self.inclusion_tracker,
-            collected_procedures: HashMap::new(),
-            procedure_registry: self.procedure_registry,
-            attrs: self.attrs,
-            local_lifetimes: Vec::new(),
-            bb_queue: Vec::new(),
-            processed_bbs: HashSet::new(),
-            ty_translator: self.ty_translator,
-            loop_specs: HashMap::new(),
-            fn_locals,
-            checked_op_temporaries: checked_op_locals,
-            const_registry: self.const_registry,
-            trait_registry: self.trait_registry,
-            collected_statics: HashSet::new(),
-        };
-        translator.translate(initial_constraints, spec_arena)
-    }
-}
-
-impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
-    /// Get type parameters of the given procedure.
-    fn get_proc_ty_params(tcx: ty::TyCtxt<'tcx>, did: DefId) -> ty::GenericArgsRef<'tcx> {
-        let ty = tcx.type_of(did).instantiate_identity();
-        match ty.kind() {
-            ty::TyKind::FnDef(_, params) => params,
-            ty::TyKind::Closure(_, closure_args) => {
-                assert!(ty.is_closure());
-                let clos = closure_args.as_closure();
-                let parent_args = clos.parent_args();
-
-                // TODO: this doesn't include lifetime parameters specific to this closure...
-                tcx.mk_args(parent_args)
-            },
-            _ => panic!("Procedure::new called on a procedure whose type is not TyKind::FnDef!"),
-        }
-    }
-
-    fn process_lifetimes_of_args(
-        env: &Environment<'tcx>,
-        params: &[ty::GenericArg<'tcx>],
-        sig: ty::Binder<'tcx, ty::FnSig<'tcx>>,
-    ) -> (Vec<ty::Ty<'tcx>>, ty::Ty<'tcx>, EarlyLateRegionMap) {
-        // a mapping of Polonius region IDs to names
-        let mut universal_lifetimes = BTreeMap::new();
-        let mut lifetime_names = HashMap::new();
-
-        let mut region_substitution_early: Vec<Option<ty::RegionVid>> = Vec::new();
-
-        // we create a substitution that replaces early bound regions with their Polonius
-        // region variables
-        let mut subst_early_bounds: Vec<ty::GenericArg<'tcx>> = Vec::new();
-        let mut num_early_bounds = 0;
-        for a in params {
-            if let ty::GenericArgKind::Lifetime(r) = a.unpack() {
-                // skip over 0 = static
-                let next_id = ty::RegionVid::from_u32(num_early_bounds + 1);
-                let revar = ty::Region::new_var(env.tcx(), next_id);
-                num_early_bounds += 1;
-                subst_early_bounds.push(ty::GenericArg::from(revar));
-
-                region_substitution_early.push(Some(next_id));
-
-                match *r {
-                    ty::RegionKind::ReEarlyBound(r) => {
-                        let name = strip_coq_ident(r.name.as_str());
-                        universal_lifetimes.insert(next_id, format!("ulft_{}", name));
-                        lifetime_names.insert(name, next_id);
-                    },
-                    _ => {
-                        universal_lifetimes.insert(next_id, format!("ulft_{}", num_early_bounds));
-                    },
-                }
-            } else {
-                subst_early_bounds.push(*a);
-                region_substitution_early.push(None);
-            }
-        }
-        let subst_early_bounds = env.tcx().mk_args(&subst_early_bounds);
-
-        info!("Computed early region map {region_substitution_early:?}");
-
-        // add names for late bound region variables
-        let mut num_late_bounds = 0;
-        let mut region_substitution_late = Vec::new();
-        for b in sig.bound_vars() {
-            let next_id = ty::RegionVid::from_u32(num_early_bounds + num_late_bounds + 1);
-
-            let ty::BoundVariableKind::Region(r) = b else {
-                continue;
-            };
-
-            region_substitution_late.push(next_id);
-
-            match r {
-                ty::BoundRegionKind::BrNamed(_, sym) => {
-                    let mut region_name = strip_coq_ident(sym.as_str());
-                    if region_name == "_" {
-                        region_name = next_id.as_usize().to_string();
-                        universal_lifetimes.insert(next_id, format!("ulft_{}", region_name));
-                    } else {
-                        universal_lifetimes.insert(next_id, format!("ulft_{}", region_name));
-                        lifetime_names.insert(region_name, next_id);
-                    }
-                },
-                ty::BoundRegionKind::BrAnon(_) => {
-                    universal_lifetimes.insert(next_id, format!("ulft_{}", next_id.as_usize()));
-                },
-                _ => (),
-            }
-
-            num_late_bounds += 1;
-        }
-
-        // replace late-bound region variables by re-enumerating them in the same way as the MIR
-        // type checker does (that this happens in the same way is important to make the names
-        // line up!)
-        let mut next_index = num_early_bounds + 1; // skip over one additional due to static
-        let mut folder = |_| {
-            let cur_index = next_index;
-            next_index += 1;
-            ty::Region::new_var(env.tcx(), ty::RegionVid::from_u32(cur_index))
-        };
-        let (late_sig, _late_region_map) = env.tcx().replace_late_bound_regions(sig, &mut folder);
-
-        // replace early bound variables
-        let inputs: Vec<_> = late_sig
-            .inputs()
-            .iter()
-            .map(|ty| ty_instantiate(*ty, env.tcx(), subst_early_bounds))
-            .collect();
-
-        let output = ty_instantiate(late_sig.output(), env.tcx(), subst_early_bounds);
-
-        info!("Computed late region map {region_substitution_late:?}");
-
-        let region_map = EarlyLateRegionMap::new(
-            region_substitution_early,
-            region_substitution_late,
-            universal_lifetimes,
-            lifetime_names,
-        );
-        (inputs, output, region_map)
-    }
-
-    /// At the start of the function, there's a universal (placeholder) region for reference argument.
-    /// These get subsequently relabeled.
-    /// Given the relabeled region, find the original placeholder region.
-    fn find_placeholder_region_for(r: ty::RegionVid, info: &PoloniusInfo) -> Option<ty::RegionVid> {
-        let root_location = Location {
-            block: BasicBlock::from_u32(0),
-            statement_index: 0,
-        };
-        let root_point = info.interner.get_point_index(&facts::Point {
-            location: root_location,
-            typ: facts::PointType::Start,
-        });
-
-        for (r1, r2, p) in &info.borrowck_in_facts.subset_base {
-            if *p == root_point && *r2 == r {
-                info!("find placeholder region for: {:?} ⊑ {:?} = r = {:?}", r1, r2, r);
-                return Some(*r1);
-            }
-        }
-        None
-    }
-
-    /// Set up the local generic scope of the function, including type parameters, lifetime
-    /// parameters, and trait constraints.
-    fn setup_local_scope(
-        env: &Environment<'tcx>,
-        ty_translator: &'def TypeTranslator<'def, 'tcx>,
-        trait_registry: &'def TraitRegistry<'tcx, 'def>,
-        proc_did: DefId,
-        params: &[ty::GenericArg<'tcx>],
-        translated_fn: &mut radium::FunctionBuilder<'def>,
-        region_substitution: EarlyLateRegionMap,
-        add_trait_specs: bool,
-        info: Option<&'def PoloniusInfo<'def, 'tcx>>,
-    ) -> Result<(TypeTranslationScope<'tcx, 'def>, TraitSpecNameScope), TranslationError<'tcx>> {
-        // add universals to the function
-        // important: these need to be in the right order!
-        for (vid, name) in &region_substitution.region_names {
-            translated_fn.add_universal_lifetime(name.to_owned());
-        }
-
-        // enter the procedure
-        let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(proc_did);
-        let type_scope = TypeTranslationScope::new_with_traits(
-            proc_did,
-            env,
-            env.tcx().mk_args(params),
-            region_substitution,
-            param_env,
-            ty_translator,
-            trait_registry,
-            info,
-        )?;
-
-        // add generic args to the fn
-        let generics = &type_scope.generic_scope;
-        for t in generics.tyvars() {
-            translated_fn.add_ty_param(t);
-        }
-
-        // add specs for traits of generics
-        let trait_uses = type_scope.generic_scope.trait_scope.get_trait_uses();
-        for ((did, params), trait_ref) in trait_uses {
-            let trait_use_ref = trait_ref.trait_use.borrow();
-            let trait_use = trait_use_ref.as_ref().unwrap();
-            translated_fn.add_trait_requirement(trait_use.clone());
-        }
-
-        // check if we are in the implementation of a trait or trait impl
-        let mut trait_attr_map = HashMap::new();
-        if let Some(trait_did) = env.tcx().trait_of_item(proc_did) {
-            // we are in a trait declaration
-            if let Some(trait_ref) = trait_registry.lookup_trait(trait_did) {
-                // make the parameter for the attrs that the function is parametric over
-                if let Some(trait_use_ref) = type_scope.generic_scope.trait_scope.get_self_trait_use() {
-                    let trait_use_ref = trait_use_ref.trait_use.borrow();
-                    let trait_use = trait_use_ref.as_ref().unwrap();
-                    let param_name = trait_use.make_spec_attrs_param_name();
-                    // add the corresponding record entries to the map
-                    for attr in &trait_ref.declared_attrs {
-                        let record_item = trait_ref.make_spec_attr_name(attr);
-                        trait_attr_map.insert(attr.to_owned(), format!("{param_name}.({record_item})"));
-                    }
-                }
-            }
-        }
-        if let Some(impl_did) = env.tcx().impl_of_method(proc_did) {
-            if let Some(trait_did) = env.tcx().trait_id_of_impl(impl_did) {
-                // we are in a trait impl
-                if let Some(trait_ref) = trait_registry.lookup_trait(trait_did) {
-                    let attrs = trait_registry.get_impl_attrs_term(impl_did)?;
-                    // add the corresponding record entries to the map
-                    for attr in &trait_ref.declared_attrs {
-                        let record_item = trait_ref.make_spec_attr_name(attr);
-                        trait_attr_map.insert(attr.to_owned(), format!("({attrs}).({record_item})"));
-                    }
-                }
-            }
-        }
-        let trait_scope = TraitSpecNameScope {
-            attrs: trait_attr_map,
-        };
-
-        // TODO: can we also setup the lifetime constraints here?
-        // TODO: understand better how these clauses relate to Polonius
-        // Note: these constraints do not seem to include implied bounds.
-        /*
-        let clauses = param_env.caller_bounds();
-        info!("looking for outlives clauses");
-        for cl in clauses.iter() {
-            let cl_kind = cl.kind();
-            let cl_kind = cl_kind.skip_binder();
-
-            // only look for trait predicates for now
-            if let ty::ClauseKind::RegionOutlives(region_pred) = cl_kind {
-                info!("region outlives: {:?} {:?}", region_pred.0, region_pred.1);
-            }
-            if let ty::ClauseKind::TypeOutlives(outlives_pred) = cl_kind {
-                info!("type outlives: {:?} {:?}", outlives_pred.0, outlives_pred.1);
-            }
-        }
-        */
-
-        Ok((type_scope, trait_scope))
-    }
-
-    /// Filter the "interesting" constraints between universal lifetimes that need to hold
-    /// (this does not include the constraints that need to hold for all universal lifetimes,
-    /// e.g. that they outlive the function lifetime and are outlived by 'static).
-    fn get_relevant_universal_constraints(&mut self) -> Vec<(radium::UniversalLft, radium::UniversalLft)> {
-        let info = &self.info;
-        let input_facts = &info.borrowck_in_facts;
-        let placeholder_subset = &input_facts.known_placeholder_subset;
-
-        let root_location = Location {
-            block: BasicBlock::from_u32(0),
-            statement_index: 0,
-        };
-        let root_point = self.info.interner.get_point_index(&facts::Point {
-            location: root_location,
-            typ: facts::PointType::Start,
-        });
-
-        let mut universal_constraints = Vec::new();
-
-        for (r1, r2) in placeholder_subset {
-            if let polonius_info::RegionKind::Universal(uk1) = info.get_region_kind(*r1) {
-                if let polonius_info::RegionKind::Universal(uk2) = info.get_region_kind(*r2) {
-                    // if LHS is static, ignore.
-                    if uk1 == polonius_info::UniversalRegionKind::Static {
-                        continue;
-                    }
-                    // if RHS is the function lifetime, ignore
-                    if uk2 == polonius_info::UniversalRegionKind::Function {
-                        continue;
-                    }
-
-                    let to_universal = || {
-                        let x = self.to_universal_lft(uk1, *r2)?;
-                        let y = self.to_universal_lft(uk2, *r1)?;
-                        Some((x, y))
-                    };
-                    // else, add this constraint
-                    // for the purpose of this analysis, it is fine to treat it as a dynamic inclusion
-                    if let Some((x, y)) = to_universal() {
-                        self.inclusion_tracker.add_dynamic_inclusion(*r1, *r2, root_point);
-                        universal_constraints.push((x, y));
-                    };
-                }
-            }
-        }
-        universal_constraints
-    }
-
-    fn process_function_requirements(
-        fn_builder: &mut radium::FunctionBuilder<'def>,
-        requirements: FunctionRequirements,
-    ) {
-        for e in requirements.early_coq_params {
-            fn_builder.add_early_coq_param(e);
-        }
-        for e in requirements.late_coq_params {
-            fn_builder.add_late_coq_param(e);
-        }
-        for e in requirements.proof_info.linktime_assumptions {
-            fn_builder.add_linktime_assumption(e);
-        }
-        for e in requirements.proof_info.sidecond_tactics {
-            fn_builder.add_manual_tactic(e);
-        }
-    }
-
-    /// Parse and process attributes of this closure.
-    fn process_closure_attrs<'b>(
-        &mut self,
-        trait_attrs: &TraitSpecNameScope,
-        inputs: &[Ty<'tcx>],
-        output: Ty<'tcx>,
-        meta: ClosureMetaInfo<'b, 'tcx, 'def>,
-    ) -> Result<(), TranslationError<'tcx>> {
-        trace!("entering process_closure_attrs");
-        let v = self.attrs;
-
-        // Translate signature
-        info!("inputs: {:?}, output: {:?}", inputs, output);
-        let mut translated_arg_types: Vec<radium::Type<'def>> = Vec::new();
-        for arg in inputs {
-            let translated: radium::Type<'def> = self.ty_translator.translate_type(*arg)?;
-            translated_arg_types.push(translated);
-        }
-        let translated_ret_type: radium::Type<'def> = self.ty_translator.translate_type(output)?;
-        info!("translated function type: {:?} → {}", translated_arg_types, translated_ret_type);
-
-        // Determine parser
-        let parser = rrconfig::attribute_parser();
-        if parser.as_str() != "verbose" {
-            trace!("leaving process_closure_attrs");
-            return Err(TranslationError::UnknownAttributeParser(parser));
-        }
-
-        let mut spec_builder = radium::LiteralFunctionSpecBuilder::new();
-
-        // add universal constraints
-        let universal_constraints = self.get_relevant_universal_constraints();
-        info!("universal constraints: {:?}", universal_constraints);
-        for (lft1, lft2) in universal_constraints {
-            spec_builder.add_lifetime_constraint(lft1, lft2);
-        }
-
-        let ty_translator = &self.ty_translator;
-        // Hack: create indirection by tracking the tuple uses we create in here.
-        // (We need a read reference to the scope, so we can't write to it at the same time)
-        let mut tuple_uses = HashMap::new();
-        {
-            let scope = ty_translator.scope.borrow();
-            let scope = FunctionSpecScope {
-                generics: &*scope,
-                attrs: trait_attrs,
-            };
-            let mut parser =
-                VerboseFunctionSpecParser::new(&translated_arg_types, &translated_ret_type, &scope, |lit| {
-                    ty_translator.translator.intern_literal(lit)
-                });
-
-            parser
-                .parse_closure_spec(v, &mut spec_builder, meta, |x| {
-                    ty_translator.make_tuple_use(x, Some(&mut tuple_uses))
-                })
-                .map_err(TranslationError::AttributeError)?;
-
-            Self::process_function_requirements(&mut self.translated_fn, parser.into());
-        }
-        let mut scope = ty_translator.scope.borrow_mut();
-        scope.tuple_uses.extend(tuple_uses);
-        self.translated_fn.add_function_spec_from_builder(spec_builder);
-
-        trace!("leaving process_closure_attrs");
-        Ok(())
-    }
-
-    /// Parse and process attributes of this function.
-    fn process_attrs(
-        attrs: &[&ast::ast::AttrItem],
-        ty_translator: &LocalTypeTranslator<'def, 'tcx>,
-        translator: &mut radium::FunctionBuilder<'def>,
-        trait_attrs: &TraitSpecNameScope,
-        inputs: &[Ty<'tcx>],
-        output: Ty<'tcx>,
-    ) -> Result<radium::LiteralFunctionSpecBuilder<'def>, TranslationError<'tcx>> {
-        info!("inputs: {:?}, output: {:?}", inputs, output);
-
-        let mut translated_arg_types: Vec<radium::Type<'def>> = Vec::new();
-        for arg in inputs {
-            let translated: radium::Type<'def> = ty_translator.translate_type(*arg)?;
-            translated_arg_types.push(translated);
-        }
-        let translated_ret_type: radium::Type<'def> = ty_translator.translate_type(output)?;
-        info!("translated function type: {:?} → {}", translated_arg_types, translated_ret_type);
-
-        let mut spec_builder = radium::LiteralFunctionSpecBuilder::new();
-
-        let parser = rrconfig::attribute_parser();
-        match parser.as_str() {
-            "verbose" => {
-                {
-                    let scope = ty_translator.scope.borrow();
-                    let scope = FunctionSpecScope {
-                        generics: &*scope,
-                        attrs: trait_attrs,
-                    };
-                    let mut parser: VerboseFunctionSpecParser<'_, 'def, _, _> =
-                        VerboseFunctionSpecParser::new(
-                            &translated_arg_types,
-                            &translated_ret_type,
-                            &scope,
-                            |lit| ty_translator.translator.intern_literal(lit),
-                        );
-
-                    parser
-                        .parse_function_spec(attrs, &mut spec_builder)
-                        .map_err(TranslationError::AttributeError)?;
-                    Self::process_function_requirements(translator, parser.into());
-                }
-
-                Ok(spec_builder)
-            },
-            _ => Err(TranslationError::UnknownAttributeParser(parser)),
-        }
-    }
-
-    /// Make a specification for a trait instance derived from the trait's default spec.
-    fn make_trait_instance_spec(
-        &self,
-    ) -> Result<Option<radium::InstantiatedTraitFunctionSpec<'def>>, TranslationError<'tcx>> {
-        let did = self.proc.get_id();
-        if let Some(impl_did) = self.env.tcx().impl_of_method(did) {
-            if let Some(trait_did) = self.env.tcx().trait_id_of_impl(impl_did) {
-                let trait_ref = self
-                    .trait_registry
-                    .lookup_trait(trait_did)
-                    .ok_or_else(|| TranslationError::TraitResolution(format!("{trait_did:?}")))?;
-                let fn_name = strip_coq_ident(self.env.tcx().item_name(self.proc.get_id()).as_str());
-
-                let trait_info = self.trait_registry.get_trait_impl_info(impl_did)?;
-                //self.trait_registry.lookup_impl(impl_did)?;
-                let attr_term = self.trait_registry.get_impl_attrs_term(impl_did)?;
-                return Ok(Some(radium::InstantiatedTraitFunctionSpec::new(trait_info, fn_name, attr_term)));
-            }
-        }
-
-        Ok(None)
-    }
-
-    // TODO refactor/ move
-    fn to_universal_lft(
-        &self,
-        k: polonius_info::UniversalRegionKind,
-        r: Region,
-    ) -> Option<radium::UniversalLft> {
-        match k {
-            polonius_info::UniversalRegionKind::Function => Some(radium::UniversalLft::Function),
-            polonius_info::UniversalRegionKind::Static => Some(radium::UniversalLft::Static),
-
-            polonius_info::UniversalRegionKind::Local => self
-                .ty_translator
-                .scope
-                .borrow()
-                .lifetime_scope
-                .lookup_region(r)
-                .map(|x| radium::UniversalLft::Local(x.to_owned())),
-
-            polonius_info::UniversalRegionKind::External => self
-                .ty_translator
-                .scope
-                .borrow()
-                .lifetime_scope
-                .lookup_region(r)
-                .map(|x| radium::UniversalLft::External(x.to_owned())),
-        }
-    }
-
-    /// Translation that only generates a specification.
-    pub fn generate_spec(
-        self,
-    ) -> Result<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>, TranslationError<'tcx>> {
-        self.translated_fn.try_into().map_err(TranslationError::AttributeError)
-    }
-
-    /// Generate a string identifier for a Local.
-    /// Tries to find the Rust source code name of the local, otherwise simply enumerates.
-    /// `used_names` keeps track of the Rust source code names that have already been used.
-    fn make_local_name(mir_body: &Body<'tcx>, local: Local, used_names: &mut HashSet<String>) -> String {
-        if let Some(mir_name) = Self::find_name_for_local(mir_body, local, used_names) {
-            let name = strip_coq_ident(&mir_name);
-            used_names.insert(mir_name);
-            name
-        } else {
-            let mut name = "__".to_owned();
-            name.push_str(&local.index().to_string());
-            name
-        }
-    }
-
-    /// Find a source name for a local of a MIR body, if possible.
-    fn find_name_for_local(
-        body: &mir::Body<'tcx>,
-        local: mir::Local,
-        used_names: &HashSet<String>,
-    ) -> Option<String> {
-        let debug_info = &body.var_debug_info;
-
-        for dbg in debug_info {
-            let name = &dbg.name;
-            let val = &dbg.value;
-            if let VarDebugInfoContents::Place(l) = *val {
-                // make sure that the place projection is empty -- otherwise this might just
-                // refer to the capture of a closure
-                if let Some(this_local) = l.as_local() {
-                    if this_local == local {
-                        // around closures, multiple symbols may be mapped to the same name.
-                        // To prevent this from happening, we check that the name hasn't been
-                        // used already
-                        // TODO: find better solution
-                        if !used_names.contains(name.as_str()) {
-                            return Some(name.as_str().to_owned());
-                        }
-                    }
-                }
-            } else {
-                // is this case used when constant propagation happens during MIR construction?
-            }
-        }
-
-        None
-    }
-
-    fn dump_body(body: &Body) {
-        // TODO: print to file
-        //for dec in &body.local_decls {
-        //info!("Local decl: {:?}", dec);
-        //}
-
-        let basic_blocks = &body.basic_blocks;
-        for (bb_idx, bb) in basic_blocks.iter_enumerated() {
-            Self::dump_basic_block(bb_idx, bb);
-        }
-    }
-
-    /// Dump a basic block as info debug output.
-    fn dump_basic_block(bb_idx: BasicBlock, bb: &BasicBlockData) {
-        info!("Basic block {:?}:", bb_idx);
-        let mut i = 0;
-        for s in &bb.statements {
-            info!("{}\t{:?}", i, s);
-            i += 1;
-        }
-        info!("{}\t{:?}", i, bb.terminator());
-    }
-
-    /// Determine initial constraints between universal regions and local place regions.
-    /// Returns an initial mapping for the name _map that initializes place regions of arguments
-    /// with universals.
-    fn get_initial_universal_arg_constraints(
-        &mut self,
-        _sig_args: &[Ty<'tcx>],
-        _local_args: &[Ty<'tcx>],
-    ) -> Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)> {
-        // Polonius generates a base subset constraint uregion ⊑ pregion.
-        // We turn that into pregion = uregion, as we do strong updates at the top-level.
-        let info = &self.info;
-        let input_facts = &info.borrowck_in_facts;
-        let subset_base = &input_facts.subset_base;
-
-        let root_location = Location {
-            block: BasicBlock::from_u32(0),
-            statement_index: 0,
-        };
-        let root_point = self.info.interner.get_point_index(&facts::Point {
-            location: root_location,
-            typ: facts::PointType::Start,
-        });
-
-        // TODO: for nested references, this doesn't really seem to work.
-        // Problem is that we don't have constraints for the mapping of nested references.
-        // Potentially, we should instead just equalize the types
-
-        let mut initial_arg_mapping = Vec::new();
-        for (r1, r2, _) in subset_base {
-            let lft1 = self.info.mk_atomic_region(*r1);
-            let lft2 = self.info.mk_atomic_region(*r2);
-
-            let polonius_info::AtomicRegion::Universal(polonius_info::UniversalRegionKind::Local, _) = lft1
-            else {
-                continue;
-            };
-
-            // this is a constraint we care about here, add it
-            if self.inclusion_tracker.check_inclusion(*r1, *r2, root_point) {
-                continue;
-            }
-
-            self.inclusion_tracker.add_static_inclusion(*r1, *r2, root_point);
-            self.inclusion_tracker.add_static_inclusion(*r2, *r1, root_point);
-
-            assert!(matches!(lft2, polonius_info::AtomicRegion::PlaceRegion(_)));
-
-            initial_arg_mapping.push((lft1, lft2));
-        }
-        initial_arg_mapping
-    }
-
-    #[allow(clippy::unused_self)]
-    fn get_initial_universal_arg_constraints2(
-        &mut self,
-        sig_args: &[Ty<'tcx>],
-        local_args: &[Ty<'tcx>],
-    ) -> Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)> {
-        // Polonius generates a base subset constraint uregion ⊑ pregion.
-        // We turn that into pregion = uregion, as we do strong updates at the top-level.
-        assert!(sig_args.len() == local_args.len());
-
-        // TODO: implement a bitypefolder to solve this issue.
-        Vec::new()
-    }
-}
-
-/**
- * Struct that keeps track of all information necessary to translate a MIR Body to a `radium::Function`.
- * `'a` is the lifetime of the translator and ends after translation has finished.
- * `'def` is the lifetime of the generated code (the code may refer to struct defs).
- * `'tcx` is the lifetime of the rustc tctx.
- */
-struct BodyTranslator<'a, 'def, 'tcx> {
-    env: &'def Environment<'tcx>,
-    /// this needs to be annotated with the right borrowck things
-    proc: &'def Procedure<'tcx>,
-    /// maps locals to variable names
-    variable_map: HashMap<Local, String>,
-    /// the Caesium function buildder
-    translated_fn: radium::FunctionBuilder<'def>,
-    /// name of the return variable
-    return_name: String,
-    /// syntactic type of the thing to return
-    return_synty: radium::SynType,
-    /// all the other procedures used by this function, and:
-    /// (code_loc_parameter_name, spec_name, type_inst, syntype_of_all_args)
-    collected_procedures: HashMap<(DefId, GenericsKey<'tcx>), radium::UsedProcedure<'def>>,
-    /// used statics
-    collected_statics: HashSet<DefId>,
-
-    /// tracking lifetime inclusions for the generation of lifetime inclusions
-    inclusion_tracker: InclusionTracker<'a, 'tcx>,
-
-    /// registry of other procedures
-    procedure_registry: &'a ProcedureScope<'def>,
-    /// scope of used consts
-    const_registry: &'a ConstScope<'def>,
-    /// trait registry
-    trait_registry: &'def TraitRegistry<'tcx, 'def>,
-    /// attributes on this function
-    attrs: &'a [&'a ast::ast::AttrItem],
-    /// polonius info for this function
-    info: &'a PoloniusInfo<'a, 'tcx>,
-    /// local lifetimes: the LHS is the lifetime name, the RHS are the super lifetimes
-    local_lifetimes: Vec<(radium::specs::Lft, Vec<radium::specs::Lft>)>,
-    /// data structures for tracking which basic blocks still need to be translated
-    /// (we only translate the basic blocks which are actually reachable, in particular when
-    /// skipping unwinding)
-    bb_queue: Vec<BasicBlock>,
-    /// set of already processed blocks
-    processed_bbs: HashSet<BasicBlock>,
-    /// translator for types
-    ty_translator: LocalTypeTranslator<'def, 'tcx>,
-
-    /// map of loop heads to their optional spec closure defid
-    loop_specs: HashMap<BasicBlock, Option<DefId>>,
-
-    /// relevant locals: (local, name, type)
-    fn_locals: Vec<(Local, String, radium::Type<'def>)>,
-
-    /// result temporaries of checked ops that we rewrite
-    /// we assume that this place is typed at (result_type, bool)
-    /// and rewrite accesses to the first component to directly use the place,
-    /// while rewriting accesses to the second component to true.
-    /// TODO: once we handle panics properly, we should use a different translation.
-    /// NOTE: we only rewrite for uses, as these are the only places these are used.
-    checked_op_temporaries: HashMap<Local, Ty<'tcx>>,
-}
-
-impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
-    /// Main translation function that actually does the translation and returns a `radium::Function`
-    /// if successful.
-    pub fn translate(
-        mut self,
-        initial_constraints: Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)>,
-        spec_arena: &'def Arena<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>,
-    ) -> Result<radium::Function<'def>, TranslationError<'tcx>> {
-        // add loop info
-        let loop_info = self.proc.loop_info();
-        loop_info.dump_body_head();
-
-        // translate the function's basic blocks
-        let basic_blocks = &self.proc.get_mir().basic_blocks;
-
-        // first translate the initial basic block; we add some additional annotations to the front
-        let initial_bb_idx = BasicBlock::from_u32(0);
-        if let Some(bb) = basic_blocks.get(initial_bb_idx) {
-            let mut translated_bb = self.translate_basic_block(initial_bb_idx, bb)?;
-            // push annotation for initial constraints that relate argument's place regions to universals
-            for (r1, r2) in initial_constraints {
-                translated_bb = radium::Stmt::Annot {
-                    a: radium::Annotation::CopyLftName(
-                        self.format_atomic_region(&r1),
-                        self.format_atomic_region(&r2),
-                    ),
-                    s: Box::new(translated_bb),
-                    why: Some("initialization".to_owned()),
-                };
-            }
-            self.translated_fn.code.add_basic_block(initial_bb_idx.as_usize(), translated_bb);
-        } else {
-            info!("No basic blocks");
-        }
-
-        while let Some(bb_idx) = self.bb_queue.pop() {
-            let bb = &basic_blocks[bb_idx];
-            let translated_bb = self.translate_basic_block(bb_idx, bb)?;
-            self.translated_fn.code.add_basic_block(bb_idx.as_usize(), translated_bb);
-        }
-
-        // assume that all generics are layoutable
-        {
-            let scope = self.ty_translator.scope.borrow();
-            for ty in scope.generic_scope.tyvars() {
-                self.translated_fn.assume_synty_layoutable(radium::SynType::Literal(ty.syn_type));
-            }
-        }
-        // assume that all used literals are layoutable
-        for g in self.ty_translator.scope.borrow().shim_uses.values() {
-            self.translated_fn.assume_synty_layoutable(g.generate_syn_type_term());
-        }
-        // assume that all used tuples are layoutable
-        for g in self.ty_translator.scope.borrow().tuple_uses.values() {
-            self.translated_fn.assume_synty_layoutable(g.generate_syn_type_term());
-        }
-
-        // TODO: process self.loop_specs
-        // - collect the relevant bb -> def_id mappings for this function (so we can eventually generate the
-        //   definition)
-        // - have a function that takes the def_id and then parses the attributes into a loop invariant
-        for (head, did) in &self.loop_specs {
-            let spec = self.parse_attributes_on_loop_spec_closure(*head, *did);
-            self.translated_fn.register_loop_invariant(head.as_usize(), spec);
-        }
-
-        // generate dependencies on other procedures.
-        for used_proc in self.collected_procedures.values() {
-            self.translated_fn.require_function(used_proc.clone());
-        }
-
-        // generate dependencies on statics
-        for did in &self.collected_statics {
-            let s = &self.const_registry.statics[did];
-            self.translated_fn.require_static(s.clone());
-        }
-
-        Ok(self.translated_fn.into_function(spec_arena))
-    }
-
-    /// Determine initial constraints between universal regions and local place regions.
-    /// Returns an initial mapping for the name _map that initializes place regions of arguments
-    /// with universals.
-    fn get_initial_universal_arg_constraints(
-        &mut self,
-        _sig_args: &[Ty<'tcx>],
-        _local_args: &[Ty<'tcx>],
-    ) -> Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)> {
-        // Polonius generates a base subset constraint uregion ⊑ pregion.
-        // We turn that into pregion = uregion, as we do strong updates at the top-level.
-        let info = &self.info;
-        let input_facts = &info.borrowck_in_facts;
-        let subset_base = &input_facts.subset_base;
-
-        let root_location = Location {
-            block: BasicBlock::from_u32(0),
-            statement_index: 0,
-        };
-        let root_point = self.info.interner.get_point_index(&facts::Point {
-            location: root_location,
-            typ: facts::PointType::Start,
-        });
-
-        // TODO: for nested references, this doesn't really seem to work.
-        // Problem is that we don't have constraints for the mapping of nested references.
-        // Potentially, we should instead just equalize the types
-
-        let mut initial_arg_mapping = Vec::new();
-        for (r1, r2, _) in subset_base {
-            let lft1 = self.info.mk_atomic_region(*r1);
-            let lft2 = self.info.mk_atomic_region(*r2);
-
-            let polonius_info::AtomicRegion::Universal(polonius_info::UniversalRegionKind::Local, _) = lft1
-            else {
-                continue;
-            };
-
-            // this is a constraint we care about here, add it
-            if self.inclusion_tracker.check_inclusion(*r1, *r2, root_point) {
-                continue;
-            }
-
-            self.inclusion_tracker.add_static_inclusion(*r1, *r2, root_point);
-            self.inclusion_tracker.add_static_inclusion(*r2, *r1, root_point);
-
-            assert!(matches!(lft2, polonius_info::AtomicRegion::PlaceRegion(_)));
-
-            initial_arg_mapping.push((lft1, lft2));
-        }
-        initial_arg_mapping
-    }
-
-    /// Abstract over the generics of a function and partially instantiate them.
-    /// Assumption: `trait_reqs` is appropriately sorted, i.e. surrounding requirements come first.
-    /// `with_surrounding_deps` determines whether we should distinguish surrounding and direct
-    /// params.
-    fn get_generic_abstraction_for_procedure(
-        &self,
-        callee_did: DefId,
-        ty_params: ty::GenericArgsRef<'tcx>,
-        trait_reqs: &[radium::TraitReqInst<'def, ty::Ty<'tcx>>],
-        with_surrounding_deps: bool,
-    ) -> Result<AbstractedGenerics<'def>, TranslationError<'tcx>> {
-        // get all the regions and type variables appearing that generics are instantiated with
-        let mut tyvar_folder = TyVarFolder::new(self.env.tcx());
-        let mut lft_folder = TyRegionCollectFolder::new(self.env.tcx());
-
-        // also count the number of regions of the function itself
-        let mut num_param_regions = 0;
-
-        let mut callee_lft_param_inst: Vec<radium::Lft> = Vec::new();
-        let mut callee_ty_param_inst = Vec::new();
-        for v in ty_params {
-            if let Some(ty) = v.as_type() {
-                tyvar_folder.fold_ty(ty);
-                lft_folder.fold_ty(ty);
-            }
-            if let Some(region) = v.as_region() {
-                num_param_regions += 1;
-
-                let lft_name = self.ty_translator.translate_region(region)?;
-                callee_lft_param_inst.push(lft_name);
-            }
-        }
-        // also find generics in the associated types
-        for req in trait_reqs {
-            for ty in &req.assoc_ty_inst {
-                tyvar_folder.fold_ty(*ty);
-                lft_folder.fold_ty(*ty);
-            }
-        }
-
-        let tyvars = tyvar_folder.get_result();
-        let regions = lft_folder.get_regions();
-
-        let mut scope = radium::GenericScope::empty();
-
-        // instantiations for the function spec's parameters
-        let mut fn_lft_param_inst = Vec::new();
-        let mut fn_ty_param_inst = Vec::new();
-
-        // re-bind the function's lifetime parameters
-        for i in 0..num_param_regions {
-            let lft_name = format!("ulft_{i}");
-            scope.add_lft_param(lft_name.clone());
-            fn_lft_param_inst.push(lft_name);
-        }
-
-        // bind the additional lifetime parameters
-        let mut next_lft = num_param_regions;
-        for region in regions {
-            // Use the name the region has inside the function as the binder name, so that the
-            // names work out when translating the types below
-            let lft_name =
-                self.ty_translator.translate_region_var(region).unwrap_or(format!("ulft_{next_lft}"));
-            scope.add_lft_param(lft_name.clone());
-
-            next_lft += 1;
-
-            callee_lft_param_inst.push(lft_name);
-        }
-
-        // bind the generics we use
-        for param in &tyvars {
-            // NOTE: this should have the same name as the using occurrences
-            let lit = radium::LiteralTyParam::new(param.name.as_str(), param.name.as_str());
-            callee_ty_param_inst.push(radium::Type::LiteralParam(lit.clone()));
-            scope.add_ty_param(lit);
-        }
-        // also bind associated types which we translate as generics
-        for req in trait_reqs {
-            for ty in &req.assoc_ty_inst {
-                // we should check if it there is a parameter in the current scope for it
-                let translated_ty = self.ty_translator.translate_type(*ty)?;
-                if let radium::Type::LiteralParam(mut lit) = translated_ty {
-                    lit.set_origin(req.origin);
-
-                    scope.add_ty_param(lit.clone());
-                    callee_ty_param_inst.push(radium::Type::LiteralParam(lit.clone()));
-                }
-            }
-        }
-
-        // NOTE: we need to be careful with the order here.
-        // - the ty_params are all the generics the function has.
-        // - the trait_reqs are also all the associated types the function has
-        // We need to distinguish these between direct and surrounding.
-        let num_surrounding_params =
-            ParamScope::determine_number_of_surrounding_params(callee_did, self.env.tcx());
-        info!("num_surrounding_params={num_surrounding_params:?}, ty_params={ty_params:?}");
-
-        // figure out instantiation for the function's generics
-        // first the surrounding parameters
-        if with_surrounding_deps {
-            for v in &ty_params.as_slice()[..num_surrounding_params] {
-                if let Some(ty) = v.as_type() {
-                    let translated_ty = self.ty_translator.translate_type(ty)?;
-                    fn_ty_param_inst.push(translated_ty);
-                }
-            }
-            // same for the associated types this function depends on
-            for req in trait_reqs {
-                if req.origin != radium::TyParamOrigin::Direct {
-                    for ty in &req.assoc_ty_inst {
-                        let translated_ty = self.ty_translator.translate_type(*ty)?;
-                        fn_ty_param_inst.push(translated_ty);
-                    }
-                }
-            }
-
-            // now the direct parameters
-            for v in &ty_params.as_slice()[num_surrounding_params..] {
-                if let Some(ty) = v.as_type() {
-                    let translated_ty = self.ty_translator.translate_type(ty)?;
-                    fn_ty_param_inst.push(translated_ty);
-                }
-            }
-            // same for the associated types this function depends on
-            for req in trait_reqs {
-                if req.origin == radium::TyParamOrigin::Direct {
-                    for ty in &req.assoc_ty_inst {
-                        let translated_ty = self.ty_translator.translate_type(*ty)?;
-                        fn_ty_param_inst.push(translated_ty);
-                    }
-                }
-            }
-        } else {
-            // now the direct parameters
-            for v in ty_params {
-                if let Some(ty) = v.as_type() {
-                    let translated_ty = self.ty_translator.translate_type(ty)?;
-                    fn_ty_param_inst.push(translated_ty);
-                }
-            }
-            // same for the associated types this function depends on
-            for req in trait_reqs {
-                if req.origin == radium::TyParamOrigin::Direct {
-                    for ty in &req.assoc_ty_inst {
-                        let translated_ty = self.ty_translator.translate_type(*ty)?;
-                        fn_ty_param_inst.push(translated_ty);
-                    }
-                }
-            }
-        }
-
-        info!("Abstraction scope: {:?}", scope);
-        info!("Fn instantiation: {:?}, {:?}", fn_lft_param_inst, fn_ty_param_inst);
-        info!("Callee instantiation: {:?}, {:?}", callee_lft_param_inst, callee_ty_param_inst);
-
-        let res = AbstractedGenerics {
-            scope,
-            callee_lft_param_inst,
-            callee_ty_param_inst,
-            fn_lft_param_inst,
-            fn_ty_param_inst,
-        };
-
-        Ok(res)
-    }
-
-    /// Internally register that we have used a procedure with a particular instantiation of generics, and
-    /// return the code parameter name.
-    /// Arguments:
-    /// - `callee_did`: the `DefId` of the callee
-    /// - `ty_params`: the instantiation for the callee's type parameters
-    /// - `trait_spec_terms`: if the callee has any trait assumptions, these are specification parameter terms
-    ///   for these traits
-    /// - `trait_assoc_tys`: if the callee has any trait assumptions, these are the instantiations for all
-    ///   associated types
-    fn register_use_procedure(
-        &mut self,
-        callee_did: DefId,
-        extra_spec_args: Vec<String>,
-        ty_params: ty::GenericArgsRef<'tcx>,
-        trait_specs: Vec<radium::TraitReqInst<'def, ty::Ty<'tcx>>>,
-    ) -> Result<(String, Vec<radium::Type<'def>>, Vec<radium::Lft>), TranslationError<'tcx>> {
-        trace!("enter register_use_procedure callee_did={callee_did:?} ty_params={ty_params:?}");
-        // The key does not include the associated types, as the resolution of the associated types
-        // should always be unique for one combination of type parameters, as long as we remain in
-        // the same environment (which is the case within this procedure).
-        // Therefore, already the type parameters are sufficient to distinguish different
-        // instantiations.
-        let key = generate_args_inst_key(self.env.tcx(), ty_params)?;
-
-        // re-quantify
-        let quantified_args =
-            self.get_generic_abstraction_for_procedure(callee_did, ty_params, &trait_specs, true)?;
-
-        let tup = (callee_did, key);
-        let res;
-        if let Some(proc_use) = self.collected_procedures.get(&tup) {
-            res = proc_use.loc_name.clone();
-        } else {
-            let meta = self
-                .procedure_registry
-                .lookup_function(callee_did)
-                .ok_or_else(|| TranslationError::UnknownProcedure(format!("{:?}", callee_did)))?;
-            // explicit instantiation is needed sometimes
-            let spec_name = format!("{} (RRGS:=RRGS)", meta.get_spec_name());
-            let code_name = meta.get_name();
-            let loc_name = format!("{}_loc", mangle_name_with_tys(code_name, tup.1.as_slice()));
-
-            let syntypes = get_arg_syntypes_for_procedure_call(
-                self.env.tcx(),
-                &self.ty_translator,
-                callee_did,
-                ty_params.as_slice(),
-            )?;
-
-            let mut translated_params = quantified_args.fn_ty_param_inst;
-
-            info!(
-                "Registered procedure instance {} of {:?} with {:?} and layouts {:?}",
-                loc_name, callee_did, translated_params, syntypes
-            );
-
-            let specs = trait_specs.into_iter().map(|x| x.try_into().unwrap()).collect();
-
-            let proc_use = radium::UsedProcedure::new(
-                loc_name,
-                spec_name,
-                extra_spec_args,
-                quantified_args.scope,
-                specs,
-                translated_params,
-                quantified_args.fn_lft_param_inst,
-                syntypes,
-            );
-
-            res = proc_use.loc_name.clone();
-            self.collected_procedures.insert(tup, proc_use);
-        }
-        trace!("leave register_use_procedure");
-        Ok((res, quantified_args.callee_ty_param_inst, quantified_args.callee_lft_param_inst))
-    }
-
-    /// Internally register that we have used a trait method with a particular instantiation of
-    /// generics, and return the code parameter name.
-    fn register_use_trait_method<'c>(
-        &'c mut self,
-        callee_did: DefId,
-        ty_params: ty::GenericArgsRef<'tcx>,
-        trait_specs: Vec<radium::TraitReqInst<'def, ty::Ty<'tcx>>>,
-    ) -> Result<(String, Vec<radium::Type<'def>>, Vec<radium::Lft>), TranslationError<'tcx>> {
-        trace!("enter register_use_trait_method did={:?} ty_params={:?}", callee_did, ty_params);
-        // Does not include the associated types in the key; see `register_use_procedure` for an
-        // explanation.
-        let key = generate_args_inst_key(self.env.tcx(), ty_params)?;
-
-        let (method_loc_name, method_spec_term, method_params) =
-            self.ty_translator.register_use_trait_procedure(self.env, callee_did, ty_params)?;
-        // re-quantify
-        let quantified_args =
-            self.get_generic_abstraction_for_procedure(callee_did, method_params, &trait_specs, false)?;
-
-        let tup = (callee_did, key);
-        let res;
-        if let Some(proc_use) = self.collected_procedures.get(&tup) {
-            res = proc_use.loc_name.clone();
-        } else {
-            // TODO: should we use ty_params or method_params?
-            let syntypes = get_arg_syntypes_for_procedure_call(
-                self.env.tcx(),
-                &self.ty_translator,
-                callee_did,
-                ty_params.as_slice(),
-            )?;
-
-            let mut translated_params = quantified_args.fn_ty_param_inst;
-
-            info!(
-                "Registered procedure instance {} of {:?} with {:?} and layouts {:?}",
-                method_loc_name, callee_did, translated_params, syntypes
-            );
-
-            let specs = trait_specs.into_iter().map(|x| x.try_into().unwrap()).collect();
-            let proc_use = radium::UsedProcedure::new(
-                method_loc_name,
-                method_spec_term,
-                vec![],
-                quantified_args.scope,
-                specs,
-                translated_params,
-                quantified_args.fn_lft_param_inst,
-                syntypes,
-            );
-
-            res = proc_use.loc_name.clone();
-            self.collected_procedures.insert(tup, proc_use);
-        }
-        trace!("leave register_use_procedure");
-        Ok((res, quantified_args.callee_ty_param_inst, quantified_args.callee_lft_param_inst))
-    }
-
-    /// Enqueues a basic block for processing, if it has not already been processed,
-    /// and marks it as having been processed.
-    fn enqueue_basic_block(&mut self, bb: BasicBlock) {
-        if !self.processed_bbs.contains(&bb) {
-            self.bb_queue.push(bb);
-            self.processed_bbs.insert(bb);
-        }
-    }
-
-    /// Format an atomic region, using the naming info for universal lifetimes available in the current
-    /// context.
-    fn format_atomic_region(&self, r: &polonius_info::AtomicRegion) -> String {
-        self.ty_translator.format_atomic_region(r)
-    }
-
-    fn format_region(&self, r: facts::Region) -> String {
-        let lft = self.info.mk_atomic_region(r);
-        self.format_atomic_region(&lft)
-    }
-
-    /// Parse the attributes on spec closure `did` as loop annotations and add it as an invariant
-    /// to the generated code.
-    fn parse_attributes_on_loop_spec_closure(
-        &self,
-        loop_head: BasicBlock,
-        did: Option<DefId>,
-    ) -> radium::LoopSpec {
-        // for now: just make invariants True.
-
-        // need to do:
-        // - find out the locals in the right order, make parameter names for them. based on their type and
-        //   initialization status, get the refinement type.
-        // - output/pretty-print this map when generating the typing proof of each function. [done]
-        //  + should not be a separate definition, but rather a "set (.. := ...)" with a marker type so
-        //    automation can find it.
-
-        // representation of loop invariants:
-        // - introduce parameters for them.
-
-        let mut rfn_binders = Vec::new();
-        let prop_body = radium::IProp::True;
-
-        // determine invariant on initialization:
-        // - we need this both for the refinement invariant (though this could be removed if we make uninit
-        //   generic over the refinement)
-        // - in order to establish the initialization invariant in each loop iteration, since we don't have
-        //   proper subtyping for uninit => maybe we could fix this too by making uninit variant in the
-        //   refinement type? then we could have proper subtyping lemmas.
-        //  + to bring it to the right refinement type initially, maybe have some automation /
-        //  annotation
-        // TODO: consider changing it like that.
-        //
-        // Note that StorageDead will not help us for determining initialization/ making it invariant, since
-        // it only applies to full stack slots, not individual paths. one thing that makes it more
-        // complicated in the frontend: initialization may in practice also be path-dependent.
-        //  - this does not cause issues with posing a too strong loop invariant,
-        //  - but this poses an issue for annotations
-        //
-        //
-
-        // get locals
-        for (_, name, ty) in &self.fn_locals {
-            // get the refinement type
-            let mut rfn_ty = ty.get_rfn_type();
-            // wrap it in place_rfn, since we reason about places
-            rfn_ty = coq::term::Type::PlaceRfn(Box::new(rfn_ty));
-
-            // determine their initialization status
-            //let initialized = true; // TODO
-            // determine the actual refinement type for the current initialization status.
-
-            let rfn_name = format!("r_{}", name);
-            rfn_binders.push(coq::binder::Binder::new(Some(rfn_name), rfn_ty));
-        }
-
-        // TODO what do we do about stuff connecting borrows?
-        if let Some(did) = did {
-            let attrs = self.env.get_attributes(did);
-            info!("attrs for loop {:?}: {:?}", loop_head, attrs);
-        } else {
-            info!("no attrs for loop {:?}", loop_head);
-        }
-
-        let pred = radium::IPropPredicate::new(rfn_binders, prop_body);
-        radium::LoopSpec {
-            func_predicate: pred,
-        }
-    }
-
-    /// Checks whether a place access descends below a reference.
-    fn check_place_below_reference(&self, place: &Place<'tcx>) -> bool {
-        if self.checked_op_temporaries.contains_key(&place.local) {
-            // temporaries are never below references
-            return false;
-        }
-
-        for (pl, _) in place.iter_projections() {
-            // check if the current ty is a reference that we then descend under with proj
-            let cur_ty_kind = pl.ty(&self.proc.get_mir().local_decls, self.env.tcx()).ty.kind();
-            if let TyKind::Ref(_, _, _) = cur_ty_kind {
-                return true;
-            }
-        }
-
-        false
-    }
-
-    fn get_assignment_strong_update_constraints(
-        &mut self,
-        loc: Location,
-    ) -> HashSet<(Region, Region, PointIndex)> {
-        let info = &self.info;
-        let input_facts = &info.borrowck_in_facts;
-        let subset_base = &input_facts.subset_base;
-
-        let mut constraints = HashSet::new();
-        // Polonius subset constraint are spawned for the midpoint
-        let midpoint = self.info.interner.get_point_index(&facts::Point {
-            location: loc,
-            typ: facts::PointType::Mid,
-        });
-
-        // for strong update: emit mutual equalities
-        // TODO: alternative implementation: structurally compare regions in LHS type and RHS type
-        for (s1, s2, point) in subset_base {
-            if *point == midpoint {
-                let lft1 = self.info.mk_atomic_region(*s1);
-                let lft2 = self.info.mk_atomic_region(*s2);
-
-                // We only care about inclusions into a place lifetime.
-                // Moreover, we want to filter out the universal inclusions which are always
-                // replicated at every point.
-                if lft2.is_place() && !lft1.is_universal() {
-                    // take this constraint and the reverse constraint
-                    constraints.insert((*s1, *s2, *point));
-                    //constraints.insert((*s2, *s1, *point));
-                }
-            }
-        }
-        constraints
-    }
-
-    fn get_assignment_weak_update_constraints(
-        &mut self,
-        loc: Location,
-    ) -> HashSet<(Region, Region, PointIndex)> {
-        let info = &self.info;
-        let input_facts = &info.borrowck_in_facts;
-        let subset_base = &input_facts.subset_base;
-
-        let mut constraints = HashSet::new();
-        // Polonius subset constraint are spawned for the midpoint
-        let midpoint = self.info.interner.get_point_index(&facts::Point {
-            location: loc,
-            typ: facts::PointType::Mid,
-        });
-
-        // for weak updates: should mirror the constraints generated by Polonius
-        for (s1, s2, point) in subset_base {
-            if *point == midpoint {
-                // take this constraint
-                // TODO should there be exceptions to this?
-
-                if !self.inclusion_tracker.check_inclusion(*s1, *s2, *point) {
-                    // only add it if it does not hold already, since we will enforce this
-                    // constraint dynamically.
-                    constraints.insert((*s1, *s2, *point));
-                }
-            }
-        }
-        constraints
-    }
-
-    /// Split the type of a function operand of a call expression to a base type and an instantiation for
-    /// generics.
-    fn call_expr_op_split_inst(
-        &self,
-        constant: &Constant<'tcx>,
-    ) -> Result<
-        (DefId, ty::PolyFnSig<'tcx>, ty::GenericArgsRef<'tcx>, ty::PolyFnSig<'tcx>),
-        TranslationError<'tcx>,
-    > {
-        match constant.literal {
-            ConstantKind::Ty(c) => {
-                match c.ty().kind() {
-                    TyKind::FnDef(def, args) => {
-                        let ty: ty::EarlyBinder<Ty<'tcx>> = self.env.tcx().type_of(def);
-                        let ty_ident = ty.instantiate_identity();
-                        assert!(ty_ident.is_fn());
-                        let ident_sig = ty_ident.fn_sig(self.env.tcx());
-
-                        let ty_instantiated = ty.instantiate(self.env.tcx(), args.as_slice());
-                        let instantiated_sig = ty_instantiated.fn_sig(self.env.tcx());
-
-                        Ok((*def, ident_sig, args, instantiated_sig))
-                    },
-                    // TODO handle FnPtr, closure
-                    _ => Err(TranslationError::Unimplemented {
-                        description: "implement function pointers".to_owned(),
-                    }),
-                }
-            },
-            ConstantKind::Val(_, ty) => {
-                match ty.kind() {
-                    TyKind::FnDef(def, args) => {
-                        let ty: ty::EarlyBinder<Ty<'tcx>> = self.env.tcx().type_of(def);
-
-                        let ty_ident = ty.instantiate_identity();
-                        assert!(ty_ident.is_fn());
-                        let ident_sig = ty_ident.fn_sig(self.env.tcx());
-
-                        let ty_instantiated = ty.instantiate(self.env.tcx(), args.as_slice());
-                        let instantiated_sig = ty_instantiated.fn_sig(self.env.tcx());
-
-                        Ok((*def, ident_sig, args, instantiated_sig))
-                    },
-                    // TODO handle FnPtr, closure
-                    _ => Err(TranslationError::Unimplemented {
-                        description: "implement function pointers".to_owned(),
-                    }),
-                }
-            },
-            ConstantKind::Unevaluated(_, _) => Err(TranslationError::Unimplemented {
-                description: "implement ConstantKind::Unevaluated".to_owned(),
-            }),
-        }
-    }
-
-    /// Find the optional `DefId` of the closure giving the invariant for the loop with head `head_bb`.
-    fn find_loop_spec_closure(&self, head_bb: BasicBlock) -> Result<Option<DefId>, TranslationError<'tcx>> {
-        let bodies = self.proc.loop_info().get_loop_body(head_bb);
-        let basic_blocks = &self.proc.get_mir().basic_blocks;
-
-        // we go in order through the bodies in order to not stumble upon an annotation for a
-        // nested loop!
-        for body in bodies {
-            // check that we did not go below a nested loop
-            if self.proc.loop_info().get_loop_head(*body) == Some(head_bb) {
-                // check the statements for an assignment
-                let data = basic_blocks.get(*body).unwrap();
-                for stmt in &data.statements {
-                    if let StatementKind::Assign(box (pl, _)) = stmt.kind {
-                        if let Some(did) = self.is_spec_closure_local(pl.local)? {
-                            return Ok(Some(did));
-                        }
-                    }
-                }
-            }
-        }
-
-        Ok(None)
-    }
-
-    /// Translate a goto-like jump to `target`.
-    fn translate_goto_like(
-        &mut self,
-        _loc: &Location,
-        target: BasicBlock,
-    ) -> Result<radium::Stmt, TranslationError<'tcx>> {
-        self.enqueue_basic_block(target);
-        let res_stmt = radium::Stmt::GotoBlock(target.as_usize());
-
-        let loop_info = self.proc.loop_info();
-        if loop_info.is_loop_head(target) && !self.loop_specs.contains_key(&target) {
-            let spec_defid = self.find_loop_spec_closure(target)?;
-            self.loop_specs.insert(target, spec_defid);
-        }
-
-        Ok(res_stmt)
-    }
-
-    /// Check if a call goes to `std::rt::begin_panic`
-    fn is_call_destination_panic(&mut self, func: &Operand) -> bool {
-        let Operand::Constant(box c) = func else {
-            return false;
-        };
-
-        let ConstantKind::Val(_, ty) = c.literal else {
-            return false;
-        };
-
-        let TyKind::FnDef(did, _) = ty.kind() else {
-            return false;
-        };
-
-        if let Some(panic_id_std) =
-            utils::try_resolve_did(self.env.tcx(), &["std", "panicking", "begin_panic"])
-        {
-            if panic_id_std == *did {
-                return true;
-            }
-        } else {
-            warn!("Failed to determine DefId of std::panicking::begin_panic");
-        }
-
-        if let Some(panic_id_core) = utils::try_resolve_did(self.env.tcx(), &["core", "panicking", "panic"]) {
-            if panic_id_core == *did {
-                return true;
-            }
-        } else {
-            warn!("Failed to determine DefId of core::panicking::panic");
-        }
-
-        false
-    }
-
-    /// Registers a drop shim for a particular type for the translation.
-    #[allow(clippy::unused_self)]
-    const fn register_drop_shim_for(&self, _ty: Ty<'tcx>) {
-        // TODO!
-        //let drop_in_place_did: DefId = utils::try_resolve_did(self.env.tcx(), &["std", "ptr",
-        // "drop_in_place"]).unwrap();
-
-        //let x: ty::InstanceDef = ty::InstanceDef::DropGlue(drop_in_place_did, Some(ty));
-        //let body: &'tcx mir::Body = self.env.tcx().mir_shims(x);
-
-        //info!("Generated drop shim for {:?}", ty);
-        //Self::dump_body(body);
-    }
-
-    fn compute_call_regions(
-        &self,
-        func: &Constant<'tcx>,
-        loc: Location,
-    ) -> Result<CallRegions, TranslationError<'tcx>> {
-        let midpoint = self.info.interner.get_point_index(&facts::Point {
-            location: loc,
-            typ: facts::PointType::Mid,
-        });
-
-        // first identify substitutions for the early-bound regions
-        let (target_did, sig, substs, _) = self.call_expr_op_split_inst(func)?;
-        info!("calling function {:?}", target_did);
-        let mut early_regions = Vec::new();
-        info!("call substs: {:?} = {:?}, {:?}", func, sig, substs);
-        for a in substs {
-            if let ty::GenericArgKind::Lifetime(r) = a.unpack() {
-                if let ty::RegionKind::ReVar(r) = r.kind() {
-                    early_regions.push(r);
-                }
-            }
-        }
-        info!("call region instantiations (early): {:?}", early_regions);
-
-        // this is a hack to identify the inference variables introduced for the
-        // call's late-bound universals.
-        // TODO: Can we get this information in a less hacky way?
-        // One approach: compute the early + late bound regions for a given DefId, similarly to how
-        // we do it when starting to translate a function
-        // Problem: this doesn't give a straightforward way to compute their instantiation
-
-        // now find all the regions that appear in type parameters we instantiate.
-        // These are regions that the callee doesn't know about.
-        let mut generic_regions = HashSet::new();
-        let mut clos = |r: ty::Region<'tcx>, _| match r.kind() {
-            ty::RegionKind::ReVar(rv) => {
-                generic_regions.insert(rv);
-                r
-            },
-            _ => r,
-        };
-
-        for a in substs {
-            if let ty::GenericArgKind::Type(c) = a.unpack() {
-                let mut folder = ty::fold::RegionFolder::new(self.env.tcx(), &mut clos);
-                folder.fold_ty(c);
-            }
-        }
-        info!("Regions of generic args: {:?}", generic_regions);
-
-        // go over all region constraints initiated at this location
-        let new_constraints = self.info.get_new_subset_constraints_at_point(midpoint);
-        let mut new_regions = HashSet::new();
-        let mut relevant_constraints = Vec::new();
-        for (r1, r2) in &new_constraints {
-            if matches!(self.info.get_region_kind(*r1), polonius_info::RegionKind::Unknown) {
-                // this is probably a inference variable for the call
-                new_regions.insert(*r1);
-                relevant_constraints.push((*r1, *r2));
-            }
-            if matches!(self.info.get_region_kind(*r2), polonius_info::RegionKind::Unknown) {
-                new_regions.insert(*r2);
-                relevant_constraints.push((*r1, *r2));
-            }
-        }
-        // first sort this to enable cycle resolution
-        let mut new_regions_sorted: Vec<Region> = new_regions.iter().copied().collect();
-        new_regions_sorted.sort();
-
-        // identify the late-bound regions
-        let mut late_regions = Vec::new();
-        for r in &new_regions_sorted {
-            // only take the ones which are not early bound and
-            // which are not due to a generic (the callee doesn't care about generic regions)
-            if !early_regions.contains(r) && !generic_regions.contains(r) {
-                late_regions.push(*r);
-            }
-        }
-        info!("call region instantiations (late): {:?}", late_regions);
-
-        // Notes:
-        // - if two of the call regions need to be equal due to constraints on the function, we define the one
-        //   with the larger id in terms of the other one
-        // - we ignore unidirectional subset constraints between call regions (these do not help in finding a
-        //   solution if we take the transitive closure beforehand)
-        // - if a call region needs to be equal to a local region, we directly define it in terms of the local
-        //   region
-        // - otherwise, it will be an intersection of local regions
-        let mut new_regions_classification = HashMap::new();
-        // compute transitive closure of constraints
-        let relevant_constraints = polonius_info::compute_transitive_closure(relevant_constraints);
-        for r in &new_regions_sorted {
-            for (r1, r2) in &relevant_constraints {
-                if *r2 != *r {
-                    continue;
-                }
-
-                // i.e. (flipping it around when we are talking about lifetimes),
-                // r needs to be a sublft of r1
-                if relevant_constraints.contains(&(*r2, *r1)) {
-                    // if r1 is also a new region and r2 is ordered before it, we will
-                    // just define r1 in terms of r2
-                    if new_regions.contains(r1) && r2.as_u32() < r1.as_u32() {
-                        continue;
-                    }
-                    // need an equality constraint
-                    new_regions_classification.insert(*r, CallRegionKind::EqR(*r1));
-                    // do not consider the rest of the constraints as r is already
-                    // fully specified
-                    break;
-                }
-
-                // the intersection also needs to contain r1
-                if new_regions.contains(r1) {
-                    // we do not need this constraint, since we already computed the
-                    // transitive closure.
-                    continue;
-                }
-
-                let kind = new_regions_classification
-                    .entry(*r)
-                    .or_insert(CallRegionKind::Intersection(HashSet::new()));
-
-                let CallRegionKind::Intersection(s) = kind else {
-                    unreachable!();
-                };
-
-                s.insert(*r1);
-            }
-        }
-        info!("call arg classification: {:?}", new_regions_classification);
-
-        Ok(CallRegions {
-            early_regions,
-            late_regions,
-            classification: new_regions_classification,
-        })
-    }
-
-    fn translate_function_call(
-        &mut self,
-        func: &Operand<'tcx>,
-        args: &[Operand<'tcx>],
-        destination: &Place<'tcx>,
-        target: Option<middle::mir::BasicBlock>,
-        loc: Location,
-        dying_loans: &[facts::Loan],
-    ) -> Result<radium::Stmt, TranslationError<'tcx>> {
-        let startpoint = self.info.interner.get_point_index(&facts::Point {
-            location: loc,
-            typ: facts::PointType::Start,
-        });
-
-        let Operand::Constant(box func_constant) = func else {
-            return Err(TranslationError::UnsupportedFeature {
-                description: format!(
-                    "RefinedRust does currently not support this kind of call operand (got: {:?})",
-                    func
-                ),
-            });
-        };
-
-        // for lifetime annotations:
-        // 1. get the regions involved here. for that, get the instantiation of the function.
-        //    + if it's a FnDef type, that should be easy.
-        //    + for a function pointer: ?
-        //    + for a closure: ?
-        //   (Polonius does not seem to distinguish early/late bound in any way, except
-        //   that they are numbered in different passes)
-        // 2. find the constraints here involving the regions.
-        // 3. solve for the regions.
-        //    + transitively propagate the constraints
-        //    + check for equalities
-        //    + otherwise compute intersection. singleton intersection just becomes an equality def.
-        // 4. add annotations accordingly
-        //    + either a startlft
-        //    + or a copy name
-        // 5. add shortenlft annotations to line up arguments.
-        //    + for that, we need the type of the LHS, and what the argument types (with
-        //    substituted regions) should be.
-        // 6. annotate the return value on assignment and establish constraints.
-
-        let classification = self.compute_call_regions(func_constant, loc)?;
-
-        // update the inclusion tracker with the new regions we have introduced
-        // We just add the inclusions and ignore that we resolve it in a "tight" way.
-        // the cases where we need the reverse inclusion should be really rare.
-        for (r, c) in &classification.classification {
-            match c {
-                CallRegionKind::EqR(r2) => {
-                    // put it at the start point, because the inclusions come into effect
-                    // at the point right before.
-                    self.inclusion_tracker.add_static_inclusion(*r, *r2, startpoint);
-                    self.inclusion_tracker.add_static_inclusion(*r2, *r, startpoint);
-                },
-                CallRegionKind::Intersection(lfts) => {
-                    // all the regions represented by lfts need to be included in r
-                    for r2 in lfts {
-                        self.inclusion_tracker.add_static_inclusion(*r2, *r, startpoint);
-                    }
-                },
-            }
-        }
-
-        // translate the function expression.
-        let func_expr = self.translate_operand(func, false)?;
-        // We expect this to be an Expr::CallTarget, being annotated with the type parameters we
-        // instantiate it with.
-        let radium::Expr::CallTarget(func_lit, ty_param_annots, mut lft_param_annots) = func_expr else {
-            unreachable!("Logic error in call target translation");
-        };
-        let func_expr = radium::Expr::MetaParam(func_lit);
-
-        // translate the arguments
-        let mut translated_args = Vec::new();
-        for arg in args {
-            // to_ty is the type the function expects
-
-            //let ty = arg.ty(&self.proc.get_mir().local_decls, self.env.tcx());
-            let translated_arg = self.translate_operand(arg, true)?;
-            translated_args.push(translated_arg);
-        }
-
-        // We have to add the late regions, since we do not requantify over them.
-        for late in &classification.late_regions {
-            let lft = self.format_region(*late);
-            lft_param_annots.push(lft);
-        }
-        info!("Call lifetime instantiation (early): {:?}", classification.early_regions);
-        info!("Call lifetime instantiation (late): {:?}", classification.late_regions);
-
-        // Get the type of the return value from the function
-        let (_, _, _, inst_sig) = self.call_expr_op_split_inst(func_constant)?;
-        // TODO: do we need to do something with late bounds?
-        let output_ty = inst_sig.output().skip_binder();
-        info!("call has instantiated type {:?}", inst_sig);
-
-        // compute the resulting annotations
-        let (rhs_annots, pre_stmt_annots, post_stmt_annots) =
-            self.get_assignment_annots(loc, destination, output_ty);
-        info!(
-            "assignment annots after call: expr: {:?}, pre-stmt: {:?}, post-stmt: {:?}",
-            rhs_annots, pre_stmt_annots, post_stmt_annots
-        );
-
-        // TODO: add annotations for the assignment
-        // for that:
-        // - get the type of the place
-        // - enforce constraints as necessary. this might spawn dyninclusions with some of the new regions =>
-        //   In Coq, also the aliases should get proper endlft events to resolve the dyninclusions.
-        // - update the name map
-        let call_expr = radium::Expr::Call {
-            f: Box::new(func_expr),
-            lfts: lft_param_annots,
-            tys: ty_param_annots,
-            args: translated_args,
-        };
-        let stmt = match target {
-            Some(target) => {
-                let mut cont_stmt = self.translate_goto_like(&loc, target)?;
-                // end loans before the goto, but after the call.
-                // TODO: may cause duplications?
-                cont_stmt = self.prepend_endlfts(cont_stmt, dying_loans.iter().copied());
-
-                let cont_stmt = radium::Stmt::with_annotations(
-                    cont_stmt,
-                    post_stmt_annots,
-                    &Some("post_function_call".to_owned()),
-                );
-
-                // assign stmt with call; then jump to bb
-                let place_ty = self.get_type_of_place(destination);
-                let place_st = self.ty_translator.translate_type_to_syn_type(place_ty.ty)?;
-                let place_expr = self.translate_place(destination)?;
-                let ot = place_st.into();
-
-                let annotated_rhs = radium::Expr::with_optional_annotation(
-                    call_expr,
-                    rhs_annots,
-                    Some("function_call".to_owned()),
-                );
-                let assign_stmt = radium::Stmt::Assign {
-                    ot,
-                    e1: place_expr,
-                    e2: annotated_rhs,
-                    s: Box::new(cont_stmt),
-                };
-                radium::Stmt::with_annotations(
-                    assign_stmt,
-                    pre_stmt_annots,
-                    &Some("function_call".to_owned()),
-                )
-            },
-            None => {
-                // expr stmt with call; then stuck (we have not provided a continuation, after all)
-                radium::Stmt::ExprS {
-                    e: call_expr,
-                    s: Box::new(radium::Stmt::Stuck),
-                }
-            },
-        };
-
-        let mut stmt_annots = Vec::new();
-
-        // add annotations to initialize the regions for the call (before the call)
-        for (r, class) in &classification.classification {
-            let lft = self.format_region(*r);
-            match class {
-                CallRegionKind::EqR(r2) => {
-                    let lft2 = self.format_region(*r2);
-                    stmt_annots.push(radium::Annotation::CopyLftName(lft2, lft));
-                },
-
-                CallRegionKind::Intersection(rs) => {
-                    match rs.len() {
-                        0 => {
-                            return Err(TranslationError::UnsupportedFeature {
-                                description: "RefinedRust does currently not support unconstrained lifetime"
-                                    .to_owned(),
-                            });
-                        },
-                        1 => {
-                            // this is really just an equality constraint
-                            if let Some(r2) = rs.iter().next() {
-                                let lft2 = self.format_region(*r2);
-                                stmt_annots.push(radium::Annotation::CopyLftName(lft2, lft));
-                            }
-                        },
-                        _ => {
-                            // a proper intersection
-                            let lfts: Vec<_> = rs.iter().map(|r| self.format_region(*r)).collect();
-                            stmt_annots.push(radium::Annotation::AliasLftIntersection(lft, lfts));
-                        },
-                    };
-                },
-            }
-        }
-
-        let stmt = radium::Stmt::with_annotations(stmt, stmt_annots, &Some("function_call".to_owned()));
-        Ok(stmt)
-    }
-
-    /// Translate a terminator.
-    /// We pass the dying loans during this terminator. They need to be added at the right
-    /// intermediate point.
-    fn translate_terminator(
-        &mut self,
-        term: &Terminator<'tcx>,
-        loc: Location,
-        dying_loans: Vec<facts::Loan>,
-    ) -> Result<radium::Stmt, TranslationError<'tcx>> {
-        match &term.kind {
-            TerminatorKind::Goto { target } => self.translate_goto_like(&loc, *target),
-
-            TerminatorKind::Call {
-                func,
-                args,
-                destination,
-                target,
-                ..
-            } => {
-                trace!("translating Call {:?}", term);
-                if self.is_call_destination_panic(func) {
-                    info!("Replacing call to std::panicking::begin_panic with Stuck");
-                    return Ok(radium::Stmt::Stuck);
-                }
-
-                self.translate_function_call(func, args, destination, *target, loc, dying_loans.as_slice())
-            },
-
-            TerminatorKind::Return => {
-                // TODO: this requires additional handling for reborrows
-
-                // read from the return place
-                // Is this semantics accurate wrt what the intended MIR semantics is?
-                // Possibly handle this differently by making the first argument of a function a dedicated
-                // return place? See also discussion at https://github.com/rust-lang/rust/issues/71117
-                let stmt = radium::Stmt::Return(radium::Expr::Use {
-                    ot: (&self.return_synty).into(),
-                    e: Box::new(radium::Expr::Var(self.return_name.clone())),
-                });
-
-                // TODO is this right?
-                Ok(self.prepend_endlfts(stmt, dying_loans.into_iter()))
-            },
-
-            //TerminatorKind::Abort => {
-            //res_stmt = radium::Stmt::Stuck;
-            //res_stmt = self.prepend_endlfts(res_stmt, dying_loans.into_iter());
-            //},
-            TerminatorKind::SwitchInt { discr, targets } => {
-                let operand = self.translate_operand(discr, true)?;
-                let all_targets: &[BasicBlock] = targets.all_targets();
-
-                if self.get_type_of_operand(discr).is_bool() {
-                    // we currently special-case this as Caesium has a built-in if and this is more
-                    // convenient to handle for the type-checker
-
-                    // implementation detail: the first index is the `false` branch, the second the
-                    // `true` branch
-                    let true_target = all_targets[1];
-                    let false_target = all_targets[0];
-
-                    let true_branch = self.translate_goto_like(&loc, true_target)?;
-                    let false_branch = self.translate_goto_like(&loc, false_target)?;
-
-                    let stmt = radium::Stmt::If {
-                        e: operand,
-                        ot: radium::OpType::Bool,
-                        s1: Box::new(true_branch),
-                        s2: Box::new(false_branch),
-                    };
-
-                    // TODO: is this right?
-                    return Ok(self.prepend_endlfts(stmt, dying_loans.into_iter()));
-                }
-
-                //info!("switchint: {:?}", term.kind);
-                let operand = self.translate_operand(discr, true)?;
-                let ty = self.get_type_of_operand(discr);
-
-                let mut target_map: HashMap<u128, usize> = HashMap::new();
-                let mut translated_targets: Vec<radium::Stmt> = Vec::new();
-
-                for (idx, (tgt, bb)) in targets.iter().enumerate() {
-                    let bb: BasicBlock = bb;
-                    let translated_target = self.translate_goto_like(&loc, bb)?;
-
-                    target_map.insert(tgt, idx);
-                    translated_targets.push(translated_target);
-                }
-
-                let translated_default = self.translate_goto_like(&loc, targets.otherwise())?;
-                // TODO: need to put endlfts infront of gotos?
-
-                let translated_ty = self.ty_translator.translate_type(ty)?;
-                let radium::Type::Int(it) = translated_ty else {
-                    return Err(TranslationError::UnknownError(
-                        "SwitchInt switching on non-integer type".to_owned(),
-                    ));
-                };
-
-                Ok(radium::Stmt::Switch {
-                    e: operand,
-                    it,
-                    index_map: target_map,
-                    bs: translated_targets,
-                    def: Box::new(translated_default),
-                })
-            },
-
-            TerminatorKind::Assert {
-                cond,
-                expected,
-                target,
-                ..
-            } => {
-                // this translation gets stuck on failure
-                let cond_translated = self.translate_operand(cond, true)?;
-                let comp = radium::Expr::BinOp {
-                    o: radium::Binop::Eq,
-                    ot1: radium::OpType::Bool,
-                    ot2: radium::OpType::Bool,
-                    e1: Box::new(cond_translated),
-                    e2: Box::new(radium::Expr::Literal(radium::Literal::Bool(*expected))),
-                };
-
-                let stmt = self.translate_goto_like(&loc, *target)?;
-
-                // TODO: should we really have this?
-                let stmt = self.prepend_endlfts(stmt, dying_loans.into_iter());
-
-                Ok(radium::Stmt::AssertS {
-                    e: comp,
-                    s: Box::new(stmt),
-                })
-            },
-
-            TerminatorKind::Drop { place, target, .. } => {
-                let ty = self.get_type_of_place(place);
-                self.register_drop_shim_for(ty.ty);
-
-                let place_translated = self.translate_place(place)?;
-                let _drope = radium::Expr::DropE(Box::new(place_translated));
-
-                let stmt = self.translate_goto_like(&loc, *target)?;
-
-                Ok(self.prepend_endlfts(stmt, dying_loans.into_iter()))
-
-                //res_stmt = radium::Stmt::ExprS { e: drope, s: Box::new(res_stmt)};
-            },
-
-            // just a goto for our purposes
-            TerminatorKind::FalseEdge { real_target, .. }
-            // this is just a virtual edge for the borrowchecker, we can translate this to a normal goto
-            | TerminatorKind::FalseUnwind { real_target, .. } => {
-                self.translate_goto_like(&loc, *real_target)
-            },
-
-            TerminatorKind::Unreachable => Ok(radium::Stmt::Stuck),
-
-            TerminatorKind::UnwindResume => Err(TranslationError::Unimplemented {
-                description: "implement UnwindResume".to_owned(),
-            }),
-
-            TerminatorKind::UnwindTerminate(_) => Err(TranslationError::Unimplemented {
-                description: "implement UnwindTerminate".to_owned(),
-            }),
-
-            TerminatorKind::GeneratorDrop
-            | TerminatorKind::InlineAsm { .. }
-            | TerminatorKind::Yield { .. } => Err(TranslationError::UnsupportedFeature {
-                description: format!(
-                    "RefinedRust does currently not support this kind of terminator (got: {:?})",
-                    term
-                ),
-            }),
-        }
-    }
-
-    /// Prepend endlft annotations for dying loans to a statement.
-    fn prepend_endlfts<I>(&self, st: radium::Stmt, dying: I) -> radium::Stmt
-    where
-        I: ExactSizeIterator<Item = facts::Loan>,
-    {
-        let mut cont_stmt = st;
-        if dying.len() > 0 {
-            //info!("Dying at {:?}: {:?}", loc, dying);
-            for d in dying {
-                let lft = self.info.atomic_region_of_loan(d);
-                cont_stmt = radium::Stmt::Annot {
-                    a: radium::Annotation::EndLft(self.format_atomic_region(&lft)),
-                    s: Box::new(cont_stmt),
-                    why: Some("endlft".to_owned()),
-                };
-            }
-        }
-        cont_stmt
-    }
-
-    /// Get predecessors in the CFG.
-    fn get_loc_predecessors(&self, loc: Location) -> Vec<Location> {
-        if loc.statement_index > 0 {
-            let pred = Location {
-                block: loc.block,
-                statement_index: loc.statement_index - 1,
-            };
-            vec![pred]
-        } else {
-            // check for gotos that go to this basic block
-            let pred_bbs = self.proc.predecessors(loc.block);
-            let basic_blocks = &self.proc.get_mir().basic_blocks;
-            pred_bbs
-                .iter()
-                .map(|bb| {
-                    let data = &basic_blocks[*bb];
-                    Location {
-                        block: *bb,
-                        statement_index: data.statements.len(),
-                    }
-                })
-                .collect()
-        }
-    }
-
-    /// Collect all the regions appearing in a type.
-    fn find_region_variables_of_place_type(&self, ty: PlaceTy<'tcx>) -> HashSet<Region> {
-        let mut collector = TyRegionCollectFolder::new(self.env.tcx());
-        if ty.variant_index.is_some() {
-            panic!("find_region_variables_of_place_type: don't support enums");
-        }
-
-        ty.ty.fold_with(&mut collector);
-        collector.get_regions()
-    }
-
-    /// Generate an annotation on an expression needed to update the region name map.
-    fn generate_strong_update_annot(&self, ty: PlaceTy<'tcx>) -> Option<radium::Annotation> {
-        let (interesting, tree) = self.generate_strong_update_annot_rec(ty.ty);
-        interesting.then(|| radium::Annotation::GetLftNames(tree))
-    }
-
-    /// Returns a tree for giving names to Coq lifetimes based on RR types.
-    /// The boolean indicates whether the tree is "interesting", i.e. whether it names at least one
-    /// lifetime.
-    fn generate_strong_update_annot_rec(&self, ty: Ty<'tcx>) -> (bool, radium::LftNameTree) {
-        // TODO for now this just handles nested references
-        match ty.kind() {
-            ty::TyKind::Ref(r, ty, _) => match r.kind() {
-                ty::RegionKind::ReVar(r) => {
-                    let name = self.format_region(r);
-                    let (_, ty_tree) = self.generate_strong_update_annot_rec(*ty);
-                    (true, radium::LftNameTree::Ref(name, Box::new(ty_tree)))
-                },
-                _ => {
-                    panic!("generate_strong_update_annot: expected region variable");
-                },
-            },
-            _ => (false, radium::LftNameTree::Leaf),
-        }
-    }
-
-    /// Generate an annotation to adapt the type of `expr` to `target_ty` from type `current_ty` by
-    /// means of shortening lifetimes.
-    fn generate_shortenlft_annot(
-        &self,
-        target_ty: Ty<'tcx>,
-        _current_ty: Ty<'tcx>,
-        mut expr: radium::Expr,
-    ) -> radium::Expr {
-        // this is not so different from the strong update annotation
-        let (interesting, tree) = self.generate_strong_update_annot_rec(target_ty);
-        if interesting {
-            expr = radium::Expr::Annot {
-                a: radium::Annotation::ShortenLft(tree),
-                e: Box::new(expr),
-                why: None,
-            };
-        }
-        expr
-    }
-
-    /// Find all regions that need to outlive a loan region at its point of creation, and
-    /// add the corresponding constraints to the inclusion tracker.
-    fn get_outliving_regions_on_loan(&mut self, r: Region, loan_point: PointIndex) -> Vec<Region> {
-        // get all base subset constraints r' ⊆ r
-        let info = &self.info;
-        let input_facts = &info.borrowck_in_facts;
-        let mut outliving = Vec::new();
-
-        let subset_base = &input_facts.subset_base;
-        for (r1, r2, p) in subset_base {
-            if *p == loan_point && *r2 == r {
-                self.inclusion_tracker.add_static_inclusion(*r1, *r2, *p);
-                outliving.push(*r1);
-            }
-            // other subset constraints at this point are due to (for instance) the assignment of
-            // the loan to a place and are handled there.
-        }
-        outliving
-    }
-
-    /// Check if a local is used for a spec closure.
-    fn is_spec_closure_local(&self, l: Local) -> Result<Option<DefId>, TranslationError<'tcx>> {
-        // check if we should ignore this
-        let local_type = self.get_type_of_local(l)?;
-
-        let TyKind::Closure(did, _) = local_type.kind() else {
-            return Ok(None);
-        };
-
-        Ok(self
-            .procedure_registry
-            .lookup_function_mode(*did)
-            .and_then(|m| m.is_ignore().then_some(*did)))
-    }
-
-    fn region_to_region_vid(r: ty::Region<'tcx>) -> facts::Region {
-        match r.kind() {
-            ty::RegionKind::ReVar(vid) => vid,
-            _ => panic!(),
-        }
-    }
-
-    /// Generate a dynamic inclusion of r1 in r2 at point p. Prepends annotations for doing so to `cont`.
-    fn generate_dyn_inclusion(
-        &mut self,
-        stmt_annots: &mut Vec<radium::Annotation>,
-        r1: Region,
-        r2: Region,
-        p: PointIndex,
-    ) {
-        // check if inclusion already holds
-        if !self.inclusion_tracker.check_inclusion(r1, r2, p) {
-            // check if the reverse inclusion already holds
-            if self.inclusion_tracker.check_inclusion(r2, r1, p) {
-                // our invariant is that this must be a static inclusion
-                assert!(self.inclusion_tracker.check_static_inclusion(r2, r1, p));
-                self.inclusion_tracker.add_dynamic_inclusion(r1, r2, p);
-
-                // we generate an extendlft instruction
-                // for this, we need to figure out a path to make this inclusion true, i.e. we need
-                // an explanation of why it is syntactically included.
-                // TODO: for now, we just assume that r1 ⊑ₗ [r2] (in terms of Coq lifetime inclusion)
-                stmt_annots.push(radium::Annotation::ExtendLft(self.format_region(r1)));
-            } else {
-                self.inclusion_tracker.add_dynamic_inclusion(r1, r2, p);
-                // we generate a dynamic inclusion instruction
-                // we flip this around because the annotations are talking about lifetimes, which are oriented
-                // the other way around.
-                stmt_annots
-                    .push(radium::Annotation::DynIncludeLft(self.format_region(r2), self.format_region(r1)));
-            }
-        }
-    }
-
-    /// Generates dynamic inclusions for the set of inclusions in `incls`.
-    /// These inclusions should not hold yet.
-    /// Skips mutual inclusions -- we cannot interpret these.
-    fn generate_dyn_inclusions(
-        &mut self,
-        incls: &HashSet<(Region, Region, PointIndex)>,
-    ) -> Vec<radium::Annotation> {
-        // before executing the assignment, first enforce dynamic inclusions
-        info!("Generating dynamic inclusions {:?}", incls);
-        let mut stmt_annots = Vec::new();
-
-        for (r1, r2, p) in incls {
-            if incls.contains(&(*r2, *r1, *p)) {
-                warn!("Skipping impossible dynamic inclusion {:?} ⊑ {:?} at {:?}", r1, r2, p);
-                continue;
-            }
-
-            self.generate_dyn_inclusion(&mut stmt_annots, *r1, *r2, *p);
-        }
-
-        stmt_annots
-    }
-
-    /// Get the annotations due to borrows appearing on the RHS of an assignment.
-    fn get_assignment_loan_annots(&mut self, loc: Location, rhs: &Rvalue<'tcx>) -> Vec<radium::Annotation> {
-        let mut stmt_annots = Vec::new();
-
-        // if we create a new loan here, start a new lifetime for it
-        let loan_point = self.info.get_point(loc, facts::PointType::Mid);
-        if let Some(loan) = self.info.get_optional_loan_at_location(loc) {
-            // TODO: is this fine for aggregates? I suppose, if I create a loan for an
-            // aggregate, I want to use the same atomic region for all of its components
-            // anyways.
-
-            let lft = self.info.atomic_region_of_loan(loan);
-            let r = lft.get_region();
-
-            // get the static inclusions we need to generate here and add them to the
-            // inclusion tracker
-            let outliving = self.get_outliving_regions_on_loan(r, loan_point);
-
-            // add statement for issuing the loan
-            stmt_annots.insert(
-                0,
-                radium::Annotation::StartLft(
-                    self.format_atomic_region(&lft),
-                    outliving.iter().map(|r| self.format_region(*r)).collect(),
-                ),
-            );
-
-            let a = self.info.get_region_kind(r);
-            info!("Issuing loan at {:?} with kind {:?}: {:?}; outliving: {:?}", loc, a, loan, outliving);
-        } else if let Rvalue::Ref(region, BorrowKind::Shared, _) = rhs {
-            // for shared reborrows, Polonius does not create a new loan, and so the
-            // previous case did not match.
-            // However, we still need to track the region created for the reborrow in an
-            // annotation.
-
-            let region = BodyTranslator::region_to_region_vid(*region);
-
-            // find inclusion ?r1 ⊑ region -- we will actually enforce region = r1
-            let new_constrs: Vec<(facts::Region, facts::Region)> =
-                self.info.get_new_subset_constraints_at_point(loan_point);
-            info!("Shared reborrow at {:?} with new constrs: {:?}", region, new_constrs);
-            let mut included_region = None;
-            for (r1, r2) in &new_constrs {
-                if *r2 == region {
-                    included_region = Some(r1);
-                    break;
-                }
-            }
-            if let Some(r) = included_region {
-                //info!("Found inclusion {:?}⊑  {:?}", r, region);
-                stmt_annots.push(radium::Annotation::CopyLftName(
-                    self.format_region(*r),
-                    self.format_region(region),
-                ));
-
-                // also add this to the inclusion checker
-                self.inclusion_tracker.add_static_inclusion(*r, region, loan_point);
-            } else {
-                // This happens e.g. when borrowing from a raw pointer etc.
-                info!("Found unconstrained shared borrow for {:?}", region);
-                let inferred_constrained = vec![];
-
-                // add statement for issuing the loan
-                stmt_annots
-                    .push(radium::Annotation::StartLft(self.format_region(region), inferred_constrained));
-            }
-        }
-
-        stmt_annots
-    }
-
-    /// Compute the annotations for an assignment: an annotation for the rhs value, and a list of
-    /// annotations to prepend to the statement, and a list of annotations to put after the
-    /// statement.
-    fn get_assignment_annots(
-        &mut self,
-        loc: Location,
-        lhs: &Place<'tcx>,
-        _rhs_ty: Ty<'tcx>,
-    ) -> (Option<radium::Annotation>, Vec<radium::Annotation>, Vec<radium::Annotation>) {
-        // check if the place is strongly writeable
-        let strongly_writeable = !self.check_place_below_reference(lhs);
-        let plc_ty = self.get_type_of_place(lhs);
-
-        let new_dyn_inclusions;
-        let expr_annot;
-        let stmt_annot;
-        if strongly_writeable {
-            // we are going to update the region mapping through annotations,
-            // and hence put up a barrier for propagation of region constraints
-
-            // structurally go over the type and find region variables.
-            // for each of the variables, issue a barrier.
-            // also track them together with the PlaceItems needed to reach them.
-            // from the latter, we can generate the necessary annotations
-            let regions = self.find_region_variables_of_place_type(plc_ty);
-
-            // put up a barrier at the Mid point
-            let barrier_point_index = self.info.interner.get_point_index(&facts::Point {
-                location: loc,
-                typ: facts::PointType::Mid,
-            });
-            for r in &regions {
-                self.inclusion_tracker.add_barrier(*r, barrier_point_index);
-            }
-            // get new constraints that should be enforced
-            let new_constraints = self.get_assignment_strong_update_constraints(loc);
-            stmt_annot = Vec::new();
-            for (r1, r2, p) in &new_constraints {
-                self.inclusion_tracker.add_static_inclusion(*r1, *r2, *p);
-                self.inclusion_tracker.add_static_inclusion(*r2, *r1, *p);
-
-                // TODO: use this instead of the expr_annot below
-                //stmt_annot.push(
-                //radium::Annotation::CopyLftName(
-                //self.format_region(*r1),
-                //self.format_region(*r2),
-                //));
-            }
-
-            // TODO: get rid of this
-            // similarly generate an annotation that encodes these constraints in the RR
-            // type system
-            expr_annot = self.generate_strong_update_annot(plc_ty);
-            //expr_annot = None;
-
-            new_dyn_inclusions = HashSet::new();
-        } else {
-            // need to filter out the constraints that are relevant here.
-            // incrementally go through them.
-            new_dyn_inclusions = self.get_assignment_weak_update_constraints(loc);
-            expr_annot = None;
-            stmt_annot = Vec::new();
-        }
-
-        // First enforce the new inclusions, then do the other annotations
-        let new_dyn_inclusions = self.generate_dyn_inclusions(&new_dyn_inclusions);
-        (expr_annot, new_dyn_inclusions, stmt_annot)
-    }
-
-    /// Get the regions appearing in a type.
-    fn get_regions_of_ty(&self, ty: Ty<'tcx>) -> HashSet<ty::RegionVid> {
-        let mut regions = HashSet::new();
-        let mut clos = |r: ty::Region<'tcx>, _| match r.kind() {
-            ty::RegionKind::ReVar(rv) => {
-                regions.insert(rv);
-                r
-            },
-            _ => r,
-        };
-        let mut folder = ty::fold::RegionFolder::new(self.env.tcx(), &mut clos);
-        folder.fold_ty(ty);
-        regions
-    }
-
-    /// On creating a composite value (e.g. a struct or enum), the composite value gets its own
-    /// Polonius regions. We need to map these regions properly to the respective lifetimes.
-    fn get_composite_rvalue_creation_annots(
-        &mut self,
-        loc: Location,
-        rhs_ty: ty::Ty<'tcx>,
-    ) -> Vec<radium::Annotation> {
-        let info = &self.info;
-        let input_facts = &info.borrowck_in_facts;
-        let subset_base = &input_facts.subset_base;
-
-        let regions_of_ty = self.get_regions_of_ty(rhs_ty);
-
-        let mut annots = Vec::new();
-
-        // Polonius subset constraint are spawned for the midpoint
-        let midpoint = self.info.interner.get_point_index(&facts::Point {
-            location: loc,
-            typ: facts::PointType::Mid,
-        });
-
-        for (s1, s2, point) in subset_base {
-            if *point == midpoint {
-                let lft1 = self.info.mk_atomic_region(*s1);
-                let lft2 = self.info.mk_atomic_region(*s2);
-
-                // a place lifetime is included in a value lifetime
-                if lft2.is_value() && lft1.is_place() {
-                    // make sure it's not due to an assignment constraint
-                    if regions_of_ty.contains(s2) && !subset_base.contains(&(*s2, *s1, midpoint)) {
-                        // we enforce this inclusion by setting the lifetimes to be equal
-                        self.inclusion_tracker.add_static_inclusion(*s1, *s2, midpoint);
-                        self.inclusion_tracker.add_static_inclusion(*s2, *s1, midpoint);
-
-                        let annot = radium::Annotation::CopyLftName(
-                            self.format_atomic_region(&lft1),
-                            self.format_atomic_region(&lft2),
-                        );
-                        annots.push(annot);
-                    }
-                }
-            }
-        }
-        annots
-    }
-
-    /**
-     * Translate a single basic block.
-     */
-    fn translate_basic_block(
-        &mut self,
-        bb_idx: BasicBlock,
-        bb: &BasicBlockData<'tcx>,
-    ) -> Result<radium::Stmt, TranslationError<'tcx>> {
-        // we translate from back to front, starting with the terminator, since Caesium statements
-        // have a continuation (the next statement to execute)
-
-        // first do the endlfts for the things right before the terminator
-        let mut idx = bb.statements.len();
-        let loc = Location {
-            block: bb_idx,
-            statement_index: idx,
-        };
-        let dying = self.info.get_dying_loans(loc);
-        // TODO zombie?
-        let _dying_zombie = self.info.get_dying_zombie_loans(loc);
-        let mut cont_stmt: radium::Stmt = self.translate_terminator(bb.terminator(), loc, dying)?;
-
-        //cont_stmt = self.prepend_endlfts(cont_stmt, loc, dying);
-        //cont_stmt = self.prepend_endlfts(cont_stmt, loc, dying_zombie);
-
-        for stmt in bb.statements.iter().rev() {
-            idx -= 1;
-            let loc = Location {
-                block: bb_idx,
-                statement_index: idx,
-            };
-
-            // get all dying loans, and emit endlfts for these.
-            // We loop over all predecessor locations, since some loans may end at the start of a
-            // basic block (in particular related to NLL stuff)
-            let pred = self.get_loc_predecessors(loc);
-            let mut dying_loans = HashSet::new();
-            for p in pred {
-                let dying_between = self.info.get_loans_dying_between(p, loc, false);
-                for l in &dying_between {
-                    dying_loans.insert(*l);
-                }
-                // also include zombies
-                let dying_between = self.info.get_loans_dying_between(p, loc, true);
-                for l in &dying_between {
-                    dying_loans.insert(*l);
-                }
-            }
-            // we prepend them before the current statement
-
-            match &stmt.kind {
-                StatementKind::Assign(b) => {
-                    let (plc, val) = b.as_ref();
-
-                    if (self.is_spec_closure_local(plc.local)?).is_some() {
-                        info!("skipping assignment to spec closure local: {:?}", plc);
-                    } else if let Some(rewritten_ty) = self.checked_op_temporaries.get(&plc.local) {
-                        // if this is a checked op, be sure to remember it
-                        info!("rewriting assignment to checked op: {:?}", plc);
-
-                        let synty = self.ty_translator.translate_type_to_syn_type(*rewritten_ty)?;
-
-                        let translated_val = self.translate_rvalue(loc, val)?;
-                        let translated_place = self.translate_place(plc)?;
-
-                        // this should be a temporary
-                        assert!(plc.projection.is_empty());
-
-                        let ot = synty.into();
-                        cont_stmt = radium::Stmt::Assign {
-                            ot,
-                            e1: translated_place,
-                            e2: translated_val,
-                            s: Box::new(cont_stmt),
-                        };
-                    } else {
-                        let plc_ty = self.get_type_of_place(plc);
-                        let rhs_ty = val.ty(&self.proc.get_mir().local_decls, self.env.tcx());
-
-                        let borrow_annots = self.get_assignment_loan_annots(loc, val);
-                        let (expr_annot, pre_stmt_annots, post_stmt_annots) =
-                            self.get_assignment_annots(loc, plc, rhs_ty);
-
-                        // TODO; maybe move this to rvalue
-                        let composite_annots = self.get_composite_rvalue_creation_annots(loc, rhs_ty);
-
-                        cont_stmt = radium::Stmt::with_annotations(
-                            cont_stmt,
-                            post_stmt_annots,
-                            &Some("post-assignment".to_owned()),
-                        );
-
-                        let translated_val = radium::Expr::with_optional_annotation(
-                            self.translate_rvalue(loc, val)?,
-                            expr_annot,
-                            Some("assignment".to_owned()),
-                        );
-                        let translated_place = self.translate_place(plc)?;
-                        let synty = self.ty_translator.translate_type_to_syn_type(plc_ty.ty)?;
-                        cont_stmt = radium::Stmt::Assign {
-                            ot: synty.into(),
-                            e1: translated_place,
-                            e2: translated_val,
-                            s: Box::new(cont_stmt),
-                        };
-                        cont_stmt = radium::Stmt::with_annotations(
-                            cont_stmt,
-                            pre_stmt_annots,
-                            &Some("assignment".to_owned()),
-                        );
-                        cont_stmt = radium::Stmt::with_annotations(
-                            cont_stmt,
-                            borrow_annots,
-                            &Some("borrow".to_owned()),
-                        );
-                        cont_stmt = radium::Stmt::with_annotations(
-                            cont_stmt,
-                            composite_annots,
-                            &Some("composite".to_owned()),
-                        );
-                    }
-                },
-
-                StatementKind::Deinit(_) => {
-                    // TODO: find out where this is emitted
-                    return Err(TranslationError::UnsupportedFeature {
-                        description: "RefinedRust does currently not support Deinit".to_owned(),
-                    });
-                },
-
-                StatementKind::FakeRead(b) => {
-                    // we can probably ignore this, but I'm not sure
-                    info!("Ignoring FakeRead: {:?}", b);
-                },
-
-                StatementKind::Intrinsic(intrinsic) => {
-                    match intrinsic.as_ref() {
-                        NonDivergingIntrinsic::Assume(_) => {
-                            // ignore
-                            info!("Ignoring Assume: {:?}", intrinsic);
-                        },
-                        NonDivergingIntrinsic::CopyNonOverlapping(_) => {
-                            return Err(TranslationError::UnsupportedFeature {
-                                description: "RefinedRust does currently not support the CopyNonOverlapping Intrinsic".to_owned(),
-                            });
-                        },
-                    }
-                },
-
-                StatementKind::PlaceMention(place) => {
-                    // TODO: this is missed UB
-                    info!("Ignoring PlaceMention: {:?}", place);
-                },
-
-                StatementKind::SetDiscriminant {
-                    place: _place,
-                    variant_index: _variant_index,
-                } => {
-                    // TODO
-                    return Err(TranslationError::UnsupportedFeature {
-                        description: "RefinedRust does currently not support SetDiscriminant".to_owned(),
-                    });
-                },
-
-                // don't need that info
-                | StatementKind::AscribeUserType(_, _)
-                // don't need that
-                | StatementKind::Coverage(_)
-                // no-op
-                | StatementKind::ConstEvalCounter
-                // ignore
-                | StatementKind::Nop
-                // just ignore
-                | StatementKind::StorageLive(_)
-                // just ignore
-                | StatementKind::StorageDead(_)
-                // just ignore retags
-                | StatementKind::Retag(_, _) => (),
-            }
-
-            cont_stmt = self.prepend_endlfts(cont_stmt, dying_loans.into_iter());
-        }
-
-        Ok(cont_stmt)
-    }
-
-    /// Translate a `BorrowKind`.
-    fn translate_borrow_kind(kind: BorrowKind) -> Result<radium::BorKind, TranslationError<'tcx>> {
-        match kind {
-            BorrowKind::Shared => Ok(radium::BorKind::Shared),
-            BorrowKind::Shallow => {
-                // TODO: figure out what to do with this
-                // arises in match lowering
-                Err(TranslationError::UnsupportedFeature {
-                    description: "RefinedRust does currently not support shallow borrows".to_owned(),
-                })
-            },
-            BorrowKind::Mut { .. } => {
-                // TODO: handle two-phase borrows?
-                Ok(radium::BorKind::Mutable)
-            },
-        }
-    }
-
-    const fn translate_mutability(mt: Mutability) -> radium::Mutability {
-        match mt {
-            Mutability::Mut => radium::Mutability::Mut,
-            Mutability::Not => radium::Mutability::Shared,
-        }
-    }
-
-    /// Get the inner type of a type to which we can apply the offset operator.
-    fn get_offset_ty(ty: Ty<'tcx>) -> Result<Ty<'tcx>, TranslationError<'tcx>> {
-        match ty.kind() {
-            TyKind::Array(t, _) | TyKind::Slice(t) | TyKind::Ref(_, t, _) => Ok(*t),
-            TyKind::RawPtr(tm) => Ok(tm.ty),
-            _ => Err(TranslationError::UnknownError(format!("cannot take offset of {}", ty))),
-        }
-    }
-
-    /// Translate binary operators.
-    /// We need access to the operands, too, to handle the offset operator and get the right
-    /// Caesium layout annotation.
-    fn translate_binop(
-        &self,
-        op: BinOp,
-        e1: &Operand<'tcx>,
-        _e2: &Operand<'tcx>,
-    ) -> Result<radium::Binop, TranslationError<'tcx>> {
-        match op {
-            BinOp::Add | BinOp::AddUnchecked => Ok(radium::Binop::Add),
-            BinOp::Sub | BinOp::SubUnchecked => Ok(radium::Binop::Sub),
-            BinOp::Mul | BinOp::MulUnchecked => Ok(radium::Binop::Mul),
-            BinOp::Div => Ok(radium::Binop::Div),
-            BinOp::Rem => Ok(radium::Binop::Mod),
-
-            BinOp::BitXor => Ok(radium::Binop::BitXor),
-            BinOp::BitAnd => Ok(radium::Binop::BitAnd),
-            BinOp::BitOr => Ok(radium::Binop::BitOr),
-            BinOp::Shl | BinOp::ShlUnchecked => Ok(radium::Binop::Shl),
-            BinOp::Shr | BinOp::ShrUnchecked => Ok(radium::Binop::Shr),
-
-            BinOp::Eq => Ok(radium::Binop::Eq),
-            BinOp::Lt => Ok(radium::Binop::Lt),
-            BinOp::Le => Ok(radium::Binop::Le),
-            BinOp::Ne => Ok(radium::Binop::Ne),
-            BinOp::Ge => Ok(radium::Binop::Ge),
-            BinOp::Gt => Ok(radium::Binop::Gt),
-
-            BinOp::Offset => {
-                // we need to get the layout of the thing we're offsetting
-                // try to get the type of e1.
-                let e1_ty = self.get_type_of_operand(e1);
-                let off_ty = BodyTranslator::get_offset_ty(e1_ty)?;
-                let st = self.ty_translator.translate_type_to_syn_type(off_ty)?;
-                let ly = st.into();
-                Ok(radium::Binop::PtrOffset(ly))
-            },
-        }
-    }
-
-    /// Translate checked binary operators.
-    /// We need access to the operands, too, to handle the offset operator and get the right
-    /// Caesium layout annotation.
-    fn translate_checked_binop(op: BinOp) -> Result<radium::Binop, TranslationError<'tcx>> {
-        match op {
-            BinOp::Add => Ok(radium::Binop::CheckedAdd),
-            BinOp::Sub => Ok(radium::Binop::CheckedSub),
-            BinOp::Mul => Ok(radium::Binop::CheckedMul),
-            BinOp::Shl => Err(TranslationError::UnsupportedFeature {
-                description: "RefinedRust does currently not support checked Shl".to_owned(),
-            }),
-            BinOp::Shr => Err(TranslationError::UnsupportedFeature {
-                description: "RefinedRust does currently not support checked Shr".to_owned(),
-            }),
-            _ => Err(TranslationError::UnknownError(
-                "unexpected checked binop that is not Add, Sub, Mul, Shl, or Shr".to_owned(),
-            )),
-        }
-    }
-
-    /// Translate unary operators.
-    fn translate_unop(op: UnOp, ty: Ty<'tcx>) -> Result<radium::Unop, TranslationError<'tcx>> {
-        match op {
-            UnOp::Not => match ty.kind() {
-                ty::TyKind::Bool => Ok(radium::Unop::NotBool),
-                ty::TyKind::Int(_) | ty::TyKind::Uint(_) => Ok(radium::Unop::NotInt),
-                _ => Err(TranslationError::UnknownError(
-                    "application of UnOp::Not to non-{Int, Bool}".to_owned(),
-                )),
-            },
-            UnOp::Neg => Ok(radium::Unop::Neg),
-        }
-    }
-
-    /// Get the type to annotate a borrow with.
-    fn get_type_annotation_for_borrow(
-        &self,
-        bk: BorrowKind,
-        pl: &Place<'tcx>,
-    ) -> Result<Option<radium::RustType>, TranslationError<'tcx>> {
-        let BorrowKind::Mut { .. } = bk else {
-            return Ok(None);
-        };
-
-        let ty = self.get_type_of_place(pl);
-
-        // For borrows, we can safely ignore the downcast type -- we cannot borrow a particularly variant
-        let translated_ty = self.ty_translator.translate_type(ty.ty)?;
-        let annot_ty = radium::RustType::of_type(&translated_ty);
-
-        Ok(Some(annot_ty))
-    }
-
-    /// Translates an Rvalue.
-    fn translate_rvalue(
-        &mut self,
-        loc: Location,
-        rval: &Rvalue<'tcx>,
-    ) -> Result<radium::Expr, TranslationError<'tcx>> {
-        match rval {
-            Rvalue::Use(op) => {
-                // converts an lvalue to an rvalue
-                self.translate_operand(op, true)
-            },
-
-            Rvalue::Ref(region, bk, pl) => {
-                let translated_pl = self.translate_place(pl)?;
-                let translated_bk = BodyTranslator::translate_borrow_kind(*bk)?;
-                let ty_annot = self.get_type_annotation_for_borrow(*bk, pl)?;
-
-                if let Some(loan) = self.info.get_optional_loan_at_location(loc) {
-                    let atomic_region = self.info.atomic_region_of_loan(loan);
-                    let lft = self.format_atomic_region(&atomic_region);
-                    Ok(radium::Expr::Borrow {
-                        lft,
-                        bk: translated_bk,
-                        ty: ty_annot,
-                        e: Box::new(translated_pl),
-                    })
-                } else {
-                    info!("Didn't find loan at {:?}: {:?}; region {:?}", loc, rval, region);
-                    let region = BodyTranslator::region_to_region_vid(*region);
-                    let lft = self.format_region(region);
-
-                    Ok(radium::Expr::Borrow {
-                        lft,
-                        bk: translated_bk,
-                        ty: ty_annot,
-                        e: Box::new(translated_pl),
-                    })
-                }
-            },
-
-            Rvalue::AddressOf(mt, pl) => {
-                let translated_pl = self.translate_place(pl)?;
-                let translated_mt = BodyTranslator::translate_mutability(*mt);
-
-                Ok(radium::Expr::AddressOf {
-                    mt: translated_mt,
-                    e: Box::new(translated_pl),
-                })
-            },
-
-            Rvalue::BinaryOp(op, operands) => {
-                let e1 = &operands.as_ref().0;
-                let e2 = &operands.as_ref().1;
-
-                let e1_ty = self.get_type_of_operand(e1);
-                let e2_ty = self.get_type_of_operand(e2);
-                let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?;
-                let e2_st = self.ty_translator.translate_type_to_syn_type(e2_ty)?;
-
-                let translated_e1 = self.translate_operand(e1, true)?;
-                let translated_e2 = self.translate_operand(e2, true)?;
-                let translated_op = self.translate_binop(*op, &operands.as_ref().0, &operands.as_ref().1)?;
-
-                Ok(radium::Expr::BinOp {
-                    o: translated_op,
-                    ot1: e1_st.into(),
-                    ot2: e2_st.into(),
-                    e1: Box::new(translated_e1),
-                    e2: Box::new(translated_e2),
-                })
-            },
-
-            Rvalue::CheckedBinaryOp(op, operands) => {
-                let e1 = &operands.as_ref().0;
-                let e2 = &operands.as_ref().1;
-
-                let e1_ty = self.get_type_of_operand(e1);
-                let e2_ty = self.get_type_of_operand(e2);
-                let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?;
-                let e2_st = self.ty_translator.translate_type_to_syn_type(e2_ty)?;
-
-                let translated_e1 = self.translate_operand(e1, true)?;
-                let translated_e2 = self.translate_operand(e2, true)?;
-                let translated_op = BodyTranslator::translate_checked_binop(*op)?;
-
-                Ok(radium::Expr::BinOp {
-                    o: translated_op,
-                    ot1: e1_st.into(),
-                    ot2: e2_st.into(),
-                    e1: Box::new(translated_e1),
-                    e2: Box::new(translated_e2),
-                })
-            },
-
-            Rvalue::UnaryOp(op, operand) => {
-                let translated_e1 = self.translate_operand(operand, true)?;
-                let e1_ty = self.get_type_of_operand(operand);
-                let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?;
-                let translated_op = BodyTranslator::translate_unop(*op, e1_ty)?;
-
-                Ok(radium::Expr::UnOp {
-                    o: translated_op,
-                    ot: e1_st.into(),
-                    e: Box::new(translated_e1),
-                })
-            },
-
-            Rvalue::NullaryOp(op, _ty) => {
-                // TODO: SizeOf
-                Err(TranslationError::UnsupportedFeature {
-                    description: "RefinedRust does currently not support nullary ops (AlignOf, Sizeof)"
-                        .to_owned(),
-                })
-            },
-
-            Rvalue::Discriminant(pl) => {
-                let ty = self.get_type_of_place(pl);
-                let translated_pl = self.translate_place(pl)?;
-                info!("getting discriminant of {:?} at type {:?}", pl, ty);
-
-                let ty::TyKind::Adt(adt_def, args) = ty.ty.kind() else {
-                    return Err(TranslationError::UnsupportedFeature {
-                        description: format!(
-                            "RefinedRust does currently not support discriminant accesses on non-enum types ({:?}, got {:?})",
-                            rval, ty.ty
-                        ),
-                    });
-                };
-
-                let enum_use = self.ty_translator.generate_enum_use(*adt_def, args.iter())?;
-                let els = enum_use.generate_raw_syn_type_term();
-
-                let discriminant_acc = radium::Expr::EnumDiscriminant {
-                    els: els.to_string(),
-                    e: Box::new(translated_pl),
-                };
-
-                // need to do a load from this place
-                let it = ty.ty.discriminant_ty(self.env.tcx());
-                let translated_it = self.ty_translator.translate_type(it)?;
-
-                let radium::Type::Int(translated_it) = translated_it else {
-                    return Err(TranslationError::UnknownError(format!(
-                        "type of discriminant is not an integer type {:?}",
-                        it
-                    )));
-                };
-
-                let ot = radium::OpType::Int(translated_it);
-
-                Ok(radium::Expr::Use {
-                    ot,
-                    e: Box::new(discriminant_acc),
-                })
-            },
-
-            Rvalue::Aggregate(kind, op) => {
-                // translate operands
-                let mut translated_ops: Vec<radium::Expr> = Vec::new();
-                let mut operand_types: Vec<Ty<'tcx>> = Vec::new();
-
-                for o in op {
-                    let translated_o = self.translate_operand(o, true)?;
-                    let type_of_o = self.get_type_of_operand(o);
-                    translated_ops.push(translated_o);
-                    operand_types.push(type_of_o);
-                }
-
-                match *kind {
-                    box mir::AggregateKind::Tuple => {
-                        if operand_types.is_empty() {
-                            // translate to unit literal
-                            return Ok(radium::Expr::Literal(radium::Literal::ZST));
-                        }
-
-                        let struct_use =
-                            self.ty_translator.generate_tuple_use(operand_types.iter().copied())?;
-                        let sl = struct_use.generate_raw_syn_type_term();
-                        let initializers: Vec<_> =
-                            translated_ops.into_iter().enumerate().map(|(i, o)| (i.to_string(), o)).collect();
-
-                        Ok(radium::Expr::StructInitE {
-                            sls: coq::term::App::new_lhs(sl.to_string()),
-                            components: initializers,
-                        })
-                    },
-
-                    box mir::AggregateKind::Adt(did, variant, args, ..) => {
-                        // get the adt def
-                        let adt_def: ty::AdtDef<'tcx> = self.env.tcx().adt_def(did);
-
-                        if adt_def.is_struct() {
-                            let variant = adt_def.variant(variant);
-                            let struct_use = self.ty_translator.generate_struct_use(variant.def_id, args)?;
-
-                            let Some(struct_use) = struct_use else {
-                                // if not, it's replaced by unit
-                                return Ok(radium::Expr::Literal(radium::Literal::ZST));
-                            };
-
-                            let sl = struct_use.generate_raw_syn_type_term();
-                            let initializers: Vec<_> = translated_ops
-                                .into_iter()
-                                .zip(variant.fields.iter())
-                                .map(|(o, field)| (field.name.to_string(), o))
-                                .collect();
-
-                            return Ok(radium::Expr::StructInitE {
-                                sls: coq::term::App::new_lhs(sl.to_string()),
-                                components: initializers,
-                            });
-                        }
-
-                        if adt_def.is_enum() {
-                            let variant_def = adt_def.variant(variant);
-
-                            let struct_use =
-                                self.ty_translator.generate_enum_variant_use(variant_def.def_id, args)?;
-                            let sl = struct_use.generate_raw_syn_type_term();
-
-                            let initializers: Vec<_> = translated_ops
-                                .into_iter()
-                                .zip(variant_def.fields.iter())
-                                .map(|(o, field)| (field.name.to_string(), o))
-                                .collect();
-
-                            let variant_e = radium::Expr::StructInitE {
-                                sls: coq::term::App::new_lhs(sl.to_string()),
-                                components: initializers,
-                            };
-
-                            let enum_use = self.ty_translator.generate_enum_use(adt_def, args)?;
-                            let els = enum_use.generate_raw_syn_type_term();
-
-                            info!("generating enum annotation for type {:?}", enum_use);
-                            let ty = radium::RustType::of_type(&radium::Type::Literal(enum_use));
-                            let variant_name = variant_def.name.to_string();
-
-                            return Ok(radium::Expr::EnumInitE {
-                                els: coq::term::App::new_lhs(els.to_string()),
-                                variant: variant_name,
-                                ty,
-                                initializer: Box::new(variant_e),
-                            });
-                        }
-
-                        // TODO
-                        Err(TranslationError::UnsupportedFeature {
-                            description: format!(
-                                "RefinedRust does currently not support aggregate rvalue for other ADTs (got: {:?})",
-                                rval
-                            ),
-                        })
-                    },
-                    box mir::AggregateKind::Closure(def, _args) => {
-                        trace!("Translating Closure aggregate value for {:?}", def);
-
-                        // We basically translate this to a tuple
-                        if operand_types.is_empty() {
-                            // translate to unit literal
-                            return Ok(radium::Expr::Literal(radium::Literal::ZST));
-                        }
-
-                        let struct_use =
-                            self.ty_translator.generate_tuple_use(operand_types.iter().copied())?;
-                        let sl = struct_use.generate_raw_syn_type_term();
-
-                        let initializers: Vec<_> =
-                            translated_ops.into_iter().enumerate().map(|(i, o)| (i.to_string(), o)).collect();
-
-                        Ok(radium::Expr::StructInitE {
-                            sls: coq::term::App::new_lhs(sl.to_string()),
-                            components: initializers,
-                        })
-                    },
-
-                    _ => {
-                        // TODO
-                        Err(TranslationError::UnsupportedFeature {
-                            description: format!(
-                                "RefinedRust does currently not support this kind of aggregate rvalue (got: {:?})",
-                                rval
-                            ),
-                        })
-                    },
-                }
-            },
-
-            Rvalue::Cast(kind, op, to_ty) => {
-                let op_ty = self.get_type_of_operand(op);
-                let op_st = self.ty_translator.translate_type_to_syn_type(op_ty)?;
-                let op_ot = op_st.into();
-
-                let translated_op = self.translate_operand(op, true)?;
-
-                let target_st = self.ty_translator.translate_type_to_syn_type(*to_ty)?;
-                let target_ot = target_st.into();
-
-                match kind {
-                    mir::CastKind::PointerCoercion(x) => {
-                        match x {
-                            ty::adjustment::PointerCoercion::MutToConstPointer => {
-                                // this is a NOP in our model
-                                Ok(translated_op)
-                            },
-
-                            ty::adjustment::PointerCoercion::ArrayToPointer
-                            | ty::adjustment::PointerCoercion::ClosureFnPointer(_)
-                            | ty::adjustment::PointerCoercion::ReifyFnPointer
-                            | ty::adjustment::PointerCoercion::UnsafeFnPointer
-                            | ty::adjustment::PointerCoercion::Unsize => {
-                                Err(TranslationError::UnsupportedFeature {
-                                    description: format!(
-                                        "RefinedRust does currently not support this kind of pointer coercion (got: {:?})",
-                                        rval
-                                    ),
-                                })
-                            },
-                        }
-                    },
-
-                    mir::CastKind::DynStar => Err(TranslationError::UnsupportedFeature {
-                        description: "RefinedRust does currently not support dyn* cast".to_owned(),
-                    }),
-
-                    mir::CastKind::IntToInt => {
-                        // Cast integer to integer
-                        Ok(radium::Expr::UnOp {
-                            o: radium::Unop::Cast(target_ot),
-                            ot: op_ot,
-                            e: Box::new(translated_op),
-                        })
-                    },
-
-                    mir::CastKind::IntToFloat => Err(TranslationError::UnsupportedFeature {
-                        description: "RefinedRust does currently not support int-to-float cast".to_owned(),
-                    }),
-
-                    mir::CastKind::FloatToInt => Err(TranslationError::UnsupportedFeature {
-                        description: "RefinedRust does currently not support float-to-int cast".to_owned(),
-                    }),
-
-                    mir::CastKind::FloatToFloat => Err(TranslationError::UnsupportedFeature {
-                        description: "RefinedRust does currently not support float-to-float cast".to_owned(),
-                    }),
-
-                    mir::CastKind::PtrToPtr => {
-                        match (op_ty.kind(), to_ty.kind()) {
-                            (TyKind::RawPtr(_), TyKind::RawPtr(_)) => {
-                                // Casts between raw pointers are NOPs for us
-                                Ok(translated_op)
-                            },
-
-                            _ => {
-                                // TODO: any other cases we should handle?
-                                Err(TranslationError::UnsupportedFeature {
-                                    description: format!(
-                                        "RefinedRust does currently not support ptr-to-ptr cast (got: {:?})",
-                                        rval
-                                    ),
-                                })
-                            },
-                        }
-                    },
-
-                    mir::CastKind::FnPtrToPtr => Err(TranslationError::UnsupportedFeature {
-                        description: format!(
-                            "RefinedRust does currently not support fnptr-to-ptr cast (got: {:?})",
-                            rval
-                        ),
-                    }),
-
-                    mir::CastKind::Transmute => Err(TranslationError::UnsupportedFeature {
-                        description: format!(
-                            "RefinedRust does currently not support transmute cast (got: {:?})",
-                            rval
-                        ),
-                    }),
-
-                    mir::CastKind::PointerExposeAddress => {
-                        // Cast pointer to integer
-                        Ok(radium::Expr::UnOp {
-                            o: radium::Unop::Cast(target_ot),
-                            ot: radium::OpType::Ptr,
-                            e: Box::new(translated_op),
-                        })
-                    },
-
-                    mir::CastKind::PointerFromExposedAddress => Err(TranslationError::UnsupportedFeature {
-                        description: format!(
-                            "RefinedRust does currently not support this kind of cast (got: {:?})",
-                            rval
-                        ),
-                    }),
-                }
-            },
-
-            Rvalue::CopyForDeref(_)
-            | Rvalue::Len(..)
-            | Rvalue::Repeat(..)
-            | Rvalue::ThreadLocalRef(..)
-            | Rvalue::ShallowInitBox(_, _) => Err(TranslationError::UnsupportedFeature {
-                description: format!(
-                    "RefinedRust does currently not support this kind of rvalue (got: {:?})",
-                    rval
-                ),
-            }),
-        }
-    }
-
-    /// Make a trivial place accessing `local`.
-    fn make_local_place(&self, local: Local) -> Place<'tcx> {
-        Place {
-            local,
-            projection: self.env.tcx().mk_place_elems(&[]),
-        }
-    }
-
-    /// Translate an operand.
-    /// This will either generate an lvalue (in case of Move or Copy) or an rvalue (in most cases
-    /// of Constant). How this is used depends on the context. (e.g., Use of an integer constant
-    /// does not typecheck, and produces a stuck program).
-    fn translate_operand(
-        &mut self,
-        op: &Operand<'tcx>,
-        to_rvalue: bool,
-    ) -> Result<radium::Expr, TranslationError<'tcx>> {
-        match op {
-            // In Caesium: typed_place needs deref (not use) for place accesses.
-            // use is used top-level to convert an lvalue to an rvalue, which is why we use it here.
-            Operand::Copy(place) | Operand::Move(place) => {
-                // check if this goes to a temporary of a checked op
-                let place_kind = if self.checked_op_temporaries.contains_key(&place.local) {
-                    assert!(place.projection.len() == 1);
-
-                    let ProjectionElem::Field(f, _0) = place.projection[0] else {
-                        unreachable!("invariant violation for access to checked op temporary");
-                    };
-
-                    if f.index() != 0 {
-                        // make this a constant false -- our semantics directly checks for overflows
-                        // and otherwise throws UB.
-                        return Ok(radium::Expr::Literal(radium::Literal::Bool(false)));
-                    }
-
-                    // access to the result of the op
-                    self.make_local_place(place.local)
-                } else {
-                    *place
-                };
-
-                let translated_place = self.translate_place(&place_kind)?;
-                let ty = self.get_type_of_place(place);
-
-                let st = self.ty_translator.translate_type_to_syn_type(ty.ty)?;
-
-                if to_rvalue {
-                    Ok(radium::Expr::Use {
-                        ot: st.into(),
-                        e: Box::new(translated_place),
-                    })
-                } else {
-                    Ok(translated_place)
-                }
-            },
-            Operand::Constant(constant) => {
-                // TODO: possibly need different handling of the rvalue flag
-                // when this also handles string literals etc.
-                return self.translate_constant(constant.as_ref());
-            },
-        }
-    }
-
-    /// Resolve the trait requirements of a function call.
-    /// The target of the call, [did], should have been resolved as much as possible,
-    /// as the requirements of a call can be different depending on which impl we consider.
-    fn resolve_trait_requirements_of_call(
-        &self,
-        did: DefId,
-        params: ty::GenericArgsRef<'tcx>,
-    ) -> Result<Vec<radium::TraitReqInst<'def, Ty<'tcx>>>, TranslationError<'tcx>> {
-        let mut scope = self.ty_translator.scope.borrow_mut();
-        let mut state = TranslationStateInner::InFunction(&mut scope);
-        self.trait_registry.resolve_trait_requirements_in_state(&mut state, did, params)
-    }
-
-    /// Translate the use of an `FnDef`, registering that the current function needs to link against
-    /// a particular monomorphization of the used function.
-    /// Is guaranteed to return a `radium::Expr::CallTarget` with the parameter instantiation of
-    /// this function annotated.
-    fn translate_fn_def_use(&mut self, ty: Ty<'tcx>) -> Result<radium::Expr, TranslationError<'tcx>> {
-        let TyKind::FnDef(defid, params) = ty.kind() else {
-            return Err(TranslationError::UnknownError("not a FnDef type".to_owned()));
-        };
-
-        let current_param_env: ty::ParamEnv<'tcx> = self.env.tcx().param_env(self.proc.get_id());
-
-        // Check whether we are calling into a trait method.
-        // This works since we did not resolve concrete instances, so this is always an abstract
-        // reference to the trait.
-        let calling_trait = self.env.tcx().trait_of_item(*defid);
-
-        // Check whether we are calling a plain function or a trait method
-        let Some(calling_trait) = calling_trait else {
-            // resolve the trait requirements
-            let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?;
-
-            // track that we are using this function and generate the Coq location name
-            let (code_param_name, ty_hint, lft_hint) =
-                self.register_use_procedure(*defid, vec![], params, trait_spec_terms)?;
-
-            let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect();
-
-            return Ok(radium::Expr::CallTarget(code_param_name, ty_hint, lft_hint));
-        };
-
-        // Otherwise, we are calling a trait method
-        // Resolve the trait instance using trait selection
-        let Some((resolved_did, resolved_params, kind)) =
-            traits::resolve_assoc_item(self.env.tcx(), current_param_env, *defid, params)
-        else {
-            return Err(TranslationError::TraitResolution(format!("Could not resolve trait {:?}", defid)));
-        };
-
-        info!(
-            "Resolved trait method {:?} as {:?} with substs {:?} and kind {:?}",
-            defid, resolved_did, resolved_params, kind
-        );
-
-        match kind {
-            traits::TraitResolutionKind::UserDefined => {
-                // We can statically resolve the particular trait instance,
-                // but need to apply the spec to the instance's spec attributes
-
-                // resolve the trait requirements
-                let trait_spec_terms =
-                    self.resolve_trait_requirements_of_call(resolved_did, resolved_params)?;
-
-                let (param_name, ty_hint, lft_hint) =
-                    self.register_use_procedure(resolved_did, vec![], resolved_params, trait_spec_terms)?;
-                let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect();
-
-                Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint))
-            },
-
-            traits::TraitResolutionKind::Param => {
-                // In this case, we have already applied it to the spec attribute
-
-                // resolve the trait requirements
-                let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?;
-
-                let (param_name, ty_hint, lft_hint) =
-                    self.register_use_trait_method(resolved_did, resolved_params, trait_spec_terms)?;
-                let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect();
-
-                Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint))
-            },
-
-            traits::TraitResolutionKind::Closure => {
-                // TODO: here, we should first generate an instance of the trait
-                //let body = self.env.tcx().instance_mir(middle::ty::InstanceDef::Item(resolved_did));
-                //let body = self.env.tcx().instance_mir(middle::ty::InstanceDef::FnPtrShim(*defid, ty));
-                //info!("closure body: {:?}", body);
-
-                //FunctionTranslator::dump_body(body);
-
-                //let res_result = ty::Instance::resolve(self.env.tcx(), callee_param_env, *defid, params);
-                //info!("Resolution {:?}", res_result);
-
-                // the args are just the closure args. We can ignore them.
-                let _clos_args = resolved_params.as_closure();
-
-                // resolve the trait requirements
-                let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?;
-
-                let (param_name, ty_hint, lft_hint) =
-                    self.register_use_procedure(resolved_did, vec![], ty::List::empty(), trait_spec_terms)?;
-                let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect();
-
-                Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint))
-            },
-        }
-    }
-
-    /// Translate a scalar at a specific type to a `radium::Expr`.
-    // TODO: Use `TryFrom` instead
-    fn translate_scalar(
-        &mut self,
-        sc: &Scalar,
-        ty: Ty<'tcx>,
-    ) -> Result<radium::Expr, TranslationError<'tcx>> {
-        // TODO: Use `TryFrom` instead
-        fn translate_literal<'tcx, T, U>(
-            sc: Result<T, U>,
-            fptr: fn(T) -> radium::Literal,
-        ) -> Result<radium::Expr, TranslationError<'tcx>> {
-            sc.map_or(Err(TranslationError::InvalidLayout), |lit| Ok(radium::Expr::Literal(fptr(lit))))
-        }
-
-        match ty.kind() {
-            TyKind::Bool => translate_literal(sc.to_bool(), radium::Literal::Bool),
-
-            TyKind::Int(it) => match it {
-                ty::IntTy::I8 => translate_literal(sc.to_i8(), radium::Literal::I8),
-                ty::IntTy::I16 => translate_literal(sc.to_i16(), radium::Literal::I16),
-                ty::IntTy::I32 => translate_literal(sc.to_i32(), radium::Literal::I32),
-                ty::IntTy::I128 => translate_literal(sc.to_i128(), radium::Literal::I128),
-
-                // For Radium, the pointer size is 8 bytes
-                ty::IntTy::I64 | ty::IntTy::Isize => translate_literal(sc.to_i64(), radium::Literal::I64),
-            },
-
-            TyKind::Uint(it) => match it {
-                ty::UintTy::U8 => translate_literal(sc.to_u8(), radium::Literal::U8),
-                ty::UintTy::U16 => translate_literal(sc.to_u16(), radium::Literal::U16),
-                ty::UintTy::U32 => translate_literal(sc.to_u32(), radium::Literal::U32),
-                ty::UintTy::U128 => translate_literal(sc.to_u128(), radium::Literal::U128),
-
-                // For Radium, the pointer is 8 bytes
-                ty::UintTy::U64 | ty::UintTy::Usize => translate_literal(sc.to_u64(), radium::Literal::U64),
-            },
-
-            TyKind::Char => translate_literal(sc.to_char(), radium::Literal::Char),
-
-            TyKind::FnDef(_, _) => self.translate_fn_def_use(ty),
-
-            TyKind::Tuple(tys) => {
-                if tys.is_empty() {
-                    return Ok(radium::Expr::Literal(radium::Literal::ZST));
-                }
-
-                Err(TranslationError::UnsupportedFeature {
-                    description: format!(
-                        "RefinedRust does currently not support compound construction of tuples using literals (got: {:?})",
-                        ty
-                    ),
-                })
-            },
-
-            TyKind::Ref(_, _, _) => match sc {
-                Scalar::Int(_) => unreachable!(),
-
-                Scalar::Ptr(pointer, _) => {
-                    let glob_alloc = self.env.tcx().global_alloc(pointer.provenance);
-                    match glob_alloc {
-                        middle::mir::interpret::GlobalAlloc::Static(did) => {
-                            info!(
-                                "Found static GlobalAlloc {:?} for Ref scalar {:?} at type {:?}",
-                                did, sc, ty
-                            );
-
-                            let Some(s) = self.const_registry.statics.get(&did) else {
-                                return Err(TranslationError::UnknownError(format!(
-                                    "Did not find a registered static for GlobalAlloc {:?} for scalar {:?} at type {:?}; registered: {:?}",
-                                    glob_alloc, sc, ty, self.const_registry.statics
-                                )));
-                            };
-
-                            self.collected_statics.insert(did);
-                            Ok(radium::Expr::Literal(radium::Literal::Loc(s.loc_name.clone())))
-                        },
-                        middle::mir::interpret::GlobalAlloc::Memory(alloc) => {
-                            // TODO: this is needed
-                            Err(TranslationError::UnsupportedFeature {
-                                description: format!(
-                                    "RefinedRust does currently not support GlobalAlloc {:?} for scalar {:?} at type {:?}",
-                                    glob_alloc, sc, ty
-                                ),
-                            })
-                        },
-                        _ => Err(TranslationError::UnsupportedFeature {
-                            description: format!(
-                                "RefinedRust does currently not support GlobalAlloc {:?} for scalar {:?} at type {:?}",
-                                glob_alloc, sc, ty
-                            ),
-                        }),
-                    }
-                },
-            },
-
-            _ => Err(TranslationError::UnsupportedFeature {
-                description: format!(
-                    "RefinedRust does currently not support layout for const value: (got: {:?})",
-                    ty
-                ),
-            }),
-        }
-    }
-
-    /// Translate a constant value from const evaluation.
-    fn translate_constant_value(
-        &mut self,
-        v: mir::interpret::ConstValue<'tcx>,
-        ty: Ty<'tcx>,
-    ) -> Result<radium::Expr, TranslationError<'tcx>> {
-        match v {
-            ConstValue::Scalar(sc) => self.translate_scalar(&sc, ty),
-            ConstValue::ZeroSized => {
-                // TODO are there more special cases we need to handle somehow?
-                match ty.kind() {
-                    TyKind::FnDef(_, _) => {
-                        info!("Translating ZST val for function call target: {:?}", ty);
-                        self.translate_fn_def_use(ty)
-                    },
-                    _ => Ok(radium::Expr::Literal(radium::Literal::ZST)),
-                }
-            },
-            _ => {
-                // TODO: do we actually care about this case or is this just something that can
-                // appear as part of CTFE/MIRI?
-                Err(TranslationError::UnsupportedFeature {
-                    description: format!("Unsupported Constant: ConstValue; {:?}", v),
-                })
-            },
-        }
-    }
-
-    /// Translate a Constant to a `radium::Expr`.
-    fn translate_constant(
-        &mut self,
-        constant: &Constant<'tcx>,
-    ) -> Result<radium::Expr, TranslationError<'tcx>> {
-        match constant.literal {
-            ConstantKind::Ty(v) => {
-                let const_ty = v.ty();
-
-                match v.kind() {
-                    ConstKind::Value(v) => {
-                        // this doesn't contain the necessary structure anymore. Need to reconstruct using the
-                        // type.
-                        match v.try_to_scalar() {
-                            Some(sc) => self.translate_scalar(&sc, const_ty),
-                            _ => Err(TranslationError::UnsupportedFeature {
-                                description: format!("const value not supported: {:?}", v),
-                            }),
-                        }
-                    },
-                    _ => Err(TranslationError::UnsupportedFeature {
-                        description: "Unsupported ConstKind".to_owned(),
-                    }),
-                }
-            },
-            ConstantKind::Val(val, ty) => self.translate_constant_value(val, ty),
-            ConstantKind::Unevaluated(c, ty) => {
-                // call const evaluation
-                let param_env: ty::ParamEnv<'tcx> = self.env.tcx().param_env(self.proc.get_id());
-                match self.env.tcx().const_eval_resolve(param_env, c, None) {
-                    Ok(res) => self.translate_constant_value(res, ty),
-                    Err(e) => match e {
-                        ErrorHandled::Reported(_) => Err(TranslationError::UnsupportedFeature {
-                            description: "Cannot interpret constant".to_owned(),
-                        }),
-                        ErrorHandled::TooGeneric => Err(TranslationError::UnsupportedFeature {
-                            description: "Const use is too generic".to_owned(),
-                        }),
-                    },
-                }
-            },
-        }
-    }
-
-    /// Translate a place to a Caesium lvalue.
-    fn translate_place(&mut self, pl: &Place<'tcx>) -> Result<radium::Expr, TranslationError<'tcx>> {
-        // Get the type of the underlying local. We will use this to
-        // get the necessary layout information for dereferencing
-        let mut cur_ty = self.get_type_of_local(pl.local).map(PlaceTy::from_ty)?;
-
-        let local_name = self
-            .variable_map
-            .get(&pl.local)
-            .ok_or_else(|| TranslationError::UnknownVar(format!("{:?}", pl.local)))?;
-
-        let mut acc_expr = radium::Expr::Var(local_name.to_string());
-
-        // iterate in evaluation order
-        for it in pl.projection {
-            match &it {
-                ProjectionElem::Deref => {
-                    // use the type of the dereferencee
-                    let st = self.ty_translator.translate_type_to_syn_type(cur_ty.ty)?;
-                    acc_expr = radium::Expr::Deref {
-                        ot: st.into(),
-                        e: Box::new(acc_expr),
-                    };
-                },
-                ProjectionElem::Field(f, _) => {
-                    // `t` is the type of the field we are accessing!
-                    let lit = self.ty_translator.generate_structlike_use(cur_ty.ty, cur_ty.variant_index)?;
-                    // TODO: does not do the right thing for accesses to fields of zero-sized objects.
-                    let struct_sls = lit.map_or(radium::SynType::Unit, |x| x.generate_raw_syn_type_term());
-                    let name = self.ty_translator.translator.get_field_name_of(
-                        *f,
-                        cur_ty.ty,
-                        cur_ty.variant_index.map(abi::VariantIdx::as_usize),
-                    )?;
-
-                    acc_expr = radium::Expr::FieldOf {
-                        e: Box::new(acc_expr),
-                        name,
-                        sls: struct_sls.to_string(),
-                    };
-                },
-                ProjectionElem::Index(_v) => {
-                    //TODO
-                    return Err(TranslationError::UnsupportedFeature {
-                        description: "places: implement index access".to_owned(),
-                    });
-                },
-                ProjectionElem::ConstantIndex { .. } => {
-                    //TODO
-                    return Err(TranslationError::UnsupportedFeature {
-                        description: "places: implement const index access".to_owned(),
-                    });
-                },
-                ProjectionElem::Subslice { .. } => {
-                    return Err(TranslationError::UnsupportedFeature {
-                        description: "places: implement subslicing".to_owned(),
-                    });
-                },
-                ProjectionElem::Downcast(_, variant_idx) => {
-                    info!("Downcast of ty {:?} to {:?}", cur_ty, variant_idx);
-                    if let ty::TyKind::Adt(def, args) = cur_ty.ty.kind() {
-                        if def.is_enum() {
-                            let enum_use = self.ty_translator.generate_enum_use(*def, args.iter())?;
-                            let els = enum_use.generate_raw_syn_type_term();
-
-                            let variant_name = TypeTranslator::get_variant_name_of(cur_ty.ty, *variant_idx)?;
-
-                            acc_expr = radium::Expr::EnumData {
-                                els: els.to_string(),
-                                variant: variant_name,
-                                e: Box::new(acc_expr),
-                            }
-                        } else {
-                            return Err(TranslationError::UnknownError(
-                                "places: ADT downcasting on non-enum type".to_owned(),
-                            ));
-                        }
-                    } else {
-                        return Err(TranslationError::UnknownError(
-                            "places: ADT downcasting on non-enum type".to_owned(),
-                        ));
-                    }
-                },
-                ProjectionElem::OpaqueCast(_) => {
-                    return Err(TranslationError::UnsupportedFeature {
-                        description: "places: implement opaque casts".to_owned(),
-                    });
-                },
-            };
-            // update cur_ty
-            cur_ty = cur_ty.projection_ty(self.env.tcx(), it);
-        }
-        info!("translating place {:?} to {:?}", pl, acc_expr);
-        Ok(acc_expr)
-    }
-
-    /// Get the type of a local in a body.
-    fn get_type_of_local(&self, local: Local) -> Result<Ty<'tcx>, TranslationError<'tcx>> {
-        self.proc
-            .get_mir()
-            .local_decls
-            .get(local)
-            .map(|decl| decl.ty)
-            .ok_or_else(|| TranslationError::UnknownVar(String::new()))
-    }
-
-    /// Get the type of a place expression.
-    fn get_type_of_place(&self, pl: &Place<'tcx>) -> PlaceTy<'tcx> {
-        pl.ty(&self.proc.get_mir().local_decls, self.env.tcx())
-    }
-
-    /// Get the type of a const.
-    fn get_type_of_const(cst: &Constant<'tcx>) -> Ty<'tcx> {
-        match cst.literal {
-            ConstantKind::Ty(cst) => cst.ty(),
-            ConstantKind::Val(_, ty) | ConstantKind::Unevaluated(_, ty) => ty,
-        }
-    }
-
-    /// Get the type of an operand.
-    fn get_type_of_operand(&self, op: &Operand<'tcx>) -> Ty<'tcx> {
-        op.ty(&self.proc.get_mir().local_decls, self.env.tcx())
-    }
-}
diff --git a/rr_frontend/translation/src/lib.rs b/rr_frontend/translation/src/lib.rs
index 9a47e257a9cc90c6facc8965a3d19a72abf73d1e..b704a46a28b7796eb81c62c272492cd06feb37b6 100644
--- a/rr_frontend/translation/src/lib.rs
+++ b/rr_frontend/translation/src/lib.rs
@@ -1,4 +1,4 @@
-// © 2023, The RefinedRust Developers and Contributors
+// © 2023, The RefinedRust Develcpers and Contributors
 //
 // This Source Code Form is subject to the terms of the BSD-3-clause License.
 // If a copy of the BSD-3-clause license was not distributed with this
@@ -8,20 +8,21 @@
 #![feature(box_patterns)]
 #![feature(let_chains)]
 #![feature(rustc_private)]
-mod arg_folder;
+
+mod attrs;
 mod base;
-mod checked_op_analysis;
+mod body;
+mod consts;
 mod data;
 pub mod environment;
 mod force_matches_macro;
-mod function_body;
-mod inclusion_tracker;
-mod shim_registry;
+mod procedures;
+mod regions;
+mod search;
+mod shims;
 mod spec_parsers;
-mod trait_registry;
 mod traits;
-mod type_translator;
-mod tyvars;
+mod types;
 mod utils;
 
 use std::collections::{HashMap, HashSet};
@@ -38,14 +39,16 @@ use rr_rustc_interface::{ast, hir, span};
 use topological_sort::TopologicalSort;
 use typed_arena::Arena;
 
+use crate::body::signature;
 use crate::environment::Environment;
-use crate::function_body::{ConstScope, FunctionTranslator, ProcedureMode, ProcedureScope};
+use crate::procedures::{Mode, Scope};
+use crate::shims::registry as shim_registry;
 use crate::spec_parsers::const_attr_parser::{ConstAttrParser, VerboseConstAttrParser};
 use crate::spec_parsers::crate_attr_parser::{CrateAttrParser, VerboseCrateAttrParser};
 use crate::spec_parsers::module_attr_parser::{ModuleAttrParser, ModuleAttrs, VerboseModuleAttrParser};
 use crate::spec_parsers::*;
-use crate::trait_registry::TraitRegistry;
-use crate::type_translator::{normalize_in_function, ParamScope, TypeTranslator};
+use crate::traits::registry;
+use crate::types::{normalize_in_function, scope};
 
 /// Order ADT definitions topologically.
 fn order_adt_defs(deps: &HashMap<DefId, HashSet<DefId>>) -> Vec<DefId> {
@@ -78,10 +81,10 @@ fn order_adt_defs(deps: &HashMap<DefId, HashSet<DefId>>) -> Vec<DefId> {
 
 pub struct VerificationCtxt<'tcx, 'rcx> {
     env: &'rcx Environment<'tcx>,
-    procedure_registry: ProcedureScope<'rcx>,
-    const_registry: ConstScope<'rcx>,
-    type_translator: &'rcx TypeTranslator<'rcx, 'tcx>,
-    trait_registry: &'rcx TraitRegistry<'tcx, 'rcx>,
+    procedure_registry: procedures::Scope<'rcx>,
+    const_registry: consts::Scope<'rcx>,
+    type_translator: &'rcx types::TX<'rcx, 'tcx>,
+    trait_registry: &'rcx registry::TR<'tcx, 'rcx>,
     functions: &'rcx [LocalDefId],
     fn_arena: &'rcx Arena<radium::FunctionSpec<'rcx, radium::InnerFunctionSpec<'rcx>>>,
 
@@ -93,7 +96,7 @@ pub struct VerificationCtxt<'tcx, 'rcx> {
 
     coq_path_prefix: String,
     dune_package: Option<String>,
-    shim_registry: shim_registry::ShimRegistry<'rcx>,
+    shim_registry: shims::registry::SR<'rcx>,
 
     /// trait implementations we generated
     trait_impls: HashMap<DefId, radium::TraitImplSpec<'rcx>>,
@@ -101,17 +104,20 @@ pub struct VerificationCtxt<'tcx, 'rcx> {
 
 impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> {
     fn get_path_for_shim(&self, did: DefId) -> Vec<&str> {
-        let path = utils::get_export_path_for_did(self.env, did);
+        let path = shims::flat::get_export_path_for_did(self.env, did);
         let interned_path = self.shim_registry.intern_path(path);
         interned_path
     }
 
-    fn make_shim_function_entry(&self, did: DefId, spec_name: &str) -> Option<shim_registry::FunctionShim> {
+    fn make_shim_function_entry(&self, did: DefId, spec_name: &str) -> Option<shims::registry::FunctionShim> {
         let Some(mode) = self.procedure_registry.lookup_function_mode(did) else {
             return None;
         };
 
-        if mode != ProcedureMode::Prove && mode != ProcedureMode::OnlySpec && mode != ProcedureMode::TrustMe {
+        if mode != procedures::Mode::Prove
+            && mode != procedures::Mode::OnlySpec
+            && mode != procedures::Mode::TrustMe
+        {
             return None;
         }
 
@@ -124,7 +130,7 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> {
         let is_method = self.env.tcx().impl_of_method(did).is_some();
         let interned_path = self.get_path_for_shim(did);
 
-        let name = type_translator::strip_coq_ident(&self.env.get_item_name(did));
+        let name = base::strip_coq_ident(&self.env.get_item_name(did));
         info!("Found function path {:?} for did {:?} with name {:?}", interned_path, did, name);
 
         Some(shim_registry::FunctionShim {
@@ -147,7 +153,10 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> {
             return None;
         };
 
-        if mode != ProcedureMode::Prove && mode != ProcedureMode::OnlySpec && mode != ProcedureMode::TrustMe {
+        if mode != procedures::Mode::Prove
+            && mode != procedures::Mode::OnlySpec
+            && mode != procedures::Mode::TrustMe
+        {
             trace!("leave make_shim_trait_method_entry (failed)");
             return None;
         }
@@ -176,11 +185,11 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> {
                 let impl_for = args[0].expect_ty();
 
                 // flatten the trait reference
-                let trait_path = utils::PathWithArgs::from_item(self.env, trait_did, trait_args)?;
+                let trait_path = shims::flat::PathWithArgs::from_item(self.env, trait_did, trait_args)?;
                 trace!("got trait path: {:?}", trait_path);
 
                 // flatten the self type.
-                let Some(for_type) = utils::convert_ty_to_flat_type(self.env, impl_for) else {
+                let Some(for_type) = shims::flat::convert_ty_to_flat_type(self.env, impl_for) else {
                     return None;
                 };
 
@@ -193,7 +202,7 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> {
                 };
 
                 let method_ident = ident.as_str().to_owned();
-                let name = type_translator::strip_coq_ident(&self.env.get_item_name(did));
+                let name = base::strip_coq_ident(&self.env.get_item_name(did));
 
                 trace!("leave make_shim_trait_method_entry (success)");
                 Some(shim_registry::TraitMethodImplShim {
@@ -237,11 +246,11 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> {
         let impl_for = args[0].expect_ty();
 
         // flatten the trait reference
-        let trait_path = utils::PathWithArgs::from_item(self.env, trait_did, trait_args)?;
+        let trait_path = shims::flat::PathWithArgs::from_item(self.env, trait_did, trait_args)?;
         trace!("got trait path: {:?}", trait_path);
 
         // flatten the self type.
-        let Some(for_type) = utils::convert_ty_to_flat_type(self.env, impl_for) else {
+        let Some(for_type) = shims::flat::convert_ty_to_flat_type(self.env, impl_for) else {
             trace!("leave make_impl_shim_entry (failed transating self type)");
             return None;
         };
@@ -296,7 +305,7 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> {
         if did.is_local() && ty::Visibility::Public == self.env.tcx().visibility(did) {
             // only export public items
             let interned_path = self.get_path_for_shim(did);
-            let name = type_translator::strip_coq_ident(&self.env.get_item_name(did));
+            let name = base::strip_coq_ident(&self.env.get_item_name(did));
 
             info!("Found adt path {:?} for did {:?} with name {:?}", interned_path, did, name);
 
@@ -408,7 +417,7 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> {
         info!("Generated module summary functions: {:?}", function_shims);
 
         let interface_file = File::create(path).unwrap();
-        shim_registry::write_shims(
+        shims::registry::write_shims(
             interface_file,
             module_path,
             module,
@@ -913,20 +922,17 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> {
 fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base::TranslationError<'tcx>> {
     for shim in vcx.shim_registry.get_function_shims() {
         let did = if shim.is_method {
-            utils::try_resolve_method_did(vcx.env.tcx(), &shim.path)
+            search::try_resolve_method_did(vcx.env.tcx(), &shim.path)
         } else {
-            utils::try_resolve_did(vcx.env.tcx(), &shim.path)
+            search::try_resolve_did(vcx.env.tcx(), &shim.path)
         };
 
         match did {
             Some(did) => {
                 // register as usual in the procedure registry
                 info!("registering shim for {:?}", shim.path);
-                let meta = function_body::ProcedureMeta::new(
-                    shim.spec_name.clone(),
-                    shim.name.clone(),
-                    function_body::ProcedureMode::Shim,
-                );
+                let meta =
+                    procedures::Meta::new(shim.spec_name.clone(), shim.name.clone(), procedures::Mode::Shim);
                 vcx.procedure_registry.register_function(did, meta)?;
             },
             _ => {
@@ -936,7 +942,7 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base
     }
 
     for shim in vcx.shim_registry.get_adt_shims() {
-        let Some(did) = utils::try_resolve_did(vcx.env.tcx(), &shim.path) else {
+        let Some(did) = search::try_resolve_did(vcx.env.tcx(), &shim.path) else {
             println!("Warning: cannot find defid for shim {:?}, skipping", shim.path);
             continue;
         };
@@ -956,7 +962,7 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base
     }
 
     for shim in vcx.shim_registry.get_trait_shims() {
-        if let Some(did) = utils::try_resolve_did(vcx.env.tcx(), &shim.path) {
+        if let Some(did) = search::try_resolve_did(vcx.env.tcx(), &shim.path) {
             let assoc_tys = vcx.trait_registry.get_associated_type_names(did);
             let spec = radium::LiteralTraitSpec {
                 assoc_tys,
@@ -994,7 +1000,7 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base
             continue;
         };
 
-        let trait_impl_did = utils::try_resolve_trait_impl_did(vcx.env.tcx(), trait_did, &args, for_type);
+        let trait_impl_did = search::try_resolve_trait_impl_did(vcx.env.tcx(), trait_did, &args, for_type);
 
         let Some(did) = trait_impl_did else {
             println!(
@@ -1029,11 +1035,7 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base
                     shim.trait_path, method_name, for_type, method_did
                 );
 
-                let meta = function_body::ProcedureMeta::new(
-                    spec_name.clone(),
-                    name.clone(),
-                    function_body::ProcedureMode::Shim,
-                );
+                let meta = procedures::Meta::new(spec_name.clone(), name.clone(), procedures::Mode::Shim);
 
                 vcx.procedure_registry.register_function(method_did, meta)?;
             }
@@ -1049,31 +1051,28 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base
     Ok(())
 }
 
-fn get_most_restrictive_function_mode(
-    vcx: &VerificationCtxt<'_, '_>,
-    did: DefId,
-) -> function_body::ProcedureMode {
+fn get_most_restrictive_function_mode(vcx: &VerificationCtxt<'_, '_>, did: DefId) -> procedures::Mode {
     let attrs = vcx.env.get_attributes_of_function(did, &propagate_method_attr_from_impl);
 
     // check if this is a purely spec function; if so, skip.
-    if utils::has_tool_attr_filtered(attrs.as_slice(), "shim") {
-        return function_body::ProcedureMode::Shim;
+    if attrs::has_tool_attr_filtered(attrs.as_slice(), "shim") {
+        return procedures::Mode::Shim;
     }
 
-    if utils::has_tool_attr_filtered(attrs.as_slice(), "trust_me") {
-        return function_body::ProcedureMode::TrustMe;
+    if attrs::has_tool_attr_filtered(attrs.as_slice(), "trust_me") {
+        return procedures::Mode::TrustMe;
     }
 
-    if utils::has_tool_attr_filtered(attrs.as_slice(), "only_spec") {
-        return function_body::ProcedureMode::OnlySpec;
+    if attrs::has_tool_attr_filtered(attrs.as_slice(), "only_spec") {
+        return procedures::Mode::OnlySpec;
     }
 
-    if utils::has_tool_attr_filtered(attrs.as_slice(), "ignore") {
+    if attrs::has_tool_attr_filtered(attrs.as_slice(), "ignore") {
         info!("Function {:?} will be ignored", did);
-        return function_body::ProcedureMode::Ignore;
+        return procedures::Mode::Ignore;
     }
 
-    function_body::ProcedureMode::Prove
+    procedures::Mode::Prove
 }
 
 /// Register functions of the crate in the procedure registry.
@@ -1083,10 +1082,10 @@ fn register_functions<'tcx>(
     for &f in vcx.functions {
         let mut mode = get_most_restrictive_function_mode(vcx, f.to_def_id());
 
-        if mode == function_body::ProcedureMode::Shim {
+        if mode == procedures::Mode::Shim {
             // TODO better error message
             let attrs = vcx.env.get_attributes(f.to_def_id());
-            let v = utils::filter_tool_attrs(attrs);
+            let v = attrs::filter_for_tool(attrs);
             let annot = spec_parsers::get_shim_attrs(v.as_slice()).unwrap();
 
             info!(
@@ -1095,29 +1094,25 @@ fn register_functions<'tcx>(
                 annot.spec_name,
                 annot.code_name
             );
-            let meta = function_body::ProcedureMeta::new(
-                annot.spec_name,
-                annot.code_name,
-                function_body::ProcedureMode::Shim,
-            );
+            let meta = procedures::Meta::new(annot.spec_name, annot.code_name, procedures::Mode::Shim);
             vcx.procedure_registry.register_function(f.to_def_id(), meta)?;
 
             continue;
         }
 
-        if mode == function_body::ProcedureMode::Prove && let Some(impl_did) = vcx.env.tcx().impl_of_method(f.to_def_id()) {
+        if mode == procedures::Mode::Prove && let Some(impl_did) = vcx.env.tcx().impl_of_method(f.to_def_id()) {
             mode = get_most_restrictive_function_mode(vcx, impl_did);
         }
 
-        if mode == function_body::ProcedureMode::Shim {
+        if mode == procedures::Mode::Shim {
             warn!("Nonsensical shim attribute on impl; ignoring");
-            mode = function_body::ProcedureMode::Prove;
+            mode = procedures::Mode::Prove;
         }
 
-        let fname = type_translator::strip_coq_ident(&vcx.env.get_item_name(f.to_def_id()));
+        let fname = base::strip_coq_ident(&vcx.env.get_item_name(f.to_def_id()));
         let spec_name = format!("type_of_{}", fname);
 
-        let meta = function_body::ProcedureMeta::new(spec_name, fname, mode);
+        let meta = procedures::Meta::new(spec_name, fname, mode);
 
         vcx.procedure_registry.register_function(f.to_def_id(), meta)?;
     }
@@ -1148,7 +1143,7 @@ fn translate_functions<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) {
         let ty = ty.instantiate_identity();
 
         let translator = match ty.kind() {
-            ty::TyKind::FnDef(_def, _args) => FunctionTranslator::new(
+            ty::TyKind::FnDef(_def, _args) => signature::TX::new(
                 vcx.env,
                 &meta,
                 proc,
@@ -1158,7 +1153,7 @@ fn translate_functions<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) {
                 &vcx.procedure_registry,
                 &vcx.const_registry,
             ),
-            ty::TyKind::Closure(_, _) => FunctionTranslator::new_closure(
+            ty::TyKind::Closure(_, _) => signature::TX::new_closure(
                 vcx.env,
                 &meta,
                 proc,
@@ -1173,7 +1168,7 @@ fn translate_functions<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) {
 
         if mode.is_only_spec() {
             // Only generate a spec
-            match translator.and_then(FunctionTranslator::generate_spec) {
+            match translator.and_then(signature::TX::generate_spec) {
                 Ok(spec) => {
                     println!("Successfully generated spec for {}", fname);
                     let spec_ref = vcx.fn_arena.alloc(spec);
@@ -1273,16 +1268,16 @@ pub fn register_consts<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) -> Re
     for s in &statics {
         let ty: ty::EarlyBinder<ty::Ty<'tcx>> = vcx.env.tcx().type_of(s.to_def_id());
 
-        let const_attrs = utils::filter_tool_attrs(vcx.env.get_attributes(s.to_def_id()));
+        let const_attrs = attrs::filter_for_tool(vcx.env.get_attributes(s.to_def_id()));
         if const_attrs.is_empty() {
             continue;
         }
 
         let ty = ty.skip_binder();
-        let scope = ParamScope::default();
+        let scope = scope::Params::default();
         match vcx.type_translator.translate_type_in_scope(&scope, ty).map_err(|x| format!("{:?}", x)) {
             Ok(translated_ty) => {
-                let _full_name = type_translator::strip_coq_ident(&vcx.env.get_item_name(s.to_def_id()));
+                let _full_name = base::strip_coq_ident(&vcx.env.get_item_name(s.to_def_id()));
 
                 let mut const_parser = VerboseConstAttrParser::new();
                 let const_spec = const_parser.parse_const_attrs(*s, &const_attrs)?;
@@ -1377,7 +1372,7 @@ fn register_trait_impls(vcx: &VerificationCtxt<'_, '_>) -> Result<(), String> {
             }
 
             // make names for the spec and inclusion proof
-            let base_name = type_translator::strip_coq_ident(&vcx.env.get_item_name(did));
+            let base_name = base::strip_coq_ident(&vcx.env.get_item_name(did));
             let spec_name = format!("{base_name}_spec");
             let spec_params_name = format!("{base_name}_spec_params");
             let spec_attrs_name = format!("{base_name}_spec_attrs");
@@ -1398,7 +1393,7 @@ fn register_trait_impls(vcx: &VerificationCtxt<'_, '_>) -> Result<(), String> {
     Ok(())
 }
 
-/// Generate trait instances
+/// Generate trait instances.
 fn assemble_trait_impls<'tcx, 'rcx>(
     vcx: &mut VerificationCtxt<'tcx, 'rcx>,
 ) -> Result<(), base::TranslationError<'tcx>> {
@@ -1459,7 +1454,7 @@ pub fn get_module_attributes(env: &Environment<'_>) -> Result<HashMap<LocalDefId
     info!("collected modules: {:?}", modules);
 
     for m in &modules {
-        let module_attrs = utils::filter_tool_attrs(env.get_attributes(m.to_def_id()));
+        let module_attrs = attrs::filter_for_tool(env.get_attributes(m.to_def_id()));
         let mut module_parser = VerboseModuleAttrParser::new();
         let module_spec = module_parser.parse_module_attrs(*m, &module_attrs)?;
         attrs.insert(*m, module_spec);
@@ -1468,42 +1463,6 @@ pub fn get_module_attributes(env: &Environment<'_>) -> Result<HashMap<LocalDefId
     Ok(attrs)
 }
 
-/// Find `RefinedRust` modules in the given loadpath.
-fn scan_loadpath(path: &Path, storage: &mut HashMap<String, PathBuf>) -> io::Result<()> {
-    if path.is_dir() {
-        for entry in fs::read_dir(path)? {
-            let entry = entry?;
-            let path = entry.path();
-            if path.is_dir() {
-                scan_loadpath(path.as_path(), storage)?;
-            } else if path.is_file() {
-                if let Some(ext) = path.extension() {
-                    if Some("rrlib") == ext.to_str() {
-                        // try to open this rrlib file
-                        let f = File::open(path.clone())?;
-                        if let Some(name) = shim_registry::is_valid_refinedrust_module(f) {
-                            storage.insert(name, path);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    Ok(())
-}
-
-/// Find `RefinedRust` modules in the given loadpaths.
-fn scan_loadpaths(paths: &[PathBuf]) -> io::Result<HashMap<String, PathBuf>> {
-    let mut found_lib_files: HashMap<String, PathBuf> = HashMap::new();
-
-    for path in paths {
-        scan_loadpath(path, &mut found_lib_files)?;
-    }
-
-    Ok(found_lib_files)
-}
-
 /// Translate a crate, creating a `VerificationCtxt` in the process.
 pub fn generate_coq_code<'tcx, F>(tcx: ty::TyCtxt<'tcx>, continuation: F) -> Result<(), String>
 where
@@ -1514,7 +1473,7 @@ where
 
     // get crate attributes
     let crate_attrs = tcx.hir().krate_attrs();
-    let crate_attrs = utils::filter_tool_attrs(crate_attrs);
+    let crate_attrs = attrs::filter_for_tool(crate_attrs);
     info!("Found crate attributes: {:?}", crate_attrs);
     // parse crate attributes
     let mut crate_parser = VerboseCrateAttrParser::new();
@@ -1558,8 +1517,8 @@ where
     let trait_impl_arena = Arena::new();
     let trait_use_arena = Arena::new();
     let fn_spec_arena = Arena::new();
-    let type_translator = TypeTranslator::new(env, &struct_arena, &enum_arena, &shim_arena);
-    let trait_registry = TraitRegistry::new(
+    let type_translator = types::TX::new(env, &struct_arena, &enum_arena, &shim_arena);
+    let trait_registry = registry::TR::new(
         env,
         &type_translator,
         &trait_arena,
@@ -1567,14 +1526,14 @@ where
         &trait_use_arena,
         &fn_spec_arena,
     );
-    let procedure_registry = ProcedureScope::new();
+    let procedure_registry = procedures::Scope::new();
     let shim_string_arena = Arena::new();
-    let mut shim_registry = shim_registry::ShimRegistry::empty(&shim_string_arena);
+    let mut shim_registry = shim_registry::SR::empty(&shim_string_arena);
 
     // add includes to the shim registry
     let library_load_paths = rrconfig::lib_load_paths();
     info!("Loading libraries from {:?}", library_load_paths);
-    let found_libs = scan_loadpaths(&library_load_paths).map_err(|e| e.to_string())?;
+    let found_libs = shims::scan_loadpaths(&library_load_paths).map_err(|e| e.to_string())?;
     info!("Found the following RefinedRust libraries in the loadpath: {:?}", found_libs);
 
     for incl in &includes {
@@ -1608,7 +1567,7 @@ where
         coq_path_prefix: path_prefix,
         shim_registry,
         dune_package: package,
-        const_registry: ConstScope::empty(),
+        const_registry: consts::Scope::empty(),
         trait_impls: HashMap::new(),
         fn_arena: &fn_spec_arena,
     };
diff --git a/rr_frontend/translation/src/procedures.rs b/rr_frontend/translation/src/procedures.rs
new file mode 100644
index 0000000000000000000000000000000000000000..28047eb7b921891b03b68faab64082f86b50896d
--- /dev/null
+++ b/rr_frontend/translation/src/procedures.rs
@@ -0,0 +1,183 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+use std::collections::{btree_map, BTreeMap, HashMap, HashSet};
+
+use rr_rustc_interface::hir::def_id::DefId;
+
+use crate::base::TranslationError;
+
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum Mode {
+    Prove,
+    OnlySpec,
+    TrustMe,
+    Shim,
+    Ignore,
+}
+
+impl Mode {
+    pub fn is_prove(self) -> bool {
+        self == Self::Prove
+    }
+
+    pub fn is_only_spec(self) -> bool {
+        self == Self::OnlySpec
+    }
+
+    pub fn is_trust_me(self) -> bool {
+        self == Self::TrustMe
+    }
+
+    pub fn is_shim(self) -> bool {
+        self == Self::Shim
+    }
+
+    pub fn is_ignore(self) -> bool {
+        self == Self::Ignore
+    }
+
+    pub fn needs_proof(self) -> bool {
+        self == Self::Prove
+    }
+
+    pub fn needs_def(self) -> bool {
+        self == Self::Prove || self == Self::TrustMe
+    }
+
+    pub fn needs_spec(self) -> bool {
+        self == Self::Prove || self == Self::TrustMe || self == Self::OnlySpec
+    }
+}
+
+#[derive(Clone)]
+pub struct Meta {
+    spec_name: String,
+    name: String,
+    mode: Mode,
+}
+
+impl Meta {
+    pub const fn new(spec_name: String, name: String, mode: Mode) -> Self {
+        Self {
+            spec_name,
+            name,
+            mode,
+        }
+    }
+
+    pub fn get_spec_name(&self) -> &str {
+        &self.spec_name
+    }
+
+    pub fn get_name(&self) -> &str {
+        &self.name
+    }
+
+    pub const fn get_mode(&self) -> Mode {
+        self.mode
+    }
+}
+
+/**
+ * Tracks the functions we translated and the Coq names they are available under.
+ * To account for dependencies between functions, we may register translated names before we have
+ * actually translated the function.
+ */
+pub struct Scope<'def> {
+    /// maps the defid to (code_name, spec_name, name)
+    name_map: BTreeMap<DefId, Meta>,
+    /// track the actually translated functions
+    translated_functions: BTreeMap<DefId, radium::Function<'def>>,
+    /// track the functions with just a specification (rr::only_spec)
+    specced_functions: BTreeMap<DefId, &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>,
+}
+
+impl<'def> Scope<'def> {
+    pub fn new() -> Self {
+        Self {
+            name_map: BTreeMap::new(),
+            translated_functions: BTreeMap::new(),
+            specced_functions: BTreeMap::new(),
+        }
+    }
+
+    /// Lookup the meta information of a function.
+    pub fn lookup_function(&self, did: DefId) -> Option<Meta> {
+        self.name_map.get(&did).cloned()
+    }
+
+    /// Lookup a translated function spec
+    pub fn lookup_function_spec(
+        &self,
+        did: DefId,
+    ) -> Option<&'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>> {
+        if let Some(translated_fn) = self.translated_functions.get(&did) {
+            Some(translated_fn.spec)
+        } else if let Some(translated_spec) = self.specced_functions.get(&did) {
+            Some(translated_spec)
+        } else {
+            None
+        }
+    }
+
+    /// Lookup the Coq spec name for a function.
+    pub fn lookup_function_spec_name(&self, did: DefId) -> Option<&str> {
+        self.name_map.get(&did).map(Meta::get_spec_name)
+    }
+
+    /// Lookup the name for a function.
+    pub fn lookup_function_mangled_name(&self, did: DefId) -> Option<&str> {
+        self.name_map.get(&did).map(Meta::get_name)
+    }
+
+    /// Lookup the mode for a function.
+    pub fn lookup_function_mode(&self, did: DefId) -> Option<Mode> {
+        self.name_map.get(&did).map(Meta::get_mode)
+    }
+
+    /// Register a function.
+    pub fn register_function<'tcx>(&mut self, did: DefId, meta: Meta) -> Result<(), TranslationError<'tcx>> {
+        if self.name_map.insert(did, meta).is_some() {
+            Err(TranslationError::ProcedureRegistry(format!(
+                "function for defid {:?} has already been registered",
+                did
+            )))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Provide the code for a translated function.
+    pub fn provide_translated_function(&mut self, did: DefId, trf: radium::Function<'def>) {
+        let meta = &self.name_map[&did];
+        assert!(meta.get_mode().needs_def());
+        assert!(self.translated_functions.insert(did, trf).is_none());
+    }
+
+    /// Provide the specification for an `only_spec` function.
+    pub fn provide_specced_function(
+        &mut self,
+        did: DefId,
+        spec: &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>,
+    ) {
+        let meta = &self.name_map[&did];
+        assert!(meta.get_mode().is_only_spec());
+        assert!(self.specced_functions.insert(did, spec).is_none());
+    }
+
+    /// Iterate over the functions we have generated code for.
+    pub fn iter_code(&self) -> btree_map::Iter<'_, DefId, radium::Function<'def>> {
+        self.translated_functions.iter()
+    }
+
+    /// Iterate over the functions we have generated only specs for.
+    pub fn iter_only_spec(
+        &self,
+    ) -> btree_map::Iter<'_, DefId, &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>> {
+        self.specced_functions.iter()
+    }
+}
diff --git a/rr_frontend/translation/src/arg_folder.rs b/rr_frontend/translation/src/regions/arg_folder.rs
similarity index 94%
rename from rr_frontend/translation/src/arg_folder.rs
rename to rr_frontend/translation/src/regions/arg_folder.rs
index 691b44845490009a16c1b86e2fcc60688f8723f0..cd7bee8bf1c584c8206cc8c416af6a02e22ce1c2 100644
--- a/rr_frontend/translation/src/arg_folder.rs
+++ b/rr_frontend/translation/src/regions/arg_folder.rs
@@ -1,13 +1,26 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
 use rr_rustc_interface::middle::ty::visit::*;
-use rr_rustc_interface::middle::ty::{
-    self, Binder, GenericArg, GenericArgKind, ParamConst, Ty, TyCtxt, TypeFolder,
-};
+use rr_rustc_interface::middle::ty::{self, GenericArg, GenericArgKind, ParamConst, Ty, TyCtxt, TypeFolder};
 use rr_rustc_interface::type_ir::fold::{TypeFoldable, TypeSuperFoldable};
-use rr_rustc_interface::type_ir::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor};
+
+/// Instantiate a type with arguments.
+/// The type may contain open region variables `ReVar`.
+pub fn ty_instantiate<'tcx>(x: Ty<'tcx>, tcx: TyCtxt<'tcx>, args: &[GenericArg<'tcx>]) -> Ty<'tcx> {
+    let mut folder = ArgFolder {
+        tcx,
+        args,
+        binders_passed: 0,
+    };
+    x.fold_with(&mut folder)
+}
 
 /// A version of the `ArgFolder` in `rr_rustc_interface::middle::src::ty::generic_args` that skips over
 /// `ReVar` (instead of triggering a bug).
-
 struct ArgFolder<'a, 'tcx> {
     tcx: TyCtxt<'tcx>,
     args: &'a [GenericArg<'tcx>],
@@ -211,12 +224,3 @@ impl<'a, 'tcx> ArgFolder<'a, 'tcx> {
         ty::fold::shift_region(self.tcx, region, self.binders_passed)
     }
 }
-
-pub fn ty_instantiate<'tcx>(x: Ty<'tcx>, tcx: TyCtxt<'tcx>, args: &[GenericArg<'tcx>]) -> Ty<'tcx> {
-    let mut folder = ArgFolder {
-        tcx,
-        args,
-        binders_passed: 0,
-    };
-    x.fold_with(&mut folder)
-}
diff --git a/rr_frontend/translation/src/regions/assignment.rs b/rr_frontend/translation/src/regions/assignment.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f7583c34e2f424939256ff6e4780f4d935d20bb2
--- /dev/null
+++ b/rr_frontend/translation/src/regions/assignment.rs
@@ -0,0 +1,359 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Provides functionality for generating lifetime annotations for assignments.
+
+use std::collections::HashSet;
+
+use log::{info, warn};
+use rr_rustc_interface::middle::mir::tcx::PlaceTy;
+use rr_rustc_interface::middle::mir::{BorrowKind, Location, Rvalue};
+use rr_rustc_interface::middle::ty;
+use rr_rustc_interface::middle::ty::{Ty, TypeFoldable};
+
+use crate::base::{self, Region};
+use crate::environment::borrowck::facts;
+use crate::environment::polonius_info::PoloniusInfo;
+use crate::environment::Environment;
+use crate::regions::inclusion_tracker::{self, InclusionTracker};
+use crate::{regions, types};
+
+/// Generate inclusions for a strong update assignment.
+fn get_assignment_strong_update_constraints(
+    inclusion_tracker: &InclusionTracker<'_, '_>,
+    loc: Location,
+) -> HashSet<(Region, Region, base::PointIndex)> {
+    let info = inclusion_tracker.info();
+    let input_facts = &info.borrowck_in_facts;
+    let subset_base = &input_facts.subset_base;
+
+    let mut constraints = HashSet::new();
+    // Polonius subset constraint are spawned for the midpoint
+    let midpoint = info.interner.get_point_index(&facts::Point {
+        location: loc,
+        typ: facts::PointType::Mid,
+    });
+
+    // for strong update: emit mutual equalities
+    // TODO: alternative implementation: structurally compare regions in LHS type and RHS type
+    for (s1, s2, point) in subset_base {
+        if *point == midpoint {
+            let lft1 = info.mk_atomic_region(*s1);
+            let lft2 = info.mk_atomic_region(*s2);
+
+            // We only care about inclusions into a place lifetime.
+            // Moreover, we want to filter out the universal inclusions which are always
+            // replicated at every point.
+            if lft2.is_place() && !lft1.is_universal() {
+                // take this constraint and the reverse constraint
+                constraints.insert((*s1, *s2, *point));
+                //constraints.insert((*s2, *s1, *point));
+            }
+        }
+    }
+    constraints
+}
+
+/// Generate inclusions for a weak update assignment.
+fn get_assignment_weak_update_constraints(
+    inclusion_tracker: &mut InclusionTracker<'_, '_>,
+    loc: Location,
+) -> HashSet<(Region, Region, base::PointIndex)> {
+    let info = inclusion_tracker.info();
+    let input_facts = &info.borrowck_in_facts;
+    let subset_base = &input_facts.subset_base;
+
+    let mut constraints = HashSet::new();
+    // Polonius subset constraint are spawned for the midpoint
+    let midpoint = info.interner.get_point_index(&facts::Point {
+        location: loc,
+        typ: facts::PointType::Mid,
+    });
+
+    // for weak updates: should mirror the constraints generated by Polonius
+    for (s1, s2, point) in subset_base {
+        if *point == midpoint {
+            // take this constraint
+            // TODO should there be exceptions to this?
+
+            if !inclusion_tracker.check_inclusion(*s1, *s2, *point) {
+                // only add it if it does not hold already, since we will enforce this
+                // constraint dynamically.
+                constraints.insert((*s1, *s2, *point));
+            }
+        }
+    }
+    constraints
+}
+
+/// Get all region variables mentioned in a place type.
+fn find_region_variables_of_place_type<'tcx>(env: &Environment<'tcx>, ty: PlaceTy<'tcx>) -> HashSet<Region> {
+    let mut collector = regions::TyRegionCollectFolder::new(env.tcx());
+    if ty.variant_index.is_some() {
+        panic!("find_region_variables_of_place_type: don't support enums");
+    }
+
+    ty.ty.fold_with(&mut collector);
+    collector.get_regions()
+}
+
+/// Compute the annotations for an assignment: an annotation for the rhs value, and a list of
+/// annotations to prepend to the statement, and a list of annotations to put after the
+/// statement.
+pub fn get_assignment_annots<'tcx>(
+    env: &Environment<'tcx>,
+    inclusion_tracker: &mut InclusionTracker<'_, 'tcx>,
+    ty_translator: &types::LocalTX<'_, 'tcx>,
+    loc: Location,
+    lhs_strongly_writeable: bool,
+    lhs_ty: PlaceTy<'tcx>,
+    _rhs_ty: Ty<'tcx>,
+) -> (Option<radium::Annotation>, Vec<radium::Annotation>, Vec<radium::Annotation>) {
+    let info = inclusion_tracker.info();
+    let new_dyn_inclusions;
+    let expr_annot;
+    let stmt_annot;
+    if lhs_strongly_writeable {
+        // we are going to update the region mapping through annotations,
+        // and hence put up a barrier for propagation of region constraints
+
+        // structurally go over the type and find region variables.
+        // for each of the variables, issue a barrier.
+        // also track them together with the PlaceItems needed to reach them.
+        // from the latter, we can generate the necessary annotations
+        let regions = find_region_variables_of_place_type(env, lhs_ty);
+
+        // put up a barrier at the Mid point
+        let barrier_point_index = info.interner.get_point_index(&facts::Point {
+            location: loc,
+            typ: facts::PointType::Mid,
+        });
+        for r in &regions {
+            inclusion_tracker.add_barrier(*r, barrier_point_index);
+        }
+        // get new constraints that should be enforced
+        let new_constraints = get_assignment_strong_update_constraints(inclusion_tracker, loc);
+        stmt_annot = Vec::new();
+        for (r1, r2, p) in &new_constraints {
+            inclusion_tracker.add_static_inclusion(*r1, *r2, *p);
+            inclusion_tracker.add_static_inclusion(*r2, *r1, *p);
+
+            // TODO: use this instead of the expr_annot below
+            //stmt_annot.push(
+            //radium::Annotation::CopyLftName(
+            //self.format_region(*r1),
+            //self.format_region(*r2),
+            //));
+        }
+
+        // TODO: get rid of this
+        // similarly generate an annotation that encodes these constraints in the RR
+        // type system
+        expr_annot = generate_strong_update_annot(ty_translator, info, lhs_ty);
+        //expr_annot = None;
+
+        new_dyn_inclusions = HashSet::new();
+    } else {
+        // need to filter out the constraints that are relevant here.
+        // incrementally go through them.
+        new_dyn_inclusions = get_assignment_weak_update_constraints(inclusion_tracker, loc);
+        expr_annot = None;
+        stmt_annot = Vec::new();
+    }
+
+    // First enforce the new inclusions, then do the other annotations
+    let new_dyn_inclusions =
+        generate_dyn_inclusion_annots(inclusion_tracker, ty_translator, &new_dyn_inclusions);
+    (expr_annot, new_dyn_inclusions, stmt_annot)
+}
+
+/// Generates dynamic inclusions for the set of inclusions in `incls`.
+/// These inclusions should not hold yet.
+/// Skips mutual inclusions -- we cannot interpret these.
+fn generate_dyn_inclusion_annots<'tcx>(
+    inclusion_tracker: &mut InclusionTracker<'_, 'tcx>,
+    ty_translator: &types::LocalTX<'_, 'tcx>,
+    incls: &HashSet<(Region, Region, base::PointIndex)>,
+) -> Vec<radium::Annotation> {
+    // before executing the assignment, first enforce dynamic inclusions
+    info!("Generating dynamic inclusions {:?}", incls);
+
+    let inclusions = inclusion_tracker.generate_dyn_inclusions(incls);
+
+    inclusions
+        .into_iter()
+        .map(|incl| match incl {
+            inclusion_tracker::DynamicInclusion::ExtendLft(l) => {
+                radium::Annotation::ExtendLft(ty_translator.format_atomic_region(&l))
+            },
+            inclusion_tracker::DynamicInclusion::IncludeLft(l1, l2) => radium::Annotation::DynIncludeLft(
+                ty_translator.format_atomic_region(&l1),
+                ty_translator.format_atomic_region(&l2),
+            ),
+        })
+        .collect()
+}
+
+/// Generate an annotation on an expression needed to update the region name map.
+fn generate_strong_update_annot<'tcx>(
+    ty_translator: &types::LocalTX<'_, 'tcx>,
+    info: &PoloniusInfo<'_, 'tcx>,
+    ty: PlaceTy<'tcx>,
+) -> Option<radium::Annotation> {
+    let (interesting, tree) = generate_strong_update_annot_rec(ty_translator, info, ty.ty);
+    interesting.then(|| radium::Annotation::GetLftNames(tree))
+}
+
+/// Returns a tree for giving names to Coq lifetimes based on RR types.
+/// The boolean indicates whether the tree is "interesting", i.e. whether it names at least one
+/// lifetime.
+fn generate_strong_update_annot_rec<'tcx>(
+    ty_translator: &types::LocalTX<'_, 'tcx>,
+    info: &PoloniusInfo<'_, 'tcx>,
+    ty: Ty<'tcx>,
+) -> (bool, radium::LftNameTree) {
+    // TODO for now this just handles nested references
+    match ty.kind() {
+        ty::TyKind::Ref(r, ty, _) => match r.kind() {
+            ty::RegionKind::ReVar(r) => {
+                let name = ty_translator.format_atomic_region(&info.mk_atomic_region(r));
+                let (_, ty_tree) = generate_strong_update_annot_rec(ty_translator, info, *ty);
+                (true, radium::LftNameTree::Ref(name, Box::new(ty_tree)))
+            },
+            _ => {
+                panic!("generate_strong_update_annot: expected region variable");
+            },
+        },
+        _ => (false, radium::LftNameTree::Leaf),
+    }
+}
+
+/// Generate an annotation to adapt the type of `expr` to `target_ty` from type `current_ty` by
+/// means of shortening lifetimes.
+fn generate_shortenlft_annot<'tcx>(
+    ty_translator: &types::LocalTX<'_, 'tcx>,
+    info: &PoloniusInfo<'_, 'tcx>,
+    target_ty: Ty<'tcx>,
+    _current_ty: Ty<'tcx>,
+    mut expr: radium::Expr,
+) -> radium::Expr {
+    // this is not so different from the strong update annotation
+    let (interesting, tree) = generate_strong_update_annot_rec(ty_translator, info, target_ty);
+    if interesting {
+        expr = radium::Expr::Annot {
+            a: radium::Annotation::ShortenLft(tree),
+            e: Box::new(expr),
+            why: None,
+        };
+    }
+    expr
+}
+
+/// Find all regions that need to outlive a loan region at its point of creation, and
+/// add the corresponding constraints to the inclusion tracker.
+fn get_outliving_regions_on_loan(
+    inclusion_tracker: &mut InclusionTracker<'_, '_>,
+    r: Region,
+    loan_point: base::PointIndex,
+) -> Vec<Region> {
+    // get all base subset constraints r' ⊆ r
+    let info = inclusion_tracker.info();
+    let input_facts = &info.borrowck_in_facts;
+    let mut outliving = Vec::new();
+
+    let subset_base = &input_facts.subset_base;
+    for (r1, r2, p) in subset_base {
+        if *p == loan_point && *r2 == r {
+            inclusion_tracker.add_static_inclusion(*r1, *r2, *p);
+            outliving.push(*r1);
+        }
+        // other subset constraints at this point are due to (for instance) the assignment of
+        // the loan to a place and are handled there.
+    }
+    outliving
+}
+
+/// Get the annotations due to borrows appearing on the RHS of an assignment.
+pub fn get_assignment_loan_annots<'tcx>(
+    inclusion_tracker: &mut InclusionTracker<'_, 'tcx>,
+    ty_translator: &types::LocalTX<'_, 'tcx>,
+    loc: Location,
+    rhs: &Rvalue<'tcx>,
+) -> Vec<radium::Annotation> {
+    let info = inclusion_tracker.info();
+    let mut stmt_annots = Vec::new();
+
+    // if we create a new loan here, start a new lifetime for it
+    let loan_point = info.get_point(loc, facts::PointType::Mid);
+    if let Some(loan) = info.get_optional_loan_at_location(loc) {
+        // TODO: is this fine for aggregates? I suppose, if I create a loan for an
+        // aggregate, I want to use the same atomic region for all of its components
+        // anyways.
+
+        let lft = info.atomic_region_of_loan(loan);
+        let r = lft.get_region();
+
+        // get the static inclusions we need to generate here and add them to the
+        // inclusion tracker
+        let outliving = get_outliving_regions_on_loan(inclusion_tracker, r, loan_point);
+
+        // add statement for issuing the loan
+        stmt_annots.insert(
+            0,
+            radium::Annotation::StartLft(
+                ty_translator.format_atomic_region(&lft),
+                outliving
+                    .iter()
+                    .map(|r| ty_translator.format_atomic_region(&info.mk_atomic_region(*r)))
+                    .collect(),
+            ),
+        );
+
+        let a = info.get_region_kind(r);
+        info!("Issuing loan at {:?} with kind {:?}: {:?}; outliving: {:?}", loc, a, loan, outliving);
+    } else if let Rvalue::Ref(region, BorrowKind::Shared, _) = rhs {
+        // for shared reborrows, Polonius does not create a new loan, and so the
+        // previous case did not match.
+        // However, we still need to track the region created for the reborrow in an
+        // annotation.
+
+        let region = regions::region_to_region_vid(*region);
+
+        // find inclusion ?r1 ⊑ region -- we will actually enforce region = r1
+        let new_constrs: Vec<(facts::Region, facts::Region)> =
+            info.get_new_subset_constraints_at_point(loan_point);
+        info!("Shared reborrow at {:?} with new constrs: {:?}", region, new_constrs);
+        let mut included_region = None;
+        for (r1, r2) in &new_constrs {
+            if *r2 == region {
+                included_region = Some(r1);
+                break;
+            }
+        }
+        if let Some(r) = included_region {
+            //info!("Found inclusion {:?}⊑  {:?}", r, region);
+            stmt_annots.push(radium::Annotation::CopyLftName(
+                ty_translator.format_atomic_region(&info.mk_atomic_region(*r)),
+                ty_translator.format_atomic_region(&info.mk_atomic_region(region)),
+            ));
+
+            // also add this to the inclusion checker
+            inclusion_tracker.add_static_inclusion(*r, region, loan_point);
+        } else {
+            // This happens e.g. when borrowing from a raw pointer etc.
+            info!("Found unconstrained shared borrow for {:?}", region);
+            let inferred_constrained = vec![];
+
+            // add statement for issuing the loan
+            stmt_annots.push(radium::Annotation::StartLft(
+                ty_translator.format_atomic_region(&info.mk_atomic_region(region)),
+                inferred_constrained,
+            ));
+        }
+    }
+
+    stmt_annots
+}
diff --git a/rr_frontend/translation/src/regions/calls.rs b/rr_frontend/translation/src/regions/calls.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8e3b93148993d1a3ad22d1f324b4035d31faf907
--- /dev/null
+++ b/rr_frontend/translation/src/regions/calls.rs
@@ -0,0 +1,177 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Provides functionality for generating lifetime annotations for calls.
+
+use std::collections::{HashMap, HashSet};
+
+use derive_more::Debug;
+use log::{info, warn};
+use rr_rustc_interface::middle::mir::Location;
+use rr_rustc_interface::middle::ty;
+use rr_rustc_interface::middle::ty::TypeFolder;
+
+use crate::environment::borrowck::facts;
+use crate::environment::polonius_info::PoloniusInfo;
+use crate::environment::{polonius_info, Environment};
+use crate::regions::inclusion_tracker::InclusionTracker;
+use crate::regions::EarlyLateRegionMap;
+use crate::{base, regions, types};
+
+// solve the constraints for the new_regions
+// we first identify what kinds of constraints these new regions are subject to
+#[derive(Debug)]
+pub enum CallRegionKind {
+    // this is just an intersection of local regions.
+    Intersection(HashSet<base::Region>),
+    // this is equal to a specific region
+    EqR(base::Region),
+}
+
+pub struct CallRegions {
+    pub early_regions: Vec<base::Region>,
+    pub late_regions: Vec<base::Region>,
+    pub classification: HashMap<base::Region, CallRegionKind>,
+}
+
+// `substs` are the substitutions for the early-bound regions
+pub fn compute_call_regions<'tcx>(
+    env: &Environment<'tcx>,
+    incl_tracker: &InclusionTracker<'_, '_>,
+    substs: &[ty::GenericArg<'tcx>],
+    loc: Location,
+) -> CallRegions {
+    let info = incl_tracker.info();
+
+    let midpoint = info.interner.get_point_index(&facts::Point {
+        location: loc,
+        typ: facts::PointType::Mid,
+    });
+
+    let mut early_regions = Vec::new();
+    for a in substs {
+        if let ty::GenericArgKind::Lifetime(r) = a.unpack() {
+            if let ty::RegionKind::ReVar(r) = r.kind() {
+                early_regions.push(r);
+            }
+        }
+    }
+    info!("call region instantiations (early): {:?}", early_regions);
+
+    // this is a hack to identify the inference variables introduced for the
+    // call's late-bound universals.
+    // TODO: Can we get this information in a less hacky way?
+    // One approach: compute the early + late bound regions for a given DefId, similarly to how
+    // we do it when starting to translate a function
+    // Problem: this doesn't give a straightforward way to compute their instantiation
+
+    // now find all the regions that appear in type parameters we instantiate.
+    // These are regions that the callee doesn't know about.
+    let mut generic_regions = HashSet::new();
+    let mut clos = |r: ty::Region<'tcx>, _| match r.kind() {
+        ty::RegionKind::ReVar(rv) => {
+            generic_regions.insert(rv);
+            r
+        },
+        _ => r,
+    };
+
+    for a in substs {
+        if let ty::GenericArgKind::Type(c) = a.unpack() {
+            let mut folder = ty::fold::RegionFolder::new(env.tcx(), &mut clos);
+            folder.fold_ty(c);
+        }
+    }
+    info!("Regions of generic args: {:?}", generic_regions);
+
+    // go over all region constraints initiated at this location
+    let new_constraints = info.get_new_subset_constraints_at_point(midpoint);
+    let mut new_regions = HashSet::new();
+    let mut relevant_constraints = Vec::new();
+    for (r1, r2) in &new_constraints {
+        if matches!(info.get_region_kind(*r1), polonius_info::RegionKind::Unknown) {
+            // this is probably a inference variable for the call
+            new_regions.insert(*r1);
+            relevant_constraints.push((*r1, *r2));
+        }
+        if matches!(info.get_region_kind(*r2), polonius_info::RegionKind::Unknown) {
+            new_regions.insert(*r2);
+            relevant_constraints.push((*r1, *r2));
+        }
+    }
+    // first sort this to enable cycle resolution
+    let mut new_regions_sorted: Vec<base::Region> = new_regions.iter().copied().collect();
+    new_regions_sorted.sort();
+
+    // identify the late-bound regions
+    let mut late_regions = Vec::new();
+    for r in &new_regions_sorted {
+        // only take the ones which are not early bound and
+        // which are not due to a generic (the callee doesn't care about generic regions)
+        if !early_regions.contains(r) && !generic_regions.contains(r) {
+            late_regions.push(*r);
+        }
+    }
+    info!("call region instantiations (late): {:?}", late_regions);
+
+    // Notes:
+    // - if two of the call regions need to be equal due to constraints on the function, we define the one
+    //   with the larger id in terms of the other one
+    // - we ignore unidirectional subset constraints between call regions (these do not help in finding a
+    //   solution if we take the transitive closure beforehand)
+    // - if a call region needs to be equal to a local region, we directly define it in terms of the local
+    //   region
+    // - otherwise, it will be an intersection of local regions
+    let mut new_regions_classification = HashMap::new();
+    // compute transitive closure of constraints
+    let relevant_constraints = polonius_info::compute_transitive_closure(relevant_constraints);
+    for r in &new_regions_sorted {
+        for (r1, r2) in &relevant_constraints {
+            if *r2 != *r {
+                continue;
+            }
+
+            // i.e. (flipping it around when we are talking about lifetimes),
+            // r needs to be a sublft of r1
+            if relevant_constraints.contains(&(*r2, *r1)) {
+                // if r1 is also a new region and r2 is ordered before it, we will
+                // just define r1 in terms of r2
+                if new_regions.contains(r1) && r2.as_u32() < r1.as_u32() {
+                    continue;
+                }
+                // need an equality constraint
+                new_regions_classification.insert(*r, CallRegionKind::EqR(*r1));
+                // do not consider the rest of the constraints as r is already
+                // fully specified
+                break;
+            }
+
+            // the intersection also needs to contain r1
+            if new_regions.contains(r1) {
+                // we do not need this constraint, since we already computed the
+                // transitive closure.
+                continue;
+            }
+
+            let kind = new_regions_classification
+                .entry(*r)
+                .or_insert(CallRegionKind::Intersection(HashSet::new()));
+
+            let CallRegionKind::Intersection(s) = kind else {
+                unreachable!();
+            };
+
+            s.insert(*r1);
+        }
+    }
+    info!("call arg classification: {:?}", new_regions_classification);
+
+    CallRegions {
+        early_regions,
+        late_regions,
+        classification: new_regions_classification,
+    }
+}
diff --git a/rr_frontend/translation/src/regions/composite.rs b/rr_frontend/translation/src/regions/composite.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2865cc38f28adde37f50d1fedbe1e5643bf12304
--- /dev/null
+++ b/rr_frontend/translation/src/regions/composite.rs
@@ -0,0 +1,82 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Provides functionality for generating lifetime annotations for composite expressions.
+
+use std::collections::HashSet;
+
+use log::{info, warn};
+use rr_rustc_interface::middle::mir::Location;
+use rr_rustc_interface::middle::ty;
+use rr_rustc_interface::middle::ty::{Ty, TypeFolder};
+
+use crate::environment::borrowck::facts;
+use crate::environment::Environment;
+use crate::regions::inclusion_tracker::InclusionTracker;
+use crate::types;
+
+/// On creating a composite value (e.g. a struct or enum), the composite value gets its own
+/// Polonius regions We need to map these regions properly to the respective lifetimes.
+pub fn get_composite_rvalue_creation_annots<'tcx>(
+    env: &Environment<'tcx>,
+    inclusion_tracker: &mut InclusionTracker<'_, 'tcx>,
+    ty_translator: &types::LocalTX<'_, 'tcx>,
+    loc: Location,
+    rhs_ty: ty::Ty<'tcx>,
+) -> Vec<radium::Annotation> {
+    let info = inclusion_tracker.info();
+    let input_facts = &info.borrowck_in_facts;
+    let subset_base = &input_facts.subset_base;
+
+    let regions_of_ty = get_regions_of_ty(env, rhs_ty);
+
+    let mut annots = Vec::new();
+
+    // Polonius subset constraint are spawned for the midpoint
+    let midpoint = info.interner.get_point_index(&facts::Point {
+        location: loc,
+        typ: facts::PointType::Mid,
+    });
+
+    for (s1, s2, point) in subset_base {
+        if *point == midpoint {
+            let lft1 = info.mk_atomic_region(*s1);
+            let lft2 = info.mk_atomic_region(*s2);
+
+            // a place lifetime is included in a value lifetime
+            if lft2.is_value() && lft1.is_place() {
+                // make sure it's not due to an assignment constraint
+                if regions_of_ty.contains(s2) && !subset_base.contains(&(*s2, *s1, midpoint)) {
+                    // we enforce this inclusion by setting the lifetimes to be equal
+                    inclusion_tracker.add_static_inclusion(*s1, *s2, midpoint);
+                    inclusion_tracker.add_static_inclusion(*s2, *s1, midpoint);
+
+                    let annot = radium::Annotation::CopyLftName(
+                        ty_translator.format_atomic_region(&lft1),
+                        ty_translator.format_atomic_region(&lft2),
+                    );
+                    annots.push(annot);
+                }
+            }
+        }
+    }
+    annots
+}
+
+/// Get the regions appearing in a type.
+fn get_regions_of_ty<'tcx>(env: &Environment<'tcx>, ty: Ty<'tcx>) -> HashSet<ty::RegionVid> {
+    let mut regions = HashSet::new();
+    let mut clos = |r: ty::Region<'tcx>, _| match r.kind() {
+        ty::RegionKind::ReVar(rv) => {
+            regions.insert(rv);
+            r
+        },
+        _ => r,
+    };
+    let mut folder = ty::fold::RegionFolder::new(env.tcx(), &mut clos);
+    folder.fold_ty(ty);
+    regions
+}
diff --git a/rr_frontend/translation/src/inclusion_tracker.rs b/rr_frontend/translation/src/regions/inclusion_tracker.rs
similarity index 72%
rename from rr_frontend/translation/src/inclusion_tracker.rs
rename to rr_frontend/translation/src/regions/inclusion_tracker.rs
index 964280cfb5376fed11a33fe0f579f7e5087bb256..46c87c120f3fc4197e5d761360a5a056f42fb113 100644
--- a/rr_frontend/translation/src/inclusion_tracker.rs
+++ b/rr_frontend/translation/src/regions/inclusion_tracker.rs
@@ -4,8 +4,17 @@
 // If a copy of the BSD-3-clause license was not distributed with this
 // file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
 
+use std::collections::HashSet;
+
+use log::{info, warn};
+
 use crate::base::*;
-use crate::environment::polonius_info::PoloniusInfo;
+use crate::environment::polonius_info::{self, PoloniusInfo};
+
+pub enum DynamicInclusion {
+    ExtendLft(polonius_info::AtomicRegion),
+    IncludeLft(polonius_info::AtomicRegion, polonius_info::AtomicRegion),
+}
 
 /// The `InclusionTracker` maintains a set of dynamic lifetime inclusions holding in the `RefinedRust`
 /// type system at given program points.
@@ -48,6 +57,10 @@ impl<'a, 'tcx: 'a> InclusionTracker<'a, 'tcx> {
         }
     }
 
+    pub const fn info(&self) -> &'a PoloniusInfo<'a, 'tcx> {
+        self.info
+    }
+
     /// Add a basic static inclusion fact.
     pub fn add_static_inclusion(&mut self, a: Region, b: Region, p: PointIndex) {
         self.static_incl_base.push((a, b, p));
@@ -167,4 +180,62 @@ impl<'a, 'tcx: 'a> InclusionTracker<'a, 'tcx> {
         }
         self.static_incl.as_ref().unwrap().contains(&(r1, r2, p)) || r1 == r2
     }
+
+    /// Generate a dynamic inclusion of r1 in r2 at point p. Prepends annotations for doing so to
+    /// `stmt_annots`.
+    fn generate_dyn_inclusion(
+        &mut self,
+        stmt_annots: &mut Vec<DynamicInclusion>,
+        r1: Region,
+        r2: Region,
+        p: PointIndex,
+    ) {
+        // check if inclusion already holds
+        if !self.check_inclusion(r1, r2, p) {
+            // check if the reverse inclusion already holds
+            if self.check_inclusion(r2, r1, p) {
+                // our invariant is that this must be a static inclusion
+                assert!(self.check_static_inclusion(r2, r1, p));
+                self.add_dynamic_inclusion(r1, r2, p);
+
+                // we generate an extendlft instruction
+                // for this, we need to figure out a path to make this inclusion true, i.e. we need
+                // an explanation of why it is syntactically included.
+                // TODO: for now, we just assume that r1 ⊑ₗ [r2] (in terms of Coq lifetime inclusion)
+                stmt_annots.push(DynamicInclusion::ExtendLft(self.info.mk_atomic_region(r1)));
+            } else {
+                self.add_dynamic_inclusion(r1, r2, p);
+                // we generate a dynamic inclusion instruction
+                // we flip this around because the annotations are talking about lifetimes, which are oriented
+                // the other way around.
+                stmt_annots.push(DynamicInclusion::IncludeLft(
+                    self.info.mk_atomic_region(r2),
+                    self.info.mk_atomic_region(r1),
+                ));
+            }
+        }
+    }
+
+    /// Generates dynamic inclusions for the set of inclusions in `incls`.
+    /// These inclusions should not hold yet.
+    /// Skips mutual inclusions -- we cannot interpret these.
+    pub fn generate_dyn_inclusions(
+        &mut self,
+        incls: &HashSet<(Region, Region, PointIndex)>,
+    ) -> Vec<DynamicInclusion> {
+        // before executing the assignment, first enforce dynamic inclusions
+        info!("Generating dynamic inclusions {:?}", incls);
+        let mut stmt_annots = Vec::new();
+
+        for (r1, r2, p) in incls {
+            if incls.contains(&(*r2, *r1, *p)) {
+                warn!("Skipping impossible dynamic inclusion {:?} ⊑ {:?} at {:?}", r1, r2, p);
+                continue;
+            }
+
+            self.generate_dyn_inclusion(&mut stmt_annots, *r1, *r2, *p);
+        }
+
+        stmt_annots
+    }
 }
diff --git a/rr_frontend/translation/src/regions/init.rs b/rr_frontend/translation/src/regions/init.rs
new file mode 100644
index 0000000000000000000000000000000000000000..571131f5274dc5d3b4da3695aeb6adc9edaa1e87
--- /dev/null
+++ b/rr_frontend/translation/src/regions/init.rs
@@ -0,0 +1,272 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Provides functionality for generating initial lifetime constraints.
+
+use std::collections::{BTreeMap, HashMap};
+
+use log::{info, warn};
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::mir::{BasicBlock, Location};
+use rr_rustc_interface::middle::ty;
+use rr_rustc_interface::middle::ty::Ty;
+
+use crate::base;
+use crate::environment::borrowck::facts;
+use crate::environment::polonius_info::PoloniusInfo;
+use crate::environment::{polonius_info, Environment};
+use crate::regions::arg_folder::ty_instantiate;
+use crate::regions::inclusion_tracker::InclusionTracker;
+use crate::regions::EarlyLateRegionMap;
+
+/// Process the signature of a function by instantiating the region variables with their
+/// Polonius variables.
+/// Returns the argument and return type signature instantiated in this way.
+/// Moreover, returns a `EarlyLateRegionMap` that contains the mapping of indices to Polonius
+/// region variables.
+pub fn replace_fnsig_args_with_polonius_vars<'tcx>(
+    env: &Environment<'tcx>,
+    params: &[ty::GenericArg<'tcx>],
+    sig: ty::Binder<'tcx, ty::FnSig<'tcx>>,
+) -> (Vec<ty::Ty<'tcx>>, ty::Ty<'tcx>, EarlyLateRegionMap) {
+    // a mapping of Polonius region IDs to names
+    let mut universal_lifetimes = BTreeMap::new();
+    let mut lifetime_names = HashMap::new();
+
+    let mut region_substitution_early: Vec<Option<ty::RegionVid>> = Vec::new();
+
+    // we create a substitution that replaces early bound regions with their Polonius
+    // region variables
+    let mut subst_early_bounds: Vec<ty::GenericArg<'tcx>> = Vec::new();
+    let mut num_early_bounds = 0;
+    for a in params {
+        if let ty::GenericArgKind::Lifetime(r) = a.unpack() {
+            // skip over 0 = static
+            let next_id = ty::RegionVid::from_u32(num_early_bounds + 1);
+            let revar = ty::Region::new_var(env.tcx(), next_id);
+            num_early_bounds += 1;
+            subst_early_bounds.push(ty::GenericArg::from(revar));
+
+            region_substitution_early.push(Some(next_id));
+
+            match *r {
+                ty::RegionKind::ReEarlyBound(r) => {
+                    let name = base::strip_coq_ident(r.name.as_str());
+                    universal_lifetimes.insert(next_id, format!("ulft_{}", name));
+                    lifetime_names.insert(name, next_id);
+                },
+                _ => {
+                    universal_lifetimes.insert(next_id, format!("ulft_{}", num_early_bounds));
+                },
+            }
+        } else {
+            subst_early_bounds.push(*a);
+            region_substitution_early.push(None);
+        }
+    }
+    let subst_early_bounds = env.tcx().mk_args(&subst_early_bounds);
+
+    info!("Computed early region map {region_substitution_early:?}");
+
+    // add names for late bound region variables
+    let mut num_late_bounds = 0;
+    let mut region_substitution_late = Vec::new();
+    for b in sig.bound_vars() {
+        let next_id = ty::RegionVid::from_u32(num_early_bounds + num_late_bounds + 1);
+
+        let ty::BoundVariableKind::Region(r) = b else {
+            continue;
+        };
+
+        region_substitution_late.push(next_id);
+
+        match r {
+            ty::BoundRegionKind::BrNamed(_, sym) => {
+                let mut region_name = base::strip_coq_ident(sym.as_str());
+                if region_name == "_" {
+                    region_name = next_id.as_usize().to_string();
+                    universal_lifetimes.insert(next_id, format!("ulft_{}", region_name));
+                } else {
+                    universal_lifetimes.insert(next_id, format!("ulft_{}", region_name));
+                    lifetime_names.insert(region_name, next_id);
+                }
+            },
+            ty::BoundRegionKind::BrAnon(_) => {
+                universal_lifetimes.insert(next_id, format!("ulft_{}", next_id.as_usize()));
+            },
+            _ => (),
+        }
+
+        num_late_bounds += 1;
+    }
+
+    // replace late-bound region variables by re-enumerating them in the same way as the MIR
+    // type checker does (that this happens in the same way is important to make the names
+    // line up!)
+    let mut next_index = num_early_bounds + 1; // skip over one additional due to static
+    let mut folder = |_| {
+        let cur_index = next_index;
+        next_index += 1;
+        ty::Region::new_var(env.tcx(), ty::RegionVid::from_u32(cur_index))
+    };
+    let (late_sig, _late_region_map) = env.tcx().replace_late_bound_regions(sig, &mut folder);
+
+    // replace early bound variables
+    let inputs: Vec<_> = late_sig
+        .inputs()
+        .iter()
+        .map(|ty| ty_instantiate(*ty, env.tcx(), subst_early_bounds))
+        .collect();
+
+    let output = ty_instantiate(late_sig.output(), env.tcx(), subst_early_bounds);
+
+    info!("Computed late region map {region_substitution_late:?}");
+
+    let region_map = EarlyLateRegionMap::new(
+        region_substitution_early,
+        region_substitution_late,
+        universal_lifetimes,
+        lifetime_names,
+    );
+    (inputs, output, region_map)
+}
+
+/// At the start of the function, there's a universal (placeholder) region for reference argument.
+/// These get subsequently relabeled.
+/// Given the relabeled region, find the original placeholder region.
+pub fn find_placeholder_region_for(r: ty::RegionVid, info: &PoloniusInfo) -> Option<ty::RegionVid> {
+    let root_location = Location {
+        block: BasicBlock::from_u32(0),
+        statement_index: 0,
+    };
+    let root_point = info.interner.get_point_index(&facts::Point {
+        location: root_location,
+        typ: facts::PointType::Start,
+    });
+
+    for (r1, r2, p) in &info.borrowck_in_facts.subset_base {
+        if *p == root_point && *r2 == r {
+            info!("find placeholder region for: {:?} ⊑ {:?} = r = {:?}", r1, r2, r);
+            return Some(*r1);
+        }
+    }
+    None
+}
+
+/// Filter the "interesting" constraints between universal lifetimes that need to hold
+/// (this does not include the constraints that need to hold for all universal lifetimes,
+/// e.g. that they outlive the function lifetime and are outlived by 'static).
+pub fn get_relevant_universal_constraints<'a>(
+    lifetime_scope: &EarlyLateRegionMap,
+    inclusion_tracker: &mut InclusionTracker,
+    info: &'a PoloniusInfo<'a, '_>,
+) -> Vec<(radium::UniversalLft, radium::UniversalLft)> {
+    let input_facts = &info.borrowck_in_facts;
+    let placeholder_subset = &input_facts.known_placeholder_subset;
+
+    let root_location = Location {
+        block: BasicBlock::from_u32(0),
+        statement_index: 0,
+    };
+    let root_point = info.interner.get_point_index(&facts::Point {
+        location: root_location,
+        typ: facts::PointType::Start,
+    });
+
+    let mut universal_constraints = Vec::new();
+
+    for (r1, r2) in placeholder_subset {
+        if let polonius_info::RegionKind::Universal(uk1) = info.get_region_kind(*r1) {
+            if let polonius_info::RegionKind::Universal(uk2) = info.get_region_kind(*r2) {
+                // if LHS is static, ignore.
+                if uk1 == polonius_info::UniversalRegionKind::Static {
+                    continue;
+                }
+                // if RHS is the function lifetime, ignore
+                if uk2 == polonius_info::UniversalRegionKind::Function {
+                    continue;
+                }
+
+                let to_universal = || {
+                    let x = lifetime_scope.lookup_region_with_kind(uk1, *r2)?;
+                    let y = lifetime_scope.lookup_region_with_kind(uk2, *r1)?;
+                    Some((x, y))
+                };
+                // else, add this constraint
+                // for the purpose of this analysis, it is fine to treat it as a dynamic inclusion
+                if let Some((x, y)) = to_universal() {
+                    inclusion_tracker.add_dynamic_inclusion(*r1, *r2, root_point);
+                    universal_constraints.push((x, y));
+                };
+            }
+        }
+    }
+    universal_constraints
+}
+
+/// Determine initial constraints between universal regions and local place regions.
+/// Returns an initial mapping for the name _map that initializes place regions of arguments
+/// with universals.
+pub fn get_initial_universal_arg_constraints<'a, 'tcx>(
+    info: &'a PoloniusInfo<'a, 'tcx>,
+    inclusion_tracker: &mut InclusionTracker<'a, 'tcx>,
+    _sig_args: &[Ty<'tcx>],
+    _local_args: &[Ty<'tcx>],
+) -> Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)> {
+    // Polonius generates a base subset constraint uregion ⊑ pregion.
+    // We turn that into pregion = uregion, as we do strong updates at the top-level.
+    let input_facts = &info.borrowck_in_facts;
+    let subset_base = &input_facts.subset_base;
+
+    let root_location = Location {
+        block: BasicBlock::from_u32(0),
+        statement_index: 0,
+    };
+    let root_point = info.interner.get_point_index(&facts::Point {
+        location: root_location,
+        typ: facts::PointType::Start,
+    });
+
+    // TODO: for nested references, this doesn't really seem to work.
+    // Problem is that we don't have constraints for the mapping of nested references.
+    // Potentially, we should instead just equalize the types
+
+    let mut initial_arg_mapping = Vec::new();
+    for (r1, r2, _) in subset_base {
+        let lft1 = info.mk_atomic_region(*r1);
+        let lft2 = info.mk_atomic_region(*r2);
+
+        let polonius_info::AtomicRegion::Universal(polonius_info::UniversalRegionKind::Local, _) = lft1
+        else {
+            continue;
+        };
+
+        // this is a constraint we care about here, add it
+        if inclusion_tracker.check_inclusion(*r1, *r2, root_point) {
+            continue;
+        }
+
+        inclusion_tracker.add_static_inclusion(*r1, *r2, root_point);
+        inclusion_tracker.add_static_inclusion(*r2, *r1, root_point);
+
+        assert!(matches!(lft2, polonius_info::AtomicRegion::PlaceRegion(_)));
+
+        initial_arg_mapping.push((lft1, lft2));
+    }
+    initial_arg_mapping
+}
+
+pub fn get_initial_universal_arg_constraints2<'tcx>(
+    sig_args: &[Ty<'tcx>],
+    local_args: &[Ty<'tcx>],
+) -> Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)> {
+    // Polonius generates a base subset constraint uregion ⊑ pregion.
+    // We turn that into pregion = uregion, as we do strong updates at the top-level.
+    assert!(sig_args.len() == local_args.len());
+
+    // TODO: implement a bitypefolder to solve this issue.
+    Vec::new()
+}
diff --git a/rr_frontend/translation/src/regions/mod.rs b/rr_frontend/translation/src/regions/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3a6039d326cc15413ec3307a569ebfcbe58851bf
--- /dev/null
+++ b/rr_frontend/translation/src/regions/mod.rs
@@ -0,0 +1,151 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Utilities for translating region information.
+
+mod arg_folder;
+pub mod assignment;
+pub mod calls;
+pub mod composite;
+pub mod inclusion_tracker;
+pub mod init;
+
+use std::collections::{BTreeMap, HashMap, HashSet};
+
+use derive_more::{Constructor, Debug};
+use log::{info, warn};
+use rr_rustc_interface::middle::ty;
+use rr_rustc_interface::middle::ty::TyCtxt;
+use ty::TypeSuperFoldable;
+
+use crate::base::Region;
+use crate::environment::borrowck::facts;
+use crate::environment::polonius_info;
+
+/// Collect all the regions appearing in a type.
+/// Data structure that maps early and late region indices inside functions to Polonius regions.
+#[derive(Constructor, Clone, Debug, Default)]
+pub struct EarlyLateRegionMap {
+    // maps indices of early and late regions to Polonius region ids
+    pub early_regions: Vec<Option<ty::RegionVid>>,
+    pub late_regions: Vec<ty::RegionVid>,
+
+    // maps Polonius region ids to names
+    pub region_names: BTreeMap<ty::RegionVid, radium::Lft>,
+
+    // maps source-level universal lifetime names to region ids
+    pub lft_names: HashMap<String, ty::RegionVid>,
+}
+impl EarlyLateRegionMap {
+    /// Lookup a Polonius region with a given kind.
+    pub fn lookup_region_with_kind(
+        &self,
+        k: polonius_info::UniversalRegionKind,
+        r: Region,
+    ) -> Option<radium::UniversalLft> {
+        match k {
+            polonius_info::UniversalRegionKind::Function => Some(radium::UniversalLft::Function),
+            polonius_info::UniversalRegionKind::Static => Some(radium::UniversalLft::Static),
+
+            polonius_info::UniversalRegionKind::Local => {
+                self.lookup_region(r).map(|x| radium::UniversalLft::Local(x.to_owned()))
+            },
+
+            polonius_info::UniversalRegionKind::External => {
+                self.lookup_region(r).map(|x| radium::UniversalLft::External(x.to_owned()))
+            },
+        }
+    }
+
+    pub fn lookup_region(&self, region: ty::RegionVid) -> Option<&radium::Lft> {
+        self.region_names.get(&region)
+    }
+
+    pub fn lookup_early_region(&self, idx: usize) -> Option<&radium::Lft> {
+        let ovid = self.early_regions.get(idx)?;
+        let vid = ovid.as_ref()?;
+        self.lookup_region(*vid)
+    }
+
+    pub fn lookup_late_region(&self, idx: usize) -> Option<&radium::Lft> {
+        let vid = self.late_regions.get(idx)?;
+        self.lookup_region(*vid)
+    }
+
+    pub fn translate_atomic_region(&self, r: &polonius_info::AtomicRegion) -> radium::Lft {
+        format_atomic_region_direct(r, Some(self))
+    }
+}
+
+/// Format the Coq representation of an atomic region.
+pub fn format_atomic_region_direct(
+    r: &polonius_info::AtomicRegion,
+    scope: Option<&EarlyLateRegionMap>,
+) -> String {
+    match r {
+        polonius_info::AtomicRegion::Loan(_, r) => format!("llft{}", r.index()),
+        polonius_info::AtomicRegion::PlaceRegion(r) => format!("plft{}", r.index()),
+        polonius_info::AtomicRegion::Unknown(r) => format!("vlft{}", r.index()),
+
+        polonius_info::AtomicRegion::Universal(_, r) => {
+            let Some(scope) = scope else {
+                return format!("ulft{}", r.index());
+            };
+
+            let Some(s) = scope.lookup_region(*r) else {
+                return format!("ulft{}", r.index());
+            };
+
+            s.to_string()
+        },
+    }
+}
+
+pub fn region_to_region_vid(r: ty::Region<'_>) -> facts::Region {
+    match r.kind() {
+        ty::RegionKind::ReVar(vid) => vid,
+        _ => panic!(),
+    }
+}
+
+/// A `TypeFolder` that finds all regions occurring in a type.
+pub struct TyRegionCollectFolder<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    regions: HashSet<Region>,
+}
+impl<'tcx> TyRegionCollectFolder<'tcx> {
+    pub fn new(tcx: TyCtxt<'tcx>) -> Self {
+        TyRegionCollectFolder {
+            tcx,
+            regions: HashSet::new(),
+        }
+    }
+
+    pub fn get_regions(self) -> HashSet<Region> {
+        self.regions
+    }
+}
+impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for TyRegionCollectFolder<'tcx> {
+    fn interner(&self) -> TyCtxt<'tcx> {
+        self.tcx
+    }
+
+    // TODO: handle the case that we pass below binders
+    fn fold_binder<T>(&mut self, t: ty::Binder<'tcx, T>) -> ty::Binder<'tcx, T>
+    where
+        T: ty::TypeFoldable<TyCtxt<'tcx>>,
+    {
+        t.super_fold_with(self)
+    }
+
+    fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
+        if let ty::RegionKind::ReVar(r) = r.kind() {
+            self.regions.insert(r);
+        }
+
+        r
+    }
+}
diff --git a/rr_frontend/translation/src/search.rs b/rr_frontend/translation/src/search.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f50038c4c5617380bfbb1ce7ed810df066d8aeb8
--- /dev/null
+++ b/rr_frontend/translation/src/search.rs
@@ -0,0 +1,318 @@
+// These functions have been adapted from Miri (https://github.com/rust-lang/miri/blob/31fb32e49f42df19b45baccb6aa80c3d726ed6d5/src/helpers.rs#L48) under the MIT license.
+
+//! Utility functions for finding Rust source objects.
+
+use std::mem;
+
+use log::{info, trace};
+use rr_rustc_interface::hir::def_id::{DefId, CRATE_DEF_INDEX};
+use rr_rustc_interface::middle::ty::{self, TyCtxt};
+use rr_rustc_interface::{middle, span};
+
+use crate::types;
+
+/// Gets an instance for a path.
+/// Taken from Miri <https://github.com/rust-lang/miri/blob/31fb32e49f42df19b45baccb6aa80c3d726ed6d5/src/helpers.rs#L48>.
+pub fn try_resolve_did_direct<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId>
+where
+    T: AsRef<str>,
+{
+    tcx.crates(())
+        .iter()
+        .find(|&&krate| tcx.crate_name(krate).as_str() == path[0].as_ref())
+        .and_then(|krate| {
+            let krate = DefId {
+                krate: *krate,
+                index: CRATE_DEF_INDEX,
+            };
+
+            let mut items: &[middle::metadata::ModChild] = tcx.module_children(krate);
+            let mut path_it = path.iter().skip(1).peekable();
+
+            while let Some(segment) = path_it.next() {
+                for item in mem::take(&mut items) {
+                    let item: &middle::metadata::ModChild = item;
+                    if item.ident.name.as_str() == segment.as_ref() {
+                        if path_it.peek().is_none() {
+                            return Some(item.res.def_id());
+                        }
+
+                        items = tcx.module_children(item.res.def_id());
+                        break;
+                    }
+                }
+            }
+            None
+        })
+}
+
+pub fn try_resolve_did<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId>
+where
+    T: AsRef<str>,
+{
+    if let Some(did) = try_resolve_did_direct(tcx, path) {
+        return Some(did);
+    }
+
+    // if the first component is "std", try if we can replace it with "alloc" or "core"
+    if path[0].as_ref() == "std" {
+        let mut components: Vec<_> = path.iter().map(|x| x.as_ref().to_owned()).collect();
+        components[0] = "core".to_owned();
+        if let Some(did) = try_resolve_did_direct(tcx, &components) {
+            return Some(did);
+        }
+        // try "alloc"
+        components[0] = "alloc".to_owned();
+        try_resolve_did_direct(tcx, &components)
+    } else {
+        None
+    }
+}
+
+/// Determine whether the two argument lists match for the type positions (ignoring consts and regions).
+/// The first argument is the authority determining which argument positions are types.
+/// The second argument may contain `None` for non-type positions.
+fn args_match_types<'tcx>(
+    reference: &[ty::GenericArg<'tcx>],
+    compare: &[Option<ty::GenericArg<'tcx>>],
+) -> bool {
+    if reference.len() != compare.len() {
+        return false;
+    }
+
+    for (arg1, arg2) in reference.iter().zip(compare.iter()) {
+        if let Some(ty1) = arg1.as_type() {
+            if let Some(arg2) = arg2 {
+                if let Some(ty2) = arg2.as_type() {
+                    if ty1 != ty2 {
+                        return false;
+                    }
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        }
+    }
+    true
+}
+
+/// Try to resolve the `DefId` of an implementation of a trait for a particular type.
+/// Note that this does not, in general, find a unique solution, in case there are complex things
+/// with different where clauses going on.
+pub fn try_resolve_trait_impl_did<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    trait_did: DefId,
+    trait_args: &[Option<ty::GenericArg<'tcx>>],
+    for_type: ty::Ty<'tcx>,
+) -> Option<DefId> {
+    // get all impls of this trait
+    let impls: &ty::trait_def::TraitImpls = tcx.trait_impls_of(trait_did);
+
+    let simplified_type =
+        middle::ty::fast_reject::simplify_type(tcx, for_type, ty::fast_reject::TreatParams::AsCandidateKey)?;
+    let defs = impls.non_blanket_impls().get(&simplified_type)?;
+    info!("found implementations: {:?}", impls);
+
+    let mut solution = None;
+    for did in defs {
+        let impl_self_ty: ty::Ty<'tcx> = tcx.type_of(did).instantiate_identity();
+        let impl_self_ty = types::normalize_in_function(*did, tcx, impl_self_ty).unwrap();
+
+        // check if this is an implementation for the right type
+        // TODO: is this the right way to compare the types?
+        if impl_self_ty == for_type {
+            let impl_ref: Option<ty::EarlyBinder<ty::TraitRef<'_>>> = tcx.impl_trait_ref(did);
+
+            if let Some(impl_ref) = impl_ref {
+                let impl_ref = types::normalize_in_function(*did, tcx, impl_ref.skip_binder()).unwrap();
+
+                let this_impl_args = impl_ref.args;
+                // filter by the generic instantiation for the trait
+                info!("found impl with args {:?}", this_impl_args);
+                // args has self at position 0 and generics of the trait at position 1..
+
+                // check if the generic argument types match up
+                if !args_match_types(&this_impl_args.as_slice()[1..], trait_args) {
+                    continue;
+                }
+
+                info!("found impl {:?}", impl_ref);
+                if solution.is_some() {
+                    println!(
+                        "Warning: Ambiguous resolution for impl of trait {:?} on type {:?}; solution {:?} but found also {:?}",
+                        trait_did,
+                        for_type,
+                        solution.unwrap(),
+                        impl_ref.def_id,
+                    );
+                } else {
+                    solution = Some(*did);
+                }
+            }
+        }
+    }
+
+    solution
+}
+
+/// Try to resolve the `DefId` of a method in an implementation of a trait for a particular type.
+/// Note that this does not, in general, find a unique solution, in case there are complex things
+/// with different where clauses going on.
+pub fn try_resolve_trait_method_did<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    trait_did: DefId,
+    trait_args: &[Option<ty::GenericArg<'tcx>>],
+    method_name: &str,
+    for_type: ty::Ty<'tcx>,
+) -> Option<DefId> {
+    // get all impls of this trait
+    let impls: &ty::trait_def::TraitImpls = tcx.trait_impls_of(trait_did);
+
+    let simplified_type =
+        middle::ty::fast_reject::simplify_type(tcx, for_type, ty::fast_reject::TreatParams::AsCandidateKey)?;
+    let defs = impls.non_blanket_impls().get(&simplified_type)?;
+    info!("found implementations: {:?}", impls);
+
+    let mut solution = None;
+    for did in defs {
+        let impl_self_ty: ty::Ty<'tcx> = tcx.type_of(did).instantiate_identity();
+        let impl_self_ty = types::normalize_in_function(*did, tcx, impl_self_ty).unwrap();
+
+        // check if this is an implementation for the right type
+        // TODO: is this the right way to compare the types?
+        if impl_self_ty == for_type {
+            let impl_ref: Option<ty::EarlyBinder<ty::TraitRef<'_>>> = tcx.impl_trait_ref(did);
+
+            if let Some(impl_ref) = impl_ref {
+                let impl_ref = types::normalize_in_function(*did, tcx, impl_ref.skip_binder()).unwrap();
+
+                let this_impl_args = impl_ref.args;
+                // filter by the generic instantiation for the trait
+                info!("found impl with args {:?}", this_impl_args);
+                // args has self at position 0 and generics of the trait at position 1..
+
+                // check if the generic argument types match up
+                if !args_match_types(&this_impl_args.as_slice()[1..], trait_args) {
+                    continue;
+                }
+
+                let impl_assoc_items: &ty::AssocItems = tcx.associated_items(did);
+                // find the right item
+                if let Some(item) = impl_assoc_items.find_by_name_and_kind(
+                    tcx,
+                    span::symbol::Ident::from_str(method_name),
+                    ty::AssocKind::Fn,
+                    trait_did,
+                ) {
+                    info!("found impl {:?} with item {:?}", impl_ref, item);
+                    if solution.is_some() {
+                        println!(
+                            "Warning: Ambiguous resolution for method {method_name} of trait {:?} on type {:?}; solution {:?} but found also {:?}",
+                            trait_did,
+                            for_type,
+                            solution.unwrap(),
+                            item.def_id
+                        );
+                    } else {
+                        solution = Some(item.def_id);
+                    }
+                }
+            }
+        }
+    }
+
+    solution
+}
+
+/// Try to get a defid of a method at the given path.
+/// This does not handle trait methods.
+/// This also does not handle overlapping method definitions/specialization well.
+pub fn try_resolve_method_did_direct<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId>
+where
+    T: AsRef<str>,
+{
+    tcx.crates(())
+        .iter()
+        .find(|&&krate| tcx.crate_name(krate).as_str() == path[0].as_ref())
+        .and_then(|krate| {
+            let krate = DefId {
+                krate: *krate,
+                index: CRATE_DEF_INDEX,
+            };
+
+            let mut items: &[middle::metadata::ModChild] = tcx.module_children(krate);
+            let mut path_it = path.iter().skip(1).peekable();
+
+            while let Some(segment) = path_it.next() {
+                //info!("items to look at: {:?}", items);
+                for item in mem::take(&mut items) {
+                    let item: &middle::metadata::ModChild = item;
+
+                    if item.ident.name.as_str() != segment.as_ref() {
+                        continue;
+                    }
+
+                    info!("taking path: {:?}", segment.as_ref());
+                    if path_it.peek().is_none() {
+                        return Some(item.res.def_id());
+                    }
+
+                    // just the method remaining
+                    if path_it.len() != 1 {
+                        items = tcx.module_children(item.res.def_id());
+                        break;
+                    }
+
+                    let did: DefId = item.res.def_id();
+                    let impls: &[DefId] = tcx.inherent_impls(did);
+                    info!("trying to find method among impls {:?}", impls);
+
+                    let find = path_it.next().unwrap();
+                    for impl_did in impls {
+                        //let ty = tcx.type_of(*impl_did);
+                        //info!("type of impl: {:?}", ty);
+                        let items: &ty::AssocItems = tcx.associated_items(impl_did);
+                        //info!("items here: {:?}", items);
+                        // TODO more robust error handling if there are multiple matches.
+                        for item in items.in_definition_order() {
+                            //info!("comparing: {:?} with {:?}", item.name.as_str(), find);
+                            if item.name.as_str() == find.as_ref() {
+                                return Some(item.def_id);
+                            }
+                        }
+                        //info!("impl items: {:?}", items);
+                    }
+
+                    //info!("inherent impls for {:?}: {:?}", did, impls);
+                    return None;
+                }
+            }
+
+            None
+        })
+}
+
+pub fn try_resolve_method_did<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId>
+where
+    T: AsRef<str>,
+{
+    if let Some(did) = try_resolve_method_did_direct(tcx, path) {
+        return Some(did);
+    }
+
+    // if the first component is "std", try if we can replace it with "alloc" or "core"
+    if path[0].as_ref() == "std" {
+        let mut components: Vec<_> = path.iter().map(|x| x.as_ref().to_owned()).collect();
+        components[0] = "core".to_owned();
+        if let Some(did) = try_resolve_method_did_direct(tcx, &components) {
+            return Some(did);
+        }
+        // try "alloc"
+        components[0] = "alloc".to_owned();
+        try_resolve_method_did_direct(tcx, &components)
+    } else {
+        None
+    }
+}
diff --git a/rr_frontend/translation/src/shims/flat.rs b/rr_frontend/translation/src/shims/flat.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cdee3783b035e51c37c0047453e189a2a5d47a03
--- /dev/null
+++ b/rr_frontend/translation/src/shims/flat.rs
@@ -0,0 +1,230 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Provides a flat representation of types that is stable across compilations.
+
+use log::{info, trace};
+use rr_rustc_interface::hir;
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::ty::{self, TyCtxt};
+use serde::{Deserialize, Serialize};
+
+use crate::spec_parsers::get_export_as_attr;
+use crate::{attrs, search, Environment};
+
+/// An item path that receives generic arguments.
+#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
+pub struct PathWithArgs {
+    path: Vec<String>,
+    /// An encoding of the GenericArgs for this definition.
+    /// This is `Some(ty)` if:
+    /// - the argument represents a type (not a constant or region)
+    /// - and the arg is not the trivial identity arg (in case of ADTs)
+    args: Vec<Option<Type>>,
+}
+
+impl PathWithArgs {
+    pub fn to_item<'tcx>(&self, tcx: ty::TyCtxt<'tcx>) -> Option<(DefId, Vec<Option<ty::GenericArg<'tcx>>>)> {
+        let did = search::try_resolve_did(tcx, self.path.as_slice())?;
+
+        let mut ty_args = Vec::new();
+
+        for arg in &self.args {
+            if let Some(arg) = arg {
+                let ty = arg.to_type(tcx)?;
+                ty_args.push(Some(ty::GenericArg::from(ty)));
+            } else {
+                ty_args.push(None);
+            }
+        }
+
+        Some((did, ty_args))
+    }
+
+    /// `args` should be normalized already.
+    pub fn from_item<'tcx>(
+        env: &Environment<'tcx>,
+        did: DefId,
+        args: &[ty::GenericArg<'tcx>],
+    ) -> Option<Self> {
+        let path = get_export_path_for_did(env, did);
+        let mut flattened_args = Vec::new();
+        // TODO: how to represent type variables in case the definition is open?
+        let mut index = 0;
+        info!("flattening args {:?}", args);
+        for arg in args {
+            if let Some(ty) = arg.as_type() {
+                // TODO not quite right yet (see above)
+                if !ty.is_param(index) {
+                    let flattened_ty = convert_ty_to_flat_type(env, ty)?;
+                    flattened_args.push(Some(flattened_ty));
+                }
+                index += 1;
+            } else {
+                flattened_args.push(None);
+            }
+        }
+        Some(Self {
+            path,
+            args: flattened_args,
+        })
+    }
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(remote = "ty::IntTy")]
+pub enum IntTyDef {
+    Isize,
+    I8,
+    I16,
+    I32,
+    I64,
+    I128,
+}
+#[derive(Serialize, Deserialize)]
+#[serde(remote = "ty::UintTy")]
+pub enum UintTyDef {
+    Usize,
+    U8,
+    U16,
+    U32,
+    U64,
+    U128,
+}
+
+#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
+/// A "flattened" representation of types that should be suitable serialized storage, and should be
+/// stable enough to resolve to the same actual type across compilations.
+/// Currently mostly geared to our trait resolution needs.
+pub enum Type {
+    /// Path + generic args
+    /// empty args represents the identity substitution
+    Adt(PathWithArgs),
+    #[serde(with = "IntTyDef")]
+    Int(ty::IntTy),
+    #[serde(with = "UintTyDef")]
+    Uint(ty::UintTy),
+    Char,
+    Bool,
+    // TODO: more cases
+}
+
+impl Type {
+    /// Try to convert a flat type to a type.
+    pub fn to_type<'tcx>(&self, tcx: ty::TyCtxt<'tcx>) -> Option<ty::Ty<'tcx>> {
+        match self {
+            Self::Adt(path_with_args) => {
+                let (did, flat_args) = path_with_args.to_item(tcx)?;
+
+                let ty: ty::EarlyBinder<ty::Ty<'tcx>> = tcx.type_of(did);
+                let ty::TyKind::Adt(_, args) = ty.skip_binder().kind() else {
+                    return None;
+                };
+
+                // build substitution
+                let mut substs = Vec::new();
+                for (ty_arg, flat_arg) in args.iter().zip(flat_args.into_iter()) {
+                    match ty_arg.unpack() {
+                        ty::GenericArgKind::Type(_) => {
+                            if let Some(flat_arg) = flat_arg {
+                                substs.push(flat_arg);
+                            }
+                        },
+                        _ => {
+                            substs.push(ty_arg);
+                        },
+                    }
+                }
+
+                // substitute
+                info!("substituting {:?} with {:?}", ty, substs);
+                let subst_ty =
+                    if substs.is_empty() { ty.instantiate_identity() } else { ty.instantiate(tcx, &substs) };
+
+                Some(subst_ty)
+            },
+            Self::Bool => Some(tcx.mk_ty_from_kind(ty::TyKind::Bool)),
+            Self::Char => Some(tcx.mk_ty_from_kind(ty::TyKind::Char)),
+            Self::Int(it) => Some(tcx.mk_ty_from_kind(ty::TyKind::Int(it.to_owned()))),
+            Self::Uint(it) => Some(tcx.mk_ty_from_kind(ty::TyKind::Uint(it.to_owned()))),
+        }
+    }
+}
+
+/// Try to convert a type to a flat type. Assumes the type has been normalized already.
+pub fn convert_ty_to_flat_type<'tcx>(env: &Environment<'tcx>, ty: ty::Ty<'tcx>) -> Option<Type> {
+    match ty.kind() {
+        ty::TyKind::Adt(def, args) => {
+            let did = def.did();
+            // TODO: if this is downcast to a variant, this might not work
+            let path_with_args = PathWithArgs::from_item(env, did, args.as_slice())?;
+            Some(Type::Adt(path_with_args))
+        },
+        ty::TyKind::Bool => Some(Type::Bool),
+        ty::TyKind::Char => Some(Type::Char),
+        ty::TyKind::Int(it) => Some(Type::Int(it.to_owned())),
+        ty::TyKind::Uint(it) => Some(Type::Uint(it.to_owned())),
+        _ => None,
+    }
+}
+
+pub fn get_cleaned_def_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> {
+    let def_path = tcx.def_path_str(did);
+    // we clean this up a bit and segment it
+    let mut components = Vec::new();
+    for i in def_path.split("::") {
+        if i.starts_with('<') && i.ends_with('>') {
+            // this is a generic specialization, skip
+            continue;
+        }
+        components.push(i.to_owned());
+    }
+    info!("split def path {:?} to {:?}", def_path, components);
+
+    components
+}
+
+// Alternative implementation of `get_cleaned_def_path`, but this doesn't always yield a rooted
+// path (but only a suffix of the full path)
+fn extract_def_path(path: &hir::definitions::DefPath) -> Vec<String> {
+    let mut components = Vec::new();
+    for p in &path.data {
+        if let Some(name) = p.data.get_opt_name() {
+            components.push(name.as_str().to_owned());
+        }
+    }
+    components
+}
+
+/// Get the path we should export an item at.
+pub fn get_export_path_for_did(env: &Environment, did: DefId) -> Vec<String> {
+    let attrs = env.get_attributes(did);
+
+    if attrs::has_tool_attr(attrs, "export_as") {
+        let filtered_attrs = attrs::filter_for_tool(attrs);
+
+        return get_export_as_attr(filtered_attrs.as_slice()).unwrap();
+    }
+
+    // Check for an annotation on the surrounding impl
+    if let Some(impl_did) = env.tcx().impl_of_method(did) {
+        let attrs = env.get_attributes(impl_did);
+
+        if attrs::has_tool_attr(attrs, "export_as") {
+            let filtered_attrs = attrs::filter_for_tool(attrs);
+            let mut path_prefix = get_export_as_attr(filtered_attrs.as_slice()).unwrap();
+
+            // push the last component of this path
+            //let def_path = env.tcx().def_path(did);
+            let mut this_path = get_cleaned_def_path(env.tcx(), did);
+            path_prefix.push(this_path.pop().unwrap());
+
+            return path_prefix;
+        }
+    }
+
+    get_cleaned_def_path(env.tcx(), did)
+}
diff --git a/rr_frontend/translation/src/shims/mod.rs b/rr_frontend/translation/src/shims/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8fa4dea374af1515b90185ef896aad251b731b02
--- /dev/null
+++ b/rr_frontend/translation/src/shims/mod.rs
@@ -0,0 +1,49 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+pub mod flat;
+pub mod registry;
+
+use std::collections::HashMap;
+use std::fs::File;
+use std::path::{Path, PathBuf};
+use std::{fs, io};
+
+/// Find `RefinedRust` modules in the given loadpath.
+fn scan_loadpath(path: &Path, storage: &mut HashMap<String, PathBuf>) -> io::Result<()> {
+    if path.is_dir() {
+        for entry in fs::read_dir(path)? {
+            let entry = entry?;
+            let path = entry.path();
+            if path.is_dir() {
+                scan_loadpath(path.as_path(), storage)?;
+            } else if path.is_file() {
+                if let Some(ext) = path.extension() {
+                    if Some("rrlib") == ext.to_str() {
+                        // try to open this rrlib file
+                        let f = File::open(path.clone())?;
+                        if let Some(name) = registry::is_valid_refinedrust_module(f) {
+                            storage.insert(name, path);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    Ok(())
+}
+
+/// Find `RefinedRust` modules in the given loadpaths.
+pub fn scan_loadpaths(paths: &[PathBuf]) -> io::Result<HashMap<String, PathBuf>> {
+    let mut found_lib_files: HashMap<String, PathBuf> = HashMap::new();
+
+    for path in paths {
+        scan_loadpath(path, &mut found_lib_files)?;
+    }
+
+    Ok(found_lib_files)
+}
diff --git a/rr_frontend/translation/src/shim_registry.rs b/rr_frontend/translation/src/shims/registry.rs
similarity index 96%
rename from rr_frontend/translation/src/shim_registry.rs
rename to rr_frontend/translation/src/shims/registry.rs
index e6e02472ea0c9a3bf3e6bbabda41ac06425d9aa6..a7a9cc40d40359c5e1557250033d5463840e57eb 100644
--- a/rr_frontend/translation/src/shim_registry.rs
+++ b/rr_frontend/translation/src/shims/registry.rs
@@ -4,10 +4,11 @@
 // If a copy of the BSD-3-clause license was not distributed with this
 // file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
 
+//! Registry of shims for Rust functions that get mapped to custom `RefinedRust`
+//! implementations.
+//! Provides deserialization from a JSON file defining this registry.
+
 use std::collections::{HashMap, HashSet};
-/// Registry of shims for Rust functions that get mapped to custom `RefinedRust`
-/// implementations.
-/// Provides deserialization from a JSON file defining this registry.
 use std::fs::File;
 use std::io::{BufReader, BufWriter};
 
@@ -16,7 +17,7 @@ use radium::coq;
 use serde::{Deserialize, Serialize};
 use typed_arena::Arena;
 
-use crate::utils::*;
+use crate::shims::flat;
 
 type Path<'a> = Vec<&'a str>;
 
@@ -62,9 +63,9 @@ struct ShimTraitEntry {
 #[derive(Serialize, Deserialize)]
 struct ShimTraitImplEntry {
     /// The rustc path for the trait
-    trait_path: PathWithArgs,
+    trait_path: flat::PathWithArgs,
     /// for which type is this implementation?
-    for_type: FlatType,
+    for_type: flat::Type,
     // TODO: additional constraints like the required clauses for disambiguation
     /// a kind: always "trait_impl"
     kind: String,
@@ -86,9 +87,9 @@ struct ShimTraitImplEntry {
 #[derive(Serialize, Deserialize)]
 struct ShimTraitMethodImplEntry {
     /// The rustc path for the trait
-    trait_path: PathWithArgs,
+    trait_path: flat::PathWithArgs,
     /// for which type is this implementation?
-    for_type: FlatType,
+    for_type: flat::Type,
     // TODO: additional constraints like the required clauses for disambiguation
     /// The method identifier
     method_ident: String,
@@ -146,8 +147,8 @@ impl<'a> From<FunctionShim<'a>> for ShimFunctionEntry {
 
 #[derive(Clone, Eq, PartialEq, Debug)]
 pub struct TraitImplShim {
-    pub trait_path: PathWithArgs,
-    pub for_type: FlatType,
+    pub trait_path: flat::PathWithArgs,
+    pub for_type: flat::Type,
 
     pub method_specs: HashMap<String, (String, String)>,
 
@@ -173,9 +174,9 @@ impl From<TraitImplShim> for ShimTraitImplEntry {
 
 #[derive(Clone, Eq, PartialEq, Debug)]
 pub struct TraitMethodImplShim {
-    pub trait_path: PathWithArgs,
+    pub trait_path: flat::PathWithArgs,
     pub method_ident: String,
-    pub for_type: FlatType,
+    pub for_type: flat::Type,
     pub name: String,
     pub spec_name: String,
 }
@@ -243,16 +244,9 @@ impl<'a> From<AdtShim<'a>> for ShimAdtEntry {
     }
 }
 
-pub struct ModuleSummary<'a> {
-    /// function/method shims
-    function_shims: Vec<FunctionShim<'a>>,
-    /// adt shims
-    adt_shims: Vec<AdtShim<'a>>,
-}
-
 /// Registry of function shims loaded by the user. Substitutes path to the function/method with a
 /// code definition name and a spec name.
-pub struct ShimRegistry<'a> {
+pub struct SR<'a> {
     arena: &'a Arena<String>,
 
     /// function/method shims
@@ -277,7 +271,7 @@ pub struct ShimRegistry<'a> {
     dependencies: Vec<coq::module::DirPath>,
 }
 
-impl<'a> ShimRegistry<'a> {
+impl<'a> SR<'a> {
     fn get_shim_kind(v: &serde_json::Value) -> Result<ShimKind, String> {
         let obj = v.as_object().ok_or_else(|| "element is not an object".to_owned())?;
         let vk = obj.get("kind").ok_or_else(|| "object does not have \"kind\" attribute".to_owned())?;
@@ -316,7 +310,7 @@ impl<'a> ShimRegistry<'a> {
         }
     }
 
-    pub fn new(arena: &'a Arena<String>) -> Result<ShimRegistry<'a>, String> {
+    pub fn new(arena: &'a Arena<String>) -> Result<SR<'a>, String> {
         let mut reg = Self::empty(arena);
 
         match rrconfig::shim_file() {
diff --git a/rr_frontend/translation/src/spec_parsers/enum_spec_parser.rs b/rr_frontend/translation/src/spec_parsers/enum_spec_parser.rs
index e64f1ae0a69bf603ba9a15246cd576b35c961b3e..6e303e39e694bacd7beb2fa8bfc9f6ee0cb27f7a 100644
--- a/rr_frontend/translation/src/spec_parsers/enum_spec_parser.rs
+++ b/rr_frontend/translation/src/spec_parsers/enum_spec_parser.rs
@@ -4,8 +4,6 @@
 // If a copy of the BSD-3-clause license was not distributed with this
 // file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
 
-use std::collections::HashMap;
-
 use attribute_parse::{parse, MToken};
 use parse::Peek;
 use radium::{coq, specs};
diff --git a/rr_frontend/translation/src/spec_parsers/parse_utils.rs b/rr_frontend/translation/src/spec_parsers/parse_utils.rs
index 158ad51e8155e9d023be580155fd2a10e49862e6..74bb004643bf9afcd300310e57970c058d39a926 100644
--- a/rr_frontend/translation/src/spec_parsers/parse_utils.rs
+++ b/rr_frontend/translation/src/spec_parsers/parse_utils.rs
@@ -5,7 +5,7 @@
 // file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
 
 /// This provides some general utilities for RefinedRust-specific attribute parsing.
-use std::collections::{HashMap, HashSet};
+use std::collections::HashSet;
 
 use attribute_parse::{parse, MToken};
 use lazy_static::lazy_static;
diff --git a/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs b/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs
index bf48d7842e753aa556ce2d69d1016c2ebff95107..a7622a512f97b92e1daa125485102535c09989b3 100644
--- a/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs
+++ b/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs
@@ -4,7 +4,6 @@
 // If a copy of the BSD-3-clause license was not distributed with this
 // file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
 
-use std::collections::HashMap;
 use std::convert::Into;
 
 /// Parsing of `RefinedRust` struct specifications.
diff --git a/rr_frontend/translation/src/spec_parsers/trait_attr_parser.rs b/rr_frontend/translation/src/spec_parsers/trait_attr_parser.rs
index 6cc516061b316481da7d90a7306e01352ca01bad..b251ecaff8e3585110a0c528a198f6a9bbcc4703 100644
--- a/rr_frontend/translation/src/spec_parsers/trait_attr_parser.rs
+++ b/rr_frontend/translation/src/spec_parsers/trait_attr_parser.rs
@@ -10,7 +10,6 @@ use attribute_parse::{parse, MToken};
 use derive_more::Constructor;
 use radium::coq;
 use rr_rustc_interface::ast::ast::AttrItem;
-use rr_rustc_interface::hir::def_id::LocalDefId;
 
 use crate::spec_parsers::parse_utils::*;
 
diff --git a/rr_frontend/translation/src/spec_parsers/trait_impl_attr_parser.rs b/rr_frontend/translation/src/spec_parsers/trait_impl_attr_parser.rs
index 35ad963cfbe0a630fc199962259246d6f1d84956..d6afa9d0657fade8410ffab92aa8978f61e13e1a 100644
--- a/rr_frontend/translation/src/spec_parsers/trait_impl_attr_parser.rs
+++ b/rr_frontend/translation/src/spec_parsers/trait_impl_attr_parser.rs
@@ -8,7 +8,6 @@ use std::collections::HashMap;
 
 use attribute_parse::{parse, MToken};
 use rr_rustc_interface::ast::ast::AttrItem;
-use rr_rustc_interface::hir::def_id::LocalDefId;
 
 use crate::spec_parsers::parse_utils::*;
 
diff --git a/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs b/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs
index 88e0c9a5962d3c2ffc6d7ae0553a4a262b34c7b6..76226758a0cdf266302e7774b521fc61e331a9e2 100644
--- a/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs
+++ b/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs
@@ -242,6 +242,7 @@ pub struct VerboseFunctionSpecParser<'a, 'def, F, T> {
     got_ret: bool,
 }
 
+/// Extra requirements of a function.
 #[derive(Default)]
 pub struct FunctionRequirements {
     /// additional late coq parameters
diff --git a/rr_frontend/translation/src/traits/mod.rs b/rr_frontend/translation/src/traits/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4fa27a4b07d25699c79384565cae83cb51a73307
--- /dev/null
+++ b/rr_frontend/translation/src/traits/mod.rs
@@ -0,0 +1,84 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+use std::collections::HashMap;
+
+use derive_more::Display;
+use log::{info, trace};
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::ty;
+
+use crate::environment::Environment;
+
+pub mod registry;
+pub mod requirements;
+pub mod resolution;
+
+#[derive(Debug, Clone, Display)]
+pub enum Error<'tcx> {
+    /// This DefId is not a trait
+    #[display("The given DefId {:?} is not a trait", _0)]
+    NotATrait(DefId),
+    /// This DefId is not an impl of a trait
+    #[display("The given DefId {:?} is not a trait implementation", _0)]
+    NotATraitImpl(DefId),
+    /// This DefId is not a trait method
+    #[display("The given DefId {:?} is not a trait method", _0)]
+    NotATraitMethod(DefId),
+    /// This DefId is not an assoc type
+    #[display("The given DefId {:?} is not an associated type", _0)]
+    NotAnAssocType(DefId),
+    /// This trait already exists
+    #[display("This trait {:?} already has been registered", _0)]
+    TraitAlreadyExists(DefId),
+    /// This trait impl already exists
+    #[display("This trait impl {:?} already has been registered", _0)]
+    ImplAlreadyExists(DefId),
+    /// Trait hasn't been registered yet but is used
+    #[display("This trait {:?} has not been registered yet", _0)]
+    UnregisteredTrait(DefId),
+    /// Trait impl hasn't been registered yet but is used
+    #[display("This trait impl {:?} has not been registered yet", _0)]
+    UnregisteredImpl(DefId),
+    /// Cannot find this trait instance in the local environment
+    #[display("An instance for this trait {:?} cannot by found with generic args {:?}", _0, _1)]
+    UnknownLocalInstance(DefId, ty::GenericArgsRef<'tcx>),
+    #[display("An error occurred when parsing the specification of the trait {:?}: {:?}", _0, _1)]
+    TraitSpec(DefId, String),
+    #[display("An error occurred when parsing the specification of the trait impl {:?}: {:?}", _0, _1)]
+    TraitImplSpec(DefId, String),
+    /// Unknown error
+    #[display("Unknown Error")]
+    Unknown,
+}
+pub type TraitResult<'tcx, T> = Result<T, Error<'tcx>>;
+
+/// Given a particular reference to a trait, get the associated type constraints for this trait reference.
+pub fn get_trait_assoc_constraints<'tcx>(
+    env: &Environment<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    trait_ref: ty::TraitRef<'tcx>,
+) -> HashMap<String, ty::Ty<'tcx>> {
+    let mut assoc_ty_map = HashMap::new();
+
+    // TODO: check if caller_bounds does the right thing for implied bounds
+    let clauses = param_env.caller_bounds();
+    for cl in clauses {
+        let cl_kind = cl.kind();
+        let cl_kind = cl_kind.skip_binder();
+
+        // only look for trait predicates for now
+        if let ty::ClauseKind::Projection(proj) = cl_kind {
+            if trait_ref.def_id == proj.trait_def_id(env.tcx()) && trait_ref.args == proj.projection_ty.args {
+                // same trait and same args
+                let name = env.get_assoc_item_name(proj.def_id()).unwrap();
+                let ty = proj.term.ty().unwrap();
+                assoc_ty_map.insert(name, ty);
+            }
+        }
+    }
+    assoc_ty_map
+}
diff --git a/rr_frontend/translation/src/trait_registry.rs b/rr_frontend/translation/src/traits/registry.rs
similarity index 70%
rename from rr_frontend/translation/src/trait_registry.rs
rename to rr_frontend/translation/src/traits/registry.rs
index 9e4835690c8b8dc5cebaf311635b5fa3b2e92059..81b97f1bec8f8943cf5752cf41c48a6466ef777f 100644
--- a/rr_frontend/translation/src/trait_registry.rs
+++ b/rr_frontend/translation/src/traits/registry.rs
@@ -1,112 +1,33 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
 use std::cell::RefCell;
-use std::cmp::Ordering;
 use std::collections::{HashMap, HashSet};
 use std::fmt::Write;
-use std::string::ToString;
 
-use derive_more::{Constructor, Display};
 use log::{info, trace};
 use radium::{self, coq, specs};
 use rr_rustc_interface::hir::def_id::{DefId, LocalDefId};
-use rr_rustc_interface::middle::{self, ty};
+use rr_rustc_interface::middle::ty;
+use traits::{resolution, Error, TraitResult};
 use typed_arena::Arena;
 
 use crate::base::TranslationError;
+use crate::body::signature;
 use crate::environment::Environment;
-use crate::function_body::{get_arg_syntypes_for_procedure_call, mangle_name_with_args, FunctionTranslator};
 use crate::spec_parsers::propagate_method_attr_from_impl;
 use crate::spec_parsers::trait_attr_parser::{TraitAttrParser, VerboseTraitAttrParser};
 use crate::spec_parsers::trait_impl_attr_parser::{TraitImplAttrParser, VerboseTraitImplAttrParser};
-use crate::type_translator::{
-    generate_args_inst_key, strip_coq_ident, GenericsKey, LocalTypeTranslator, ParamScope, TranslationState,
-    TypeTranslator,
-};
-use crate::{traits, utils};
-
-#[derive(Debug, Clone, Display)]
-pub enum Error<'tcx> {
-    /// This DefId is not a trait
-    #[display("The given DefId {:?} is not a trait", _0)]
-    NotATrait(DefId),
-    /// This DefId is not an impl of a trait
-    #[display("The given DefId {:?} is not a trait implementation", _0)]
-    NotATraitImpl(DefId),
-    /// This DefId is not a trait method
-    #[display("The given DefId {:?} is not a trait method", _0)]
-    NotATraitMethod(DefId),
-    /// This DefId is not an assoc type
-    #[display("The given DefId {:?} is not an associated type", _0)]
-    NotAnAssocType(DefId),
-    /// This trait already exists
-    #[display("This trait {:?} already has been registered", _0)]
-    TraitAlreadyExists(DefId),
-    /// This trait impl already exists
-    #[display("This trait impl {:?} already has been registered", _0)]
-    ImplAlreadyExists(DefId),
-    /// Trait hasn't been registered yet but is used
-    #[display("This trait {:?} has not been registered yet", _0)]
-    UnregisteredTrait(DefId),
-    /// Trait impl hasn't been registered yet but is used
-    #[display("This trait impl {:?} has not been registered yet", _0)]
-    UnregisteredImpl(DefId),
-    /// Cannot find this trait instance in the local environment
-    #[display("An instance for this trait {:?} cannot by found with generic args {:?}", _0, _1)]
-    UnknownLocalInstance(DefId, ty::GenericArgsRef<'tcx>),
-    #[display("An error occurred when parsing the specification of the trait {:?}: {:?}", _0, _1)]
-    TraitSpec(DefId, String),
-    #[display("An error occurred when parsing the specification of the trait impl {:?}: {:?}", _0, _1)]
-    TraitImplSpec(DefId, String),
-    /// Unknown error
-    #[display("Unknown Error")]
-    Unknown,
-}
-pub type TraitResult<'tcx, T> = Result<T, Error<'tcx>>;
-
-/// What does a trait registry need?
-///
-/// for reach Rust trait:
-///   - mapping to the Coq representation, i.e. functions with specifications (`FunctionSpec`?) as well as
-///     types
-///   - list of impls we have translated and which we will emit.
-///
-/// for each generic in the function scope: (-> `LocalTraitRegistry`)
-///   - list of identifiers to the trait instance, and means to add that to the spec later
-///     + these are Param instances that we cannot statically resolve
-///
-///   - a list of trait uses: next to the generic args, what trait implementations do we assume to be
-///     available for types? e.g. we might require a trait instance of `Ord` for (T, i32) where T is a generic
-///     arg. This instance might not be a parameter, but concretely resolved. i.e. we have a pair of a
-///     location, but get the spec for the trait impl as part of the thing.
-///   => or do we want to merge these two?
-///         e.g., will the second part really be handled differently from the first kind?
-///         The difference is:
-///         + the second kind depends on the semantic type, the first one should not depend on the semantic
-///           type. i.e. the first one will assume a function has a type at the linking level. while the
-///           second one assumes the type assignment at the precondition-level.
-///         + Note that the first one might still get type parameters for some instances, e.g. if I have an
-///           instance for (T, i32). That should be fine and not create any complications.
-///       => No, we should not merge these.
-///   => Are the trait uses different from just using functions?
-///         + They are only different when they directly involve one of the Param traits, otherwise
-///         they are statically resolvable.
-///
-///
-///  In the case of closures, we would add the fact that the closure implements the invocation
-///  method as an assumption to the `ProcedureScope`.
-///  We would generate the spec for that from the annotated spec.
-///    In this case, since the instance is auto-generated usually, it can be statically resolved,
-///    but will not have a specification or the obligation to generate it yet.
-///    So we have to put the obligation on it. Add support to `ProcedureScope` for that.
-///    (For now, we'll not resolve that obligation)
-///    - generate a new name for that
-///    - register a specification for that
-///      + the specification is not provided in the form of annotations, but we should generate a
-///        `FunctionSpec` nevertheless.
-///    -
-pub struct TraitRegistry<'tcx, 'def> {
+use crate::types::scope;
+use crate::{attrs, base, traits, types};
+
+pub struct TR<'tcx, 'def> {
     /// environment
     env: &'def Environment<'tcx>,
-    type_translator: &'def TypeTranslator<'def, 'tcx>,
+    type_translator: &'def types::TX<'def, 'tcx>,
 
     /// trait declarations
     trait_decls: RefCell<HashMap<LocalDefId, radium::TraitSpecDecl<'def>>>,
@@ -127,11 +48,11 @@ pub struct TraitRegistry<'tcx, 'def> {
     fn_spec_arena: &'def Arena<specs::FunctionSpec<'def, specs::InnerFunctionSpec<'def>>>,
 }
 
-impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
+impl<'tcx, 'def> TR<'tcx, 'def> {
     /// Create an empty trait registry.
     pub fn new(
         env: &'def Environment<'tcx>,
-        ty_translator: &'def TypeTranslator<'def, 'tcx>,
+        ty_translator: &'def types::TX<'def, 'tcx>,
         trait_arena: &'def Arena<specs::LiteralTraitSpec>,
         impl_arena: &'def Arena<specs::LiteralTraitImpl>,
         trait_use_arena: &'def Arena<radium::LiteralTraitSpecUseCell<'def>>,
@@ -244,7 +165,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
 
     /// Register a new annotated trait in the local crate with the registry.
     pub fn register_trait(&'def self, did: LocalDefId) -> Result<(), TranslationError<'tcx>> {
-        trace!("enter TraitRegistry::register_trait for did={did:?}");
+        trace!("enter TR::register_trait for did={did:?}");
 
         {
             let scope = self.trait_decls.borrow();
@@ -259,13 +180,13 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
 
         // get generics
         let trait_generics: &'tcx ty::Generics = self.env.tcx().generics_of(did.to_def_id());
-        let mut param_scope = ParamScope::from(trait_generics.params.as_slice());
+        let mut param_scope = scope::Params::from(trait_generics.params.as_slice());
         param_scope.add_param_env(did.to_def_id(), self.env, self.type_translator, self)?;
 
-        let trait_name = strip_coq_ident(&self.env.get_absolute_item_name(did.to_def_id()));
+        let trait_name = base::strip_coq_ident(&self.env.get_absolute_item_name(did.to_def_id()));
 
         // parse trait spec
-        let trait_attrs = utils::filter_tool_attrs(self.env.get_attributes(did.into()));
+        let trait_attrs = attrs::filter_for_tool(self.env.get_attributes(did.into()));
         // As different attributes of the spec may depend on each other, we need to pass a closure
         // determining under which Coq name we are going to introduce them
         // Note: This needs to match up with `radium::LiteralTraitSpec.make_spec_attr_name`!
@@ -296,13 +217,13 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
                 // get function name
                 let method_name =
                     self.env.get_assoc_item_name(c.def_id).ok_or(Error::NotATraitMethod(c.def_id))?;
-                let method_name = strip_coq_ident(&method_name);
+                let method_name = base::strip_coq_ident(&method_name);
 
                 let name = format!("{trait_name}_{method_name}");
                 let spec_name = format!("{name}_base_spec");
 
                 // get spec
-                let spec = FunctionTranslator::spec_for_trait_method(
+                let spec = signature::TX::spec_for_trait_method(
                     self.env,
                     c.def_id,
                     &name,
@@ -318,7 +239,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
                 // get name
                 let type_name =
                     self.env.get_assoc_item_name(c.def_id).ok_or(Error::NotATraitMethod(c.def_id))?;
-                let type_name = strip_coq_ident(&type_name);
+                let type_name = base::strip_coq_ident(&type_name);
                 let lit = radium::LiteralTyParam::new(&type_name, &type_name);
                 assoc_types.push(lit);
             }
@@ -337,7 +258,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
         let mut scope = self.trait_decls.borrow_mut();
         scope.insert(did, decl);
 
-        trace!("leave TraitRegistry::register_trait");
+        trace!("leave TR::register_trait");
         Ok(())
     }
 
@@ -423,13 +344,13 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
     /// as well as the list of associated types.
     pub fn get_impl_spec_term(
         &self,
-        state: TranslationState<'_, '_, 'def, 'tcx>,
+        state: types::ST<'_, '_, 'def, 'tcx>,
         impl_did: DefId,
         impl_args: &[ty::GenericArg<'tcx>],
         trait_args: &[ty::GenericArg<'tcx>],
     ) -> Result<(radium::SpecializedTraitImpl<'def>, Vec<ty::Ty<'tcx>>), TranslationError<'tcx>> {
         trace!(
-            "enter TraitRegistry::get_impl_spec_term for impl_did={impl_did:?} impl_args={impl_args:?} trait_args={trait_args:?}"
+            "enter TR::get_impl_spec_term for impl_did={impl_did:?} impl_args={impl_args:?} trait_args={trait_args:?}"
         );
         let trait_did = self.env.tcx().trait_id_of_impl(impl_did).ok_or(Error::NotATraitImpl(impl_did))?;
 
@@ -466,7 +387,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
             return Err(TranslationError::TraitTranslation(Error::UnregisteredImpl(impl_did)));
         };
 
-        trace!("leave TraitRegistry::get_impl_spec_term");
+        trace!("leave TR::get_impl_spec_term");
         Ok((term, assoc_args))
     }
 
@@ -477,7 +398,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
     /// declaration order.
     pub fn resolve_trait_requirements_in_state(
         &self,
-        state: TranslationState<'_, '_, 'def, 'tcx>,
+        state: types::ST<'_, '_, 'def, 'tcx>,
         did: DefId,
         params: ty::GenericArgsRef<'tcx>,
     ) -> Result<Vec<radium::TraitReqInst<'def, ty::Ty<'tcx>>>, TranslationError<'tcx>> {
@@ -489,7 +410,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
         trace!("callee param env {callee_param_env:?}");
 
         // Get the trait requirements of the callee
-        let callee_requirements = ParamScope::get_trait_requirements_with_origin(self.env, did);
+        let callee_requirements = scope::Params::get_trait_requirements_with_origin(self.env, did);
         trace!("non-trivial callee requirements: {callee_requirements:?}");
         trace!("subsituting with args {:?}", params);
 
@@ -517,7 +438,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
                 if let Some(trait_did) = self.env.tcx().trait_of_item(did) {
                     // Get the params of the trait we're calling
                     let calling_trait_params =
-                        LocalTypeTranslator::split_trait_method_args(self.env, trait_did, params).0;
+                        types::LocalTX::split_trait_method_args(self.env, trait_did, params).0;
                     if trait_ref.def_id == trait_did && subst_args == calling_trait_params.as_slice() {
                         // if they match, this is the Self assumption, so skip
                         continue;
@@ -531,12 +452,12 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
             let subst_args = self.env.tcx().mk_args(subst_args.as_slice());
             trace!("Trying to resolve requirement def_id={:?} with args = {subst_args:?}", trait_ref.def_id);
             if let Some((impl_did, impl_args, kind)) =
-                traits::resolve_trait(self.env.tcx(), current_param_env, trait_ref.def_id, subst_args)
+                resolution::resolve_trait(self.env.tcx(), current_param_env, trait_ref.def_id, subst_args)
             {
                 info!("resolved trait impl as {impl_did:?} with {args:?} {kind:?}");
 
                 let req_inst = match kind {
-                    traits::TraitResolutionKind::UserDefined => {
+                    resolution::TraitResolutionKind::UserDefined => {
                         // we can resolve it to a concrete implementation of the trait that the
                         // call links up against
                         // therefore, we specialize it to the specification for this implementation
@@ -556,7 +477,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
                             assoc_tys,
                         )
                     },
-                    traits::TraitResolutionKind::Param => {
+                    resolution::TraitResolutionKind::Param => {
                         // Lookup in our current parameter environment to satisfy this trait
                         // assumption
                         let trait_did = trait_ref.def_id;
@@ -585,7 +506,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
                             assoc_types,
                         )
                     },
-                    traits::TraitResolutionKind::Closure => {
+                    resolution::TraitResolutionKind::Closure => {
                         // The callee requires a closure trait bound.
                         // This happens when we pass a closure as an argument?
                         return Err(TranslationError::UnsupportedFeature {
@@ -614,6 +535,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
         Ok(trait_spec_terms)
     }
 
+    /// Get information on a trait implementation and create its Radium encoding.
     pub fn get_trait_impl_info(
         &self,
         trait_impl_did: DefId,
@@ -635,11 +557,11 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
 
         // figure out the parameters this impl gets and make a scope
         let impl_generics: &'tcx ty::Generics = self.env.tcx().generics_of(trait_impl_did);
-        let mut param_scope = ParamScope::from(impl_generics.params.as_slice());
+        let mut param_scope = scope::Params::from(impl_generics.params.as_slice());
         param_scope.add_param_env(trait_impl_did, self.env, self.type_translator, self)?;
 
         // parse specification
-        let trait_impl_attrs = utils::filter_tool_attrs(self.env.get_attributes(trait_impl_did));
+        let trait_impl_attrs = attrs::filter_for_tool(self.env.get_attributes(trait_impl_did));
         let mut attr_parser = VerboseTraitImplAttrParser::new(&param_scope);
         let impl_spec = attr_parser
             .parse_trait_impl_attrs(&trait_impl_attrs)
@@ -656,7 +578,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
                     let ty = self.type_translator.translate_type_in_scope(&param_scope, ty)?;
                     params_inst.push(ty);
                 } else if let Some(lft) = arg.as_region() {
-                    let lft = TypeTranslator::translate_region_in_scope(&param_scope, lft)?;
+                    let lft = types::TX::translate_region_in_scope(&param_scope, lft)?;
                     lft_inst.push(lft);
                 }
             }
@@ -699,7 +621,51 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
             unreachable!("Expected trait impl");
         }
     }
+}
+
+/// A using occurrence of a trait in the signature of the function.
+#[derive(Debug, Clone)]
+pub struct GenericTraitUse<'def> {
+    /// the DefId of the trait
+    pub did: DefId,
+    /// the self type this is implemented for
+    pub self_ty: ty::ParamTy,
+    /// the Coq-level trait use
+    pub trait_use: radium::LiteralTraitSpecUseRef<'def>,
+}
 
+impl<'def> GenericTraitUse<'def> {
+    /// Get the names of associated types of this trait.
+    pub fn get_associated_type_names(&self, env: &Environment<'_>) -> Vec<String> {
+        let mut assoc_tys = Vec::new();
+
+        // get associated types
+        let assoc_types = env.get_trait_assoc_types(self.did);
+        for ty_did in &assoc_types {
+            let ty_name = env.get_assoc_item_name(*ty_did).unwrap();
+            assoc_tys.push(ty_name);
+        }
+        assoc_tys
+    }
+
+    /// Get the associated type instantiations for this trait use.
+    pub fn get_associated_type_uses(&self, env: &Environment<'_>) -> Vec<radium::Type<'def>> {
+        let mut assoc_tys: Vec<radium::Type> = Vec::new();
+
+        // get associated types
+        let assoc_types = env.get_trait_assoc_types(self.did);
+        for ty_did in &assoc_types {
+            let ty_name = env.get_assoc_item_name(*ty_did).unwrap();
+            let trait_use_ref = self.trait_use.borrow();
+            let trait_use = trait_use_ref.as_ref().unwrap();
+            let lit = trait_use.make_assoc_type_use(&base::strip_coq_ident(&ty_name));
+            assoc_tys.push(lit);
+        }
+        assoc_tys
+    }
+}
+
+impl<'tcx, 'def> TR<'tcx, 'def> {
     /// Allocate an empty trait use reference.
     pub fn make_empty_trait_use(&self, trait_ref: ty::TraitRef<'tcx>) -> GenericTraitUse<'def> {
         let dummy_trait_use = RefCell::new(None);
@@ -723,12 +689,12 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
 
     /// Fills an existing trait use.
     /// Does not compute the dependencies on other traits yet,
-    /// these, have to be filled later.
+    /// these have to be filled later.
     #[allow(clippy::unnecessary_wraps)]
     pub fn fill_trait_use(
         &self,
         trait_use: &GenericTraitUse<'def>,
-        scope: TranslationState<'_, '_, 'def, 'tcx>,
+        scope: types::ST<'_, '_, 'def, 'tcx>,
         trait_ref: ty::TraitRef<'tcx>,
         spec_ref: radium::LiteralTraitSpecRef<'def>,
         is_used_in_self_trait: bool,
@@ -766,7 +732,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
         let spec_override = None;
 
         // create a name for this instance by including the args
-        let mangled_base = mangle_name_with_args(&spec_ref.name, trait_ref.args.as_slice());
+        let mangled_base = types::mangle_name_with_args(&spec_ref.name, trait_ref.args.as_slice());
         let spec_use = radium::LiteralTraitSpecUse::new(
             spec_ref,
             translated_args,
@@ -792,7 +758,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
     pub fn finalize_trait_use(
         &self,
         trait_use: &GenericTraitUse<'def>,
-        scope: TranslationState<'_, '_, 'def, 'tcx>,
+        scope: types::ST<'_, '_, 'def, 'tcx>,
         trait_ref: ty::TraitRef<'tcx>,
     ) -> Result<(), TranslationError<'tcx>> {
         let trait_reqs = self.resolve_trait_requirements_in_state(scope, trait_ref.def_id, trait_ref.args)?;
@@ -816,203 +782,3 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> {
         Ok(())
     }
 }
-
-/// Check if this is a built-in trait
-pub fn is_builtin_trait(tcx: ty::TyCtxt<'_>, trait_did: DefId) -> Option<bool> {
-    let sized_did = utils::try_resolve_did(tcx, &["core", "marker", "Sized"])?;
-
-    // TODO: for these, should instead require the primitive encoding of our Coq formalization
-    let send_did = utils::try_resolve_did(tcx, &["core", "marker", "Send"])?;
-    let sync_did = utils::try_resolve_did(tcx, &["core", "marker", "Sync"])?;
-    let copy_did = utils::try_resolve_did(tcx, &["core", "marker", "Copy"])?;
-
-    // used for closures
-    let copy_did = utils::try_resolve_did(tcx, &["core", "marker", "Tuple"])?;
-
-    Some(trait_did == sized_did || trait_did == copy_did)
-}
-
-/// Compare two `GenericArg` deterministically.
-/// Should only be called on equal discriminants.
-fn cmp_arg_ref<'tcx>(tcx: ty::TyCtxt<'tcx>, a: ty::GenericArg<'tcx>, b: ty::GenericArg<'tcx>) -> Ordering {
-    match (a.unpack(), b.unpack()) {
-        (ty::GenericArgKind::Const(c1), ty::GenericArgKind::Const(c2)) => c1.cmp(&c2),
-        (ty::GenericArgKind::Type(ty1), ty::GenericArgKind::Type(ty2)) => {
-            // we should make sure that this always orders the Self instance first.
-            match (ty1.kind(), ty2.kind()) {
-                (ty::TyKind::Param(p1), ty::TyKind::Param(p2)) => p1.cmp(p2),
-                (ty::TyKind::Param(p1), _) => Ordering::Less,
-                (_, _) => ty1.cmp(&ty2),
-            }
-        },
-        (ty::GenericArgKind::Lifetime(r1), ty::GenericArgKind::Lifetime(r2)) => r1.cmp(&r2),
-        (_, _) => {
-            unreachable!("Comparing GenericArg with different discriminant");
-        },
-    }
-}
-
-/// Compare two sequences of `GenericArg`s for the same `DefId`, where the discriminants are pairwise equal.
-fn cmp_arg_refs<'tcx>(
-    tcx: ty::TyCtxt<'tcx>,
-    a: &[ty::GenericArg<'tcx>],
-    b: &[ty::GenericArg<'tcx>],
-) -> Ordering {
-    match a.len().cmp(&b.len()) {
-        Ordering::Equal => {
-            // compare elements
-            for (x, y) in a.iter().zip(b.iter()) {
-                // the discriminants are the same as the DefId we are calling into is the same
-                let xy_cmp = cmp_arg_ref(tcx, *x, *y);
-                if xy_cmp != Ordering::Equal {
-                    return xy_cmp;
-                }
-            }
-            Ordering::Equal
-        },
-        o => o,
-    }
-}
-
-/// Compare two `TraitRef`s deterministically, giving a
-/// consistent order that is stable across compilations.
-fn cmp_trait_ref<'tcx>(
-    tcx: ty::TyCtxt<'tcx>,
-    in_trait_decl: Option<DefId>,
-    a: &ty::TraitRef<'tcx>,
-    b: &ty::TraitRef<'tcx>,
-) -> Ordering {
-    // if one of them is the self trait, that should be smaller.
-    if let Some(trait_did) = in_trait_decl {
-        if a.def_id == trait_did && a.args[0].expect_ty().is_param(0) {
-            return Ordering::Less;
-        }
-        if b.def_id == trait_did && b.args[0].expect_ty().is_param(0) {
-            return Ordering::Greater;
-        }
-    }
-
-    let path_a = utils::get_cleaned_def_path(tcx, a.def_id);
-    let path_b = utils::get_cleaned_def_path(tcx, b.def_id);
-    let path_cmp = path_a.cmp(&path_b);
-    info!("cmp_trait_ref: comparing paths {path_a:?} and {path_b:?}");
-
-    if path_cmp == Ordering::Equal {
-        let args_a = a.args.as_slice();
-        let args_b = b.args.as_slice();
-        cmp_arg_refs(tcx, args_a, args_b)
-    } else {
-        path_cmp
-    }
-}
-
-/// Get non-trivial trait requirements of a function's `ParamEnv`,
-/// ordered deterministically.
-pub fn get_nontrivial_trait_requirements<'tcx>(
-    tcx: ty::TyCtxt<'tcx>,
-    param_env: ty::ParamEnv<'tcx>,
-    in_trait_decl: Option<DefId>,
-) -> Vec<ty::TraitRef<'tcx>> {
-    let mut trait_refs = Vec::new();
-    trace!(
-        "Enter get_nontrivial_trait_requirements with param_env = {param_env:?}, in_trait_decl = {in_trait_decl:?}"
-    );
-
-    let clauses = param_env.caller_bounds();
-    for cl in clauses {
-        let cl_kind = cl.kind();
-        let cl_kind = cl_kind.skip_binder();
-
-        // only look for trait predicates for now
-        if let ty::ClauseKind::Trait(trait_pred) = cl_kind {
-            // We ignore negative polarities for now
-            if trait_pred.polarity == ty::ImplPolarity::Positive {
-                let trait_ref = trait_pred.trait_ref;
-
-                // filter Sized, Copy, Send, Sync?
-                if Some(true) == is_builtin_trait(tcx, trait_ref.def_id) {
-                    continue;
-                }
-
-                // this is a nontrivial requirement
-                trait_refs.push(trait_ref);
-            }
-        }
-    }
-
-    // Make sure the order is stable across compilations
-    trait_refs.sort_by(|a, b| cmp_trait_ref(tcx, in_trait_decl, a, b));
-
-    trace!("Leave get_nontrivial_trait_requirements with trait_refs = {trait_refs:?}");
-
-    trait_refs
-}
-
-/// Given a particular reference to a trait, get the associated type constraints for this trait reference.
-pub fn get_trait_assoc_constraints<'tcx>(
-    env: &Environment<'tcx>,
-    param_env: ty::ParamEnv<'tcx>,
-    trait_ref: ty::TraitRef<'tcx>,
-) -> HashMap<String, ty::Ty<'tcx>> {
-    let mut assoc_ty_map = HashMap::new();
-
-    // TODO: check if caller_bounds does the right thing for implied bounds
-    let clauses = param_env.caller_bounds();
-    for cl in clauses {
-        let cl_kind = cl.kind();
-        let cl_kind = cl_kind.skip_binder();
-
-        // only look for trait predicates for now
-        if let ty::ClauseKind::Projection(proj) = cl_kind {
-            if trait_ref.def_id == proj.trait_def_id(env.tcx()) && trait_ref.args == proj.projection_ty.args {
-                // same trait and same args
-                let name = env.get_assoc_item_name(proj.def_id()).unwrap();
-                let ty = proj.term.ty().unwrap();
-                assoc_ty_map.insert(name, ty);
-            }
-        }
-    }
-    assoc_ty_map
-}
-
-/// A using occurrence of a trait in the signature of the function.
-#[derive(Debug, Clone)]
-pub struct GenericTraitUse<'def> {
-    /// the DefId of the trait
-    pub did: DefId,
-    /// the self type this is implemented for
-    pub self_ty: ty::ParamTy,
-    /// the Coq-level trait use
-    pub trait_use: radium::LiteralTraitSpecUseRef<'def>,
-}
-
-impl<'def> GenericTraitUse<'def> {
-    /// Get the names of associated types of this trait.
-    pub fn get_associated_type_names(&self, env: &Environment<'_>) -> Vec<String> {
-        let mut assoc_tys = Vec::new();
-
-        // get associated types
-        let assoc_types = env.get_trait_assoc_types(self.did);
-        for ty_did in &assoc_types {
-            let ty_name = env.get_assoc_item_name(*ty_did).unwrap();
-            assoc_tys.push(ty_name);
-        }
-        assoc_tys
-    }
-
-    /// Get the associated type instantiations for this trait use.
-    pub fn get_associated_type_uses(&self, env: &Environment<'_>) -> Vec<radium::Type<'def>> {
-        let mut assoc_tys: Vec<radium::Type> = Vec::new();
-
-        // get associated types
-        let assoc_types = env.get_trait_assoc_types(self.did);
-        for ty_did in &assoc_types {
-            let ty_name = env.get_assoc_item_name(*ty_did).unwrap();
-            let trait_use_ref = self.trait_use.borrow();
-            let trait_use = trait_use_ref.as_ref().unwrap();
-            let lit = trait_use.make_assoc_type_use(&strip_coq_ident(&ty_name));
-            assoc_tys.push(lit);
-        }
-        assoc_tys
-    }
-}
diff --git a/rr_frontend/translation/src/traits/requirements.rs b/rr_frontend/translation/src/traits/requirements.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1d4875d7f1c52be429258ff26e1c48e99af79e3f
--- /dev/null
+++ b/rr_frontend/translation/src/traits/requirements.rs
@@ -0,0 +1,152 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Get trait requirements of objects.
+
+use std::cmp::Ordering;
+
+use log::{info, trace};
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::ty;
+
+use crate::{search, shims};
+
+/// Get non-trivial trait requirements of a `ParamEnv`,
+/// ordered deterministically.
+pub fn get_nontrivial<'tcx>(
+    tcx: ty::TyCtxt<'tcx>,
+    param_env: ty::ParamEnv<'tcx>,
+    in_trait_decl: Option<DefId>,
+) -> Vec<ty::TraitRef<'tcx>> {
+    let mut trait_refs = Vec::new();
+    trace!(
+        "Enter get_nontrivial_trait_requirements with param_env = {param_env:?}, in_trait_decl = {in_trait_decl:?}"
+    );
+
+    let clauses = param_env.caller_bounds();
+    for cl in clauses {
+        let cl_kind = cl.kind();
+        let cl_kind = cl_kind.skip_binder();
+
+        // only look for trait predicates for now
+        if let ty::ClauseKind::Trait(trait_pred) = cl_kind {
+            // We ignore negative polarities for now
+            if trait_pred.polarity == ty::ImplPolarity::Positive {
+                let trait_ref = trait_pred.trait_ref;
+
+                // filter Sized, Copy, Send, Sync?
+                if Some(true) == is_builtin_trait(tcx, trait_ref.def_id) {
+                    continue;
+                }
+
+                // this is a nontrivial requirement
+                trait_refs.push(trait_ref);
+            }
+        }
+    }
+
+    // Make sure the order is stable across compilations
+    trait_refs.sort_by(|a, b| cmp_trait_ref(tcx, in_trait_decl, a, b));
+
+    trace!("Leave get_nontrivial_trait_requirements with trait_refs = {trait_refs:?}");
+
+    trait_refs
+}
+
+/// Check if this is a built-in trait
+fn is_builtin_trait(tcx: ty::TyCtxt<'_>, trait_did: DefId) -> Option<bool> {
+    let sized_did = search::try_resolve_did(tcx, &["core", "marker", "Sized"])?;
+
+    // TODO: for these, should instead require the primitive encoding of our Coq formalization
+    let send_did = search::try_resolve_did(tcx, &["core", "marker", "Send"])?;
+    let sync_did = search::try_resolve_did(tcx, &["core", "marker", "Sync"])?;
+    let copy_did = search::try_resolve_did(tcx, &["core", "marker", "Copy"])?;
+
+    // used for closures
+    let tuple_did = search::try_resolve_did(tcx, &["core", "marker", "Tuple"])?;
+
+    Some(
+        trait_did == sized_did
+            || trait_did == copy_did
+            || trait_did == tuple_did
+            || trait_did == send_did
+            || trait_did == sync_did,
+    )
+}
+
+/// Compare two `GenericArg` deterministically.
+/// Should only be called on equal discriminants.
+fn cmp_arg_ref<'tcx>(tcx: ty::TyCtxt<'tcx>, a: ty::GenericArg<'tcx>, b: ty::GenericArg<'tcx>) -> Ordering {
+    match (a.unpack(), b.unpack()) {
+        (ty::GenericArgKind::Const(c1), ty::GenericArgKind::Const(c2)) => c1.cmp(&c2),
+        (ty::GenericArgKind::Type(ty1), ty::GenericArgKind::Type(ty2)) => {
+            // we should make sure that this always orders the Self instance first.
+            match (ty1.kind(), ty2.kind()) {
+                (ty::TyKind::Param(p1), ty::TyKind::Param(p2)) => p1.cmp(p2),
+                (ty::TyKind::Param(p1), _) => Ordering::Less,
+                (_, _) => ty1.cmp(&ty2),
+            }
+        },
+        (ty::GenericArgKind::Lifetime(r1), ty::GenericArgKind::Lifetime(r2)) => r1.cmp(&r2),
+        (_, _) => {
+            unreachable!("Comparing GenericArg with different discriminant");
+        },
+    }
+}
+
+/// Compare two sequences of `GenericArg`s for the same `DefId`, where the discriminants are pairwise equal.
+fn cmp_arg_refs<'tcx>(
+    tcx: ty::TyCtxt<'tcx>,
+    a: &[ty::GenericArg<'tcx>],
+    b: &[ty::GenericArg<'tcx>],
+) -> Ordering {
+    match a.len().cmp(&b.len()) {
+        Ordering::Equal => {
+            // compare elements
+            for (x, y) in a.iter().zip(b.iter()) {
+                // the discriminants are the same as the DefId we are calling into is the same
+                let xy_cmp = cmp_arg_ref(tcx, *x, *y);
+                if xy_cmp != Ordering::Equal {
+                    return xy_cmp;
+                }
+            }
+            Ordering::Equal
+        },
+        o => o,
+    }
+}
+
+/// Compare two `TraitRef`s deterministically, giving a
+/// consistent order that is stable across compilations.
+fn cmp_trait_ref<'tcx>(
+    tcx: ty::TyCtxt<'tcx>,
+    in_trait_decl: Option<DefId>,
+    a: &ty::TraitRef<'tcx>,
+    b: &ty::TraitRef<'tcx>,
+) -> Ordering {
+    // if one of them is the self trait, that should be smaller.
+    if let Some(trait_did) = in_trait_decl {
+        if a.def_id == trait_did && a.args[0].expect_ty().is_param(0) {
+            return Ordering::Less;
+        }
+        if b.def_id == trait_did && b.args[0].expect_ty().is_param(0) {
+            return Ordering::Greater;
+        }
+    }
+
+    let path_a = shims::flat::get_cleaned_def_path(tcx, a.def_id);
+    let path_b = shims::flat::get_cleaned_def_path(tcx, b.def_id);
+    let path_cmp = path_a.cmp(&path_b);
+    info!("cmp_trait_ref: comparing paths {path_a:?} and {path_b:?}");
+
+    if path_cmp == Ordering::Equal {
+        let args_a = a.args.as_slice();
+        let args_b = b.args.as_slice();
+        cmp_arg_refs(tcx, args_a, args_b)
+    } else {
+        path_cmp
+    }
+}
diff --git a/rr_frontend/translation/src/traits.rs b/rr_frontend/translation/src/traits/resolution.rs
similarity index 98%
rename from rr_frontend/translation/src/traits.rs
rename to rr_frontend/translation/src/traits/resolution.rs
index b38d6a21ced9792dc2eeadd1655e8148fcea54e9..30c8a63fffd30b25dc55f57150cf55847fc515fa 100644
--- a/rr_frontend/translation/src/traits.rs
+++ b/rr_frontend/translation/src/traits/resolution.rs
@@ -1,3 +1,4 @@
+//! Interface for resolving trait requirements using `rustc`'s trait resolution.
 /// Inspired by (in terms of rustc APIs used) by
 /// <https://github.com/xldenis/creusot/blob/9d8b1822cd0c43154a6d5d4d05460be56710399c/creusot/src/translation/traits.rs>
 use log::info;
diff --git a/rr_frontend/translation/src/types/local.rs b/rr_frontend/translation/src/types/local.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d7b4074911dad1448ec65b8426a575ba37b057f5
--- /dev/null
+++ b/rr_frontend/translation/src/types/local.rs
@@ -0,0 +1,476 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! A wrapper around a `translator::TX` for the case we are translating the body of a function.
+
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::fmt::Write;
+
+use log::{info, trace, warn};
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::ty::{self, Ty, TypeFolder};
+use rr_rustc_interface::target;
+
+use crate::base::*;
+use crate::environment::borrowck::facts;
+use crate::environment::{polonius_info, Environment};
+use crate::regions::TyRegionCollectFolder;
+use crate::traits::registry::GenericTraitUse;
+use crate::traits::resolution;
+use crate::types::translator::*;
+use crate::types::tyvars::*;
+use crate::types::{self, scope};
+use crate::{base, regions, traits};
+
+/// Information we compute when calling a function from another function.
+/// Determines how to specialize the callee's generics in our spec assumption.
+pub struct AbstractedGenerics<'def> {
+    /// the scope with new generics to quantify over for the function's specialized spec
+    pub scope: radium::GenericScope<'def, radium::LiteralTraitSpecUse<'def>>,
+    /// instantiations for the specialized spec hint
+    pub callee_lft_param_inst: Vec<radium::Lft>,
+    pub callee_ty_param_inst: Vec<radium::Type<'def>>,
+    /// instantiations for the function use
+    pub fn_lft_param_inst: Vec<radium::Lft>,
+    pub fn_ty_param_inst: Vec<radium::Type<'def>>,
+}
+
+/// Type translator bundling the function scope
+#[allow(clippy::module_name_repetitions)]
+pub struct LocalTX<'def, 'tcx> {
+    pub translator: &'def TX<'def, 'tcx>,
+    pub scope: RefCell<FunctionState<'tcx, 'def>>,
+}
+
+impl<'def, 'tcx> LocalTX<'def, 'tcx> {
+    pub const fn new(translator: &'def TX<'def, 'tcx>, scope: FunctionState<'tcx, 'def>) -> Self {
+        Self {
+            translator,
+            scope: RefCell::new(scope),
+        }
+    }
+
+    /// Get the `DefId` of the current function.
+    pub fn get_proc_did(&self) -> DefId {
+        let scope = self.scope.borrow();
+        scope.did
+    }
+
+    /// Translate a MIR type to the Radium syntactic type we need when storing an element of the type,
+    /// substituting all generics.
+    pub fn translate_type_to_syn_type(
+        &self,
+        ty: Ty<'tcx>,
+    ) -> Result<radium::SynType, TranslationError<'tcx>> {
+        let mut scope = self.scope.borrow_mut();
+        let mut state = STInner::InFunction(&mut scope);
+        self.translator.translate_type_to_syn_type(ty, &mut state)
+    }
+
+    /// Translate a region in the scope of the current function.
+    pub fn translate_region(&self, region: ty::Region<'tcx>) -> Result<radium::Lft, TranslationError<'tcx>> {
+        let mut scope = self.scope.borrow_mut();
+        let mut scope = STInner::InFunction(&mut scope);
+        TX::translate_region(&mut scope, region)
+    }
+
+    /// Translate a Polonius region variable in the scope of the current function.
+    pub fn translate_region_var(&self, region: facts::Region) -> Result<radium::Lft, TranslationError<'tcx>> {
+        let mut scope = self.scope.borrow_mut();
+        let mut scope = STInner::InFunction(&mut scope);
+        scope.lookup_polonius_var(region)
+    }
+
+    /// Translate type.
+    pub fn translate_type(&self, ty: Ty<'tcx>) -> Result<radium::Type<'def>, TranslationError<'tcx>> {
+        let mut scope = self.scope.borrow_mut();
+        self.translator.translate_type(ty, &mut scope)
+    }
+
+    /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently
+    /// registering a new ADT.
+    pub fn generate_structlike_use(
+        &self,
+        ty: Ty<'tcx>,
+        variant: Option<target::abi::VariantIdx>,
+    ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError<'tcx>> {
+        let mut scope = self.scope.borrow_mut();
+        self.translator.generate_structlike_use(ty, variant, &mut scope)
+    }
+
+    /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently
+    /// registering a new ADT.
+    pub fn generate_enum_use<F>(
+        &self,
+        adt_def: ty::AdtDef<'tcx>,
+        args: F,
+    ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>>
+    where
+        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
+    {
+        let mut scope = self.scope.borrow_mut();
+        self.translator.generate_enum_use(adt_def, args, &mut scope)
+    }
+
+    /// Generate a struct use.
+    /// Returns None if this should be unit.
+    /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently
+    /// registering a new ADT.
+    pub fn generate_struct_use<F>(
+        &self,
+        variant_id: DefId,
+        args: F,
+    ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError<'tcx>>
+    where
+        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
+    {
+        let mut scope = self.scope.borrow_mut();
+        self.translator.generate_struct_use(variant_id, args, &mut scope)
+    }
+
+    /// Generate a struct use.
+    /// Returns None if this should be unit.
+    pub fn generate_enum_variant_use<F>(
+        &self,
+        variant_id: DefId,
+        args: F,
+    ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>>
+    where
+        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
+    {
+        let mut scope = self.scope.borrow_mut();
+        self.translator.generate_enum_variant_use(variant_id, args, &mut scope)
+    }
+
+    /// Make a tuple use.
+    /// Hack: This does not take the translation state but rather a direct reference to the tuple
+    /// use map so that we can this function when parsing closure specifications.
+    pub fn make_tuple_use(
+        &self,
+        translated_tys: Vec<radium::Type<'def>>,
+        uses: Option<&mut HashMap<Vec<radium::SynType>, radium::LiteralTypeUse<'def>>>,
+    ) -> radium::Type<'def> {
+        self.translator.make_tuple_use(translated_tys, uses)
+    }
+
+    pub fn generate_tuple_use<F>(
+        &self,
+        tys: F,
+    ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>>
+    where
+        F: IntoIterator<Item = Ty<'tcx>>,
+    {
+        let mut scope = self.scope.borrow_mut();
+        self.translator.generate_tuple_use(tys, &mut STInner::InFunction(&mut scope))
+    }
+
+    /// Format the Coq representation of an atomic region.
+    pub fn format_atomic_region(&self, r: &polonius_info::AtomicRegion) -> String {
+        let scope = self.scope.borrow();
+        scope.lifetime_scope.translate_atomic_region(r)
+    }
+
+    /// Normalize a type in the given function environment.
+    pub fn normalize<T>(&self, ty: T) -> Result<T, TranslationError<'tcx>>
+    where
+        T: ty::TypeFoldable<ty::TyCtxt<'tcx>>,
+    {
+        let scope = self.scope.borrow();
+        normalize_in_function(scope.did, self.translator.env().tcx(), ty)
+    }
+
+    pub fn get_trait_of_method(env: &Environment<'tcx>, method_did: DefId) -> Option<DefId> {
+        if let Some(impl_did) = env.tcx().impl_of_method(method_did) {
+            env.tcx().trait_id_of_impl(impl_did)
+        } else {
+            // else expect it to be an abstract method of a trait decl
+            env.tcx().trait_of_item(method_did)
+        }
+    }
+
+    /// Split the params of a trait method into params of the trait and params of the method
+    /// itself.
+    pub fn split_trait_method_args(
+        env: &Environment<'tcx>,
+        trait_did: DefId,
+        ty_params: ty::GenericArgsRef<'tcx>,
+    ) -> (ty::GenericArgsRef<'tcx>, ty::GenericArgsRef<'tcx>) {
+        // split args
+        let trait_generics: &'tcx ty::Generics = env.tcx().generics_of(trait_did);
+        let trait_generic_count = trait_generics.params.len();
+
+        let trait_args = &ty_params.as_slice()[..trait_generic_count];
+        let method_args = &ty_params.as_slice()[trait_generic_count..];
+
+        (env.tcx().mk_args(trait_args), env.tcx().mk_args(method_args))
+    }
+
+    /// Lookup a trait parameter.
+    pub fn lookup_trait_param(
+        &self,
+        env: &Environment<'tcx>,
+        trait_did: DefId,
+        args: ty::GenericArgsRef<'tcx>,
+    ) -> Result<GenericTraitUse<'def>, traits::Error<'tcx>> {
+        let scope = self.scope.borrow();
+        scope.generic_scope.trait_scope().lookup_trait_use(env.tcx(), trait_did, args).cloned()
+    }
+
+    /// Register a procedure use of a trait method.
+    /// The given `ty_params` need to include the args to both the trait and the method.
+    /// Returns:
+    /// - the parameter name for the method loc
+    /// - the spec term for the method spec
+    /// - the arguments of the method
+    pub fn register_use_trait_procedure(
+        &self,
+        env: &Environment<'tcx>,
+        trait_method_did: DefId,
+        ty_params: ty::GenericArgsRef<'tcx>,
+    ) -> Result<(String, String, ty::GenericArgsRef<'tcx>), TranslationError<'tcx>> {
+        let trait_did = env
+            .tcx()
+            .trait_of_item(trait_method_did)
+            .ok_or(traits::Error::NotATrait(trait_method_did))?;
+
+        // split args
+        let (trait_args, method_args) = Self::split_trait_method_args(env, trait_did, ty_params);
+
+        let trait_spec_use = {
+            let scope = self.scope.borrow();
+            let entry = scope.generic_scope.trait_scope().lookup_trait_use(
+                env.tcx(),
+                trait_did,
+                trait_args.as_slice(),
+            )?;
+            let trait_use_ref = entry.trait_use.borrow();
+            trait_use_ref.as_ref().unwrap().clone()
+        };
+
+        // get name of the trait
+        let trait_name = trait_spec_use.trait_ref.name.clone();
+
+        // get name of the method
+        let method_name = env.get_assoc_item_name(trait_method_did).unwrap();
+        let mangled_method_name =
+            types::mangle_name_with_args(&base::strip_coq_ident(&method_name), method_args.as_slice());
+
+        let method_loc_name = trait_spec_use.make_loc_name(&mangled_method_name);
+
+        // get spec. the spec takes the generics of the method as arguments
+        let method_spec_term = trait_spec_use.make_method_spec_term(&method_name);
+
+        Ok((method_loc_name, method_spec_term, method_args))
+    }
+
+    /// Abstract over the generics of a function and partially instantiate them.
+    /// Assumption: `trait_reqs` is appropriately sorted, i.e. surrounding requirements come first.
+    /// `with_surrounding_deps` determines whether we should distinguish surrounding and direct
+    /// params.
+    pub fn get_generic_abstraction_for_procedure(
+        &self,
+        callee_did: DefId,
+        ty_params: ty::GenericArgsRef<'tcx>,
+        trait_reqs: &[radium::TraitReqInst<'def, ty::Ty<'tcx>>],
+        with_surrounding_deps: bool,
+    ) -> Result<AbstractedGenerics<'def>, TranslationError<'tcx>> {
+        // get all the regions and type variables appearing that generics are instantiated with
+        let mut tyvar_folder = TyVarFolder::new(self.translator.env().tcx());
+        let mut lft_folder = TyRegionCollectFolder::new(self.translator.env().tcx());
+
+        // also count the number of regions of the function itself
+        let mut num_param_regions = 0;
+
+        let mut callee_lft_param_inst: Vec<radium::Lft> = Vec::new();
+        let mut callee_ty_param_inst = Vec::new();
+        for v in ty_params {
+            if let Some(ty) = v.as_type() {
+                tyvar_folder.fold_ty(ty);
+                lft_folder.fold_ty(ty);
+            }
+            if let Some(region) = v.as_region() {
+                num_param_regions += 1;
+
+                let lft_name = self.translate_region(region)?;
+                callee_lft_param_inst.push(lft_name);
+            }
+        }
+        // also find generics in the associated types
+        for req in trait_reqs {
+            for ty in &req.assoc_ty_inst {
+                tyvar_folder.fold_ty(*ty);
+                lft_folder.fold_ty(*ty);
+            }
+        }
+
+        let tyvars = tyvar_folder.get_result();
+        let regions = lft_folder.get_regions();
+
+        let mut scope = radium::GenericScope::empty();
+
+        // instantiations for the function spec's parameters
+        let mut fn_lft_param_inst = Vec::new();
+        let mut fn_ty_param_inst = Vec::new();
+
+        // re-bind the function's lifetime parameters
+        for i in 0..num_param_regions {
+            let lft_name = format!("ulft_{i}");
+            scope.add_lft_param(lft_name.clone());
+            fn_lft_param_inst.push(lft_name);
+        }
+
+        // bind the additional lifetime parameters
+        let mut next_lft = num_param_regions;
+        for region in regions {
+            // Use the name the region has inside the function as the binder name, so that the
+            // names work out when translating the types below
+            let lft_name = self.translate_region_var(region).unwrap_or(format!("ulft_{next_lft}"));
+            scope.add_lft_param(lft_name.clone());
+
+            next_lft += 1;
+
+            callee_lft_param_inst.push(lft_name);
+        }
+
+        // bind the generics we use
+        for param in &tyvars {
+            // NOTE: this should have the same name as the using occurrences
+            let lit = radium::LiteralTyParam::new(param.name.as_str(), param.name.as_str());
+            callee_ty_param_inst.push(radium::Type::LiteralParam(lit.clone()));
+            scope.add_ty_param(lit);
+        }
+        // also bind associated types which we translate as generics
+        for req in trait_reqs {
+            for ty in &req.assoc_ty_inst {
+                // we should check if it there is a parameter in the current scope for it
+                let translated_ty = self.translate_type(*ty)?;
+                if let radium::Type::LiteralParam(mut lit) = translated_ty {
+                    lit.set_origin(req.origin);
+
+                    scope.add_ty_param(lit.clone());
+                    callee_ty_param_inst.push(radium::Type::LiteralParam(lit.clone()));
+                }
+            }
+        }
+
+        // NOTE: we need to be careful with the order here.
+        // - the ty_params are all the generics the function has.
+        // - the trait_reqs are also all the associated types the function has
+        // We need to distinguish these between direct and surrounding.
+        let num_surrounding_params =
+            scope::Params::determine_number_of_surrounding_params(callee_did, self.translator.env().tcx());
+        info!("num_surrounding_params={num_surrounding_params:?}, ty_params={ty_params:?}");
+
+        // figure out instantiation for the function's generics
+        // first the surrounding parameters
+        if with_surrounding_deps {
+            for v in &ty_params.as_slice()[..num_surrounding_params] {
+                if let Some(ty) = v.as_type() {
+                    let translated_ty = self.translate_type(ty)?;
+                    fn_ty_param_inst.push(translated_ty);
+                }
+            }
+            // same for the associated types this function depends on
+            for req in trait_reqs {
+                if req.origin != radium::TyParamOrigin::Direct {
+                    for ty in &req.assoc_ty_inst {
+                        let translated_ty = self.translate_type(*ty)?;
+                        fn_ty_param_inst.push(translated_ty);
+                    }
+                }
+            }
+
+            // now the direct parameters
+            for v in &ty_params.as_slice()[num_surrounding_params..] {
+                if let Some(ty) = v.as_type() {
+                    let translated_ty = self.translate_type(ty)?;
+                    fn_ty_param_inst.push(translated_ty);
+                }
+            }
+            // same for the associated types this function depends on
+            for req in trait_reqs {
+                if req.origin == radium::TyParamOrigin::Direct {
+                    for ty in &req.assoc_ty_inst {
+                        let translated_ty = self.translate_type(*ty)?;
+                        fn_ty_param_inst.push(translated_ty);
+                    }
+                }
+            }
+        } else {
+            // now the direct parameters
+            for v in ty_params {
+                if let Some(ty) = v.as_type() {
+                    let translated_ty = self.translate_type(ty)?;
+                    fn_ty_param_inst.push(translated_ty);
+                }
+            }
+            // same for the associated types this function depends on
+            for req in trait_reqs {
+                if req.origin == radium::TyParamOrigin::Direct {
+                    for ty in &req.assoc_ty_inst {
+                        let translated_ty = self.translate_type(*ty)?;
+                        fn_ty_param_inst.push(translated_ty);
+                    }
+                }
+            }
+        }
+
+        info!("Abstraction scope: {:?}", scope);
+        info!("Fn instantiation: {:?}, {:?}", fn_lft_param_inst, fn_ty_param_inst);
+        info!("Callee instantiation: {:?}, {:?}", callee_lft_param_inst, callee_ty_param_inst);
+
+        let res = AbstractedGenerics {
+            scope,
+            callee_lft_param_inst,
+            callee_ty_param_inst,
+            fn_lft_param_inst,
+            fn_ty_param_inst,
+        };
+
+        Ok(res)
+    }
+}
+
+/// Normalize a type in the given function environment.
+pub fn normalize_in_function<'tcx, T>(
+    function_did: DefId,
+    tcx: ty::TyCtxt<'tcx>,
+    ty: T,
+) -> Result<T, TranslationError<'tcx>>
+where
+    T: ty::TypeFoldable<ty::TyCtxt<'tcx>>,
+{
+    let param_env = tcx.param_env(function_did);
+    info!("Normalizing type {ty:?} in env {param_env:?}");
+    resolution::normalize_type(tcx, param_env, ty)
+        .map_err(|e| TranslationError::TraitResolution(format!("normalization error: {:?}", e)))
+}
+
+pub fn normalize_erasing_regions_in_function<'tcx, T>(
+    function_did: DefId,
+    tcx: ty::TyCtxt<'tcx>,
+    ty: T,
+) -> Result<T, TranslationError<'tcx>>
+where
+    T: ty::TypeFoldable<ty::TyCtxt<'tcx>>,
+{
+    let param_env = tcx.param_env(function_did);
+    info!("Normalizing type {ty:?} in env {param_env:?}");
+    tcx.try_normalize_erasing_regions(param_env, ty)
+        .map_err(|e| TranslationError::TraitResolution(format!("normalization error: {:?}", e)))
+}
+
+pub fn normalize_projection_in_function<'tcx>(
+    function_did: DefId,
+    tcx: ty::TyCtxt<'tcx>,
+    ty: ty::AliasTy<'tcx>,
+) -> Result<ty::Ty<'tcx>, TranslationError<'tcx>> {
+    let param_env = tcx.param_env(function_did);
+    info!("Normalizing type {ty:?} in env {param_env:?}");
+    resolution::normalize_projection_type(tcx, param_env, ty)
+        .map_err(|e| TranslationError::TraitResolution(format!("could not normalize projection {ty:?}")))
+}
diff --git a/rr_frontend/translation/src/types/mod.rs b/rr_frontend/translation/src/types/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..77ab789be0da146de93927f5eeee197f33a6151a
--- /dev/null
+++ b/rr_frontend/translation/src/types/mod.rs
@@ -0,0 +1,43 @@
+// © 2023, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Utilities for translating Rust types into `RefinedRust` types.
+
+mod local;
+pub mod scope;
+mod translator;
+mod tyvars;
+
+use std::fmt::Write;
+
+pub use local::{normalize_erasing_regions_in_function, normalize_in_function, LocalTX};
+/// We export these parts of the private modules
+use rr_rustc_interface::middle::ty;
+pub use scope::{generate_args_inst_key, GenericsKey};
+pub use translator::{AdtState, CalleeState, FunctionState, STInner, ST, TX};
+
+use crate::base;
+
+/// Mangle a name by appending type parameters to it.
+pub fn mangle_name_with_tys(method_name: &str, args: &[ty::Ty<'_>]) -> String {
+    // TODO: maybe come up with some better way to generate names
+    let mut mangled_name = method_name.to_owned();
+    for arg in args {
+        mangled_name.push_str(format!("_{}", arg).as_str());
+    }
+    base::strip_coq_ident(&mangled_name)
+}
+
+/// Mangle a name by appending generic args to it.
+pub fn mangle_name_with_args(name: &str, args: &[ty::GenericArg<'_>]) -> String {
+    let mut mangled_base = name.to_owned();
+    for arg in args {
+        if let ty::GenericArgKind::Type(ty) = arg.unpack() {
+            write!(mangled_base, "_{}", base::strip_coq_ident(&format!("{ty}"))).unwrap();
+        }
+    }
+    mangled_base
+}
diff --git a/rr_frontend/translation/src/types/scope.rs b/rr_frontend/translation/src/types/scope.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5ed043ea4290d231e2cce8471bded5bfdbb6b203
--- /dev/null
+++ b/rr_frontend/translation/src/types/scope.rs
@@ -0,0 +1,572 @@
+// © 2024, The RefinedRust Developers and Contributors
+//
+// This Source Code Form is subject to the terms of the BSD-3-clause License.
+// If a copy of the BSD-3-clause license was not distributed with this
+// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
+
+//! Defines scopes for maintaining generics and trait requirements.
+
+use std::collections::{HashMap, HashSet};
+use std::fmt::Write;
+
+use derive_more::{Constructor, Debug};
+use log::{info, trace, warn};
+use rr_rustc_interface::hir::def_id::DefId;
+use rr_rustc_interface::middle::ty;
+use rr_rustc_interface::middle::ty::TypeFoldable;
+
+use crate::base;
+use crate::base::*;
+use crate::environment::Environment;
+use crate::spec_parsers::parse_utils::ParamLookup;
+use crate::traits::registry::GenericTraitUse;
+use crate::traits::{self, registry};
+use crate::types::translator::*;
+use crate::types::tyvars::*;
+
+/// Key used for resolving early-bound parameters for function calls.
+/// Invariant: All regions contained in these types should be erased, as type parameter instantiation is
+/// independent of lifetimes.
+/// TODO: handle early-bound lifetimes?
+pub type GenericsKey<'tcx> = Vec<ty::Ty<'tcx>>;
+
+/// Generate a key for indexing into structures indexed by `GenericArg`s.
+pub fn generate_args_inst_key<'tcx>(
+    tcx: ty::TyCtxt<'tcx>,
+    ty_params: &[ty::GenericArg<'tcx>],
+) -> Result<GenericsKey<'tcx>, TranslationError<'tcx>> {
+    // erase parameters to their syntactic types
+    let mut key = Vec::new();
+    let mut region_eraser = TyRegionEraseFolder::new(tcx);
+    for p in ty_params {
+        match p.unpack() {
+            ty::GenericArgKind::Lifetime(_) => {
+                // lifetimes are not relevant here
+            },
+            ty::GenericArgKind::Type(t) => {
+                // TODO: this should erase to the syntactic type.
+                // Is erasing regions enough for that?
+                let t_erased = t.fold_with(&mut region_eraser);
+                key.push(t_erased);
+            },
+            ty::GenericArgKind::Const(_c) => {
+                return Err(TranslationError::UnsupportedFeature {
+                    description: "RefinedRust does not support const generics".to_owned(),
+                });
+            },
+        }
+    }
+    Ok(key)
+}
+
+/// Keys used to deduplicate adt uses for `syn_type` assumptions.
+/// TODO maybe we should use `SimplifiedType` + `simplify_type` instead of the syntys?
+/// Or types with erased regions?
+#[derive(Eq, PartialEq, Hash, Debug)]
+pub struct AdtUseKey {
+    pub base_did: DefId,
+    pub generics: Vec<radium::SynType>,
+}
+
+impl AdtUseKey {
+    pub fn new(defid: DefId, params: &[radium::Type<'_>]) -> Self {
+        let generic_syntys: Vec<_> = params.iter().map(radium::SynType::from).collect();
+        Self {
+            base_did: defid,
+            generics: generic_syntys,
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+enum Param {
+    Region(radium::Lft),
+    Ty(radium::LiteralTyParam),
+    // Note: we do not currently support Const params
+    Const,
+}
+impl Param {
+    const fn as_type(&self) -> Option<&radium::LiteralTyParam> {
+        match self {
+            Self::Ty(lit) => Some(lit),
+            _ => None,
+        }
+    }
+
+    const fn as_region(&self) -> Option<&radium::Lft> {
+        match self {
+            Self::Region(lit) => Some(lit),
+            _ => None,
+        }
+    }
+}
+
+/// Data structure that maps generic parameters for ADT/trait translation
+#[derive(Constructor, Clone, Debug, Default)]
+pub struct Params<'tcx, 'def> {
+    /// maps generic indices (De Bruijn) to the corresponding Coq names in the current environment
+    scope: Vec<Param>,
+
+    /// conversely, map the declaration name of a lifetime to an index
+    lft_names: HashMap<String, usize>,
+    /// map types to their index
+    ty_names: HashMap<String, usize>,
+
+    /// the trait instances which are in scope
+    trait_scope: Traits<'tcx, 'def>,
+}
+
+#[allow(clippy::fallible_impl_from)]
+impl<'tcx, 'def> From<Params<'tcx, 'def>> for radium::GenericScope<'def, radium::LiteralTraitSpecUse<'def>> {
+    fn from(mut x: Params<'tcx, 'def>) -> Self {
+        let mut scope = Self::empty();
+        for x in x.scope {
+            match x {
+                Param::Region(lft) => {
+                    scope.add_lft_param(lft);
+                },
+                Param::Ty(ty) => {
+                    scope.add_ty_param(ty);
+                },
+                Param::Const => (),
+            }
+        }
+        for key in x.trait_scope.ordered_assumptions {
+            let trait_use = x.trait_scope.used_traits.remove(&key).unwrap().trait_use;
+            let trait_use = trait_use.borrow_mut().take().unwrap();
+            scope.add_trait_requirement(trait_use);
+        }
+        scope
+    }
+}
+impl<'tcx, 'def> ParamLookup for Params<'tcx, 'def> {
+    fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> {
+        if path.len() == 1 {
+            let idx = self.ty_names.get(path[0])?;
+            self.lookup_ty_param_idx(*idx)
+        } else {
+            None
+        }
+    }
+
+    fn lookup_lft(&self, lft: &str) -> Option<&radium::Lft> {
+        let idx = self.lft_names.get(lft)?;
+        self.lookup_region_idx(*idx)
+    }
+
+    fn lookup_literal(&self, path: &[&str]) -> Option<&str> {
+        None
+    }
+}
+impl<'tcx, 'def> Params<'tcx, 'def> {
+    pub const fn trait_scope(&self) -> &Traits<'tcx, 'def> {
+        &self.trait_scope
+    }
+
+    /// Create from generics, optionally annotating the type parameters with their origin.
+    pub fn new_from_generics(
+        x: ty::GenericArgsRef<'tcx>,
+        with_origin: Option<(ty::TyCtxt<'tcx>, DefId)>,
+    ) -> Self {
+        let mut scope = Vec::new();
+        let mut region_count = 0;
+
+        let mut ty_names = HashMap::new();
+        let mut lft_names = HashMap::new();
+
+        for p in x {
+            if let Some(r) = p.as_region() {
+                if let Some(name) = r.get_name() {
+                    lft_names.insert(name.as_str().to_owned(), scope.len());
+                    scope.push(Param::Region(base::strip_coq_ident(name.as_str())));
+                } else {
+                    let name = format!("ulft_{region_count}");
+                    region_count += 1;
+                    scope.push(Param::Region(name));
+                }
+            } else if let Some(ty) = p.as_type() {
+                if let ty::TyKind::Param(x) = ty.kind() {
+                    ty_names.insert(x.name.as_str().to_owned(), scope.len());
+                    let name = base::strip_coq_ident(x.name.as_str());
+
+                    let lit = if let Some((tcx, of_did)) = with_origin {
+                        let origin = Self::determine_origin_of_param(of_did, tcx, *x);
+                        radium::LiteralTyParam::new_with_origin(&name, &name, origin)
+                    } else {
+                        radium::LiteralTyParam::new(&name, &name)
+                    };
+                    scope.push(Param::Ty(lit));
+                } else {
+                    unreachable!("Should not convert a non-parametric GenericArgsRef to a Params");
+                }
+            } else if p.as_const().is_some() {
+                scope.push(Param::Const);
+            }
+        }
+        Self {
+            scope,
+            lft_names,
+            ty_names,
+            trait_scope: Traits::default(),
+        }
+    }
+
+    /// Lookup a type parameter by its De Bruijn index.
+    #[must_use]
+    pub fn lookup_ty_param_idx(&self, idx: usize) -> Option<&radium::LiteralTyParam> {
+        let ty = self.scope.get(idx)?;
+        ty.as_type()
+    }
+
+    /// Lookup a region parameter by its De Bruijn index.
+    #[must_use]
+    pub fn lookup_region_idx(&self, idx: usize) -> Option<&radium::Lft> {
+        let lft = self.scope.get(idx)?;
+        lft.as_region()
+    }
+
+    /// Get all type parameters in scope.
+    #[must_use]
+    pub fn tyvars(&self) -> Vec<radium::LiteralTyParam> {
+        let mut tyvars = Vec::new();
+        for x in &self.scope {
+            if let Param::Ty(ty) = x {
+                tyvars.push(ty.to_owned());
+            }
+        }
+        tyvars
+    }
+
+    /// Create a scope of typarams masked by a set of parameters.
+    /// The input must be sorted.
+    #[must_use]
+    pub fn mask_typarams(&self, used_params: &[ty::ParamTy]) -> Vec<radium::LiteralTyParam> {
+        let mut res = Vec::new();
+        for x in used_params {
+            let ty = self.lookup_ty_param_idx(x.index as usize).unwrap();
+            res.push(ty.to_owned());
+        }
+        res
+    }
+
+    /// Add a `ParamEnv` of a given `DefId` to the scope to process trait obligations.
+    pub fn add_param_env(
+        &mut self,
+        did: DefId,
+        env: &Environment<'tcx>,
+        type_translator: &TX<'def, 'tcx>,
+        trait_registry: &registry::TR<'tcx, 'def>,
+    ) -> Result<(), TranslationError<'tcx>> {
+        trace!("Enter add_param_env for did = {did:?}");
+        let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(did);
+
+        // TODO
+        // What happens when we encounter the Self requirement when registering a trait?
+        // Is it okay to skip that?
+
+        // TODO: add scope for referring to associated types in specs
+
+        let requirements = Self::get_trait_requirements_with_origin(env, did);
+
+        // pre-register all the requirements, in order to resolve dependencies
+        for (trait_ref, _, _) in &requirements {
+            let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap());
+            let dummy_trait_use = trait_registry.make_empty_trait_use(*trait_ref);
+            self.trait_scope.used_traits.insert(key.clone(), dummy_trait_use);
+        }
+
+        for (trait_ref, origin, is_used_in_self_trait) in &requirements {
+            // lookup the trait in the trait registry
+            if let Some(trait_spec) = trait_registry.lookup_trait(trait_ref.def_id) {
+                let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap());
+                let entry = &self.trait_scope.used_traits[&key];
+
+                let mut deps = HashSet::new();
+                // the scope to translate the arguments in
+                let mut state = STInner::TranslateAdt(AdtState::new(&mut deps, &*self, &param_env));
+
+                trait_registry.fill_trait_use(
+                    entry,
+                    &mut state,
+                    trait_ref.to_owned(),
+                    trait_spec,
+                    *is_used_in_self_trait,
+                    // trait associated types are fully generic for now, we make a second pass
+                    // below
+                    HashMap::new(),
+                    *origin,
+                )?;
+
+                self.trait_scope.ordered_assumptions.push(key);
+            } else {
+                return Err(traits::Error::UnregisteredTrait(trait_ref.def_id).into());
+            }
+        }
+
+        // make a second pass to specify constraints on associated types
+        // We do this in a second pass so that we can refer to the other associated types
+        for (trait_ref, origin, _) in requirements {
+            let assoc_constraints = traits::get_trait_assoc_constraints(env, param_env, trait_ref);
+
+            let translated_constraints: HashMap<_, _> = assoc_constraints
+                .into_iter()
+                .map(|(name, ty)| {
+                    let translated_ty = type_translator.translate_type_in_scope(self, ty).unwrap();
+                    (name, translated_ty)
+                })
+                .collect();
+
+            // lookup the trait use
+            let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap());
+            let entry = &self.trait_scope.used_traits[&key];
+
+            {
+                let mut trait_use_ref = entry.trait_use.borrow_mut();
+                let trait_use = trait_use_ref.as_mut().unwrap();
+                // and add the constraints
+                for (name, constr) in translated_constraints {
+                    trait_use.specialize_assoc_type(name, constr);
+                }
+            }
+
+            // finalize the entry by adding dependencies on other trait parameters
+            let mut deps = HashSet::new();
+            let mut state = STInner::TranslateAdt(AdtState::new(&mut deps, &*self, &param_env));
+            trait_registry.finalize_trait_use(entry, &mut state, trait_ref)?;
+        }
+
+        trace!("Leave add_param_env for did = {did:?}");
+        Ok(())
+    }
+}
+impl<'tcx, 'def> Params<'tcx, 'def> {
+    /// Determine the declaration origin of a type parameter of a function.
+    fn determine_origin_of_param(
+        did: DefId,
+        tcx: ty::TyCtxt<'tcx>,
+        param: ty::ParamTy,
+    ) -> radium::TyParamOrigin {
+        // Check if there is a surrounding trait decl that introduces this parameter
+        if let Some(trait_did) = tcx.trait_of_item(did) {
+            let generics: &'tcx ty::Generics = tcx.generics_of(trait_did);
+
+            for this_param in &generics.params {
+                if this_param.name == param.name {
+                    return radium::TyParamOrigin::SurroundingTrait;
+                }
+            }
+        }
+        // Check if there is a surrounding trait impl that introduces this parameter
+        if let Some(impl_did) = tcx.impl_of_method(did) {
+            let generics: &'tcx ty::Generics = tcx.generics_of(impl_did);
+
+            for this_param in &generics.params {
+                if this_param.name == param.name {
+                    return radium::TyParamOrigin::SurroundingImpl;
+                }
+            }
+        }
+
+        radium::TyParamOrigin::Direct
+    }
+
+    /// Determine the number of args of a surrounding trait or impl.
+    pub fn determine_number_of_surrounding_params(did: DefId, tcx: ty::TyCtxt<'tcx>) -> usize {
+        // Check if there is a surrounding trait decl that introduces this parameter
+        if let Some(trait_did) = tcx.trait_of_item(did) {
+            let generics: &'tcx ty::Generics = tcx.generics_of(trait_did);
+
+            return generics.params.len();
+        }
+        // Check if there is a surrounding trait impl that introduces this parameter
+        if let Some(impl_did) = tcx.impl_of_method(did) {
+            let generics: &'tcx ty::Generics = tcx.generics_of(impl_did);
+
+            return generics.params.len();
+        }
+
+        0
+    }
+
+    /// Determine the origin of a trait obligation.
+    /// `surrounding_reqs` are the requirements of a surrounding impl or decl.
+    fn determine_origin_of_trait_requirement(
+        did: DefId,
+        tcx: ty::TyCtxt<'tcx>,
+        surrounding_reqs: &Option<Vec<ty::TraitRef<'tcx>>>,
+        req: ty::TraitRef<'tcx>,
+    ) -> radium::TyParamOrigin {
+        if let Some(surrounding_reqs) = surrounding_reqs {
+            let in_trait_decl = tcx.trait_of_item(did);
+
+            if surrounding_reqs.contains(&req) {
+                if in_trait_decl.is_some() {
+                    return radium::TyParamOrigin::SurroundingTrait;
+                }
+                return radium::TyParamOrigin::SurroundingImpl;
+            }
+        }
+        radium::TyParamOrigin::Direct
+    }
+
+    /// Get the trait requirements of a [did], also determining their origin relative to the [did].
+    /// The requirements are sorted in a way that is stable across compilations.
+    pub fn get_trait_requirements_with_origin(
+        env: &Environment<'tcx>,
+        did: DefId,
+    ) -> Vec<(ty::TraitRef<'tcx>, radium::TyParamOrigin, bool)> {
+        trace!("Enter get_trait_requirements_with_origin for did={did:?}");
+        let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(did);
+
+        // Are we declaring the scope of a trait?
+        let is_trait = env.tcx().is_trait(did);
+
+        // Determine whether we are declaring the scope of a trait method or trait impl method
+        let in_trait_decl = env.tcx().trait_of_item(did);
+        let in_trait_impl = env.trait_impl_of_method(did);
+
+        // if this has a surrounding scope, get the requirements declared on that, so that we can
+        // determine the origin of this requirement below
+        let surrounding_reqs = if let Some(trait_did) = in_trait_decl {
+            let trait_param_env = env.tcx().param_env(trait_did);
+            Some(traits::requirements::get_nontrivial(env.tcx(), trait_param_env, None))
+        } else if let Some(impl_did) = in_trait_impl {
+            let impl_param_env = env.tcx().param_env(impl_did);
+            Some(traits::requirements::get_nontrivial(env.tcx(), impl_param_env, None))
+        } else {
+            None
+        };
+
+        let clauses = param_env.caller_bounds();
+        info!("Caller bounds: {:?}", clauses);
+
+        let in_trait_decl = if is_trait { Some(did) } else { in_trait_decl };
+        let requirements = traits::requirements::get_nontrivial(env.tcx(), param_env, in_trait_decl);
+        let mut annotated_requirements = Vec::new();
+
+        for trait_ref in requirements {
+            // check if we are in the process of translating a trait decl
+            let is_self = trait_ref.args[0].as_type().unwrap().is_param(0);
+            let mut is_used_in_self_trait = false;
+            if let Some(trait_decl_did) = in_trait_decl {
+                // is this a reference to the trait we are currently declaring
+                let is_use_of_self_trait = trait_decl_did == trait_ref.def_id;
+
+                if is_use_of_self_trait && is_self {
+                    // This is the self assumption of the trait we are currently implementing
+                    // For a function spec in a trait decl, we remember this, as we do not require
+                    // a quantified spec for the Self trait.
+                    is_used_in_self_trait = true;
+                }
+            }
+
+            // we are processing the Self requirement in the scope of a trait declaration, so skip
+            // this.
+            if is_trait && is_self {
+                continue;
+            }
+
+            let origin =
+                Self::determine_origin_of_trait_requirement(did, env.tcx(), &surrounding_reqs, trait_ref);
+            info!("Determined origin of requirement {trait_ref:?} as {origin:?}");
+
+            annotated_requirements.push((trait_ref, origin, is_used_in_self_trait));
+        }
+
+        trace!(
+            "Leave get_trait_requirements_with_origin for did={did:?} with annotated_requirements={annotated_requirements:?}"
+        );
+        annotated_requirements
+    }
+}
+
+impl<'tcx, 'def> From<ty::GenericArgsRef<'tcx>> for Params<'tcx, 'def> {
+    fn from(x: ty::GenericArgsRef<'tcx>) -> Self {
+        Self::new_from_generics(x, None)
+    }
+}
+
+impl<'a, 'tcx, 'def> From<&'a [ty::GenericParamDef]> for Params<'tcx, 'def> {
+    fn from(x: &[ty::GenericParamDef]) -> Self {
+        let mut scope = Vec::new();
+
+        let mut ty_names = HashMap::new();
+        let mut lft_names = HashMap::new();
+
+        for p in x {
+            let name = base::strip_coq_ident(p.name.as_str());
+            match p.kind {
+                ty::GenericParamDefKind::Const { .. } => {
+                    scope.push(Param::Const);
+                },
+                ty::GenericParamDefKind::Type { .. } => {
+                    let lit = radium::LiteralTyParam::new(&name, &name);
+                    ty_names.insert(p.name.as_str().to_owned(), scope.len());
+                    scope.push(Param::Ty(lit));
+                },
+                ty::GenericParamDefKind::Lifetime => {
+                    let name = format!("ulft_{name}");
+                    lft_names.insert(p.name.as_str().to_owned(), scope.len());
+                    scope.push(Param::Region(name));
+                },
+            }
+        }
+        Self {
+            scope,
+            lft_names,
+            ty_names,
+            trait_scope: Traits::default(),
+        }
+    }
+}
+
+/// A scope for translated trait requirements from `where` clauses.
+#[derive(Clone, Debug, Default)]
+pub struct Traits<'tcx, 'def> {
+    used_traits: HashMap<(DefId, GenericsKey<'tcx>), GenericTraitUse<'def>>,
+    ordered_assumptions: Vec<(DefId, GenericsKey<'tcx>)>,
+}
+
+impl<'tcx, 'def> Traits<'tcx, 'def> {
+    /// Lookup the trait use for a specific trait with given parameters.
+    /// (here, args includes the self parameter as the first element)
+    pub fn lookup_trait_use(
+        &self,
+        tcx: ty::TyCtxt<'tcx>,
+        trait_did: DefId,
+        args: &[ty::GenericArg<'tcx>],
+    ) -> Result<&GenericTraitUse<'def>, traits::Error<'tcx>> {
+        if !tcx.is_trait(trait_did) {
+            return Err(traits::Error::NotATrait(trait_did));
+        }
+
+        let key = (trait_did, generate_args_inst_key(tcx, args).unwrap());
+        trace!("looking up trait use {key:?} in {:?}", self.used_traits);
+        if let Some(trait_ref) = self.used_traits.get(&key) {
+            Ok(trait_ref)
+        } else {
+            Err(traits::Error::UnknownLocalInstance(trait_did, tcx.mk_args(args)))
+        }
+    }
+
+    /// Get trait uses in the current scope.
+    pub const fn get_trait_uses(&self) -> &HashMap<(DefId, GenericsKey<'tcx>), GenericTraitUse<'def>> {
+        &self.used_traits
+    }
+
+    /// Within a trait declaration, get the Self trait use.
+    pub fn get_self_trait_use(&self) -> Option<&GenericTraitUse<'def>> {
+        for trait_use in self.used_traits.values() {
+            // check if this is the Self trait use
+            {
+                let spec_use_ref = trait_use.trait_use.borrow();
+                let spec_use = spec_use_ref.as_ref().unwrap();
+                if !spec_use.is_used_in_self_trait {
+                    continue;
+                }
+            }
+            return Some(trait_use);
+        }
+        None
+    }
+}
diff --git a/rr_frontend/translation/src/type_translator.rs b/rr_frontend/translation/src/types/translator.rs
similarity index 63%
rename from rr_frontend/translation/src/type_translator.rs
rename to rr_frontend/translation/src/types/translator.rs
index 5525d420a3a2816f83fb51082a587c25fc2778a5..9cdf7ab02d6645dba24e540befcf912a6d30418b 100644
--- a/rr_frontend/translation/src/type_translator.rs
+++ b/rr_frontend/translation/src/types/translator.rs
@@ -1,12 +1,13 @@
-// © 2023, The RefinedRust Developers and Contributors
+// © 2024, The RefinedRust Developers and Contributors
 //
 // This Source Code Form is subject to the terms of the BSD-3-clause License.
 // If a copy of the BSD-3-clause license was not distributed with this
 // file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
 
+//! The main translator for translating types within certain environments.
+
 use std::cell::{OnceCell, RefCell};
-use std::collections::{BTreeMap, HashMap, HashSet};
-use std::fmt::Write;
+use std::collections::{HashMap, HashSet};
 
 use derive_more::{Constructor, Debug};
 use log::{info, trace, warn};
@@ -19,614 +20,27 @@ use typed_arena::Arena;
 
 use crate::base::*;
 use crate::environment::borrowck::facts;
-use crate::environment::polonius_info::{self, PoloniusInfo};
+use crate::environment::polonius_info::PoloniusInfo;
 use crate::environment::Environment;
-use crate::function_body::{get_arg_syntypes_for_procedure_call, mangle_name_with_args};
+use crate::regions::{format_atomic_region_direct, EarlyLateRegionMap};
 use crate::spec_parsers::enum_spec_parser::{parse_enum_refine_as, EnumSpecParser, VerboseEnumSpecParser};
 use crate::spec_parsers::parse_utils::ParamLookup;
 use crate::spec_parsers::struct_spec_parser::{self, InvariantSpecParser, StructFieldSpecParser};
-use crate::trait_registry::{self, Error, GenericTraitUse, TraitRegistry, TraitResult};
-use crate::traits::{normalize_projection_type, normalize_type};
-use crate::tyvars::*;
-use crate::utils;
-
-/// Strip symbols from an identifier to be compatible with Coq.
-/// In particular things like ' or ::.
-pub fn strip_coq_ident(s: &str) -> String {
-    String::from(s)
-        .replace('\'', "")
-        .replace("::", "_")
-        .replace(|c: char| !(c.is_alphanumeric() || c == '_'), "")
-}
-
-/// Key used for resolving early-bound parameters for function calls.
-/// Invariant: All regions contained in these types should be erased, as type parameter instantiation is
-/// independent of lifetimes.
-/// TODO: handle early-bound lifetimes?
-pub type GenericsKey<'tcx> = Vec<ty::Ty<'tcx>>;
-
-/// Generate a key for indexing into structures indexed by `GenericArg`s.
-pub fn generate_args_inst_key<'tcx>(
-    tcx: ty::TyCtxt<'tcx>,
-    ty_params: &[ty::GenericArg<'tcx>],
-) -> Result<GenericsKey<'tcx>, TranslationError<'tcx>> {
-    // erase parameters to their syntactic types
-    let mut key = Vec::new();
-    let mut region_eraser = TyRegionEraseFolder::new(tcx);
-    for p in ty_params {
-        match p.unpack() {
-            ty::GenericArgKind::Lifetime(_) => {
-                // lifetimes are not relevant here
-            },
-            ty::GenericArgKind::Type(t) => {
-                // TODO: this should erase to the syntactic type.
-                // Is erasing regions enough for that?
-                let t_erased = t.fold_with(&mut region_eraser);
-                key.push(t_erased);
-            },
-            ty::GenericArgKind::Const(_c) => {
-                return Err(TranslationError::UnsupportedFeature {
-                    description: "RefinedRust does not support const generics".to_owned(),
-                });
-            },
-        }
-    }
-    Ok(key)
-}
-
-/// Keys used to deduplicate adt uses for `syn_type` assumptions.
-/// TODO maybe we should use `SimplifiedType` + `simplify_type` instead of the syntys?
-/// Or types with erased regions?
-#[derive(Eq, PartialEq, Hash, Debug)]
-pub struct AdtUseKey {
-    pub base_did: DefId,
-    pub generics: Vec<radium::SynType>,
-}
-
-impl AdtUseKey {
-    pub fn new(defid: DefId, params: &[radium::Type<'_>]) -> Self {
-        let generic_syntys: Vec<_> = params.iter().map(radium::SynType::from).collect();
-        Self {
-            base_did: defid,
-            generics: generic_syntys,
-        }
-    }
-}
-
-/// Data structure that maps early and late region indices to Polonius regions.
-#[derive(Constructor, Clone, Debug, Default)]
-pub struct EarlyLateRegionMap {
-    // maps indices of early and late regions to Polonius region ids
-    pub early_regions: Vec<Option<ty::RegionVid>>,
-    pub late_regions: Vec<ty::RegionVid>,
-
-    // maps Polonius region ids to names
-    pub region_names: BTreeMap<ty::RegionVid, radium::Lft>,
-
-    // maps source-level universal lifetime names to region ids
-    pub lft_names: HashMap<String, ty::RegionVid>,
-}
-impl EarlyLateRegionMap {
-    pub fn lookup_region(&self, region: ty::RegionVid) -> Option<&radium::Lft> {
-        self.region_names.get(&region)
-    }
-
-    pub fn lookup_early_region(&self, idx: usize) -> Option<&radium::Lft> {
-        let ovid = self.early_regions.get(idx)?;
-        let vid = ovid.as_ref()?;
-        self.lookup_region(*vid)
-    }
-
-    pub fn lookup_late_region(&self, idx: usize) -> Option<&radium::Lft> {
-        let vid = self.late_regions.get(idx)?;
-        self.lookup_region(*vid)
-    }
-}
-
-#[derive(Clone, Debug)]
-pub enum Param {
-    Region(radium::Lft),
-    Ty(radium::LiteralTyParam),
-    // Note: we do not currently support Const params
-    Const,
-}
-impl Param {
-    const fn as_type(&self) -> Option<&radium::LiteralTyParam> {
-        match self {
-            Self::Ty(lit) => Some(lit),
-            _ => None,
-        }
-    }
-
-    const fn as_region(&self) -> Option<&radium::Lft> {
-        match self {
-            Self::Region(lit) => Some(lit),
-            _ => None,
-        }
-    }
-}
-
-/// Data structure that maps generic parameters for ADT/trait translation
-#[derive(Constructor, Clone, Debug, Default)]
-pub struct ParamScope<'tcx, 'def> {
-    /// maps generic indices (De Bruijn) to the corresponding Coq names in the current environment
-    pub(crate) scope: Vec<Param>,
-
-    /// conversely, map the declaration name of a lifetime to an index
-    lft_names: HashMap<String, usize>,
-    /// map types to their index
-    ty_names: HashMap<String, usize>,
-
-    /// the trait instances which are in scope
-    pub(crate) trait_scope: TraitScope<'tcx, 'def>,
-}
-
-#[allow(clippy::fallible_impl_from)]
-impl<'tcx, 'def> From<ParamScope<'tcx, 'def>>
-    for radium::GenericScope<'def, radium::LiteralTraitSpecUse<'def>>
-{
-    fn from(mut x: ParamScope<'tcx, 'def>) -> Self {
-        let mut scope = Self::empty();
-        for x in x.scope {
-            match x {
-                Param::Region(lft) => {
-                    scope.add_lft_param(lft);
-                },
-                Param::Ty(ty) => {
-                    scope.add_ty_param(ty);
-                },
-                Param::Const => (),
-            }
-        }
-        for key in x.trait_scope.ordered_assumptions {
-            let trait_use = x.trait_scope.used_traits.remove(&key).unwrap().trait_use;
-            let trait_use = trait_use.borrow_mut().take().unwrap();
-            scope.add_trait_requirement(trait_use);
-        }
-        scope
-    }
-}
-impl<'tcx, 'def> ParamLookup for ParamScope<'tcx, 'def> {
-    fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> {
-        if path.len() == 1 {
-            let idx = self.ty_names.get(path[0])?;
-            self.lookup_ty_param_idx(*idx)
-        } else {
-            None
-        }
-    }
-
-    fn lookup_lft(&self, lft: &str) -> Option<&radium::Lft> {
-        let idx = self.lft_names.get(lft)?;
-        self.lookup_region_idx(*idx)
-    }
-
-    fn lookup_literal(&self, path: &[&str]) -> Option<&str> {
-        None
-    }
-}
-impl<'tcx, 'def> ParamScope<'tcx, 'def> {
-    /// Get the trait requirements of a [did], also determining their origin relative to the [did].
-    /// The requirements are sorted in a way that is stable across compilations.
-    pub fn get_trait_requirements_with_origin(
-        env: &Environment<'tcx>,
-        did: DefId,
-    ) -> Vec<(ty::TraitRef<'tcx>, radium::TyParamOrigin, bool)> {
-        trace!("Enter get_trait_requirements_with_origin for did={did:?}");
-        let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(did);
-
-        // Are we declaring the scope of a trait?
-        let is_trait = env.tcx().is_trait(did);
-
-        // Determine whether we are declaring the scope of a trait method or trait impl method
-        let in_trait_decl = env.tcx().trait_of_item(did);
-        let in_trait_impl = env.trait_impl_of_method(did);
-
-        // if this has a surrounding scope, get the requirements declared on that, so that we can
-        // determine the origin of this requirement below
-        let surrounding_reqs = if let Some(trait_did) = in_trait_decl {
-            let trait_param_env = env.tcx().param_env(trait_did);
-            Some(trait_registry::get_nontrivial_trait_requirements(env.tcx(), trait_param_env, None))
-        } else if let Some(impl_did) = in_trait_impl {
-            let impl_param_env = env.tcx().param_env(impl_did);
-            Some(trait_registry::get_nontrivial_trait_requirements(env.tcx(), impl_param_env, None))
-        } else {
-            None
-        };
-
-        let clauses = param_env.caller_bounds();
-        info!("Caller bounds: {:?}", clauses);
-
-        let in_trait_decl = if is_trait { Some(did) } else { in_trait_decl };
-        let requirements =
-            trait_registry::get_nontrivial_trait_requirements(env.tcx(), param_env, in_trait_decl);
-        let mut annotated_requirements = Vec::new();
-
-        for trait_ref in requirements {
-            // check if we are in the process of translating a trait decl
-            let is_self = trait_ref.args[0].as_type().unwrap().is_param(0);
-            let mut is_used_in_self_trait = false;
-            if let Some(trait_decl_did) = in_trait_decl {
-                // is this a reference to the trait we are currently declaring
-                let is_use_of_self_trait = trait_decl_did == trait_ref.def_id;
-
-                if is_use_of_self_trait && is_self {
-                    // This is the self assumption of the trait we are currently implementing
-                    // For a function spec in a trait decl, we remember this, as we do not require
-                    // a quantified spec for the Self trait.
-                    is_used_in_self_trait = true;
-                }
-            }
-
-            // we are processing the Self requirement in the scope of a trait declaration, so skip
-            // this.
-            if is_trait && is_self {
-                continue;
-            }
-
-            let origin =
-                Self::determine_origin_of_trait_requirement(did, env.tcx(), &surrounding_reqs, trait_ref);
-            info!("Determined origin of requirement {trait_ref:?} as {origin:?}");
-
-            annotated_requirements.push((trait_ref, origin, is_used_in_self_trait));
-        }
-
-        trace!(
-            "Leave get_trait_requirements_with_origin for did={did:?} with annotated_requirements={annotated_requirements:?}"
-        );
-        annotated_requirements
-    }
-
-    /// Determine the declaration origin of a type parameter of a function.
-    fn determine_origin_of_param(
-        did: DefId,
-        tcx: ty::TyCtxt<'tcx>,
-        param: ty::ParamTy,
-    ) -> radium::TyParamOrigin {
-        // Check if there is a surrounding trait decl that introduces this parameter
-        if let Some(trait_did) = tcx.trait_of_item(did) {
-            let generics: &'tcx ty::Generics = tcx.generics_of(trait_did);
-
-            for this_param in &generics.params {
-                if this_param.name == param.name {
-                    return radium::TyParamOrigin::SurroundingTrait;
-                }
-            }
-        }
-        // Check if there is a surrounding trait impl that introduces this parameter
-        if let Some(impl_did) = tcx.impl_of_method(did) {
-            let generics: &'tcx ty::Generics = tcx.generics_of(impl_did);
-
-            for this_param in &generics.params {
-                if this_param.name == param.name {
-                    return radium::TyParamOrigin::SurroundingImpl;
-                }
-            }
-        }
-
-        radium::TyParamOrigin::Direct
-    }
-
-    /// Determine the number of args of a surrounding trait or impl.
-    pub fn determine_number_of_surrounding_params(did: DefId, tcx: ty::TyCtxt<'tcx>) -> usize {
-        // Check if there is a surrounding trait decl that introduces this parameter
-        if let Some(trait_did) = tcx.trait_of_item(did) {
-            let generics: &'tcx ty::Generics = tcx.generics_of(trait_did);
-
-            return generics.params.len();
-        }
-        // Check if there is a surrounding trait impl that introduces this parameter
-        if let Some(impl_did) = tcx.impl_of_method(did) {
-            let generics: &'tcx ty::Generics = tcx.generics_of(impl_did);
-
-            return generics.params.len();
-        }
-
-        0
-    }
-
-    /// Determine the origin of a trait obligation.
-    /// `surrounding_reqs` are the requirements of a surrounding impl or decl.
-    fn determine_origin_of_trait_requirement(
-        did: DefId,
-        tcx: ty::TyCtxt<'tcx>,
-        surrounding_reqs: &Option<Vec<ty::TraitRef<'tcx>>>,
-        req: ty::TraitRef<'tcx>,
-    ) -> radium::TyParamOrigin {
-        if let Some(surrounding_reqs) = surrounding_reqs {
-            let in_trait_decl = tcx.trait_of_item(did);
-
-            if surrounding_reqs.contains(&req) {
-                if in_trait_decl.is_some() {
-                    return radium::TyParamOrigin::SurroundingTrait;
-                }
-                return radium::TyParamOrigin::SurroundingImpl;
-            }
-        }
-        radium::TyParamOrigin::Direct
-    }
-
-    /// Create from generics, optionally annotating the type parameters with their origin.
-    pub fn new_from_generics(
-        x: ty::GenericArgsRef<'tcx>,
-        with_origin: Option<(ty::TyCtxt<'tcx>, DefId)>,
-    ) -> Self {
-        let mut scope = Vec::new();
-        let mut region_count = 0;
-
-        let mut ty_names = HashMap::new();
-        let mut lft_names = HashMap::new();
-
-        for p in x {
-            if let Some(r) = p.as_region() {
-                if let Some(name) = r.get_name() {
-                    lft_names.insert(name.as_str().to_owned(), scope.len());
-                    scope.push(Param::Region(strip_coq_ident(name.as_str())));
-                } else {
-                    let name = format!("ulft_{region_count}");
-                    region_count += 1;
-                    scope.push(Param::Region(name));
-                }
-            } else if let Some(ty) = p.as_type() {
-                if let ty::TyKind::Param(x) = ty.kind() {
-                    ty_names.insert(x.name.as_str().to_owned(), scope.len());
-                    let name = strip_coq_ident(x.name.as_str());
-
-                    let lit = if let Some((tcx, of_did)) = with_origin {
-                        let origin = Self::determine_origin_of_param(of_did, tcx, *x);
-                        radium::LiteralTyParam::new_with_origin(&name, &name, origin)
-                    } else {
-                        radium::LiteralTyParam::new(&name, &name)
-                    };
-                    scope.push(Param::Ty(lit));
-                } else {
-                    unreachable!("Should not convert a non-parametric GenericArgsRef to a ParamScope");
-                }
-            } else if p.as_const().is_some() {
-                scope.push(Param::Const);
-            }
-        }
-        Self {
-            scope,
-            lft_names,
-            ty_names,
-            trait_scope: TraitScope::default(),
-        }
-    }
-
-    /// Lookup a type parameter by its De Bruijn index.
-    #[must_use]
-    pub fn lookup_ty_param_idx(&self, idx: usize) -> Option<&radium::LiteralTyParam> {
-        let ty = self.scope.get(idx)?;
-        ty.as_type()
-    }
-
-    /// Lookup a region parameter by its De Bruijn index.
-    #[must_use]
-    pub fn lookup_region_idx(&self, idx: usize) -> Option<&radium::Lft> {
-        let lft = self.scope.get(idx)?;
-        lft.as_region()
-    }
-
-    /// Get all type parameters in scope.
-    #[must_use]
-    pub fn tyvars(&self) -> Vec<radium::LiteralTyParam> {
-        let mut tyvars = Vec::new();
-        for x in &self.scope {
-            if let Param::Ty(ty) = x {
-                tyvars.push(ty.to_owned());
-            }
-        }
-        tyvars
-    }
-
-    /// Create a scope of typarams masked by a set of parameters.
-    /// The input must be sorted.
-    #[must_use]
-    pub fn mask_typarams(&self, used_params: &[ty::ParamTy]) -> Vec<radium::LiteralTyParam> {
-        let mut res = Vec::new();
-        for x in used_params {
-            let ty = self.lookup_ty_param_idx(x.index as usize).unwrap();
-            res.push(ty.to_owned());
-        }
-        res
-    }
-
-    /// Add a `ParamEnv` of a given `DefId` to the scope to process trait obligations.
-    pub fn add_param_env(
-        &mut self,
-        did: DefId,
-        env: &Environment<'tcx>,
-        type_translator: &TypeTranslator<'def, 'tcx>,
-        trait_registry: &TraitRegistry<'tcx, 'def>,
-    ) -> Result<(), TranslationError<'tcx>> {
-        trace!("Enter add_param_env for did = {did:?}");
-        let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(did);
-
-        // TODO
-        // What happens when we encounter the Self requirement when registering a trait?
-        // Is it okay to skip that?
-
-        // TODO: add scope for referring to associated types in specs
-
-        let requirements = Self::get_trait_requirements_with_origin(env, did);
-
-        // pre-register all the requirements, in order to resolve dependencies
-        for (trait_ref, _, _) in &requirements {
-            let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap());
-            let dummy_trait_use = trait_registry.make_empty_trait_use(*trait_ref);
-            self.trait_scope.used_traits.insert(key.clone(), dummy_trait_use);
-        }
-
-        for (trait_ref, origin, is_used_in_self_trait) in &requirements {
-            // lookup the trait in the trait registry
-            if let Some(trait_spec) = trait_registry.lookup_trait(trait_ref.def_id) {
-                let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap());
-                let entry = &self.trait_scope.used_traits[&key];
-
-                let mut deps = HashSet::new();
-                // the scope to translate the arguments in
-                let mut state = TranslationStateInner::TranslateAdt(AdtTranslationState::new(
-                    &mut deps, &*self, &param_env,
-                ));
-
-                trait_registry.fill_trait_use(
-                    entry,
-                    &mut state,
-                    trait_ref.to_owned(),
-                    trait_spec,
-                    *is_used_in_self_trait,
-                    // trait associated types are fully generic for now, we make a second pass
-                    // below
-                    HashMap::new(),
-                    *origin,
-                )?;
-
-                self.trait_scope.ordered_assumptions.push(key);
-            } else {
-                return Err(trait_registry::Error::UnregisteredTrait(trait_ref.def_id).into());
-            }
-        }
-
-        // make a second pass to specify constraints on associated types
-        // We do this in a second pass so that we can refer to the other associated types
-        for (trait_ref, origin, _) in requirements {
-            let assoc_constraints = trait_registry::get_trait_assoc_constraints(env, param_env, trait_ref);
-
-            let translated_constraints: HashMap<_, _> = assoc_constraints
-                .into_iter()
-                .map(|(name, ty)| {
-                    let translated_ty = type_translator.translate_type_in_scope(self, ty).unwrap();
-                    (name, translated_ty)
-                })
-                .collect();
-
-            // lookup the trait use
-            let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap());
-            let entry = &self.trait_scope.used_traits[&key];
-
-            {
-                let mut trait_use_ref = entry.trait_use.borrow_mut();
-                let trait_use = trait_use_ref.as_mut().unwrap();
-                // and add the constraints
-                for (name, constr) in translated_constraints {
-                    trait_use.specialize_assoc_type(name, constr);
-                }
-            }
-
-            // finalize the entry by adding dependencies on other trait parameters
-            let mut deps = HashSet::new();
-            let mut state =
-                TranslationStateInner::TranslateAdt(AdtTranslationState::new(&mut deps, &*self, &param_env));
-            trait_registry.finalize_trait_use(entry, &mut state, trait_ref)?;
-        }
-
-        trace!("Leave add_param_env for did = {did:?}");
-        Ok(())
-    }
-}
-
-impl<'tcx, 'def> From<ty::GenericArgsRef<'tcx>> for ParamScope<'tcx, 'def> {
-    fn from(x: ty::GenericArgsRef<'tcx>) -> Self {
-        Self::new_from_generics(x, None)
-    }
-}
-
-impl<'a, 'tcx, 'def> From<&'a [ty::GenericParamDef]> for ParamScope<'tcx, 'def> {
-    fn from(x: &[ty::GenericParamDef]) -> Self {
-        let mut scope = Vec::new();
-
-        let mut ty_names = HashMap::new();
-        let mut lft_names = HashMap::new();
-
-        for p in x {
-            let name = strip_coq_ident(p.name.as_str());
-            match p.kind {
-                ty::GenericParamDefKind::Const { .. } => {
-                    scope.push(Param::Const);
-                },
-                ty::GenericParamDefKind::Type { .. } => {
-                    let lit = radium::LiteralTyParam::new(&name, &name);
-                    ty_names.insert(p.name.as_str().to_owned(), scope.len());
-                    scope.push(Param::Ty(lit));
-                },
-                ty::GenericParamDefKind::Lifetime => {
-                    let name = format!("ulft_{name}");
-                    lft_names.insert(p.name.as_str().to_owned(), scope.len());
-                    scope.push(Param::Region(name));
-                },
-            }
-        }
-        Self {
-            scope,
-            lft_names,
-            ty_names,
-            trait_scope: TraitScope::default(),
-        }
-    }
-}
-
-/// A scope for translated trait requirements from `where` clauses.
-#[derive(Clone, Debug, Default)]
-pub struct TraitScope<'tcx, 'def> {
-    used_traits: HashMap<(DefId, GenericsKey<'tcx>), GenericTraitUse<'def>>,
-    ordered_assumptions: Vec<(DefId, GenericsKey<'tcx>)>,
-}
-
-impl<'tcx, 'def> TraitScope<'tcx, 'def> {
-    /// Lookup the trait use for a specific trait with given parameters.
-    /// (here, args includes the self parameter as the first element)
-    pub fn lookup_trait_use(
-        &self,
-        tcx: ty::TyCtxt<'tcx>,
-        trait_did: DefId,
-        args: &[ty::GenericArg<'tcx>],
-    ) -> TraitResult<'tcx, &GenericTraitUse<'def>> {
-        if !tcx.is_trait(trait_did) {
-            return Err(trait_registry::Error::NotATrait(trait_did));
-        }
-
-        let key = (trait_did, generate_args_inst_key(tcx, args).unwrap());
-        trace!("looking up trait use {key:?} in {:?}", self.used_traits);
-        if let Some(trait_ref) = self.used_traits.get(&key) {
-            Ok(trait_ref)
-        } else {
-            Err(trait_registry::Error::UnknownLocalInstance(trait_did, tcx.mk_args(args)))
-        }
-    }
-
-    /// Get trait uses in the current scope.
-    pub const fn get_trait_uses(&self) -> &HashMap<(DefId, GenericsKey<'tcx>), GenericTraitUse<'def>> {
-        &self.used_traits
-    }
-
-    /// Within a trait declaration, get the Self trait use.
-    pub fn get_self_trait_use(&self) -> Option<&GenericTraitUse<'def>> {
-        for trait_use in self.used_traits.values() {
-            // check if this is the Self trait use
-            {
-                let spec_use_ref = trait_use.trait_use.borrow();
-                let spec_use = spec_use_ref.as_ref().unwrap();
-                if !spec_use.is_used_in_self_trait {
-                    continue;
-                }
-            }
-            return Some(trait_use);
-        }
-        None
-    }
-}
+use crate::traits::{self, registry};
+use crate::types::scope;
+use crate::types::tyvars::*;
+use crate::{attrs, base, search};
 
 /// A scope tracking the type translation state when translating the body of a function.
 /// This also includes the state needed for tracking trait constraints, as type translation for
 /// associated types in the current scope depends on that.
 #[derive(Debug)]
-pub struct TypeTranslationScope<'tcx, 'def> {
+pub struct FunctionState<'tcx, 'def> {
     /// defid of the current function
     pub(crate) did: DefId,
 
     /// generic mapping: map DeBruijn indices to type parameters
-    pub(crate) generic_scope: ParamScope<'tcx, 'def>,
+    pub(crate) generic_scope: scope::Params<'tcx, 'def>,
     /// mapping for regions
     pub(crate) lifetime_scope: EarlyLateRegionMap,
 
@@ -634,14 +48,14 @@ pub struct TypeTranslationScope<'tcx, 'def> {
     pub(crate) tuple_uses: HashMap<Vec<radium::SynType>, radium::LiteralTypeUse<'def>>,
 
     /// Shim uses for the current function
-    pub(crate) shim_uses: HashMap<AdtUseKey, radium::LiteralTypeUse<'def>>,
+    pub(crate) shim_uses: HashMap<scope::AdtUseKey, radium::LiteralTypeUse<'def>>,
 
     /// optional Polonius Info for the current function
     #[debug(ignore)]
     polonius_info: Option<&'def PoloniusInfo<'def, 'tcx>>,
 }
 
-impl<'tcx, 'def> ParamLookup for TypeTranslationScope<'tcx, 'def> {
+impl<'tcx, 'def> ParamLookup for FunctionState<'tcx, 'def> {
     fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> {
         if path.len() == 1 { self.generic_scope.lookup_ty_param(path) } else { None }
     }
@@ -656,14 +70,14 @@ impl<'tcx, 'def> ParamLookup for TypeTranslationScope<'tcx, 'def> {
     }
 }
 
-impl<'tcx, 'def> TypeTranslationScope<'tcx, 'def> {
+impl<'tcx, 'def> FunctionState<'tcx, 'def> {
     /// Create a new empty scope for a function.
     pub fn empty(did: DefId) -> Self {
         Self {
             did,
             tuple_uses: HashMap::new(),
             shim_uses: HashMap::new(),
-            generic_scope: ParamScope::default(),
+            generic_scope: scope::Params::default(),
             polonius_info: None,
             lifetime_scope: EarlyLateRegionMap::default(),
         }
@@ -679,7 +93,7 @@ impl<'tcx, 'def> TypeTranslationScope<'tcx, 'def> {
     ) -> Self {
         info!("Entering procedure with ty_params {:?} and lifetimes {:?}", ty_params, lifetimes);
 
-        let generics = ParamScope::new_from_generics(ty_params, Some((tcx, did)));
+        let generics = scope::Params::new_from_generics(ty_params, Some((tcx, did)));
 
         Self {
             did,
@@ -699,12 +113,12 @@ impl<'tcx, 'def> TypeTranslationScope<'tcx, 'def> {
         ty_params: ty::GenericArgsRef<'tcx>,
         lifetimes: EarlyLateRegionMap,
         param_env: ty::ParamEnv<'tcx>,
-        type_translator: &TypeTranslator<'def, 'tcx>,
-        trait_registry: &TraitRegistry<'tcx, 'def>,
+        type_translator: &TX<'def, 'tcx>,
+        trait_registry: &registry::TR<'tcx, 'def>,
         info: Option<&'def PoloniusInfo<'def, 'tcx>>,
     ) -> Result<Self, TranslationError<'tcx>> {
         info!("Entering procedure with ty_params {:?} and lifetimes {:?}", ty_params, lifetimes);
-        let mut generics = ParamScope::new_from_generics(ty_params, Some((env.tcx(), did)));
+        let mut generics = scope::Params::new_from_generics(ty_params, Some((env.tcx(), did)));
 
         generics.add_param_env(did, env, type_translator, trait_registry)?;
 
@@ -721,18 +135,18 @@ impl<'tcx, 'def> TypeTranslationScope<'tcx, 'def> {
 
 /// Type translation state when translating an ADT.
 #[derive(Debug)]
-pub struct AdtTranslationState<'a, 'tcx, 'def> {
+pub struct AdtState<'a, 'tcx, 'def> {
     /// track dependencies on other ADTs
     deps: &'a mut HashSet<DefId>,
     /// scope to track parameters
-    scope: &'a ParamScope<'tcx, 'def>,
+    scope: &'a scope::Params<'tcx, 'def>,
     /// the current paramenv
     param_env: &'a ty::ParamEnv<'tcx>,
 }
-impl<'a, 'tcx, 'def> AdtTranslationState<'a, 'tcx, 'def> {
+impl<'a, 'tcx, 'def> AdtState<'a, 'tcx, 'def> {
     pub fn new(
         deps: &'a mut HashSet<DefId>,
-        scope: &'a ParamScope<'tcx, 'def>,
+        scope: &'a scope::Params<'tcx, 'def>,
         param_env: &'a ty::ParamEnv<'tcx>,
     ) -> Self {
         Self {
@@ -746,32 +160,33 @@ impl<'a, 'tcx, 'def> AdtTranslationState<'a, 'tcx, 'def> {
 /// Translation state for translating the interface of a called function.
 /// Lifetimes are completely erased since we only need these types for syntactic types.
 #[derive(Constructor, Debug)]
-pub struct CalleeTranslationState<'a, 'tcx, 'def> {
+pub struct CalleeState<'a, 'tcx, 'def> {
     /// the env of the caller
     param_env: &'a ty::ParamEnv<'tcx>,
     /// the generic scope of the caller
-    param_scope: &'a ParamScope<'tcx, 'def>,
+    param_scope: &'a scope::Params<'tcx, 'def>,
 }
 
-/// The `TypeTranslator` has two modes:
+/// The type translator `TX` has three modes:
 /// - translation of a type within the generic scope of a function
-/// - translation of a type as part of an ADT definition, where we always translate parameters as
-/// variables, but need to track dependencies on other ADTs.
+/// - translation of a type as part of an ADT definition, where we always translate parameters as variables,
+///   but need to track dependencies on other ADTs.
+/// - translation of a type when translating the signature of a callee. Regions are always erased.
 #[derive(Debug)]
-pub enum TranslationStateInner<'b, 'def: 'b, 'tcx: 'def> {
+pub enum STInner<'b, 'def: 'b, 'tcx: 'def> {
     /// This type is used in a function
-    InFunction(&'b mut TypeTranslationScope<'tcx, 'def>),
+    InFunction(&'b mut FunctionState<'tcx, 'def>),
     /// We are generating the definition of an ADT and this type is used in there
-    TranslateAdt(AdtTranslationState<'b, 'tcx, 'def>),
+    TranslateAdt(AdtState<'b, 'tcx, 'def>),
     /// We are translating in an empty dummy scope.
     /// All regions are erased.
-    CalleeTranslation(CalleeTranslationState<'b, 'tcx, 'def>),
+    CalleeTranslation(CalleeState<'b, 'tcx, 'def>),
 }
-pub type TranslationState<'a, 'b, 'def, 'tcx> = &'a mut TranslationStateInner<'b, 'def, 'tcx>;
-pub type InFunctionState<'a, 'def, 'tcx> = &'a mut TypeTranslationScope<'tcx, 'def>;
-pub type TranslateAdtState<'a, 'tcx, 'def> = AdtTranslationState<'a, 'tcx, 'def>;
+pub type ST<'a, 'b, 'def, 'tcx> = &'a mut STInner<'b, 'def, 'tcx>;
+pub type InFunctionState<'a, 'def, 'tcx> = &'a mut FunctionState<'tcx, 'def>;
+pub type TranslateAdtState<'a, 'tcx, 'def> = AdtState<'a, 'tcx, 'def>;
 
-impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> {
+impl<'a, 'def, 'tcx> STInner<'a, 'def, 'tcx> {
     const fn in_function(&self) -> bool {
         matches!(*self, Self::InFunction(_))
     }
@@ -807,19 +222,19 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> {
         tcx: ty::TyCtxt<'tcx>,
         trait_did: DefId,
         args: &[ty::GenericArg<'tcx>],
-    ) -> Result<&GenericTraitUse<'def>, TranslationError<'tcx>> {
+    ) -> Result<&registry::GenericTraitUse<'def>, TranslationError<'tcx>> {
         trace!("Enter lookup_trait_use for trait_did = {trait_did:?}, args = {args:?}, self = {self:?}");
         let res = match &self {
             Self::InFunction(state) => {
-                let res = state.generic_scope.trait_scope.lookup_trait_use(tcx, trait_did, args)?;
+                let res = state.generic_scope.trait_scope().lookup_trait_use(tcx, trait_did, args)?;
                 res
             },
             Self::TranslateAdt(state) => {
-                let res = state.scope.trait_scope.lookup_trait_use(tcx, trait_did, args)?;
+                let res = state.scope.trait_scope().lookup_trait_use(tcx, trait_did, args)?;
                 res
             },
             Self::CalleeTranslation(state) => {
-                let res = state.param_scope.trait_scope.lookup_trait_use(tcx, trait_did, args)?;
+                let res = state.param_scope.trait_scope().lookup_trait_use(tcx, trait_did, args)?;
                 res
             },
         };
@@ -830,14 +245,14 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> {
     /// Lookup a type parameter in the current state
     fn lookup_ty_param(&self, param_ty: ty::ParamTy) -> Result<radium::Type<'def>, TranslationError<'tcx>> {
         match self {
-            TranslationStateInner::InFunction(scope) => {
+            STInner::InFunction(scope) => {
                 let ty =
                     scope.generic_scope.lookup_ty_param_idx(param_ty.index as usize).ok_or_else(|| {
                         TranslationError::UnknownVar(format!("unknown generic parameter {:?}", param_ty))
                     })?;
                 Ok(radium::Type::LiteralParam(ty.clone()))
             },
-            TranslationStateInner::TranslateAdt(scope) => {
+            STInner::TranslateAdt(scope) => {
                 let ty = scope.scope.lookup_ty_param_idx(param_ty.index as usize).ok_or_else(|| {
                     TranslationError::UnknownVar(format!("unknown generic parameter {:?}", param_ty))
                 })?;
@@ -858,7 +273,7 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> {
         region: &ty::EarlyBoundRegion,
     ) -> Result<radium::Lft, TranslationError<'tcx>> {
         match self {
-            TranslationStateInner::InFunction(scope) => {
+            STInner::InFunction(scope) => {
                 info!("Looking up lifetime {region:?} in scope {:?}", scope.lifetime_scope);
                 let lft = scope
                     .lifetime_scope
@@ -866,22 +281,22 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> {
                     .ok_or(TranslationError::UnknownEarlyRegion(*region))?;
                 Ok(lft.to_owned())
             },
-            TranslationStateInner::TranslateAdt(scope) => {
+            STInner::TranslateAdt(scope) => {
                 // TODO: ?
                 if region.has_name() {
                     let name = region.name.as_str();
-                    return Ok(format!("ulft_{}", strip_coq_ident(name)));
+                    return Ok(format!("ulft_{}", base::strip_coq_ident(name)));
                 }
                 return Err(TranslationError::UnknownEarlyRegion(*region));
             },
-            TranslationStateInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()),
+            STInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()),
         }
     }
 
     /// Lookup a late-bound region.
     fn lookup_late_region(&self, region: usize) -> Result<radium::Lft, TranslationError<'tcx>> {
         match self {
-            TranslationStateInner::InFunction(scope) => {
+            STInner::InFunction(scope) => {
                 info!("Looking up lifetime {region:?} in scope {:?}", scope.lifetime_scope);
                 let lft = scope
                     .lifetime_scope
@@ -889,22 +304,22 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> {
                     .ok_or(TranslationError::UnknownLateRegion(region))?;
                 Ok(lft.to_owned())
             },
-            TranslationStateInner::TranslateAdt(scope) => {
+            STInner::TranslateAdt(scope) => {
                 info!("Translating region: ReLateBound {region:?} as None (outside of function)");
                 return Err(TranslationError::UnknownLateRegionOutsideFunction(region));
             },
-            TranslationStateInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()),
+            STInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()),
         }
     }
 
     /// Try to translate a Polonius region variable to a Caesium lifetime.
-    fn lookup_polonius_var(&self, v: facts::Region) -> Result<radium::Lft, TranslationError<'tcx>> {
+    pub fn lookup_polonius_var(&self, v: facts::Region) -> Result<radium::Lft, TranslationError<'tcx>> {
         match self {
-            TranslationStateInner::InFunction(scope) => {
+            STInner::InFunction(scope) => {
                 if let Some(info) = scope.polonius_info {
                     // If there is Polonius Info available, use that for translation
                     let x = info.mk_atomic_region(v);
-                    let r = format_atomic_region_direct(&x, Some(&**scope));
+                    let r = format_atomic_region_direct(&x, Some(&scope.lifetime_scope));
                     info!("Translating region: ReVar {:?} as {:?}", v, r);
                     Ok(r)
                 } else {
@@ -917,16 +332,16 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> {
                     Ok(r.to_owned())
                 }
             },
-            TranslationStateInner::TranslateAdt(scope) => {
+            STInner::TranslateAdt(scope) => {
                 info!("Translating region: ReVar {:?} as None (outside of function)", v);
                 return Err(TranslationError::PoloniusRegionOutsideFunction(v));
             },
-            TranslationStateInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()),
+            STInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()),
         }
     }
 }
 
-pub struct TypeTranslator<'def, 'tcx> {
+pub struct TX<'def, 'tcx> {
     env: &'def Environment<'tcx>,
 
     /// arena for keeping ownership of structs
@@ -976,14 +391,14 @@ pub struct TypeTranslator<'def, 'tcx> {
     adt_shims: RefCell<HashMap<DefId, radium::LiteralTypeRef<'def>>>,
 }
 
-impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
+impl<'def, 'tcx: 'def> TX<'def, 'tcx> {
     pub fn new(
         env: &'def Environment<'tcx>,
         struct_arena: &'def Arena<OnceCell<radium::AbstractStruct<'def>>>,
         enum_arena: &'def Arena<OnceCell<radium::AbstractEnum<'def>>>,
         shim_arena: &'def Arena<radium::LiteralType>,
     ) -> Self {
-        TypeTranslator {
+        TX {
             env,
             adt_deps: RefCell::new(HashMap::new()),
             adt_shims: RefCell::new(HashMap::new()),
@@ -1001,6 +416,10 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         self.shim_arena.alloc(lit)
     }
 
+    pub const fn env(&self) -> &'def Environment<'tcx> {
+        self.env
+    }
+
     /// Register a shim for an ADT.
     pub fn register_adt_shim(&self, did: DefId, lit: &radium::LiteralType) -> Result<(), String> {
         let lit_ref = self.intern_literal(lit.clone());
@@ -1118,19 +537,19 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
     }
 
     pub fn translate_region_in_scope(
-        scope: &ParamScope<'tcx, 'def>,
+        scope: &scope::Params<'tcx, 'def>,
         region: ty::Region<'tcx>,
     ) -> Result<radium::Lft, TranslationError<'tcx>> {
         let mut deps = HashSet::new();
         let env = ty::ParamEnv::empty();
-        let mut state = AdtTranslationState::new(&mut deps, scope, &env);
-        let mut state = TranslationStateInner::TranslateAdt(state);
+        let mut state = AdtState::new(&mut deps, scope, &env);
+        let mut state = STInner::TranslateAdt(state);
         Self::translate_region(&mut state, region)
     }
 
     /// Try to translate a region to a Caesium lifetime.
     pub fn translate_region<'a, 'b>(
-        translation_state: TranslationState<'a, 'b, 'def, 'tcx>,
+        translation_state: ST<'a, 'b, 'def, 'tcx>,
         region: ty::Region<'tcx>,
     ) -> Result<radium::Lft, TranslationError<'tcx>> {
         match *region {
@@ -1229,7 +648,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         &self,
         ty: Ty<'tcx>,
         variant: Option<target::abi::VariantIdx>,
-        adt_deps: TranslationState<'a, 'b, 'def, 'tcx>,
+        adt_deps: ST<'a, 'b, 'def, 'tcx>,
     ) -> Result<radium::Type<'def>, TranslationError<'tcx>> {
         match ty.kind() {
             TyKind::Adt(adt, args) => {
@@ -1285,7 +704,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         &self,
         adt_def: ty::AdtDef<'tcx>,
         args: F,
-        state: TranslationState<'a, 'b, 'def, 'tcx>,
+        state: ST<'a, 'b, 'def, 'tcx>,
     ) -> Result<radium::AbstractEnumUse<'def>, TranslationError<'tcx>>
     where
         F: IntoIterator<Item = ty::GenericArg<'tcx>>,
@@ -1295,10 +714,10 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 
         let (enum_ref, lit_ref) = self.lookup_enum(adt_def.did())?;
         let params = self.translate_generic_args(args, &mut *state)?;
-        let key = AdtUseKey::new(adt_def.did(), &params);
+        let key = scope::AdtUseKey::new(adt_def.did(), &params);
 
         // track this enum use for the current function
-        if let TranslationStateInner::InFunction(state) = state {
+        if let STInner::InFunction(state) = state {
             let lit_uses = &mut state.shim_uses;
 
             lit_uses
@@ -1327,7 +746,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
     fn translate_generic_args<'a, 'b, F>(
         &self,
         args: F,
-        state: TranslationState<'a, 'b, 'def, 'tcx>,
+        state: ST<'a, 'b, 'def, 'tcx>,
     ) -> Result<Vec<radium::Type<'def>>, TranslationError<'tcx>>
     where
         F: IntoIterator<Item = ty::GenericArg<'tcx>>,
@@ -1355,7 +774,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         &self,
         variant_id: DefId,
         args: F,
-        state: TranslationState<'a, 'b, 'def, 'tcx>,
+        state: ST<'a, 'b, 'def, 'tcx>,
     ) -> Result<radium::AbstractStructUse<'def>, TranslationError<'tcx>>
     where
         F: IntoIterator<Item = ty::GenericArg<'tcx>>,
@@ -1369,9 +788,9 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 
         let (struct_ref, lit_ref) = self.lookup_adt_variant(variant_id)?;
         let params = self.translate_generic_args(args, &mut *state)?;
-        let key = AdtUseKey::new(variant_id, &params);
+        let key = scope::AdtUseKey::new(variant_id, &params);
 
-        if let TranslationStateInner::InFunction(scope) = state {
+        if let STInner::InFunction(scope) = state {
             let lit_uses = &mut scope.shim_uses;
 
             lit_uses
@@ -1390,7 +809,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         adt_id: DefId,
         variant_idx: target::abi::VariantIdx,
         args: F,
-        state: TranslationState<'a, 'b, 'def, 'tcx>,
+        state: ST<'a, 'b, 'def, 'tcx>,
     ) -> Result<radium::AbstractStructUse<'def>, TranslationError<'tcx>>
     where
         F: IntoIterator<Item = ty::GenericArg<'tcx>>,
@@ -1421,7 +840,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
     pub fn generate_tuple_use<'a, 'b, F>(
         &self,
         tys: F,
-        state: TranslationState<'a, 'b, 'def, 'tcx>,
+        state: ST<'a, 'b, 'def, 'tcx>,
     ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>>
     where
         F: IntoIterator<Item = Ty<'tcx>>,
@@ -1436,7 +855,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 
         let key: Vec<_> = params.iter().map(radium::SynType::from).collect();
         let struct_use = radium::LiteralTypeUse::new(lit, params);
-        if let TranslationStateInner::InFunction(ref mut scope) = *state {
+        if let STInner::InFunction(ref mut scope) = *state {
             let tuple_uses = &mut scope.tuple_uses;
 
             tuple_uses.entry(key).or_insert_with(|| struct_use.clone());
@@ -1514,7 +933,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 
         let generics: &'tcx ty::Generics = self.env.tcx().generics_of(adt.did());
         let mut deps = HashSet::new();
-        let scope = ParamScope::from(generics.params.as_slice());
+        let scope = scope::Params::from(generics.params.as_slice());
         let param_env = self.env.tcx().param_env(adt.did());
 
         // first thing: figure out the generics we are using here.
@@ -1535,20 +954,16 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         let struct_def_init = self.struct_arena.alloc(OnceCell::new());
 
         let tcx = self.env.tcx();
-        let struct_name = strip_coq_ident(&ty.ident(tcx).to_string());
+        let struct_name = base::strip_coq_ident(&ty.ident(tcx).to_string());
 
         self.variant_registry
             .borrow_mut()
             .insert(ty.def_id, (struct_name, struct_def_init, ty, false, None));
 
         let translate_adt = || {
-            let struct_name = strip_coq_ident(&ty.ident(tcx).to_string());
-            let (variant_def, invariant_def) = self.make_adt_variant(
-                &struct_name,
-                ty,
-                adt,
-                AdtTranslationState::new(&mut deps, &scope, &param_env),
-            )?;
+            let struct_name = base::strip_coq_ident(&ty.ident(tcx).to_string());
+            let (variant_def, invariant_def) =
+                self.make_adt_variant(&struct_name, ty, adt, AdtState::new(&mut deps, &scope, &param_env))?;
 
             let mut struct_def = radium::AbstractStruct::new(variant_def, ty_param_defs);
             if let Some(invariant_def) = invariant_def {
@@ -1606,10 +1021,10 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 
         let expect_refinement;
         let mut invariant_spec;
-        if utils::has_tool_attr(outer_attrs, "refined_by") {
-            let outer_attrs = utils::filter_tool_attrs(outer_attrs);
+        if attrs::has_tool_attr(outer_attrs, "refined_by") {
+            let outer_attrs = attrs::filter_for_tool(outer_attrs);
             let mut spec_parser = struct_spec_parser::VerboseInvariantSpecParser::new(adt_deps.scope);
-            let ty_name = strip_coq_ident(format!("{}_inv_t", struct_name).as_str());
+            let ty_name = base::strip_coq_ident(format!("{}_inv_t", struct_name).as_str());
             let res = spec_parser
                 .parse_invariant_spec(&ty_name, &outer_attrs)
                 .map_err(TranslationError::FatalError)?;
@@ -1628,16 +1043,12 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
             let f_name = f.ident(tcx).to_string();
 
             let attrs = self.env.get_attributes(f.did);
-            let attrs = utils::filter_tool_attrs(attrs);
+            let attrs = attrs::filter_for_tool(attrs);
 
             let f_ty = self.env.tcx().type_of(f.did).instantiate_identity();
             let mut ty = self.translate_type_in_state(
                 f_ty,
-                &mut TranslationStateInner::TranslateAdt(AdtTranslationState::new(
-                    adt_deps.deps,
-                    adt_deps.scope,
-                    adt_deps.param_env,
-                )),
+                &mut STInner::TranslateAdt(AdtState::new(adt_deps.deps, adt_deps.scope, adt_deps.param_env)),
             )?;
 
             let mut parser = struct_spec_parser::VerboseStructFieldSpecParser::new(
@@ -1765,7 +1176,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
     }
 
     fn does_did_match(&self, did: DefId, path: &[&str]) -> bool {
-        let lookup_did = utils::try_resolve_did(self.env.tcx(), path);
+        let lookup_did = search::try_resolve_did(self.env.tcx(), path);
         if let Some(lookup_did) = lookup_did {
             if lookup_did == did {
                 return true;
@@ -1867,7 +1278,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         let tcx = self.env.tcx();
 
         let generics: &'tcx ty::Generics = self.env.tcx().generics_of(def.did());
-        let scope = ParamScope::from(generics.params.as_slice());
+        let scope = scope::Params::from(generics.params.as_slice());
         let mut deps = HashSet::new();
         let param_env = tcx.param_env(def.did());
 
@@ -1875,7 +1286,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         let enum_def_init = self.enum_arena.alloc(OnceCell::new());
 
         // TODO: currently a hack, I don't know how to query the name properly
-        let enum_name = strip_coq_ident(format!("{:?}", def).as_str());
+        let enum_name = base::strip_coq_ident(format!("{:?}", def).as_str());
 
         // first thing: figure out the generics we are using here.
         // we need to search all of the variant types for that.
@@ -1904,7 +1315,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
                 // now generate the variant
                 let struct_def_init = self.struct_arena.alloc(OnceCell::new());
 
-                let struct_name = strip_coq_ident(format!("{}_{}", enum_name, v.ident(tcx)).as_str());
+                let struct_name = base::strip_coq_ident(format!("{}_{}", enum_name, v.ident(tcx)).as_str());
                 self.variant_registry
                     .borrow_mut()
                     .insert(v.def_id, (struct_name.clone(), &*struct_def_init, v, true, None));
@@ -1913,7 +1324,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
                     struct_name.as_str(),
                     v,
                     def,
-                    AdtTranslationState::new(&mut deps, &scope, &param_env),
+                    AdtState::new(&mut deps, &scope, &param_env),
                 )?;
 
                 let mut struct_def = radium::AbstractStruct::new(variant_def, ty_param_defs.clone());
@@ -1923,7 +1334,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 
                 // also remember the attributes for additional processing
                 let outer_attrs = self.env.get_attributes(v.def_id);
-                let outer_attrs = utils::filter_tool_attrs(outer_attrs);
+                let outer_attrs = attrs::filter_for_tool(outer_attrs);
                 variant_attrs.push(outer_attrs);
 
                 // finalize the definition
@@ -1940,7 +1351,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
             // get the type of the discriminant
             let it = def.repr().discr_type();
             let it_is_signed = it.is_signed();
-            let translated_it = TypeTranslator::translate_integer_type(it);
+            let translated_it = TX::translate_integer_type(it);
 
             // build the discriminant map
             let discrs = self.build_discriminant_map(def, it_is_signed)?;
@@ -1957,7 +1368,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
                 enum_spec = spec;
             } else if self.env.has_tool_attribute(def.did(), "refined_by") {
                 let attributes = self.env.get_attributes(def.did());
-                let attributes = utils::filter_tool_attrs(attributes);
+                let attributes = attrs::filter_for_tool(attributes);
 
                 let mut parser = VerboseEnumSpecParser::new(&scope);
 
@@ -1987,7 +1398,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
             // now build the enum itself
             for v in def.variants() {
                 let (variant_ref, _) = self.lookup_adt_variant(v.def_id)?;
-                let variant_name = strip_coq_ident(&v.ident(tcx).to_string());
+                let variant_name = base::strip_coq_ident(&v.ident(tcx).to_string());
                 let discriminant = discrs[&variant_name];
                 enum_builder.add_variant(&variant_name, variant_ref, discriminant);
             }
@@ -2022,7 +1433,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         &self,
         adt: ty::AdtDef<'tcx>,
         substs: ty::GenericArgsRef<'tcx>,
-        state: TranslationState<'a, 'b, 'def, 'tcx>,
+        state: ST<'a, 'b, 'def, 'tcx>,
     ) -> Result<radium::Type<'def>, TranslationError<'tcx>> {
         let Some(shim) = self.lookup_adt_shim(adt.did()) else {
             return Err(TranslationError::UnknownError(format!(
@@ -2033,10 +1444,10 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 
         let params = self.translate_generic_args(substs.iter(), &mut *state)?;
 
-        let key = AdtUseKey::new(adt.did(), &params);
+        let key = scope::AdtUseKey::new(adt.did(), &params);
         let shim_use = radium::LiteralTypeUse::new(shim, params);
 
-        if let TranslationStateInner::InFunction(scope) = state {
+        if let STInner::InFunction(scope) = state {
             // track this shim use for the current function
             scope.shim_uses.entry(key).or_insert_with(|| shim_use.clone());
         }
@@ -2049,7 +1460,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
     pub fn translate_type_in_state<'a, 'b>(
         &self,
         ty: Ty<'tcx>,
-        state: TranslationState<'a, 'b, 'def, 'tcx>,
+        state: ST<'a, 'b, 'def, 'tcx>,
     ) -> Result<radium::Type<'def>, TranslationError<'tcx>> {
         match ty.kind() {
             TyKind::Bool => Ok(radium::Type::Bool),
@@ -2088,7 +1499,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 
                 let translated_ty = self.translate_type_in_state(*ty, &mut *state)?;
 
-                let lft = TypeTranslator::translate_region(&mut *state, *region)?;
+                let lft = TX::translate_region(&mut *state, *region)?;
 
                 match mutability {
                     ast::ast::Mutability::Mut => Ok(radium::Type::MutRef(Box::new(translated_ty), lft)),
@@ -2117,7 +1528,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
                     return Ok(radium::Type::Unit);
                 }
 
-                if let TranslationStateInner::TranslateAdt(state) = state {
+                if let STInner::TranslateAdt(state) = state {
                     state.deps.insert(adt.did());
                 }
 
@@ -2196,8 +1607,8 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
                         let type_name = self
                             .env
                             .get_assoc_item_name(alias_ty.def_id)
-                            .ok_or(trait_registry::Error::NotAnAssocType(alias_ty.def_id))?;
-                        let assoc_type = trait_use.make_assoc_type_use(&strip_coq_ident(&type_name));
+                            .ok_or(traits::Error::NotAnAssocType(alias_ty.def_id))?;
+                        let assoc_type = trait_use.make_assoc_type_use(&base::strip_coq_ident(&type_name));
 
                         info!("Resolved projection to {assoc_type:?}");
 
@@ -2360,13 +1771,13 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 }
 
 /// Public functions used by the `BodyTranslator`.
-impl<'def, 'tcx> TypeTranslator<'def, 'tcx> {
+impl<'def, 'tcx> TX<'def, 'tcx> {
     /// Translate a MIR type to the Caesium syntactic type we need when storing an element of the type,
     /// substituting all generics.
     pub fn translate_type_to_syn_type(
         &self,
         ty: Ty<'tcx>,
-        scope: TranslationState<'_, '_, 'def, 'tcx>,
+        scope: ST<'_, '_, 'def, 'tcx>,
     ) -> Result<radium::SynType, TranslationError<'tcx>> {
         self.translate_type_in_state(ty, scope).map(radium::SynType::from)
     }
@@ -2377,19 +1788,19 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> {
         ty: Ty<'tcx>,
         scope: InFunctionState<'_, 'def, 'tcx>,
     ) -> Result<radium::Type<'def>, TranslationError<'tcx>> {
-        self.translate_type_in_state(ty, &mut TranslationStateInner::InFunction(&mut *scope))
+        self.translate_type_in_state(ty, &mut STInner::InFunction(&mut *scope))
     }
 
     /// Translate type in an empty scope.
     pub fn translate_type_in_scope(
         &self,
-        scope: &ParamScope<'tcx, 'def>,
+        scope: &scope::Params<'tcx, 'def>,
         ty: Ty<'tcx>,
     ) -> Result<radium::Type<'def>, TranslationError<'tcx>> {
         let mut deps = HashSet::new();
         let param_env = ty::ParamEnv::empty();
-        let mut state = AdtTranslationState::new(&mut deps, scope, &param_env);
-        self.translate_type_in_state(ty, &mut TranslationStateInner::TranslateAdt(state))
+        let mut state = AdtState::new(&mut deps, scope, &param_env);
+        self.translate_type_in_state(ty, &mut STInner::TranslateAdt(state))
     }
 
     /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently
@@ -2423,14 +1834,12 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> {
                     })
                 }
             },
-            TyKind::Tuple(args) => {
-                self.generate_tuple_use(*args, &mut TranslationStateInner::InFunction(scope)).map(Some)
-            },
+            TyKind::Tuple(args) => self.generate_tuple_use(*args, &mut STInner::InFunction(scope)).map(Some),
             TyKind::Closure(_, args) => {
                 // use the upvar tuple
                 let closure_args = args.as_closure();
                 let upvars = closure_args.upvar_tys();
-                self.generate_tuple_use(upvars, &mut TranslationStateInner::InFunction(scope)).map(Some)
+                self.generate_tuple_use(upvars, &mut STInner::InFunction(scope)).map(Some)
             },
             _ => Err(TranslationError::UnknownError("not a structlike".to_owned())),
         }
@@ -2451,9 +1860,8 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> {
         self.register_adt(adt_def)?;
 
         let enum_ref: radium::LiteralTypeRef<'def> = self.lookup_enum_literal(adt_def.did())?;
-        let params =
-            self.translate_generic_args(args, &mut TranslationStateInner::InFunction(&mut *state))?;
-        let key = AdtUseKey::new(adt_def.did(), &params);
+        let params = self.translate_generic_args(args, &mut STInner::InFunction(&mut *state))?;
+        let key = scope::AdtUseKey::new(adt_def.did(), &params);
         let enum_use = radium::LiteralTypeUse::new(enum_ref, params);
 
         // track this enum use for the current function
@@ -2483,9 +1891,8 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> {
             return Ok(None);
         }
 
-        let params =
-            self.translate_generic_args(args, &mut TranslationStateInner::InFunction(&mut *scope))?;
-        let key = AdtUseKey::new(variant_id, &params);
+        let params = self.translate_generic_args(args, &mut STInner::InFunction(&mut *scope))?;
+        let key = scope::AdtUseKey::new(variant_id, &params);
 
         let struct_ref: radium::LiteralTypeRef<'def> = self.lookup_adt_variant_literal(variant_id)?;
         let struct_use = radium::LiteralTypeUse::new(struct_ref, params);
@@ -2508,9 +1915,9 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> {
     {
         info!("generating enum variant use for {:?}", variant_id);
 
-        let x: TranslationState<'_, '_, 'def, 'tcx> = &mut TranslationStateInner::InFunction(&mut *scope);
+        let x: ST<'_, '_, 'def, 'tcx> = &mut STInner::InFunction(&mut *scope);
         let params = self.translate_generic_args(args, x)?;
-        let _key = AdtUseKey::new(variant_id, &params);
+        let _key = scope::AdtUseKey::new(variant_id, &params);
 
         let struct_ref: radium::LiteralTypeRef<'def> = self.lookup_adt_variant_literal(variant_id)?;
         let struct_use = radium::LiteralTypeUse::new(struct_ref, params);
@@ -2528,7 +1935,7 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> {
     /// Make a tuple use.
     /// Hack: This does not take the translation state but rather a direct reference to the tuple
     /// use map so that we can this function when parsing closure specifications.
-    fn make_tuple_use(
+    pub fn make_tuple_use(
         &self,
         translated_tys: Vec<radium::Type<'def>>,
         uses: Option<&mut HashMap<Vec<radium::SynType>, radium::LiteralTypeUse<'def>>>,
@@ -2548,300 +1955,3 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> {
         radium::Type::Literal(struct_use)
     }
 }
-
-/// Type translator bundling the function scope
-#[allow(clippy::module_name_repetitions)]
-pub struct LocalTypeTranslator<'def, 'tcx> {
-    pub translator: &'def TypeTranslator<'def, 'tcx>,
-    pub scope: RefCell<TypeTranslationScope<'tcx, 'def>>,
-}
-
-impl<'def, 'tcx> LocalTypeTranslator<'def, 'tcx> {
-    pub const fn new(
-        translator: &'def TypeTranslator<'def, 'tcx>,
-        scope: TypeTranslationScope<'tcx, 'def>,
-    ) -> Self {
-        Self {
-            translator,
-            scope: RefCell::new(scope),
-        }
-    }
-
-    /// Get the `DefId` of the current function.
-    pub fn get_proc_did(&self) -> DefId {
-        let scope = self.scope.borrow();
-        scope.did
-    }
-
-    /// Translate a MIR type to the Radium syntactic type we need when storing an element of the type,
-    /// substituting all generics.
-    pub fn translate_type_to_syn_type(
-        &self,
-        ty: Ty<'tcx>,
-    ) -> Result<radium::SynType, TranslationError<'tcx>> {
-        let mut scope = self.scope.borrow_mut();
-        let mut state = TranslationStateInner::InFunction(&mut scope);
-        self.translator.translate_type_to_syn_type(ty, &mut state)
-    }
-
-    /// Translate a region in the scope of the current function.
-    pub fn translate_region(&self, region: ty::Region<'tcx>) -> Result<radium::Lft, TranslationError<'tcx>> {
-        let mut scope = self.scope.borrow_mut();
-        let mut scope = TranslationStateInner::InFunction(&mut scope);
-        TypeTranslator::translate_region(&mut scope, region)
-    }
-
-    /// Translate a Polonius region variable in the scope of the current function.
-    pub fn translate_region_var(&self, region: facts::Region) -> Result<radium::Lft, TranslationError<'tcx>> {
-        let mut scope = self.scope.borrow_mut();
-        let mut scope = TranslationStateInner::InFunction(&mut scope);
-        scope.lookup_polonius_var(region)
-    }
-
-    /// Translate type.
-    pub fn translate_type(&self, ty: Ty<'tcx>) -> Result<radium::Type<'def>, TranslationError<'tcx>> {
-        let mut scope = self.scope.borrow_mut();
-        self.translator.translate_type(ty, &mut scope)
-    }
-
-    /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently
-    /// registering a new ADT.
-    pub fn generate_structlike_use(
-        &self,
-        ty: Ty<'tcx>,
-        variant: Option<target::abi::VariantIdx>,
-    ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError<'tcx>> {
-        let mut scope = self.scope.borrow_mut();
-        self.translator.generate_structlike_use(ty, variant, &mut scope)
-    }
-
-    /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently
-    /// registering a new ADT.
-    pub fn generate_enum_use<F>(
-        &self,
-        adt_def: ty::AdtDef<'tcx>,
-        args: F,
-    ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>>
-    where
-        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
-    {
-        let mut scope = self.scope.borrow_mut();
-        self.translator.generate_enum_use(adt_def, args, &mut scope)
-    }
-
-    /// Generate a struct use.
-    /// Returns None if this should be unit.
-    /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently
-    /// registering a new ADT.
-    pub fn generate_struct_use<F>(
-        &self,
-        variant_id: DefId,
-        args: F,
-    ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError<'tcx>>
-    where
-        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
-    {
-        let mut scope = self.scope.borrow_mut();
-        self.translator.generate_struct_use(variant_id, args, &mut scope)
-    }
-
-    /// Generate a struct use.
-    /// Returns None if this should be unit.
-    pub fn generate_enum_variant_use<F>(
-        &self,
-        variant_id: DefId,
-        args: F,
-    ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>>
-    where
-        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
-    {
-        let mut scope = self.scope.borrow_mut();
-        self.translator.generate_enum_variant_use(variant_id, args, &mut scope)
-    }
-
-    /// Make a tuple use.
-    /// Hack: This does not take the translation state but rather a direct reference to the tuple
-    /// use map so that we can this function when parsing closure specifications.
-    pub fn make_tuple_use(
-        &self,
-        translated_tys: Vec<radium::Type<'def>>,
-        uses: Option<&mut HashMap<Vec<radium::SynType>, radium::LiteralTypeUse<'def>>>,
-    ) -> radium::Type<'def> {
-        self.translator.make_tuple_use(translated_tys, uses)
-    }
-
-    pub fn generate_tuple_use<F>(
-        &self,
-        tys: F,
-    ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>>
-    where
-        F: IntoIterator<Item = Ty<'tcx>>,
-    {
-        let mut scope = self.scope.borrow_mut();
-        self.translator
-            .generate_tuple_use(tys, &mut TranslationStateInner::InFunction(&mut scope))
-    }
-
-    /// Format the Coq representation of an atomic region.
-    pub fn format_atomic_region(&self, r: &polonius_info::AtomicRegion) -> String {
-        let scope = self.scope.borrow();
-        format_atomic_region_direct(r, Some(&*scope))
-    }
-
-    /// Normalize a type in the given function environment.
-    pub fn normalize<T>(&self, ty: T) -> Result<T, TranslationError<'tcx>>
-    where
-        T: ty::TypeFoldable<ty::TyCtxt<'tcx>>,
-    {
-        let scope = self.scope.borrow();
-        normalize_in_function(scope.did, self.translator.env.tcx(), ty)
-    }
-
-    pub fn get_trait_of_method(env: &Environment<'tcx>, method_did: DefId) -> Option<DefId> {
-        if let Some(impl_did) = env.tcx().impl_of_method(method_did) {
-            env.tcx().trait_id_of_impl(impl_did)
-        } else {
-            // else expect it to be an abstract method of a trait decl
-            env.tcx().trait_of_item(method_did)
-        }
-    }
-
-    /// Split the params of a trait method into params of the trait and params of the method
-    /// itself.
-    pub fn split_trait_method_args(
-        env: &Environment<'tcx>,
-        trait_did: DefId,
-        ty_params: ty::GenericArgsRef<'tcx>,
-    ) -> (ty::GenericArgsRef<'tcx>, ty::GenericArgsRef<'tcx>) {
-        // split args
-        let trait_generics: &'tcx ty::Generics = env.tcx().generics_of(trait_did);
-        let trait_generic_count = trait_generics.params.len();
-
-        let trait_args = &ty_params.as_slice()[..trait_generic_count];
-        let method_args = &ty_params.as_slice()[trait_generic_count..];
-
-        (env.tcx().mk_args(trait_args), env.tcx().mk_args(method_args))
-    }
-
-    /// Lookup a trait parameter.
-    pub fn lookup_trait_param(
-        &self,
-        env: &Environment<'tcx>,
-        trait_did: DefId,
-        args: ty::GenericArgsRef<'tcx>,
-    ) -> TraitResult<'tcx, GenericTraitUse<'def>> {
-        let scope = self.scope.borrow();
-        scope.generic_scope.trait_scope.lookup_trait_use(env.tcx(), trait_did, args).cloned()
-    }
-
-    /// Register a procedure use of a trait method.
-    /// The given `ty_params` need to include the args to both the trait and the method.
-    /// Returns:
-    /// - the parameter name for the method loc
-    /// - the spec term for the method spec
-    /// - the arguments of the method
-    pub fn register_use_trait_procedure(
-        &self,
-        env: &Environment<'tcx>,
-        trait_method_did: DefId,
-        ty_params: ty::GenericArgsRef<'tcx>,
-    ) -> Result<(String, String, ty::GenericArgsRef<'tcx>), TranslationError<'tcx>> {
-        let trait_did = env
-            .tcx()
-            .trait_of_item(trait_method_did)
-            .ok_or(trait_registry::Error::NotATrait(trait_method_did))?;
-
-        // split args
-        let (trait_args, method_args) = Self::split_trait_method_args(env, trait_did, ty_params);
-
-        let trait_spec_use = {
-            let scope = self.scope.borrow();
-            let entry = scope.generic_scope.trait_scope.lookup_trait_use(
-                env.tcx(),
-                trait_did,
-                trait_args.as_slice(),
-            )?;
-            let trait_use_ref = entry.trait_use.borrow();
-            trait_use_ref.as_ref().unwrap().clone()
-        };
-
-        // get name of the trait
-        let trait_name = trait_spec_use.trait_ref.name.clone();
-
-        // get name of the method
-        let method_name = env.get_assoc_item_name(trait_method_did).unwrap();
-        let mangled_method_name =
-            mangle_name_with_args(&strip_coq_ident(&method_name), method_args.as_slice());
-
-        let method_loc_name = trait_spec_use.make_loc_name(&mangled_method_name);
-
-        // get spec. the spec takes the generics of the method as arguments
-        let method_spec_term = trait_spec_use.make_method_spec_term(&method_name);
-
-        Ok((method_loc_name, method_spec_term, method_args))
-    }
-}
-
-/// Normalize a type in the given function environment.
-pub fn normalize_in_function<'tcx, T>(
-    function_did: DefId,
-    tcx: ty::TyCtxt<'tcx>,
-    ty: T,
-) -> Result<T, TranslationError<'tcx>>
-where
-    T: ty::TypeFoldable<ty::TyCtxt<'tcx>>,
-{
-    let param_env = tcx.param_env(function_did);
-    info!("Normalizing type {ty:?} in env {param_env:?}");
-    normalize_type(tcx, param_env, ty)
-        .map_err(|e| TranslationError::TraitResolution(format!("normalization error: {:?}", e)))
-}
-
-pub fn normalize_erasing_regions_in_function<'tcx, T>(
-    function_did: DefId,
-    tcx: ty::TyCtxt<'tcx>,
-    ty: T,
-) -> Result<T, TranslationError<'tcx>>
-where
-    T: ty::TypeFoldable<ty::TyCtxt<'tcx>>,
-{
-    let param_env = tcx.param_env(function_did);
-    info!("Normalizing type {ty:?} in env {param_env:?}");
-    tcx.try_normalize_erasing_regions(param_env, ty)
-        .map_err(|e| TranslationError::TraitResolution(format!("normalization error: {:?}", e)))
-}
-
-pub fn normalize_projection_in_function<'tcx>(
-    function_did: DefId,
-    tcx: ty::TyCtxt<'tcx>,
-    ty: ty::AliasTy<'tcx>,
-) -> Result<ty::Ty<'tcx>, TranslationError<'tcx>> {
-    let param_env = tcx.param_env(function_did);
-    info!("Normalizing type {ty:?} in env {param_env:?}");
-    normalize_projection_type(tcx, param_env, ty)
-        .map_err(|e| TranslationError::TraitResolution(format!("could not normalize projection {ty:?}")))
-}
-
-/// Format the Coq representation of an atomic region.
-pub fn format_atomic_region_direct(
-    r: &polonius_info::AtomicRegion,
-    scope: Option<&TypeTranslationScope<'_, '_>>,
-) -> String {
-    match r {
-        polonius_info::AtomicRegion::Loan(_, r) => format!("llft{}", r.index()),
-        polonius_info::AtomicRegion::PlaceRegion(r) => format!("plft{}", r.index()),
-        polonius_info::AtomicRegion::Unknown(r) => format!("vlft{}", r.index()),
-
-        polonius_info::AtomicRegion::Universal(_, r) => {
-            let Some(scope) = scope else {
-                return format!("ulft{}", r.index());
-            };
-
-            let Some(s) = scope.lifetime_scope.lookup_region(*r) else {
-                return format!("ulft{}", r.index());
-            };
-
-            s.to_string()
-        },
-    }
-}
diff --git a/rr_frontend/translation/src/tyvars.rs b/rr_frontend/translation/src/types/tyvars.rs
similarity index 79%
rename from rr_frontend/translation/src/tyvars.rs
rename to rr_frontend/translation/src/types/tyvars.rs
index 76952d000a29a89182c6ba50990b642e268391c3..7cb852619112f6dd9dee2bdd1a98ad9916eee4f4 100644
--- a/rr_frontend/translation/src/tyvars.rs
+++ b/rr_frontend/translation/src/types/tyvars.rs
@@ -4,14 +4,14 @@
 // If a copy of the BSD-3-clause license was not distributed with this
 // file, You can obtain one at https://opensource.org/license/bsd-3-clause/.
 
+//! Utility folders for handling type variables.
+
 use std::collections::{HashMap, HashSet};
 
 use rr_rustc_interface::middle::ty;
 use rr_rustc_interface::middle::ty::{Ty, TyCtxt, TyKind};
 use ty::TypeSuperFoldable;
 
-use crate::base::*;
-
 /// A `TypeFolder` that gathers all the type variables.
 pub struct TyVarFolder<'tcx> {
     tcx: TyCtxt<'tcx>,
@@ -141,42 +141,3 @@ impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for TyRegionEraseFolder<'tcx> {
         ty::Region::new_from_kind(self.interner(), ty::RegionKind::ReErased)
     }
 }
-
-/// A `TypeFolder` that finds all regions occurring in a type.
-pub struct TyRegionCollectFolder<'tcx> {
-    tcx: TyCtxt<'tcx>,
-    regions: HashSet<Region>,
-}
-impl<'tcx> TyRegionCollectFolder<'tcx> {
-    pub fn new(tcx: TyCtxt<'tcx>) -> Self {
-        TyRegionCollectFolder {
-            tcx,
-            regions: HashSet::new(),
-        }
-    }
-
-    pub fn get_regions(self) -> HashSet<Region> {
-        self.regions
-    }
-}
-impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for TyRegionCollectFolder<'tcx> {
-    fn interner(&self) -> TyCtxt<'tcx> {
-        self.tcx
-    }
-
-    // TODO: handle the case that we pass below binders
-    fn fold_binder<T>(&mut self, t: ty::Binder<'tcx, T>) -> ty::Binder<'tcx, T>
-    where
-        T: ty::TypeFoldable<TyCtxt<'tcx>>,
-    {
-        t.super_fold_with(self)
-    }
-
-    fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
-        if let ty::RegionKind::ReVar(r) = r.kind() {
-            self.regions.insert(r);
-        }
-
-        r
-    }
-}
diff --git a/rr_frontend/translation/src/utils.rs b/rr_frontend/translation/src/utils.rs
index cb00fd77a826021a04ae7f66db3697b5a0aa2dae..2c8e7e7cd600bb223caa7cf68f05c0a30494211d 100644
--- a/rr_frontend/translation/src/utils.rs
+++ b/rr_frontend/translation/src/utils.rs
@@ -1,554 +1,17 @@
-// Except for the exceptions specified below, this code is © 2019, ETH Zurich
+// © 2019, ETH Zurich
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
-//
-// The following functions have been adapted from Miri (https://github.com/rust-lang/miri/blob/31fb32e49f42df19b45baccb6aa80c3d726ed6d5/src/helpers.rs#L48) under the MIT license;
-// - `try_resolve_did`
 
-//! Various helper functions for working with `mir::Place`.
-
-use std::mem;
+//! Utility functions for manipulating places.
 
 use log::{info, trace};
-use rr_rustc_interface::ast::ast;
 use rr_rustc_interface::data_structures::fx::FxHashSet;
-use rr_rustc_interface::hir::def_id::{DefId, CRATE_DEF_INDEX};
 use rr_rustc_interface::middle::mir;
 use rr_rustc_interface::middle::ty::{self, TyCtxt};
 use rr_rustc_interface::{hir, middle, span};
-use serde::{Deserialize, Serialize};
-
-use crate::spec_parsers::get_export_as_attr;
-use crate::type_translator::normalize_in_function;
-use crate::{force_matches, Environment};
-
-/// An item path that receives generic arguments.
-#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
-pub struct PathWithArgs {
-    path: Vec<String>,
-    /// An encoding of the GenericArgs for this definition.
-    /// This is `Some(ty)` if:
-    /// - the argument represents a type (not a constant or region)
-    /// - and the arg is not the trivial identity arg (in case of ADTs)
-    args: Vec<Option<FlatType>>,
-}
-
-impl PathWithArgs {
-    pub fn to_item<'tcx>(&self, tcx: ty::TyCtxt<'tcx>) -> Option<(DefId, Vec<Option<ty::GenericArg<'tcx>>>)> {
-        let did = try_resolve_did(tcx, self.path.as_slice())?;
 
-        let mut ty_args = Vec::new();
-
-        for arg in &self.args {
-            if let Some(arg) = arg {
-                let ty = arg.to_type(tcx)?;
-                ty_args.push(Some(ty::GenericArg::from(ty)));
-            } else {
-                ty_args.push(None);
-            }
-        }
-
-        Some((did, ty_args))
-    }
-
-    /// `args` should be normalized already.
-    pub fn from_item<'tcx>(
-        env: &Environment<'tcx>,
-        did: DefId,
-        args: &[ty::GenericArg<'tcx>],
-    ) -> Option<Self> {
-        let path = get_export_path_for_did(env, did);
-        let mut flattened_args = Vec::new();
-        // TODO: how to represent type variables in case the definition is open?
-        let mut index = 0;
-        info!("flattening args {:?}", args);
-        for arg in args {
-            if let Some(ty) = arg.as_type() {
-                // TODO not quite right yet (see above)
-                if !ty.is_param(index) {
-                    let flattened_ty = convert_ty_to_flat_type(env, ty)?;
-                    flattened_args.push(Some(flattened_ty));
-                }
-                index += 1;
-            } else {
-                flattened_args.push(None);
-            }
-        }
-        Some(Self {
-            path,
-            args: flattened_args,
-        })
-    }
-}
-
-#[derive(Serialize, Deserialize)]
-#[serde(remote = "ty::IntTy")]
-pub enum IntTyDef {
-    Isize,
-    I8,
-    I16,
-    I32,
-    I64,
-    I128,
-}
-#[derive(Serialize, Deserialize)]
-#[serde(remote = "ty::UintTy")]
-pub enum UintTyDef {
-    Usize,
-    U8,
-    U16,
-    U32,
-    U64,
-    U128,
-}
-
-#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
-/// A "flattened" representation of types that should be suitable serialized storage, and should be
-/// stable enough to resolve to the same actual type across compilations.
-/// Currently mostly geared to our trait resolution needs.
-pub enum FlatType {
-    /// Path + generic args
-    /// empty args represents the identity substitution
-    Adt(PathWithArgs),
-    #[serde(with = "IntTyDef")]
-    Int(ty::IntTy),
-    #[serde(with = "UintTyDef")]
-    Uint(ty::UintTy),
-    Char,
-    Bool,
-    // TODO: more cases
-}
-
-impl FlatType {
-    /// Try to convert a flat type to a type.
-    pub fn to_type<'tcx>(&self, tcx: ty::TyCtxt<'tcx>) -> Option<ty::Ty<'tcx>> {
-        match self {
-            Self::Adt(path_with_args) => {
-                let (did, flat_args) = path_with_args.to_item(tcx)?;
-
-                let ty: ty::EarlyBinder<ty::Ty<'tcx>> = tcx.type_of(did);
-                let ty::TyKind::Adt(_, args) = ty.skip_binder().kind() else {
-                    return None;
-                };
-
-                // build substitution
-                let mut substs = Vec::new();
-                for (ty_arg, flat_arg) in args.iter().zip(flat_args.into_iter()) {
-                    match ty_arg.unpack() {
-                        ty::GenericArgKind::Type(_) => {
-                            if let Some(flat_arg) = flat_arg {
-                                substs.push(flat_arg);
-                            }
-                        },
-                        _ => {
-                            substs.push(ty_arg);
-                        },
-                    }
-                }
-
-                // substitute
-                info!("substituting {:?} with {:?}", ty, substs);
-                let subst_ty =
-                    if substs.is_empty() { ty.instantiate_identity() } else { ty.instantiate(tcx, &substs) };
-
-                Some(subst_ty)
-            },
-            Self::Bool => Some(tcx.mk_ty_from_kind(ty::TyKind::Bool)),
-            Self::Char => Some(tcx.mk_ty_from_kind(ty::TyKind::Char)),
-            Self::Int(it) => Some(tcx.mk_ty_from_kind(ty::TyKind::Int(it.to_owned()))),
-            Self::Uint(it) => Some(tcx.mk_ty_from_kind(ty::TyKind::Uint(it.to_owned()))),
-        }
-    }
-}
-
-/// Try to convert a type to a flat type. Assumes the type has been normalized already.
-pub fn convert_ty_to_flat_type<'tcx>(env: &Environment<'tcx>, ty: ty::Ty<'tcx>) -> Option<FlatType> {
-    match ty.kind() {
-        ty::TyKind::Adt(def, args) => {
-            let did = def.did();
-            // TODO: if this is downcast to a variant, this might not work
-            let path_with_args = PathWithArgs::from_item(env, did, args.as_slice())?;
-            Some(FlatType::Adt(path_with_args))
-        },
-        ty::TyKind::Bool => Some(FlatType::Bool),
-        ty::TyKind::Char => Some(FlatType::Char),
-        ty::TyKind::Int(it) => Some(FlatType::Int(it.to_owned())),
-        ty::TyKind::Uint(it) => Some(FlatType::Uint(it.to_owned())),
-        _ => None,
-    }
-}
-
-pub fn get_cleaned_def_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> {
-    let def_path = tcx.def_path_str(did);
-    // we clean this up a bit and segment it
-    let mut components = Vec::new();
-    for i in def_path.split("::") {
-        if i.starts_with('<') && i.ends_with('>') {
-            // this is a generic specialization, skip
-            continue;
-        }
-        components.push(i.to_owned());
-    }
-    info!("split def path {:?} to {:?}", def_path, components);
-
-    components
-}
-
-// Alternative implementation of `get_cleaned_def_path`, but this doesn't always yield a rooted
-// path (but only a suffix of the full path)
-fn extract_def_path(path: &hir::definitions::DefPath) -> Vec<String> {
-    let mut components = Vec::new();
-    for p in &path.data {
-        if let Some(name) = p.data.get_opt_name() {
-            components.push(name.as_str().to_owned());
-        }
-    }
-    components
-}
-
-/// Get the path we should export an item at.
-pub fn get_export_path_for_did(env: &Environment, did: DefId) -> Vec<String> {
-    let attrs = env.get_attributes(did);
-
-    if has_tool_attr(attrs, "export_as") {
-        let filtered_attrs = filter_tool_attrs(attrs);
-
-        return get_export_as_attr(filtered_attrs.as_slice()).unwrap();
-    }
-
-    // Check for an annotation on the surrounding impl
-    if let Some(impl_did) = env.tcx().impl_of_method(did) {
-        let attrs = env.get_attributes(impl_did);
-
-        if has_tool_attr(attrs, "export_as") {
-            let filtered_attrs = filter_tool_attrs(attrs);
-            let mut path_prefix = get_export_as_attr(filtered_attrs.as_slice()).unwrap();
-
-            // push the last component of this path
-            //let def_path = env.tcx().def_path(did);
-            let mut this_path = get_cleaned_def_path(env.tcx(), did);
-            path_prefix.push(this_path.pop().unwrap());
-
-            return path_prefix;
-        }
-    }
-
-    get_cleaned_def_path(env.tcx(), did)
-}
-
-/// Gets an instance for a path.
-/// Taken from Miri <https://github.com/rust-lang/miri/blob/31fb32e49f42df19b45baccb6aa80c3d726ed6d5/src/helpers.rs#L48>.
-pub fn try_resolve_did_direct<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId>
-where
-    T: AsRef<str>,
-{
-    tcx.crates(())
-        .iter()
-        .find(|&&krate| tcx.crate_name(krate).as_str() == path[0].as_ref())
-        .and_then(|krate| {
-            let krate = DefId {
-                krate: *krate,
-                index: CRATE_DEF_INDEX,
-            };
-
-            let mut items: &[middle::metadata::ModChild] = tcx.module_children(krate);
-            let mut path_it = path.iter().skip(1).peekable();
-
-            while let Some(segment) = path_it.next() {
-                for item in mem::take(&mut items) {
-                    let item: &middle::metadata::ModChild = item;
-                    if item.ident.name.as_str() == segment.as_ref() {
-                        if path_it.peek().is_none() {
-                            return Some(item.res.def_id());
-                        }
-
-                        items = tcx.module_children(item.res.def_id());
-                        break;
-                    }
-                }
-            }
-            None
-        })
-}
-
-pub fn try_resolve_did<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId>
-where
-    T: AsRef<str>,
-{
-    if let Some(did) = try_resolve_did_direct(tcx, path) {
-        return Some(did);
-    }
-
-    // if the first component is "std", try if we can replace it with "alloc" or "core"
-    if path[0].as_ref() == "std" {
-        let mut components: Vec<_> = path.iter().map(|x| x.as_ref().to_owned()).collect();
-        components[0] = "core".to_owned();
-        if let Some(did) = try_resolve_did_direct(tcx, &components) {
-            return Some(did);
-        }
-        // try "alloc"
-        components[0] = "alloc".to_owned();
-        try_resolve_did_direct(tcx, &components)
-    } else {
-        None
-    }
-}
-
-/// Determine whether the two argument lists match for the type positions (ignoring consts and regions).
-/// The first argument is the authority determining which argument positions are types.
-/// The second argument may contain `None` for non-type positions.
-fn args_match_types<'tcx>(
-    reference: &[ty::GenericArg<'tcx>],
-    compare: &[Option<ty::GenericArg<'tcx>>],
-) -> bool {
-    if reference.len() != compare.len() {
-        return false;
-    }
-
-    for (arg1, arg2) in reference.iter().zip(compare.iter()) {
-        if let Some(ty1) = arg1.as_type() {
-            if let Some(arg2) = arg2 {
-                if let Some(ty2) = arg2.as_type() {
-                    if ty1 != ty2 {
-                        return false;
-                    }
-                } else {
-                    return false;
-                }
-            } else {
-                return false;
-            }
-        }
-    }
-    true
-}
-
-// Useful queries:
-//tcx.trait_id_of_impl
-//tcx.associated_items
-//tcx.impl_trait_parent
-//tcx.implementations_of_trait
-//tcx.trait_impls_of
-//tcx.trait_impls_in_crate
-/// Try to resolve the `DefId` of an implementation of a trait for a particular type.
-/// Note that this does not, in general, find a unique solution, in case there are complex things
-/// with different where clauses going on.
-pub fn try_resolve_trait_impl_did<'tcx>(
-    tcx: TyCtxt<'tcx>,
-    trait_did: DefId,
-    trait_args: &[Option<ty::GenericArg<'tcx>>],
-    for_type: ty::Ty<'tcx>,
-) -> Option<DefId> {
-    // get all impls of this trait
-    let impls: &ty::trait_def::TraitImpls = tcx.trait_impls_of(trait_did);
-
-    let simplified_type =
-        middle::ty::fast_reject::simplify_type(tcx, for_type, ty::fast_reject::TreatParams::AsCandidateKey)?;
-    let defs = impls.non_blanket_impls().get(&simplified_type)?;
-    info!("found implementations: {:?}", impls);
-
-    let mut solution = None;
-    for did in defs {
-        let impl_self_ty: ty::Ty<'tcx> = tcx.type_of(did).instantiate_identity();
-        let impl_self_ty = normalize_in_function(*did, tcx, impl_self_ty).unwrap();
-
-        // check if this is an implementation for the right type
-        // TODO: is this the right way to compare the types?
-        if impl_self_ty == for_type {
-            let impl_ref: Option<ty::EarlyBinder<ty::TraitRef<'_>>> = tcx.impl_trait_ref(did);
-
-            if let Some(impl_ref) = impl_ref {
-                let impl_ref = normalize_in_function(*did, tcx, impl_ref.skip_binder()).unwrap();
-
-                let this_impl_args = impl_ref.args;
-                // filter by the generic instantiation for the trait
-                info!("found impl with args {:?}", this_impl_args);
-                // args has self at position 0 and generics of the trait at position 1..
-
-                // check if the generic argument types match up
-                if !args_match_types(&this_impl_args.as_slice()[1..], trait_args) {
-                    continue;
-                }
-
-                info!("found impl {:?}", impl_ref);
-                if solution.is_some() {
-                    println!(
-                        "Warning: Ambiguous resolution for impl of trait {:?} on type {:?}; solution {:?} but found also {:?}",
-                        trait_did,
-                        for_type,
-                        solution.unwrap(),
-                        impl_ref.def_id,
-                    );
-                } else {
-                    solution = Some(*did);
-                }
-            }
-        }
-    }
-
-    solution
-}
-
-/// Try to resolve the `DefId` of a method in an implementation of a trait for a particular type.
-/// Note that this does not, in general, find a unique solution, in case there are complex things
-/// with different where clauses going on.
-pub fn try_resolve_trait_method_did<'tcx>(
-    tcx: TyCtxt<'tcx>,
-    trait_did: DefId,
-    trait_args: &[Option<ty::GenericArg<'tcx>>],
-    method_name: &str,
-    for_type: ty::Ty<'tcx>,
-) -> Option<DefId> {
-    // get all impls of this trait
-    let impls: &ty::trait_def::TraitImpls = tcx.trait_impls_of(trait_did);
-
-    let simplified_type =
-        middle::ty::fast_reject::simplify_type(tcx, for_type, ty::fast_reject::TreatParams::AsCandidateKey)?;
-    let defs = impls.non_blanket_impls().get(&simplified_type)?;
-    info!("found implementations: {:?}", impls);
-
-    let mut solution = None;
-    for did in defs {
-        let impl_self_ty: ty::Ty<'tcx> = tcx.type_of(did).instantiate_identity();
-        let impl_self_ty = normalize_in_function(*did, tcx, impl_self_ty).unwrap();
-
-        // check if this is an implementation for the right type
-        // TODO: is this the right way to compare the types?
-        if impl_self_ty == for_type {
-            let impl_ref: Option<ty::EarlyBinder<ty::TraitRef<'_>>> = tcx.impl_trait_ref(did);
-
-            if let Some(impl_ref) = impl_ref {
-                let impl_ref = normalize_in_function(*did, tcx, impl_ref.skip_binder()).unwrap();
-
-                let this_impl_args = impl_ref.args;
-                // filter by the generic instantiation for the trait
-                info!("found impl with args {:?}", this_impl_args);
-                // args has self at position 0 and generics of the trait at position 1..
-
-                // check if the generic argument types match up
-                if !args_match_types(&this_impl_args.as_slice()[1..], trait_args) {
-                    continue;
-                }
-
-                let impl_assoc_items: &ty::AssocItems = tcx.associated_items(did);
-                // find the right item
-                if let Some(item) = impl_assoc_items.find_by_name_and_kind(
-                    tcx,
-                    span::symbol::Ident::from_str(method_name),
-                    ty::AssocKind::Fn,
-                    trait_did,
-                ) {
-                    info!("found impl {:?} with item {:?}", impl_ref, item);
-                    if solution.is_some() {
-                        println!(
-                            "Warning: Ambiguous resolution for method {method_name} of trait {:?} on type {:?}; solution {:?} but found also {:?}",
-                            trait_did,
-                            for_type,
-                            solution.unwrap(),
-                            item.def_id
-                        );
-                    } else {
-                        solution = Some(item.def_id);
-                    }
-                }
-            }
-        }
-    }
-
-    solution
-}
-
-/// Try to get a defid of a method at the given path.
-/// This does not handle trait methods.
-/// This also does not handle overlapping method definitions/specialization well.
-pub fn try_resolve_method_did_direct<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId>
-where
-    T: AsRef<str>,
-{
-    tcx.crates(())
-        .iter()
-        .find(|&&krate| tcx.crate_name(krate).as_str() == path[0].as_ref())
-        .and_then(|krate| {
-            let krate = DefId {
-                krate: *krate,
-                index: CRATE_DEF_INDEX,
-            };
-
-            let mut items: &[middle::metadata::ModChild] = tcx.module_children(krate);
-            let mut path_it = path.iter().skip(1).peekable();
-
-            while let Some(segment) = path_it.next() {
-                //info!("items to look at: {:?}", items);
-                for item in mem::take(&mut items) {
-                    let item: &middle::metadata::ModChild = item;
-
-                    if item.ident.name.as_str() != segment.as_ref() {
-                        continue;
-                    }
-
-                    info!("taking path: {:?}", segment.as_ref());
-                    if path_it.peek().is_none() {
-                        return Some(item.res.def_id());
-                    }
-
-                    // just the method remaining
-                    if path_it.len() != 1 {
-                        items = tcx.module_children(item.res.def_id());
-                        break;
-                    }
-
-                    let did: DefId = item.res.def_id();
-                    let impls: &[DefId] = tcx.inherent_impls(did);
-                    info!("trying to find method among impls {:?}", impls);
-
-                    let find = path_it.next().unwrap();
-                    for impl_did in impls {
-                        //let ty = tcx.type_of(*impl_did);
-                        //info!("type of impl: {:?}", ty);
-                        let items: &ty::AssocItems = tcx.associated_items(impl_did);
-                        //info!("items here: {:?}", items);
-                        // TODO more robust error handling if there are multiple matches.
-                        for item in items.in_definition_order() {
-                            //info!("comparing: {:?} with {:?}", item.name.as_str(), find);
-                            if item.name.as_str() == find.as_ref() {
-                                return Some(item.def_id);
-                            }
-                        }
-                        //info!("impl items: {:?}", items);
-                    }
-
-                    //info!("inherent impls for {:?}: {:?}", did, impls);
-                    return None;
-                }
-            }
-
-            None
-        })
-}
-
-pub fn try_resolve_method_did<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId>
-where
-    T: AsRef<str>,
-{
-    if let Some(did) = try_resolve_method_did_direct(tcx, path) {
-        return Some(did);
-    }
-
-    // if the first component is "std", try if we can replace it with "alloc" or "core"
-    if path[0].as_ref() == "std" {
-        let mut components: Vec<_> = path.iter().map(|x| x.as_ref().to_owned()).collect();
-        components[0] = "core".to_owned();
-        if let Some(did) = try_resolve_method_did_direct(tcx, &components) {
-            return Some(did);
-        }
-        // try "alloc"
-        components[0] = "alloc".to_owned();
-        try_resolve_method_did_direct(tcx, &components)
-    } else {
-        None
-    }
-}
+use crate::force_matches;
 
 /// Check if the place `potential_prefix` is a prefix of `place`. For example:
 ///
@@ -777,64 +240,3 @@ impl<'tcx> VecPlace<'tcx> {
         self.components.len()
     }
 }
-
-/// Check if `<tool>::<name>` is among the attributes, where `tool` is determined by the
-/// `spec_hotword` config.
-/// Any arguments of the attribute are ignored.
-pub fn has_tool_attr(attrs: &[ast::Attribute], name: &str) -> bool {
-    get_tool_attr(attrs, name).is_some()
-}
-
-/// Get the arguments for a tool attribute, if it exists.
-pub fn get_tool_attr<'a>(attrs: &'a [ast::Attribute], name: &str) -> Option<&'a ast::AttrArgs> {
-    attrs.iter().find_map(|attr| match &attr.kind {
-        ast::AttrKind::Normal(na) => {
-            let segments = &na.item.path.segments;
-            let args = &na.item.args;
-            (segments.len() == 2
-                && segments[0].ident.as_str() == rrconfig::spec_hotword().as_str()
-                && segments[1].ident.as_str() == name)
-                .then_some(args)
-        },
-        _ => None,
-    })
-}
-
-/// Check if `<tool>::name` is among the filtered attributes.
-pub fn has_tool_attr_filtered(attrs: &[&ast::AttrItem], name: &str) -> bool {
-    attrs.iter().any(|item| {
-        let segments = &item.path.segments;
-        segments.len() == 2
-            && segments[0].ident.as_str() == rrconfig::spec_hotword().as_str()
-            && segments[1].ident.as_str() == name
-    })
-}
-
-/// Check if any attribute starting with `<tool>` is among the attributes.
-pub fn has_any_tool_attr(attrs: &[ast::Attribute]) -> bool {
-    attrs.iter().any(|attr| match &attr.kind {
-        ast::AttrKind::Normal(na) => {
-            let segments = &na.item.path.segments;
-            segments[0].ident.as_str() == rrconfig::spec_hotword().as_str()
-        },
-        _ => false,
-    })
-}
-
-/// Get all tool attributes, i.e. attributes of the shape `<tool>::attr`, where `tool` is
-/// determined by the `spec_hotword` config.
-pub fn filter_tool_attrs(attrs: &[ast::Attribute]) -> Vec<&ast::AttrItem> {
-    attrs
-        .iter()
-        .filter_map(|attr| match &attr.kind {
-            ast::AttrKind::Normal(na) => {
-                let item = &na.item;
-
-                let seg = item.path.segments.get(0)?;
-
-                (seg.ident.name.as_str() == rrconfig::spec_hotword()).then_some(item)
-            },
-            _ => None,
-        })
-        .collect()
-}