From eb76e5d5eca16782cadaa7592ba724a2d6acca8a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lennard=20G=C3=A4her?= <lennard.gaeher@ibm.com>
Date: Wed, 10 Apr 2024 09:42:33 +0200
Subject: [PATCH] refactor type translator and properly normalize

---
 rr_frontend/translation/src/function_body.rs  |  159 +--
 rr_frontend/translation/src/lib.rs            |    4 +-
 rr_frontend/translation/src/traits.rs         |   21 +-
 .../translation/src/type_translator.rs        | 1065 ++++++++++-------
 4 files changed, 765 insertions(+), 484 deletions(-)

diff --git a/rr_frontend/translation/src/function_body.rs b/rr_frontend/translation/src/function_body.rs
index 622da2d3..f73562fd 100644
--- a/rr_frontend/translation/src/function_body.rs
+++ b/rr_frontend/translation/src/function_body.rs
@@ -218,7 +218,7 @@ pub struct FunctionTranslator<'a, 'def, 'tcx> {
     /// polonius info for this function
     info: &'a PoloniusInfo<'a, 'tcx>,
     /// translator for types
-    ty_translator: &'a TypeTranslator<'def, 'tcx>,
+    ty_translator: LocalTypeTranslator<'a, 'def, 'tcx>,
     /// argument types (from the signature, with generics substituted)
     inputs: Vec<Ty<'tcx>>,
 }
@@ -402,7 +402,8 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
             let tupled_upvars_tys = clos.tupled_upvars_ty();
             upvars_tys = clos.upvar_tys();
             parent_args = clos.parent_args();
-            sig = clos.sig();
+            let unnormalized_sig = clos.sig();
+            sig = normalize_in_function(proc.get_id(), env.tcx(), unnormalized_sig)?;
             info!("closure sig: {:?}", sig);
 
             captures = env.tcx().closure_captures(did.as_local().unwrap());
@@ -453,7 +454,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
                     // 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 = ty_translator.format_atomic_region(&lft);
+                        let name = format_atomic_region_direct(&lft, None);
                         universal_lifetimes.insert(r, (name, None));
                     } else {
                         unreachable!();
@@ -470,7 +471,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
 
                         info!("using lifetime {:?} for closure universal", r2);
                         let lft = info.mk_atomic_region(r2);
-                        let name = ty_translator.format_atomic_region(&lft);
+                        let name = format_atomic_region_direct(&lft, None);
                         universal_lifetimes.insert(r2, (name, None));
 
                         maybe_outer_lifetime = Some(r2);
@@ -531,9 +532,13 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
                 // enter the procedure
                 let universal_lifetime_map: HashMap<_, _> =
                     universal_lifetimes.into_iter().map(|(x, y)| (x, y.0)).collect();
-                ty_translator.enter_procedure(env.tcx().mk_args(params), universal_lifetime_map)?;
+                let type_scope = TypeTranslationScope::new(
+                    proc.get_id(),
+                    env.tcx().mk_args(params),
+                    universal_lifetime_map,
+                )?;
                 // add generic args to the fn
-                let generics = ty_translator.generic_scope.borrow();
+                let generics = &type_scope.generic_scope;
                 for t in generics.iter() {
                     if let Some(ref t) = t {
                         translated_fn.add_generic_type(t.clone());
@@ -548,7 +553,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
                     inclusion_tracker,
                     procedure_registry: proc_registry,
                     attrs,
-                    ty_translator,
+                    ty_translator: LocalTypeTranslator::new(ty_translator, type_scope),
                     const_registry,
                     inputs: inputs.clone(),
                 };
@@ -568,13 +573,17 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
                     let translated_ty = t.ty_translator.translate_type(&ty)?;
                     translated_upvars_types.push(translated_ty);
                 }
-                let meta = ClosureMetaInfo {
-                    kind: closure_kind,
-                    captures,
-                    capture_tys: &translated_upvars_types,
-                    closure_lifetime: maybe_outer_lifetime
-                        .map(|x| t.ty_translator.lookup_universal_region(x).unwrap()),
-                };
+                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.lookup_universal_region(x).unwrap()),
+                    };
+                }
 
                 // process attributes
                 t.process_closure_attrs(&inputs, &output, meta)?;
@@ -614,6 +623,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
             },
             _ => panic!("can not handle non-fns"),
         };
+        let sig = normalize_in_function(proc.get_id(), env.tcx(), sig)?;
 
         info!("Function signature: {:?}", sig);
 
@@ -660,9 +670,9 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
                 // enter the procedure
                 let universal_lifetime_map: HashMap<_, _> =
                     universal_lifetimes.into_iter().map(|(x, y)| (x, y.0)).collect();
-                ty_translator.enter_procedure(&params, universal_lifetime_map)?;
+                let type_scope = TypeTranslationScope::new(proc.get_id(), params, universal_lifetime_map)?;
                 // add generic args to the fn
