Skip to main content

charon_driver/translate/
user_type_annotations.rs

1//! Recover the type annotations provided by the user inside a body.
2use itertools::Itertools;
3use rustc_hash::FxHashMap as HashMap;
4use rustc_middle::{mir, ty};
5
6use super::translate_crate::RustcItem;
7use super::translate_ctx::*;
8use crate::hax;
9use charon_lib::ast::*;
10
11struct UserTypeAnnotation {
12    projections: HashMap<Vec<mir::ProjectionKind>, Ty>,
13}
14
15/// User-provided type annotations for this MIR body.
16pub(crate) struct UserTypeAnnotations {
17    annotations: rustc_index::IndexVec<ty::UserTypeAnnotationIndex, UserTypeAnnotation>,
18    pub outlives: Vec<OutlivesPred>,
19}
20
21impl UserTypeAnnotations {
22    /// Translate the annotations for this body. The returned value can then be used to fetch the
23    /// types of annotations.
24    pub(crate) fn new<'tcx>(
25        ctx: &mut ItemTransCtx<'tcx, '_>,
26        body: &mir::Body<'tcx>,
27    ) -> Result<Self, Error> {
28        use mir::visit::Visitor as _;
29
30        let mut annotations = body.user_type_annotations.clone();
31        if let RustcItem::Mono(item) = &ctx.item_src.item {
32            // `CanonicalUserTypeAnnotation::user_ty` is deliberately not folded when rustc
33            // instantiates a MIR body, so do the item substitution explicitly.
34            let item = item.clone();
35            let args = item.rustc_args(ctx.hax_state_with_id());
36            for annotation in &mut annotations {
37                annotation.user_ty.value = hax::substitute(
38                    ctx.tcx,
39                    hax::UnderOwnerState::typing_env(&ctx.hax_state),
40                    Some(args),
41                    annotation.user_ty.value,
42                );
43            }
44        }
45
46        let mut recovered = Self {
47            annotations: Default::default(),
48            outlives: Vec::new(),
49        };
50        if annotations.is_empty() {
51            return Ok(recovered);
52        }
53
54        // Gather the projections on user annotations that are actually used. Unused annotation
55        // types can reference items like intrinsics that have already been lowered away.
56        struct CollectProjections(
57            rustc_index::IndexVec<
58                ty::UserTypeAnnotationIndex,
59                SeqHashSet<Vec<mir::ProjectionKind>>,
60            >,
61        );
62        impl<'tcx> mir::visit::Visitor<'tcx> for CollectProjections {
63            fn visit_user_type_projection(&mut self, projection: &mir::UserTypeProjection) {
64                self.0[projection.base].insert(projection.projs.clone());
65            }
66
67            fn visit_const_operand(
68                &mut self,
69                operand: &mir::ConstOperand<'tcx>,
70                location: mir::Location,
71            ) {
72                if let Some(base) = operand.user_ty {
73                    self.0[base].insert(vec![]);
74                }
75                self.super_const_operand(operand, location);
76            }
77
78            fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: mir::Location) {
79                if let mir::Rvalue::Aggregate(kind, _) = rvalue
80                    && let mir::AggregateKind::Adt(_, _, _, Some(base), _) = **kind
81                {
82                    self.0[base].insert(vec![]);
83                }
84                self.super_rvalue(rvalue, location);
85            }
86        }
87        let mut projections =
88            CollectProjections(annotations.iter().map(|_| SeqHashSet::default()).collect());
89        projections.visit_body(body);
90        for (base, projections) in projections.0.into_iter_enumerated() {
91            let annotation = &annotations[base];
92            let span = ctx.translate_span(&annotation.span);
93            let projections = projections.into_iter().collect_vec();
94            let (tys, outlives) = translate_user_annotation(ctx, span, annotation, &projections)?;
95            let annotation = UserTypeAnnotation {
96                projections: projections.into_iter().zip_eq(tys).collect(),
97            };
98            assert_eq!(recovered.annotations.push(annotation), base);
99            recovered.outlives.extend(outlives);
100        }
101        recovered.outlives.sort_unstable();
102        recovered.outlives.dedup();
103        Ok(recovered)
104    }
105
106    /// Get the type of this local and the constraints from its type annotations.
107    pub(crate) fn translate_local_ty<'tcx>(
108        &self,
109        ctx: &mut ItemTransCtx<'tcx, '_>,
110        span: Span,
111        local_id: LocalId,
112        decl: &mir::LocalDecl<'tcx>,
113    ) -> Result<(Ty, Vec<PlaceTypeConstraint>), Error> {
114        let ty = ctx.translate_rustc_ty(span, &decl.ty)?;
115        let mut constraints = Vec::new();
116        if let Some(user_tys) = &decl.user_ty
117            && user_tys.projections().len() != 0
118        {
119            let outer_ref = if decl.is_nonref_binding() {
120                None
121            } else {
122                // In `let ref x: T`, the annotation describes the pointer target. We grab the
123                // appropriate region by using the normal type of the local.
124                let TyKind::Ref(region, _, kind) = ty.kind() else {
125                    raise_error!(ctx, span, "ref binding has non-reference type");
126                };
127                Some((region.clone(), *kind))
128            };
129
130            let annotated_tys = user_tys
131                .projections()
132                .map(|pj| self.translate_projected_annotation(pj))
133                .map(|ty| {
134                    if let Some((region, kind)) = &outer_ref {
135                        TyKind::Ref(region.clone(), ty, *kind).into()
136                    } else {
137                        ty
138                    }
139                });
140            for annotated_ty in annotated_tys {
141                constraints.push(PlaceTypeConstraint {
142                    place: Place::new(local_id, ty.clone()),
143                    ty: annotated_ty,
144                    variance: Variance::Covariant,
145                });
146            }
147        }
148        Ok((ty, constraints))
149    }
150
151    /// Look up the (unprojected) type of an annotation.
152    pub(crate) fn translate_annotation(&self, index: ty::UserTypeAnnotationIndex) -> Option<Ty> {
153        if self.annotations.is_empty() {
154            None
155        } else {
156            Some(self.annotations[index].projections[&[][..]].clone())
157        }
158    }
159
160    /// Look up the type of an annotation.
161    pub(crate) fn translate_projected_annotation(
162        &self,
163        projection: &mir::UserTypeProjection,
164    ) -> Ty {
165        let annotation = &self.annotations[projection.base];
166        annotation.projections[projection.projs.as_slice()].clone()
167    }
168}
169
170/// Translate a user annotation and the outlives obligations needed to validate it.
171fn translate_user_annotation<'tcx>(
172    ctx: &mut ItemTransCtx<'tcx, '_>,
173    span: Span,
174    annotation: &ty::CanonicalUserTypeAnnotation<'tcx>,
175    projections: &[hax::rustc::UserAnnotationProj],
176) -> Result<(Vec<Ty>, Vec<OutlivesPred>), Error> {
177    let Some(owner) = ctx.item_src.def_id().as_real_or_promoted() else {
178        raise_error!(ctx, span, "user type annotation without a rustc owner");
179    };
180    let ty_constraints = hax::rustc::translate_user_annotation(
181        ctx.tcx,
182        hax::UnderOwnerState::typing_env(&ctx.hax_state),
183        owner,
184        annotation,
185        projections,
186    );
187    let ty_constraints = match ty_constraints {
188        Ok(ty_constraints) => ty_constraints,
189        Err(message) => raise_error!(ctx, span, "{message}"),
190    };
191    let ty_constraints: hax::Binder<(Vec<hax::Ty<'tcx>>, Vec<hax::Clause<'tcx>>)> =
192        ctx.catch_sinto(span, &ty_constraints)?;
193    let bound_constraints =
194        ctx.translate_region_binder(span, &ty_constraints, |ctx, (tys, clauses)| {
195            let tys: Vec<Ty> = tys
196                .iter()
197                .map(|ty| ctx.translate_ty(span, ty))
198                .try_collect()?;
199            let mut outlives = Vec::new();
200            for clause in clauses {
201                let pred = ctx.translate_region_binder(span, &clause.kind, |ctx, kind| {
202                    Ok(match kind {
203                        hax::ClauseKind::RegionOutlives(p) => OutlivesPred(
204                            ctx.translate_region(span, &p.lhs)?,
205                            ctx.translate_region(span, &p.rhs)?,
206                        ),
207                        hax::ClauseKind::TypeOutlives(p) => OutlivesPred(
208                            Region::Lub(ctx.translate_ty(span, &p.lhs)?),
209                            ctx.translate_region(span, &p.rhs)?,
210                        ),
211                        _ => unreachable!("recovered ascription contains a non-region constraint"),
212                    })
213                })?;
214                outlives.push(pred.no_bound_vars());
215            }
216            Ok((tys, outlives))
217        })?;
218    // Instantiate the binder with fresh body regions.
219    let (tys, outlives) = ctx.erase_region_binder(bound_constraints);
220    let outlives = outlives.iter().flat_map(|pred| pred.normalize()).collect();
221    Ok((tys, outlives))
222}