-                let generics = ty_translator.generic_scope.borrow();
+                let generics = &type_scope.generic_scope;
                 for t in generics.iter() {
                     if let Some(t) = t {
                         translated_fn.add_generic_type(t.clone());
@@ -677,7 +687,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
                     inclusion_tracker,
                     procedure_registry: proc_registry,
                     attrs,
-                    ty_translator,
+                    ty_translator: LocalTypeTranslator::new(ty_translator, type_scope),
                     const_registry,
                     inputs: inputs.clone(),
                 };
@@ -750,23 +760,22 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
     /// Parse and process attributes of this closure.
     fn process_closure_attrs<'b>(
         &mut self,
-        inputs: &[Ty<'tcx>],
-        output: &Ty<'tcx>,
+        normalized_inputs: &[Ty<'tcx>],
+        normalized_output: &Ty<'tcx>,
         meta: ClosureMetaInfo<'b, 'tcx, 'def>,
     ) -> Result<(), TranslationError> {
         trace!("entering process_closure_attrs");
         let v = self.attrs;
 
-        info!("inputs: {:?}, output: {:?}", inputs, output);
+        info!("inputs: {:?}, output: {:?}", normalized_inputs, normalized_output);
         let mut translated_arg_types: Vec<radium::Type<'def>> = Vec::new();
-        let generic_env = &*self.ty_translator.generic_scope.borrow();
-        for arg in inputs.iter() {
-            let mut translated: radium::Type<'def> = self.ty_translator.translate_type(arg)?;
-            translated.subst_params(generic_env.as_slice());
+        for arg in normalized_inputs.iter() {
+            let mut translated: radium::Type<'def> = self.ty_translator.translate_type_no_normalize(arg)?;
             translated_arg_types.push(translated);
         }
-        let mut translated_ret_type: radium::Type<'def> = self.ty_translator.translate_type(output)?;
-        translated_ret_type.subst_params(generic_env.as_slice());
+        let mut translated_ret_type: radium::Type<'def> =
+            // output is already normalized
+            self.ty_translator.translate_type_no_normalize(normalized_output)?;
         info!("translated function type: {:?} → {}", translated_arg_types, translated_ret_type);
 
         let parser = rrconfig::attribute_parser();
@@ -775,7 +784,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
                 let ty_translator = &self.ty_translator;
                 let mut parser: VerboseFunctionSpecParser<'_, 'def, _> =
                     VerboseFunctionSpecParser::new(&translated_arg_types, &translated_ret_type, |lit| {
-                        ty_translator.intern_literal(lit)
+                        ty_translator.translator.intern_literal(lit)
                     });
                 parser
                     .parse_closure_spec(v, &mut self.translated_fn, meta, |x| ty_translator.make_tuple_use(x))
@@ -791,19 +800,21 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
     }
 
     /// Parse and process attributes of this function.
-    fn process_attrs(&mut self, inputs: &[Ty<'tcx>], output: &Ty<'tcx>) -> Result<(), TranslationError> {
+    fn process_attrs(
+        &mut self,
+        normalized_inputs: &[Ty<'tcx>],
+        normalized_output: &Ty<'tcx>,
+    ) -> Result<(), TranslationError> {
         let v = self.attrs;
 
-        info!("inputs: {:?}, output: {:?}", inputs, output);
+        info!("inputs: {:?}, output: {:?}", normalized_inputs, normalized_output);
         let mut translated_arg_types: Vec<radium::Type<'def>> = Vec::new();
-        let generic_env = &*self.ty_translator.generic_scope.borrow();
-        for arg in inputs.iter() {
-            let mut translated: radium::Type<'def> = self.ty_translator.translate_type(arg)?;
-            translated.subst_params(generic_env.as_slice());
+        for arg in normalized_inputs.iter() {
+            let mut translated: radium::Type<'def> = self.ty_translator.translate_type_no_normalize(arg)?;
             translated_arg_types.push(translated);
         }
-        let mut translated_ret_type: radium::Type<'def> = self.ty_translator.translate_type(output)?;
-        translated_ret_type.subst_params(generic_env.as_slice());
+        let mut translated_ret_type: radium::Type<'def> =
+            self.ty_translator.translate_type_no_normalize(normalized_output)?;
         info!("translated function type: {:?} → {}", translated_arg_types, translated_ret_type);
 
         let parser = rrconfig::attribute_parser();
@@ -812,7 +823,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
                 let ty_translator = &self.ty_translator;
                 let mut parser: VerboseFunctionSpecParser<'_, 'def, _> =
                     VerboseFunctionSpecParser::new(&translated_arg_types, &translated_ret_type, |lit| {
-                        ty_translator.intern_literal(lit)
+                        ty_translator.translator.intern_literal(lit)
                     });
                 parser
                     .parse_function_spec(v, &mut self.translated_fn)
@@ -828,18 +839,23 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
         match k {
             info::UniversalRegionKind::Function => Some(radium::UniversalLft::Function),
             info::UniversalRegionKind::Static => Some(radium::UniversalLft::Static),
-            info::UniversalRegionKind::Local => {
-                self.ty_translator.lookup_universal_region(r).map(|x| radium::UniversalLft::Local(x))
-            },
-            info::UniversalRegionKind::External => {
-                self.ty_translator.lookup_universal_region(r).map(|x| radium::UniversalLft::External(x))
-            },
+            info::UniversalRegionKind::Local => self
+                .ty_translator
+                .scope
+                .borrow()
+                .lookup_universal_region(r)
+                .map(|x| radium::UniversalLft::Local(x)),
+            info::UniversalRegionKind::External => self
+                .ty_translator
+                .scope
+                .borrow()
+                .lookup_universal_region(r)
+                .map(|x| radium::UniversalLft::External(x)),
         }
     }
 
     /// Translation that only generates a specification.
     pub fn generate_spec(mut self) -> Result<radium::FunctionSpec<'def>, TranslationError> {
-        self.ty_translator.leave_procedure();
         Ok(self.translated_fn.into())
     }
 
@@ -958,7 +974,6 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
 
             // type:
             let mut tr_ty = self.ty_translator.translate_type(ty)?;
-            tr_ty.subst_params(&self.ty_translator.generic_scope.borrow());
             let st = tr_ty.get_syn_type();
 
             let name = Self::make_local_name(body, &local, &mut used_names);
@@ -1124,7 +1139,7 @@ struct BodyTranslator<'a, 'def, 'tcx> {
     /// set of already processed blocks
     processed_bbs: HashSet<BasicBlock>,
     /// translator for types
-    ty_translator: &'a TypeTranslator<'def, 'tcx>,
+    ty_translator: LocalTypeTranslator<'a, 'def, 'tcx>,
 
     /// map of loop heads to their optional spec closure defid
     loop_specs: HashMap<BasicBlock, Option<DefId>>,
@@ -1188,19 +1203,19 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
 
         // assume that all generics are layoutable
         {
-            let scope = self.ty_translator.generic_scope.borrow();
-            for g in scope.iter() {
+            let scope = self.ty_translator.scope.borrow();
+            for g in scope.generic_scope.iter() {
                 if let Some(ty) = g {
                     self.translated_fn.assume_synty_layoutable(radium::SynType::Literal(ty.syn_type.clone()));
                 }
             }
         }
         // assume that all used literals are layoutable
-        for (_, g) in self.ty_translator.shim_uses.borrow().iter() {
+        for (_, g) in self.ty_translator.scope.borrow().shim_uses.iter() {
             self.translated_fn.assume_synty_layoutable(g.generate_syn_type_term());
         }
         // assume that all used tuples are layoutable
-        for g in self.ty_translator.tuple_uses.borrow().iter() {
+        for g in self.ty_translator.scope.borrow().tuple_uses.iter() {
             self.translated_fn.assume_synty_layoutable(g.generate_syn_type_term());
         }
 
@@ -1229,7 +1244,6 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
             self.translated_fn.require_static(s.clone());
         }
 
-        self.ty_translator.leave_procedure();
         Ok(self.translated_fn.into())
     }
 
@@ -1334,7 +1348,6 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
 
             let mut mangled_name = name.to_string();
             let mut translated_params = Vec::new();
-            let generic_env = &*self.ty_translator.generic_scope.borrow();
 
             // TODO: maybe come up with some better way to generate names
             info!("register_use_procedure: translating args {:?}", tup.1);
@@ -1342,8 +1355,6 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                 mangled_name.push_str(format!("_{}", p).as_str());
 
                 let mut translated_ty = self.ty_translator.translate_type(p)?;
-                // we need to substitute in the variables according to the function scope
-                translated_ty.subst_params(generic_env.as_slice());
 
                 translated_params.push(translated_ty);
             }
@@ -1353,18 +1364,21 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
             let full_ty: ty::EarlyBinder<Ty<'tcx>> = self.env.tcx().type_of(*did);
             let full_ty: Ty<'tcx> = full_ty.instantiate_identity();
 
-            let mut inputs = Vec::new();
+            let mut normalized_inputs = Vec::new();
             let mut syntypes = Vec::new();
             match full_ty.kind() {
                 ty::TyKind::FnDef(_, _) => {
                     let sig = full_ty.fn_sig(self.env.tcx());
+                    let sig = normalize_in_function(*did, self.env.tcx(), sig)?;
                     for ty in sig.inputs().skip_binder().iter() {
-                        inputs.push(*ty);
+                        normalized_inputs.push(*ty);
                     }
                 },
                 ty::TyKind::Closure(_, args) => {
                     let clos_args = args.as_closure();
-                    let pre_sig = clos_args.sig().skip_binder();
+                    let sig = clos_args.sig();
+                    let sig = normalize_in_function(*did, self.env.tcx(), sig)?;
+                    let pre_sig = sig.skip_binder();
                     // we also need to add the closure argument here
                     // in this case, we need to patch the region first
 
@@ -1377,22 +1391,22 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                             syntypes.push(radium::SynType::Ptr);
                         },
                         ty::ClosureKind::FnOnce => {
-                            inputs.push(tuple_ty);
+                            normalized_inputs.push(tuple_ty);
                         },
                     }
                     for ty in pre_sig.inputs().iter() {
-                        inputs.push(*ty);
+                        normalized_inputs.push(*ty);
                     }
                 },
                 _ => unimplemented!(),
             }
 
             //info!("substs: {:?}, inputs {:?} ", ty_params, inputs);
-            for i in inputs.iter() {
+            for i in normalized_inputs.iter() {
                 // need to wrap it, because there's no Subst instance for Ty
                 let i = ty::EarlyBinder::bind(*i);
                 let ty = i.instantiate(self.env.tcx(), ty_params);
-                let t = self.ty_translator.translate_type_to_syn_type(&ty)?;
+                let t = self.ty_translator.translate_type_to_syn_type_no_normalize(&ty)?;
                 syntypes.push(t);
             }
 
@@ -2558,7 +2572,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
         &mut self,
         loc: Location,
         lhs: &Place<'tcx>,
-        rhs_ty: Ty<'tcx>,
+        _rhs_ty: Ty<'tcx>,
     ) -> Result<
         (Option<radium::Annotation>, Vec<radium::Annotation>, Vec<radium::Annotation>),
         TranslationError,
@@ -3004,8 +3018,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
             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 env = self.ty_translator.generic_scope.borrow();
-            let annot_ty = radium::RustType::of_type(&translated_ty, env.as_ref());
+            let annot_ty = radium::RustType::of_type(&translated_ty, &[]);
             Ok(Some(annot_ty))
         } else {
             Ok(None)
@@ -3179,10 +3192,8 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                             // translate to unit literal
                             Ok(radium::Expr::Literal(radium::Literal::LitZST))
                         } else {
-                            let struct_use = self.ty_translator.generate_tuple_use(
-                                operand_types.iter().map(|r| *r),
-                                TranslationState::InFunction,
-                            )?;
+                            let struct_use =
+                                self.ty_translator.generate_tuple_use(operand_types.iter().map(|r| *r))?;
                             let sl = struct_use.generate_raw_syn_type_term();
                             let initializers: Vec<_> = translated_ops
                                 .into_iter()
@@ -3238,10 +3249,8 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                             let enum_use = self.ty_translator.generate_enum_use(adt_def, args)?;
                             let els = enum_use.generate_raw_syn_type_term();
 
-                            let scope = self.ty_translator.generic_scope.borrow();
                             info!("generating enum annotation for type {:?}", enum_use);
-                            let ty =
-                                radium::RustType::of_type(&radium::Type::Literal(enum_use), scope.as_ref());
+                            let ty = radium::RustType::of_type(&radium::Type::Literal(enum_use), &[]);
                             let variant_name = variant_def.name.to_string();
                             Ok(radium::Expr::EnumInitE {
                                 els: radium::CoqAppTerm::new_lhs(els.to_string()),
@@ -3267,10 +3276,8 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                             // translate to unit literal
                             Ok(radium::Expr::Literal(radium::Literal::LitZST))
                         } else {
-                            let struct_use = self.ty_translator.generate_tuple_use(
-                                operand_types.iter().map(|r| *r),
-                                TranslationState::InFunction,
-                            )?;
+                            let struct_use =
+                                self.ty_translator.generate_tuple_use(operand_types.iter().map(|r| *r))?;
                             let sl = struct_use.generate_raw_syn_type_term();
                             let initializers: Vec<_> = translated_ops
                                 .into_iter()
@@ -3707,7 +3714,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                     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.get_field_name_of(
+                    let name = self.ty_translator.translator.get_field_name_of(
                         f,
                         cur_ty.ty,
                         cur_ty.variant_index.map(|a| a.as_usize()),
@@ -3744,7 +3751,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                             let els = enum_use.generate_raw_syn_type_term();
 
                             let variant_name =
-                                self.ty_translator.get_variant_name_of(cur_ty.ty, *variant_idx)?;
+                                self.ty_translator.translator.get_variant_name_of(cur_ty.ty, *variant_idx)?;
 
                             acc_expr = radium::Expr::EnumData {
                                 els: els.to_string(),
diff --git a/rr_frontend/translation/src/lib.rs b/rr_frontend/translation/src/lib.rs
index 3be96a75..87d7b406 100644
--- a/rr_frontend/translation/src/lib.rs
+++ b/rr_frontend/translation/src/lib.rs
@@ -1026,9 +1026,9 @@ pub fn register_consts<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) -> Re
         }
 
         let ty = ty.skip_binder();
-        match vcx.type_translator.translate_type(&ty).map_err(|x| format!("{:?}", x)) {
+        match vcx.type_translator.translate_type_in_empty_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 = type_translator::strip_coq_ident(&vcx.env.get_item_name(s.to_def_id()));
 
                 let mut const_parser = const_parser::VerboseConstAttrParser::new();
                 let const_spec = const_parser.parse_const_attrs(*s, &const_attrs)?;
diff --git a/rr_frontend/translation/src/traits.rs b/rr_frontend/translation/src/traits.rs
index 6d87263c..24a6fc61 100644
--- a/rr_frontend/translation/src/traits.rs
+++ b/rr_frontend/translation/src/traits.rs
@@ -3,15 +3,34 @@
 use log::info;
 use rustc_hir::def_id::DefId;
 use rustc_infer::infer::TyCtxtInferExt;
+use rustc_middle::ty;
 use rustc_middle::ty::{
     AssocItem, AssocItemContainer, GenericArgsRef, ParamEnv, TraitRef, TyCtxt, TypeVisitableExt,
 };
-use rustc_trait_selection::traits::ImplSource;
+use rustc_trait_selection::traits::{ImplSource, NormalizeExt};
 
 pub(crate) fn associated_items(tcx: TyCtxt, def_id: DefId) -> impl Iterator<Item = &AssocItem> {
     tcx.associated_items(def_id).in_definition_order()
 }
 
+/// Normalize a type in the given environment.
+pub fn normalize_type<'tcx, T>(
+    tcx: TyCtxt<'tcx>,
+    param_env: ParamEnv<'tcx>,
+    ty: T,
+) -> Result<T, Vec<rustc_trait_selection::traits::FulfillmentError<'tcx>>>
+where
+    T: ty::TypeFoldable<ty::TyCtxt<'tcx>>,
+{
+    let infer_ctx = tcx.infer_ctxt().build();
+    rustc_trait_selection::traits::fully_normalize(
+        &infer_ctx,
+        rustc_middle::traits::ObligationCause::dummy(),
+        param_env,
+        ty,
+    )
+}
+
 /// Resolve an implementation of a trait using codegen candidate selection.
 /// `did` can be the id of a trait, or the id of an associated item of a trait.
 pub fn resolve_impl_source<'tcx>(
diff --git a/rr_frontend/translation/src/type_translator.rs b/rr_frontend/translation/src/type_translator.rs
index 32903277..0e10672f 100644
--- a/rr_frontend/translation/src/type_translator.rs
+++ b/rr_frontend/translation/src/type_translator.rs
@@ -7,7 +7,6 @@
 use std::cell::RefCell;
 use std::collections::{HashMap, HashSet};
 use std::fmt::Write;
-use std::ops::DerefMut;
 
 use log::{info, trace, warn};
 use radium;
@@ -18,10 +17,10 @@ use typed_arena::Arena;
 
 pub use crate::base::*;
 use crate::environment::{polonius_info as info, Environment};
-//use rustc_middle::mir::Field;
 use crate::rustc_middle::ty::TypeFoldable;
 use crate::spec_parsers::enum_spec_parser::{EnumSpecParser, VerboseEnumSpecParser};
 use crate::spec_parsers::struct_spec_parser::{self, InvariantSpecParser, StructFieldSpecParser};
+use crate::traits::normalize_type;
 use crate::tyvars::*;
 
 /// Strip symbols from an identifier to be compatible with Coq.
@@ -57,12 +56,154 @@ impl AdtUseKey {
     }
 }
 
-// TODO(refactor): move the function-specific stuff out of here and into the BodyTranslator.
-pub struct TypeTranslator<'def, 'tcx> {
-    env: &'def Environment<'tcx>,
+/// A scope tracking the type translation state when translating the body of a function.
+#[derive(Debug)]
+pub struct TypeTranslationScope<'def> {
+    /// defid of the current function
+    pub(crate) did: DefId,
+
+    /// maps generic indices (De Bruijn) to the corresponding Coq names in the current environment
+    /// the invariant is that they are Literals
+    pub generic_scope: Vec<Option<radium::LiteralTyParam>>,
 
     /// maps universal lifetime indices (Polonius) to their names. offset by 1 because 0 = static.
-    universal_lifetimes: RefCell<HashMap<ty::RegionVid, String>>,
+    universal_lifetimes: HashMap<ty::RegionVid, String>,
+
+    // TODO currently, these may contain duplicates
+    /// collection of tuple types that we use
+    pub(crate) tuple_uses: Vec<radium::LiteralTypeUse<'def>>,
+    /// Shim uses for the current function
+    pub(crate) shim_uses: HashMap<AdtUseKey, radium::LiteralTypeUse<'def>>,
+}
+
+impl<'def> TypeTranslationScope<'def> {
+    /// Lookup a universal region.
+    pub fn lookup_universal_region(&self, lft: ty::RegionVid) -> Option<radium::Lft> {
+        info!("Looking up universal lifetime {:?}", lft);
+        self.universal_lifetimes.get(&lft).map(|s| s.to_string())
+    }
+
+    /// Create a new empty scope for a function.
+    pub fn empty(did: DefId) -> Self {
+        Self {
+            did,
+            tuple_uses: Vec::new(),
+            shim_uses: HashMap::new(),
+            generic_scope: Vec::new(),
+            universal_lifetimes: HashMap::new(),
+        }
+    }
+
+    /// Create a new scope for a function translation with the given generic parameters.
+    pub fn new<'tcx>(
+        did: DefId,
+        ty_params: ty::GenericArgsRef<'tcx>,
+        univ_lfts: HashMap<ty::RegionVid, String>,
+    ) -> Result<Self, TranslationError> {
+        info!("Entering procedure with ty_params {:?} and univ_lfts {:?}", ty_params, univ_lfts);
+
+        let mut v: Vec<Option<radium::LiteralTyParam>> = Vec::new();
+        let mut num_lifetimes = 0;
+        for gen_arg in ty_params.iter() {
+            match gen_arg.unpack() {
+                ty::GenericArgKind::Type(ty) => match ty.kind() {
+                    TyKind::Param(p) => {
+                        info!("ty param {} @ {}", p.name, p.index);
+                        let type_name = format!("{}_ty", p.name);
+                        let st_name = format!("{}_st", p.name);
+                        let rt_name = format!("{}_rt", p.name);
+
+                        let ty_term = type_name;
+                        let st_term = st_name;
+
+                        let lit_ty = radium::LiteralTyParam {
+                            rust_name: p.name.as_str().to_string(),
+                            type_term: ty_term,
+                            refinement_type: rt_name,
+                            syn_type: st_term,
+                        };
+
+                        v.push(Some(lit_ty));
+                    },
+                    _ => {
+                        panic!("enter_generic_scope: not a type parameter");
+                    },
+                },
+                ty::GenericArgKind::Lifetime(r) => {
+                    num_lifetimes += 1;
+                    match *r {
+                        ty::RegionKind::ReLateBound(..) | ty::RegionKind::ReEarlyBound(..) => {
+                            // ignore
+                            v.push(None);
+                        },
+                        ty::RegionKind::ReVar(..) => {
+                            // ignore
+                            v.push(None);
+                        },
+                        _ => {
+                            return Err(TranslationError::UnsupportedFeature {
+                                description: format!("Unsupported lifetime generic {:?}", r),
+                            });
+                        },
+                    }
+                },
+                ty::GenericArgKind::Const(_c) => {
+                    return Err(TranslationError::UnsupportedFeature {
+                        description: "RefinedRust does not currently support const generics".to_string(),
+                    });
+                },
+            }
+        }
+
+        // for closures, not all lifetime parameters materialize in `ty_params`, so we fill them up
+        // afterwards
+        for _ in 0..(univ_lfts.len() - num_lifetimes) {
+            v.push(None);
+        }
+
+        Ok(Self {
+            did,
+            tuple_uses: Vec::new(),
+            generic_scope: v,
+            universal_lifetimes: univ_lfts,
+            shim_uses: HashMap::new(),
+        })
+    }
+}
+
+/// The `TypeTranslator` has two 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.
+#[derive(Debug)]
+pub enum TranslationStateInner<'b, 'def: 'b> {
+    /// This type is used in a function
+    InFunction(&'b mut TypeTranslationScope<'def>),
+    /// We are generating the definition of an ADT and this type is used in there
+    TranslateAdt(&'b mut HashSet<DefId>),
+}
+pub type TranslationState<'a, 'b, 'def> = &'a mut TranslationStateInner<'b, 'def>;
+pub type InFunctionState<'a, 'def> = &'a mut TypeTranslationScope<'def>;
+pub type TranslateAdtState<'a> = &'a mut HashSet<DefId>;
+
+impl<'a, 'def> TranslationStateInner<'a, 'def> {
+    fn in_function(&self) -> bool {
+        match *self {
+            Self::InFunction(_) => true,
+            _ => false,
+        }
+    }
+
+    fn translate_adt(&self) -> bool {
+        match *self {
+            Self::TranslateAdt(_) => true,
+            _ => false,
+        }
+    }
+}
+
+pub struct TypeTranslator<'def, 'tcx> {
+    env: &'def Environment<'tcx>,
 
     /// arena for keeping ownership of structs
     /// during building, it will be None, afterwards it will always be Some
@@ -103,25 +244,6 @@ pub struct TypeTranslator<'def, 'tcx> {
 
     /// shims for ADTs
     adt_shims: RefCell<HashMap<DefId, radium::LiteralTypeRef<'def>>>,
-
-    // TODO currently, these may contain duplicates
-    /// collection of tuple types that we use
-    pub(crate) tuple_uses: RefCell<Vec<radium::LiteralTypeUse<'def>>>,
-    /// Shim uses for the current function
-    pub(crate) shim_uses: RefCell<HashMap<AdtUseKey, radium::LiteralTypeUse<'def>>>,
-
-    /// maps generic indices (De Bruijn) to the corresponding Coq names in the current environment
-    /// the invariant is that they are Literals
-    pub generic_scope: RefCell<Vec<Option<radium::LiteralTyParam>>>,
-}
-
-/// Why are we translating this type?
-#[derive(PartialEq, Debug)]
-pub enum TranslationState<'a> {
-    /// This type is used in a function
-    InFunction,
-    /// We are generating the definition of an ADT and this type is used in there
-    TranslateAdt(&'a mut HashSet<DefId>),
 }
 
 impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
@@ -133,8 +255,6 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
     ) -> Self {
         TypeTranslator {
             env,
-            generic_scope: RefCell::new(Vec::new()),
-            universal_lifetimes: RefCell::new(HashMap::new()),
             adt_deps: RefCell::new(HashMap::new()),
             adt_shims: RefCell::new(HashMap::new()),
             struct_arena,
@@ -142,8 +262,6 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
             shim_arena,
             variant_registry: RefCell::new(HashMap::new()),
             enum_registry: RefCell::new(HashMap::new()),
-            tuple_uses: RefCell::new(Vec::new()),
-            shim_uses: RefCell::new(HashMap::new()),
             tuple_registry: RefCell::new(HashMap::new()),
         }
     }
@@ -206,117 +324,13 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         deps.clone()
     }
 
-    /// Enter a procedure and add corresponding type parameters to the scope, as well as universal
-    /// lifetimes with given names.
-    pub fn enter_procedure(
-        &self,
-        ty_params: ty::GenericArgsRef<'tcx>,
-        univ_lfts: HashMap<ty::RegionVid, String>,
-    ) -> Result<(), TranslationError> {
-        info!("Entering procedure with ty_params {:?} and univ_lfts {:?}", ty_params, univ_lfts);
-
-        let mut v: Vec<Option<radium::LiteralTyParam>> = Vec::new();
-        let mut num_lifetimes = 0;
-        for gen_arg in ty_params.iter() {
-            match gen_arg.unpack() {
-                ty::GenericArgKind::Type(ty) => match ty.kind() {
-                    TyKind::Param(p) => {
-                        info!("ty param {} @ {}", p.name, p.index);
-                        let type_name = format!("{}_ty", p.name);
-                        let st_name = format!("{}_st", p.name);
-                        let rt_name = format!("{}_rt", p.name);
-
-                        let ty_term = type_name;
-                        let st_term = st_name;
-
-                        let lit_ty = radium::LiteralTyParam {
-                            rust_name: p.name.as_str().to_string(),
-                            type_term: ty_term,
-                            refinement_type: rt_name,
-                            syn_type: st_term,
-                        };
-
-                        v.push(Some(lit_ty));
-                    },
-                    _ => {
-                        panic!("enter_generic_scope: not a type parameter");
-                    },
-                },
-                ty::GenericArgKind::Lifetime(r) => {
-                    num_lifetimes += 1;
-                    match *r {
-                        ty::RegionKind::ReLateBound(..) | ty::RegionKind::ReEarlyBound(..) => {
-                            // ignore
-                            v.push(None);
-                        },
-                        ty::RegionKind::ReVar(..) => {
-                            // ignore
-                            v.push(None);
-                        },
-                        _ => {
-                            return Err(TranslationError::UnsupportedFeature {
-                                description: format!("Unsupported lifetime generic {:?}", r),
-                            });
-                        },
-                    }
-                },
-                ty::GenericArgKind::Const(_c) => {
-                    return Err(TranslationError::UnsupportedFeature {
-                        description: "RefinedRust does not currently support const generics".to_string(),
-                    });
-                },
-            }
-        }
-
-        // for closures, not all lifetime parameters materialize in `ty_params`, so we fill them up
-        // afterwards
-        for _ in 0..(univ_lfts.len() - num_lifetimes) {
-            v.push(None);
-        }
-
-        self.universal_lifetimes.replace(univ_lfts);
-        self.generic_scope.replace(v);
-        Ok(())
-    }
-
-    /// Leave a procedure and remove the corresponding type parameters from the scope.
-    pub fn leave_procedure(&self) {
-        self.generic_scope.replace(Vec::new());
-        self.universal_lifetimes.replace(HashMap::new());
-        self.tuple_uses.replace(Vec::new());
-        self.shim_uses.replace(HashMap::new());
-    }
-
-    /// Lookup a universal region.
-    pub fn lookup_universal_region(&self, lft: ty::RegionVid) -> Option<radium::Lft> {
-        info!("Looking up universal lifetime {:?}", lft);
-        self.universal_lifetimes.borrow().get(&lft).map(|s| s.to_string())
-    }
-
-    /// Format the Coq representation of an atomic region.
-    pub fn format_atomic_region(&self, r: &info::AtomicRegion) -> String {
-        match r {
-            info::AtomicRegion::Loan(_, r) => {
-                format!("llft{}", r.index())
-            },
-            info::AtomicRegion::Universal(_, r) => match self.lookup_universal_region(*r) {
-                Some(s) => s,
-                None => format!("ulft{}", r.index()),
-            },
-            info::AtomicRegion::PlaceRegion(r) => {
-                format!("plft{}", r.index())
-            },
-            info::AtomicRegion::Unknown(r) => {
-                format!("vlft{}", r.index())
-            },
-        }
-    }
-
     /// Try to translate a region to a Caesium lifetime.
     /// Note: This relies on all the regions being ReVar inference variables.
-    // TODO: This currently uses the current function's specific scope.
-    // Once we handle ADTs with lifetime parameters, this will need to change.
-    fn translate_region(&self, region: &ty::Region<'tcx>) -> Option<radium::Lft> {
+    fn translate_region<'a, 'b>(
+        &self,
+        translation_state: TranslationState<'a, 'b, 'def>,
+        region: &ty::Region<'tcx>,
+    ) -> Option<radium::Lft> {
         match **region {
             ty::RegionKind::ReEarlyBound(early) => {
                 info!("Translating region: EarlyBound {:?}", early);
@@ -333,11 +347,16 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
             },
             ty::RegionKind::ReStatic => Some("static".to_string()),
             ty::RegionKind::ReErased => Some("erased".to_string()),
-            ty::RegionKind::ReVar(v) => {
-                //self.env.info.mk
-                let r = self.lookup_universal_region(v);
-                info!("Translating region: ReVar {:?} as {:?}", v, r);
-                r
+            ty::RegionKind::ReVar(v) => match translation_state {
+                TranslationStateInner::InFunction(ref scope) => {
+                    let r = scope.lookup_universal_region(v);
+                    info!("Translating region: ReVar {:?} as {:?}", v, r);
+                    r
+                },
+                _ => {
+                    info!("Translating region: ReVar {:?} as None (outside of function)", v);
+                    None
+                },
             },
             _ => {
                 info!("Translating region: {:?}", region);
@@ -399,13 +418,12 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
     /// Generate a use of a struct, instantiated with type parameters.
     /// This should only be called on tuples and struct ADTs.
     /// Only for internal references as part of type translation.
-    fn generate_structlike_use_internal(
+    fn generate_structlike_use_internal<'a, 'b>(
         &self,
         ty: &Ty<'tcx>,
         variant: Option<rustc_target::abi::VariantIdx>,
-        adt_deps: TranslationState,
+        adt_deps: TranslationState<'a, 'b, 'def>,
     ) -> Result<radium::Type<'def>, TranslationError> {
-        let in_function = adt_deps == TranslationState::InFunction;
         match ty.kind() {
             TyKind::Adt(adt, args) => {
                 // Check if we have a shim
@@ -444,56 +462,13 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         }
     }
 
-    /// 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<rustc_target::abi::VariantIdx>,
-    ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError> {
-        match ty.kind() {
-            TyKind::Adt(adt, args) => {
-                if adt.is_struct() {
-                    info!("generating struct use for {:?}", adt.did());
-                    // register the ADT, if necessary
-                    self.register_adt(*adt)?;
-                    self.generate_struct_use(adt.did(), *args)
-                } else if adt.is_enum() {
-                    if let Some(variant) = variant {
-                        self.register_adt(*adt)?;
-                        let v = &adt.variants()[variant];
-                        self.generate_enum_variant_use(v.def_id, args.iter()).map(|x| Some(x))
-                    } else {
-                        Err(TranslationError::UnknownError(
-                            "a non-downcast enum is not a structlike".to_string(),
-                        ))
-                    }
-                } else {
-                    Err(TranslationError::UnsupportedType {
-                        description: "non-{enum, struct, tuple} ADTs are not supported".to_string(),
-                    })
-                }
-            },
-            TyKind::Tuple(args) => {
-                self.generate_tuple_use(args.into_iter(), TranslationState::InFunction).map(|x| Some(x))
-            },
-            TyKind::Closure(_, args) => {
-                // use the upvar tuple
-                let closure_args = args.as_closure();
-                let upvars = closure_args.upvar_tys();
-                self.generate_tuple_use(upvars.into_iter(), TranslationState::InFunction).map(|x| Some(x))
-            },
-            _ => Err(TranslationError::UnknownError("not a structlike".to_string())),
-        }
-    }
-
     /// Generate the use of an enum.
     /// Only for internal references as part of type translation.
-    fn generate_enum_use_noshim<F>(
+    fn generate_enum_use_noshim<'a, 'b, F>(
         &self,
         adt_def: ty::AdtDef<'tcx>,
         args: F,
-        mut adt_deps: TranslationState,
+        state: TranslationState<'a, 'b, 'def>,
     ) -> Result<radium::AbstractEnumUse<'def>, TranslationError>
     where
         F: IntoIterator<Item = ty::GenericArg<'tcx>>,
@@ -501,14 +476,13 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         info!("generating enum use for {:?}", adt_def.did());
         self.register_adt(adt_def)?;
 
-        let in_function = adt_deps == TranslationState::InFunction;
         let (enum_ref, lit_ref) = self.lookup_enum(adt_def.did())?;
-        let params = self.translate_generic_args(args, adt_deps)?;
+        let params = self.translate_generic_args(args, &mut *state)?;
         let key = AdtUseKey::new(adt_def.did(), &params);
 
         // track this enum use for the current function
-        if in_function {
-            let mut lit_uses = self.shim_uses.borrow_mut();
+        if let TranslationStateInner::InFunction(state) = state {
+            let lit_uses = &mut state.shim_uses;
             if !lit_uses.contains_key(&key) {
                 let lit_use = radium::LiteralTypeUse::new(lit_ref.unwrap(), params.clone());
                 lit_uses.insert(key, lit_use);
@@ -519,71 +493,51 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         Ok(enum_use)
     }
 
-    /// 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>(
+    /// Check if a variant given by a [DefId] is [std::marker::PhantomData].
+    fn is_phantom_data(&self, did: DefId) -> Option<bool> {
+        let ty: ty::Ty<'tcx> = self.env.tcx().type_of(did).instantiate_identity();
+        match ty.kind() {
+            ty::TyKind::Adt(def, _) => Some(def.is_phantom_data()),
+            _ => None,
+        }
+    }
+
+    /// Check if a struct is definitely zero-sized.
+    fn is_struct_definitely_zero_sized(&self, did: DefId) -> Option<bool> {
+        self.is_phantom_data(did)
+    }
+
+    /// Translate generic_args of an ADT instantiation, tracking dependencies on other ADTs in `adt_deps`.
+    fn translate_generic_args<'a, 'b, F>(
         &self,
-        adt_def: ty::AdtDef<'tcx>,
         args: F,
-    ) -> Result<radium::LiteralTypeUse<'def>, TranslationError>
-    where
-        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
-    {
-        info!("generating enum use for {:?}", adt_def.did());
-        // TODO: register?
-        //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, TranslationState::InFunction)?;
-        let key = AdtUseKey::new(adt_def.did(), &params);
-        let enum_use = radium::LiteralTypeUse::new(enum_ref, params);
-
-        // track this enum use for the current function
-        let mut enum_uses = self.shim_uses.borrow_mut();
-        if !enum_uses.contains_key(&key) {
-            enum_uses.insert(key, enum_use.clone());
-        }
-
-        Ok(enum_use)
-    }
-
-    /// Check if a variant given by a [DefId] is [std::marker::PhantomData].
-    fn is_phantom_data(&self, did: DefId) -> Option<bool> {
-        let ty: ty::Ty<'tcx> = self.env.tcx().type_of(did).instantiate_identity();
-        match ty.kind() {
-            ty::TyKind::Adt(def, _) => Some(def.is_phantom_data()),
-            _ => None,
-        }
-    }
-
-    /// Check if a struct is definitely zero-sized.
-    fn is_struct_definitely_zero_sized(&self, did: DefId) -> Option<bool> {
-        self.is_phantom_data(did)
-    }
-
-    /// Translate generic_args of an ADT instantiation, tracking dependencies on other ADTs in `adt_deps`.
-    fn translate_generic_args<F>(
-        &self,
-        args: F,
-        mut adt_deps: TranslationState,
-    ) -> Result<Vec<radium::Type<'def>>, TranslationError>
+        state: TranslationState<'a, 'b, 'def>,
+    ) -> Result<Vec<radium::Type<'def>>, TranslationError>
     where
         F: IntoIterator<Item = ty::GenericArg<'tcx>>,
     {
         let mut params = Vec::new();
-        let generic_env = &*self.generic_scope.borrow();
 
         for arg in args.into_iter() {
             if let Some(arg_ty) = arg.as_type() {
                 let mut translated_ty;
-                if let TranslationState::TranslateAdt(ref mut adt_deps) = adt_deps {
-                    translated_ty = self
-                        .translate_type_with_deps(&arg_ty, TranslationState::TranslateAdt(&mut *adt_deps))?;
-                } else {
-                    translated_ty = self.translate_type_with_deps(&arg_ty, TranslationState::InFunction)?;
+                match state {
+                    TranslationStateInner::TranslateAdt(ref mut deps) => {
+                        translated_ty = self.translate_type_with_deps(
+                            &arg_ty,
+                            &mut TranslationStateInner::TranslateAdt(deps),
+                        )?;
+                    },
+                    TranslationStateInner::InFunction(ref mut scope) => {
+                        translated_ty = self.translate_type_with_deps(
+                            &arg_ty,
+                            &mut TranslationStateInner::InFunction(scope),
+                        )?;
+                        // we need to substitute in the variables according to the function scope
+                        translated_ty.subst_params(scope.generic_scope.as_slice());
+                    },
                 }
-                // we need to substitute in the variables according to the function scope
-                translated_ty.subst_params(generic_env.as_slice());
+
                 params.push(translated_ty);
             } else {
                 return Err(TranslationError::UnsupportedFeature {
@@ -597,11 +551,11 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 
     /// Generate the use of a struct.
     /// Only for internal references as part of type translation.
-    fn generate_struct_use_noshim<F>(
+    fn generate_struct_use_noshim<'a, 'b, F>(
         &self,
         variant_id: DefId,
         args: F,
-        mut adt_deps: TranslationState,
+        state: TranslationState<'a, 'b, 'def>,
     ) -> Result<radium::AbstractStructUse<'def>, TranslationError>
     where
         F: IntoIterator<Item = ty::GenericArg<'tcx>>,
@@ -613,13 +567,12 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
             return Ok(radium::AbstractStructUse::new_unit());
         }
 
-        let in_function = adt_deps == TranslationState::InFunction;
         let (struct_ref, lit_ref) = self.lookup_adt_variant(variant_id)?;
-        let params = self.translate_generic_args(args, adt_deps)?;
+        let params = self.translate_generic_args(args, &mut *state)?;
         let key = AdtUseKey::new(variant_id, &params);
 
-        if in_function {
-            let mut lit_uses = self.shim_uses.borrow_mut();
+        if let TranslationStateInner::InFunction(ref mut scope) = state {
+            let lit_uses = &mut scope.shim_uses;
             if !lit_uses.contains_key(&key) {
                 let lit_use = radium::LiteralTypeUse::new(lit_ref.unwrap(), params.clone());
                 lit_uses.insert(key, lit_use);
@@ -630,75 +583,14 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         Ok(struct_use)
     }
 
-    /// 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>
-    where
-        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
-    {
-        info!("generating struct use for {:?}", variant_id);
-
-        if let Some(true) = self.is_struct_definitely_zero_sized(variant_id) {
-            info!("replacing zero-sized struct with unit");
-            return Ok(None);
-        }
-
-        let params = self.translate_generic_args(args, TranslationState::InFunction)?;
-        let key = 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);
-
-        let mut shim_uses = self.shim_uses.borrow_mut();
-        if !shim_uses.contains_key(&key) {
-            shim_uses.insert(key, struct_use.clone());
-        }
-
-        Ok(Some(struct_use))
-    }
-
-    /// 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>
-    where
-        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
-    {
-        info!("generating enum variant use for {:?}", variant_id);
-
-        let params = self.translate_generic_args(args, TranslationState::InFunction)?;
-        let key = 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);
-
-        // TODO: track?
-        // generate the struct use key
-        //let mut shim_uses = self.shim_uses.borrow_mut();
-        //if !shim_uses.contains_key(&key) {
-        //shim_uses.insert(key, struct_use.clone());
-        //}
-
-        Ok(struct_use)
-    }
-
     /// Generate the use of an enum variant.
     /// Only for internal references as part of type translation.
-    fn generate_enum_variant_use_noshim<F>(
+    fn generate_enum_variant_use_noshim<'a, 'b, F>(
         &self,
         adt_id: DefId,
         variant_idx: rustc_target::abi::VariantIdx,
         args: F,
-        mut adt_deps: TranslationState,
+        state: TranslationState<'a, 'b, 'def>,
     ) -> Result<radium::AbstractStructUse<'def>, TranslationError>
     where
         F: IntoIterator<Item = ty::GenericArg<'tcx>>,
@@ -706,7 +598,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         info!("generating variant use for variant {:?} of {:?}", variant_idx, adt_id);
 
         let variant_idx = variant_idx.as_usize();
-        let (enum_ref, lit_ref) = self.lookup_enum(adt_id)?;
+        let (enum_ref, _lit_ref) = self.lookup_enum(adt_id)?;
         let enum_ref = enum_ref.borrow();
         let enum_ref = enum_ref.as_ref().unwrap();
 
@@ -714,7 +606,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         let struct_ref: radium::AbstractStructRef<'def> = *struct_ref;
 
         // apply the generic parameters according to the mask
-        let params = self.translate_generic_args(args, adt_deps)?;
+        let params = self.translate_generic_args(args, state)?;
 
         let struct_use = radium::AbstractStructUse::new(struct_ref, params, radium::TypeIsRaw::No);
 
@@ -727,48 +619,31 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
     }
 
     /// Generate a tuple use for a tuple with the given types.
-    pub fn generate_tuple_use<F>(
+    pub fn generate_tuple_use<'a, 'b, F>(
         &self,
         tys: F,
-        mut adt_deps: TranslationState,
+        state: TranslationState<'a, 'b, 'def>,
     ) -> Result<radium::LiteralTypeUse<'def>, TranslationError>
     where
         F: IntoIterator<Item = Ty<'tcx>>,
     {
         let tys = tys.into_iter();
 
-        let in_function = adt_deps == TranslationState::InFunction;
         let generic_args: Vec<_> = tys.into_iter().map(|ty| ty.into()).collect();
-        let params = self.translate_generic_args(generic_args, adt_deps)?;
+        let params = self.translate_generic_args(generic_args, &mut *state)?;
 
         let num_components = params.len();
         let (_, lit) = self.get_tuple_struct_ref(num_components);
 
         // TODO: don't generate duplicates
         let struct_use = radium::LiteralTypeUse::new(lit, params);
-        if in_function {
-            self.tuple_uses.borrow_mut().push(struct_use.clone());
+        if let TranslationStateInner::InFunction(ref mut scope) = *state {
+            scope.tuple_uses.push(struct_use.clone());
         }
 
         Ok(struct_use)
     }
 
-    pub fn make_tuple_use(&self, translated_tys: Vec<radium::Type<'def>>) -> radium::Type<'def> {
-        let num_components = translated_tys.len();
-        if num_components == 0 {
-            return radium::Type::Unit;
-        }
-
-        let (_, lit) = self.get_tuple_struct_ref(num_components);
-        // TODO: don't generate duplicates
-        //let struct_use = radium::AbstractStructUse::new(struct_ref, translated_tys,
-        // radium::TypeIsRaw::Yes);
-        let struct_use = radium::LiteralTypeUse::new(lit, translated_tys);
-        self.tuple_uses.borrow_mut().push(struct_use.clone());
-
-        radium::Type::Literal(struct_use)
-    }
-
     /// Get the struct ref for a tuple with [num_components] components.
     fn get_tuple_struct_ref(
         &self,
@@ -902,13 +777,13 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
     /// Make an ADT variant.
     /// This assumes that this variant has already been pre-registered to account for recursive
     /// occurrences.
-    fn make_adt_variant(
+    fn make_adt_variant<'a>(
         &self,
         struct_name: &str,
         ty: &'tcx ty::VariantDef,
         adt: ty::AdtDef,
         ty_param_defs: &[radium::LiteralTyParam],
-        mut adt_deps: &mut HashSet<DefId>,
+        adt_deps: TranslateAdtState<'a>,
     ) -> Result<(radium::AbstractVariant<'def>, Option<radium::InvariantSpec>), TranslationError> {
         info!("adt variant: {:?}", ty);
 
@@ -972,7 +847,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
 
             let f_ty = self.env.tcx().type_of(f.did).instantiate_identity();
             let mut ty =
-                self.translate_type_with_deps(&f_ty, TranslationState::TranslateAdt(&mut *adt_deps))?;
+                self.translate_type_with_deps(&f_ty, &mut TranslationStateInner::TranslateAdt(adt_deps))?;
 
             // substitute all the type parameters by literals
             ty.subst_params(&ty_env);
@@ -1375,27 +1250,25 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         }
     }
 
-    /// Translate type.
-    pub fn translate_type(&self, ty: &Ty<'tcx>) -> Result<radium::Type<'def>, TranslationError> {
-        self.translate_type_with_deps(ty, TranslationState::InFunction)
-    }
-
-    fn generate_adt_shim_use(
+    fn generate_adt_shim_use<'a, 'b>(
         &self,
         adt: &ty::AdtDef<'tcx>,
         substs: ty::GenericArgsRef<'tcx>,
-        adt_deps: TranslationState,
+        state: TranslationState<'a, 'b, 'def>,
     ) -> Result<radium::Type<'def>, TranslationError> {
-        let in_function = adt_deps == TranslationState::InFunction;
         if let Some(shim) = self.lookup_adt_shim(adt.did()) {
-            let params = self.translate_generic_args(substs.iter(), adt_deps)?;
-            // track this shim use for the current function
+            let params = self.translate_generic_args(substs.iter(), &mut *state)?;
+
             let key = AdtUseKey::new(adt.did(), &params);
             let shim_use = radium::LiteralTypeUse::new(shim, params);
-            let mut shim_uses = self.shim_uses.borrow_mut();
-            if !shim_uses.contains_key(&key) && in_function {
-                shim_uses.insert(key, shim_use.clone());
+
+            if let TranslationStateInner::InFunction(ref mut scope) = state {
+                // track this shim use for the current function
+                if !scope.shim_uses.contains_key(&key) {
+                    scope.shim_uses.insert(key, shim_use.clone());
+                }
             }
+
             Ok(radium::Type::Literal(shim_use))
         } else {
             Err(TranslationError::UnknownError(format!(
@@ -1405,12 +1278,23 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         }
     }
 
+    // Okay, I don't know what the problem is.
+    // 'def outlives 'a
+    // 'b outlives 'a
+    // I dont' know why it infers that 'a outlives 'def or so.
+    //
+    // What do I want?
+    // I want to pass mutable access to the translation context.
+    // In some cases, I need to be able to wrap the translation context.
+    // I guess I can just do interior mutability with RefCell.
+    //
+
     /// Translate types, while placing the DefIds of ADTs that this type uses in the adt_deps
     /// argument, if provided.
-    pub fn translate_type_with_deps(
+    pub fn translate_type_with_deps<'a, 'b>(
         &self,
         ty: &Ty<'tcx>,
-        mut adt_deps: TranslationState,
+        state: TranslationState<'a, 'b, 'def>,
     ) -> Result<radium::Type<'def>, TranslationError> {
         match ty.kind() {
             TyKind::Char => Ok(radium::Type::Char),
@@ -1444,10 +1328,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
                     description: "Slices are currently unsupported".to_string(),
                 })
             },
-            TyKind::RawPtr(_) =>
-            {
-                Ok(radium::Type::RawPtr)
-            },
+            TyKind::RawPtr(_) => Ok(radium::Type::RawPtr),
             // TODO: this will have to change for handling fat ptrs. see the corresponding rustc
             // def for inspiration.
             TyKind::Ref(region, ty, mutability) => {
@@ -1458,9 +1339,9 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
                     return Ok(radium::Type::Unit);
                 }
 
-                let translated_ty = self.translate_type_with_deps(ty, adt_deps)?;
+                let translated_ty = self.translate_type_with_deps(ty, &mut *state)?;
                 // in case this isn't a universal region, we usually won't care about it.
-                let lft = if let Some(lft) = self.translate_region(region) {
+                let lft = if let Some(lft) = self.translate_region(&mut *state, region) {
                     lft
                 } else {
                     warn!("Failed to translate region {:?}", region);
@@ -1479,22 +1360,22 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
                     // the second parameter is the allocator, which we ignore for now
                     assert!(substs.len() == 2);
                     let ty = substs[0].expect_ty();
-                    let translated_ty = self.translate_type_with_deps(&ty, adt_deps)?;
+                    let translated_ty = self.translate_type_with_deps(&ty, &mut *state)?;
                     Ok(radium::Type::BoxType(Box::new(translated_ty)))
                 } else if let Some(true) = self.is_struct_definitely_zero_sized(adt.did()) {
                     // make this unit
                     Ok(radium::Type::Unit)
                 } else {
-                    if let TranslationState::TranslateAdt(ref mut adt_deps) = adt_deps {
+                    if let TranslationStateInner::TranslateAdt(ref mut adt_deps) = *state {
                         adt_deps.insert(adt.did());
                     }
 
                     if let Some(_) = self.lookup_adt_shim(adt.did()) {
-                        self.generate_adt_shim_use(adt, substs, adt_deps)
+                        self.generate_adt_shim_use(adt, substs, &mut *state)
                     } else if adt.is_struct() {
-                        self.generate_structlike_use_internal(ty, None, adt_deps)
+                        self.generate_structlike_use_internal(ty, None, &mut *state)
                     } else if adt.is_enum() {
-                        let eu = self.generate_enum_use_noshim(*adt, *substs, adt_deps)?;
+                        let eu = self.generate_enum_use_noshim(*adt, *substs, &mut *state)?;
                         Ok(radium::Type::Enum(eu))
                     } else {
                         Err(TranslationError::UnsupportedFeature {
@@ -1507,15 +1388,30 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
                 if params.len() == 0 {
                     Ok(radium::Type::Unit)
                 } else {
-                    let su = self.generate_tuple_use(params.iter(), adt_deps)?;
+                    let su = self.generate_tuple_use(params.iter(), &mut *state)?;
                     Ok(radium::Type::Literal(su))
                 }
             },
             TyKind::Param(param_ty) => {
-                // NOTE: we are not using the scope of the function because we might be translating
-                // ADT defs outside that scope
-                info!("using generic type param: {}", param_ty);
-                Ok(radium::Type::Var(param_ty.index as usize))
+                if let TranslationStateInner::InFunction(ref mut scope) = state {
+                    // lookup in the function's scope
+                    info!("using generic type param: {}", param_ty);
+                    if let Some(param) = scope.generic_scope.get(param_ty.index as usize) {
+                        if let Some(ty) = param {
+                            Ok(radium::Type::LiteralParam(ty.clone()))
+                        } else {
+                            Err(TranslationError::UnknownVar(format!(
+                                "unknown generic parameter {:?} is a region {:?}, not a type",
+                                param_ty, param
+                            )))
+                        }
+                    } else {
+                        Err(TranslationError::UnknownVar(format!("unknown generic parameter {:?}", param_ty)))
+                    }
+                } else {
+                    info!("using generic type param: {}", param_ty);
+                    Ok(radium::Type::Var(param_ty.index as usize))
+                }
             },
             TyKind::Foreign(_) => Err(TranslationError::UnsupportedType {
                 description: "RefinedRust does not support extern types".to_string(),
@@ -1537,7 +1433,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
                 let clos = closure_args.as_closure();
 
                 let upvar_tys = clos.tupled_upvars_ty();
-                self.translate_type_with_deps(&upvar_tys, adt_deps)
+                self.translate_type_with_deps(&upvar_tys, &mut *state)
             },
             TyKind::Generator(..) => Err(TranslationError::UnsupportedType {
                 description: "RefinedRust does currently not support generators".to_string(),
@@ -1545,16 +1441,16 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
             TyKind::GeneratorWitness(..) => Err(TranslationError::UnsupportedType {
                 description: "RefinedRust does currently not support generators".to_string(),
             }),
-            //TyKind::Projection(..) => Err(TranslationError::UnsupportedType {description:
-            //"RefinedRust does currently not support associated types".to_string()}),
-            //TyKind::Opaque(..) => Err(TranslationError::UnsupportedType {description:
-            //"RefinedRust does currently not support returning impls".to_string()}),
             TyKind::GeneratorWitnessMIR(_, _) => Err(TranslationError::UnsupportedType {
                 description: "RefinedRust does currently not support generators".to_string(),
             }),
-            TyKind::Alias(kind, ty) => Err(TranslationError::UnsupportedType {
-                description: "RefinedRust does not support Alias types".to_string(),
-            }),
+            TyKind::Alias(_, _) =>
+            // TODO: probably need this when translating trait definitions
+            {
+                Err(TranslationError::UnsupportedType {
+                    description: "RefinedRust does not support Alias types".to_string(),
+                })
+            },
             TyKind::Bound(_, _) => Err(TranslationError::UnsupportedType {
                 description: "RefinedRust does not support higher-ranked types".to_string(),
             }),
@@ -1627,27 +1523,6 @@ impl<'def, 'tcx: 'def> TypeTranslator<'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>) -> Result<radium::SynType, TranslationError> {
-        // give an environment substituting in the generics
-        self.translate_type(ty).map(|mut ty| {
-            let env = self.generic_scope.borrow();
-            ty.subst_params(&env);
-            ty.get_syn_type()
-        })
-    }
-
-    /// Translates a syntactic type to an op type.
-    pub fn translate_syn_type_to_op_type(&self, st: &radium::SynType) -> radium::OpType {
-        st.optype_typaram(self.generic_scope.borrow().as_ref())
-    }
-
-    /// Translates a syntactic type to a layout term.
-    pub fn translate_syn_type_to_layout(&self, st: &radium::SynType) -> radium::Layout {
-        st.layout_term_typaram(self.generic_scope.borrow().as_ref())
-    }
-
     /// Get the name for a field of an ADT or tuple type
     pub fn get_field_name_of(
         &self,
@@ -1701,3 +1576,383 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> {
         }
     }
 }
+
+/// Public functions used by the BodyTranslator.
+impl<'def, 'tcx> TypeTranslator<'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: InFunctionState<'_, 'def>,
+    ) -> Result<radium::SynType, TranslationError> {
+        // give an environment substituting in the generics
+        self.translate_type(ty, &mut *scope).map(|mut ty| {
+            ty.subst_params(&scope.generic_scope);
+            ty.get_syn_type()
+        })
+    }
+
+    /// Translate type in the scope of a function.
+    pub fn translate_type(
+        &self,
+        ty: &Ty<'tcx>,
+        scope: InFunctionState<'_, 'def>,
+    ) -> Result<radium::Type<'def>, TranslationError> {
+        self.translate_type_with_deps(&ty, &mut TranslationStateInner::InFunction(&mut *scope))
+    }
+
+    /// Translate type in an empty scope.
+    pub fn translate_type_in_empty_scope(
+        &self,
+        ty: &Ty<'tcx>,
+    ) -> Result<radium::Type<'def>, TranslationError> {
+        let mut s = HashSet::new();
+        self.translate_type_with_deps(ty, &mut TranslationStateInner::TranslateAdt(&mut s))
+    }
+
+    /// 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<'a>(
+        &self,
+        ty: &Ty<'tcx>,
+        variant: Option<rustc_target::abi::VariantIdx>,
+        scope: InFunctionState<'a, 'def>,
+    ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError> {
+        match ty.kind() {
+            TyKind::Adt(adt, args) => {
+                if adt.is_struct() {
+                    info!("generating struct use for {:?}", adt.did());
+                    // register the ADT, if necessary
+                    self.register_adt(*adt)?;
+                    self.generate_struct_use(adt.did(), *args, &mut *scope)
+                } else if adt.is_enum() {
+                    if let Some(variant) = variant {
+                        self.register_adt(*adt)?;
+                        let v = &adt.variants()[variant];
+                        self.generate_enum_variant_use(v.def_id, args.iter(), scope).map(|x| Some(x))
+                    } else {
+                        Err(TranslationError::UnknownError(
+                            "a non-downcast enum is not a structlike".to_string(),
+                        ))
+                    }
+                } else {
+                    Err(TranslationError::UnsupportedType {
+                        description: "non-{enum, struct, tuple} ADTs are not supported".to_string(),
+                    })
+                }
+            },
+            TyKind::Tuple(args) => self
+                .generate_tuple_use(args.into_iter(), &mut TranslationStateInner::InFunction(scope))
+                .map(|x| Some(x)),
+            TyKind::Closure(_, args) => {
+                // use the upvar tuple
+                let closure_args = args.as_closure();
+                let upvars = closure_args.upvar_tys();
+                self.generate_tuple_use(upvars.into_iter(), &mut TranslationStateInner::InFunction(scope))
+                    .map(|x| Some(x))
+            },
+            _ => Err(TranslationError::UnknownError("not a structlike".to_string())),
+        }
+    }
+
+    /// 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,
+        state: InFunctionState<'_, 'def>,
+    ) -> Result<radium::LiteralTypeUse<'def>, TranslationError>
+    where
+        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
+    {
+        info!("generating enum use for {:?}", adt_def.did());
+        // TODO: register?
+        //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 enum_use = radium::LiteralTypeUse::new(enum_ref, params);
+
+        // track this enum use for the current function
+        let enum_uses = &mut state.shim_uses;
+        if !enum_uses.contains_key(&key) {
+            enum_uses.insert(key, enum_use.clone());
+        }
+
+        Ok(enum_use)
+    }
+
+    /// 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,
+        scope: InFunctionState<'_, 'def>,
+    ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError>
+    where
+        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
+    {
+        info!("generating struct use for {:?}", variant_id);
+
+        if let Some(true) = self.is_struct_definitely_zero_sized(variant_id) {
+            info!("replacing zero-sized struct with unit");
+            return Ok(None);
+        }
+
+        let params =
+            self.translate_generic_args(args, &mut TranslationStateInner::InFunction(&mut *scope))?;
+        let key = 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);
+
+        if !scope.shim_uses.contains_key(&key) {
+            scope.shim_uses.insert(key, struct_use.clone());
+        }
+
+        Ok(Some(struct_use))
+    }
+
+    /// Generate a struct use.
+    /// Returns None if this should be unit.
+    pub fn generate_enum_variant_use<'a, F>(
+        &self,
+        variant_id: DefId,
+        args: F,
+        scope: InFunctionState<'a, 'def>,
+    ) -> Result<radium::LiteralTypeUse<'def>, TranslationError>
+    where
+        F: IntoIterator<Item = ty::GenericArg<'tcx>>,
+    {
+        info!("generating enum variant use for {:?}", variant_id);
+
+        let x: TranslationState<'_, '_, 'def> = &mut TranslationStateInner::InFunction(&mut *scope);
+        let params = self.translate_generic_args(args, x)?;
+        let _key = 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);
+
+        // TODO: track?
+        // generate the struct use key
+        //let mut shim_uses = self.shim_uses.borrow_mut();
+        //if !shim_uses.contains_key(&key) {
+        //shim_uses.insert(key, struct_use.clone());
+        //}
+
+        Ok(struct_use)
+    }
+
+    pub fn make_tuple_use<'a, 'b>(
+        &self,
+        translated_tys: Vec<radium::Type<'def>>,
+        state: TranslationState<'a, 'b, 'def>,
+    ) -> radium::Type<'def> {
+        let num_components = translated_tys.len();
+        if num_components == 0 {
+            return radium::Type::Unit;
+        }
+
+        let (_, lit) = self.get_tuple_struct_ref(num_components);
+        // TODO: don't generate duplicates
+        //let struct_use = radium::AbstractStructUse::new(struct_ref, translated_tys,
+        // radium::TypeIsRaw::Yes);
+        let struct_use = radium::LiteralTypeUse::new(lit, translated_tys);
+        if let TranslationStateInner::InFunction(ref mut scope) = *state {
+            scope.tuple_uses.push(struct_use.clone());
+        }
+
+        radium::Type::Literal(struct_use)
+    }
+}
+
+/// Type translator bundling the function scope
+pub struct LocalTypeTranslator<'a, 'def, 'tcx> {
+    pub translator: &'a TypeTranslator<'def, 'tcx>,
+    pub scope: RefCell<TypeTranslationScope<'def>>,
+}
+impl<'a, 'def, 'tcx> LocalTypeTranslator<'a, 'def, 'tcx> {
+    pub fn new(translator: &'a TypeTranslator<'def, 'tcx>, scope: TypeTranslationScope<'def>) -> Self {
+        Self {
+            translator,
+            scope: RefCell::new(scope),
+        }
+    }
+
+    /// 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>) -> Result<radium::SynType, TranslationError> {
+        let ty = self.normalize(*ty)?;
+        let mut scope = self.scope.borrow_mut();
+        self.translator.translate_type_to_syn_type(&ty, &mut *scope)
+    }
+
+    /// Translate type.
+    pub fn translate_type(&self, ty: &Ty<'tcx>) -> Result<radium::Type<'def>, TranslationError> {
+        let ty = self.normalize(*ty)?;
+        let mut scope = self.scope.borrow_mut();
+        self.translator.translate_type(&ty, &mut *scope)
+    }
+
+    /// Translate type without normalizing first.
+    pub fn translate_type_no_normalize(&self, ty: &Ty<'tcx>) -> Result<radium::Type<'def>, TranslationError> {
+        let mut scope = self.scope.borrow_mut();
+        self.translator.translate_type(ty, &mut *scope)
+    }
+
+    /// Translate a MIR type to the Caesium syntactic type we need when storing an element of the type,
+    /// substituting all generics, without normalizing first.
+    pub fn translate_type_to_syn_type_no_normalize(
+        &self,
+        ty: &Ty<'tcx>,
+    ) -> Result<radium::SynType, TranslationError> {
+        let mut scope = self.scope.borrow_mut();
+        self.translator.translate_type_to_syn_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<rustc_target::abi::VariantIdx>,
+    ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError> {
+        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>
+    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>
+    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>
+    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)
+    }
+
+    pub fn make_tuple_use(&self, translated_tys: Vec<radium::Type<'def>>) -> radium::Type<'def> {
+        let mut scope = self.scope.borrow_mut();
+        self.translator
+            .make_tuple_use(translated_tys, &mut TranslationStateInner::InFunction(&mut *scope))
+    }
+
+    /// Translates a syntactic type to an op type.
+    pub fn translate_syn_type_to_op_type(&self, st: &radium::SynType) -> radium::OpType {
+        let scope = self.scope.borrow();
+        st.optype_typaram(&scope.generic_scope)
+    }
+
+    /// Translates a syntactic type to a layout term.
+    pub fn translate_syn_type_to_layout(&self, st: &radium::SynType) -> radium::Layout {
+        let scope = self.scope.borrow();
+        st.layout_term_typaram(&scope.generic_scope)
+    }
+
+    pub fn generate_tuple_use<F>(&self, tys: F) -> Result<radium::LiteralTypeUse<'def>, TranslationError>
+    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: &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>
+    where
+        T: ty::TypeFoldable<ty::TyCtxt<'tcx>>,
+    {
+        let scope = self.scope.borrow();
+        normalize_in_function(scope.did, self.translator.env.tcx(), ty)
+    }
+}
+
+/// 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>
+where
+    T: ty::TypeFoldable<ty::TyCtxt<'tcx>>,
+{
+    let param_env = tcx.param_env(function_did);
+    normalize_type(tcx, param_env, ty)
+        .map_err(|e| TranslationError::TraitResolution(format!("normalization error: {:?}", e)))
+}
+
+/// Format the Coq representation of an atomic region.
+pub fn format_atomic_region_direct<'def>(
+    r: &info::AtomicRegion,
+    scope: Option<&TypeTranslationScope<'def>>,
+) -> String {
+    match r {
+        info::AtomicRegion::Loan(_, r) => {
+            format!("llft{}", r.index())
+        },
+        info::AtomicRegion::Universal(_, r) => {
+            if let Some(scope) = scope {
+                match scope.lookup_universal_region(*r) {
+                    Some(s) => s,
+                    None => format!("ulft{}", r.index()),
+                }
+            } else {
+                format!("ulft{}", r.index())
+            }
+        },
+        info::AtomicRegion::PlaceRegion(r) => {
+            format!("plft{}", r.index())
+        },
+        info::AtomicRegion::Unknown(r) => {
+            format!("vlft{}", r.index())
+        },
+    }
+}
-- 
GitLab