rustc_hir_typeck/
gather_locals.rs

1use rustc_hir as hir;
2use rustc_hir::intravisit::{self, Visitor};
3use rustc_hir::{HirId, PatKind};
4use rustc_infer::traits::ObligationCauseCode;
5use rustc_middle::ty::{self, Ty};
6use rustc_span::Span;
7use rustc_span::def_id::LocalDefId;
8use tracing::debug;
9
10use crate::FnCtxt;
11
12/// Provides context for checking patterns in declarations. More specifically this
13/// allows us to infer array types if the pattern is irrefutable and allows us to infer
14/// the size of the array. See issue #76342.
15#[derive(Debug, Copy, Clone)]
16pub(super) enum DeclOrigin<'a> {
17    // from an `if let` expression
18    LetExpr,
19    // from `let x = ..`
20    LocalDecl { els: Option<&'a hir::Block<'a>> },
21}
22
23impl<'a> DeclOrigin<'a> {
24    pub(super) fn try_get_else(&self) -> Option<&'a hir::Block<'a>> {
25        match self {
26            Self::LocalDecl { els } => *els,
27            Self::LetExpr => None,
28        }
29    }
30}
31
32/// A declaration is an abstraction of [hir::LetStmt] and [hir::LetExpr].
33///
34/// It must have a hir_id, as this is how we connect gather_locals to the check functions.
35pub(super) struct Declaration<'a> {
36    pub hir_id: HirId,
37    pub pat: &'a hir::Pat<'a>,
38    pub ty: Option<&'a hir::Ty<'a>>,
39    pub span: Span,
40    pub init: Option<&'a hir::Expr<'a>>,
41    pub origin: DeclOrigin<'a>,
42}
43
44impl<'a> From<&'a hir::LetStmt<'a>> for Declaration<'a> {
45    fn from(local: &'a hir::LetStmt<'a>) -> Self {
46        let hir::LetStmt { hir_id, pat, ty, span, init, els, source: _ } = *local;
47        Declaration { hir_id, pat, ty, span, init, origin: DeclOrigin::LocalDecl { els } }
48    }
49}
50
51impl<'a> From<(&'a hir::LetExpr<'a>, HirId)> for Declaration<'a> {
52    fn from((let_expr, hir_id): (&'a hir::LetExpr<'a>, HirId)) -> Self {
53        let hir::LetExpr { pat, ty, span, init, recovered: _ } = *let_expr;
54        Declaration { hir_id, pat, ty, span, init: Some(init), origin: DeclOrigin::LetExpr }
55    }
56}
57
58pub(super) struct GatherLocalsVisitor<'a, 'tcx> {
59    fcx: &'a FnCtxt<'a, 'tcx>,
60    // parameters are special cases of patterns, but we want to handle them as
61    // *distinct* cases. so track when we are hitting a pattern *within* an fn
62    // parameter.
63    outermost_fn_param_pat: Option<(Span, HirId)>,
64}
65
66impl<'a, 'tcx> GatherLocalsVisitor<'a, 'tcx> {
67    pub(super) fn new(fcx: &'a FnCtxt<'a, 'tcx>) -> Self {
68        Self { fcx, outermost_fn_param_pat: None }
69    }
70
71    fn assign(&mut self, span: Span, nid: HirId, ty_opt: Option<Ty<'tcx>>) -> Ty<'tcx> {
72        match ty_opt {
73            None => {
74                // Infer the variable's type.
75                let var_ty = self.fcx.next_ty_var(span);
76                self.fcx.locals.borrow_mut().insert(nid, var_ty);
77                var_ty
78            }
79            Some(typ) => {
80                // Take type that the user specified.
81                self.fcx.locals.borrow_mut().insert(nid, typ);
82                typ
83            }
84        }
85    }
86
87    /// Allocates a type for a declaration, which may have a type annotation. If it does have
88    /// a type annotation, then the [`Ty`] stored will be the resolved type. This may be found
89    /// again during type checking by querying [`FnCtxt::local_ty`] for the same hir_id.
90    fn declare(&mut self, decl: Declaration<'tcx>) {
91        let local_ty = match decl.ty {
92            Some(ref ty) => {
93                let o_ty = self.fcx.lower_ty(ty);
94
95                let c_ty = self.fcx.infcx.canonicalize_user_type_annotation(
96                    ty::UserType::new_with_bounds(
97                        ty::UserTypeKind::Ty(o_ty.raw),
98                        self.fcx.collect_impl_trait_clauses_from_hir_ty(ty),
99                    ),
100                );
101                debug!("visit_local: ty.hir_id={:?} o_ty={:?} c_ty={:?}", ty.hir_id, o_ty, c_ty);
102                self.fcx
103                    .typeck_results
104                    .borrow_mut()
105                    .user_provided_types_mut()
106                    .insert(ty.hir_id, c_ty);
107
108                Some(o_ty.normalized)
109            }
110            None => None,
111        };
112        self.assign(decl.span, decl.hir_id, local_ty);
113
114        debug!(
115            "local variable {:?} is assigned type {}",
116            decl.pat,
117            self.fcx.ty_to_string(*self.fcx.locals.borrow().get(&decl.hir_id).unwrap())
118        );
119    }
120}
121
122impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> {
123    // Add explicitly-declared locals.
124    fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) {
125        self.declare(local.into());
126        intravisit::walk_local(self, local)
127    }
128
129    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
130        if let hir::ExprKind::Let(let_expr) = expr.kind {
131            self.declare((let_expr, expr.hir_id).into());
132        }
133        intravisit::walk_expr(self, expr)
134    }
135
136    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
137        let old_outermost_fn_param_pat =
138            self.outermost_fn_param_pat.replace((param.ty_span, param.hir_id));
139        intravisit::walk_param(self, param);
140        self.outermost_fn_param_pat = old_outermost_fn_param_pat;
141    }
142
143    // Add pattern bindings.
144    fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {
145        if let PatKind::Binding(_, _, ident, _) = p.kind {
146            let var_ty = self.assign(p.span, p.hir_id, None);
147
148            if let Some((ty_span, hir_id)) = self.outermost_fn_param_pat {
149                if !self.fcx.tcx.features().unsized_fn_params() {
150                    self.fcx.require_type_is_sized(
151                        var_ty,
152                        ty_span,
153                        // ty_span == ident.span iff this is a closure parameter with no type
154                        // ascription, or if it's an implicit `self` parameter
155                        ObligationCauseCode::SizedArgumentType(
156                            if ty_span == ident.span
157                                && self.fcx.tcx.is_closure_like(self.fcx.body_id.into())
158                            {
159                                None
160                            } else {
161                                Some(hir_id)
162                            },
163                        ),
164                    );
165                }
166            } else if !self.fcx.tcx.features().unsized_locals() {
167                self.fcx.require_type_is_sized(
168                    var_ty,
169                    p.span,
170                    ObligationCauseCode::VariableType(p.hir_id),
171                );
172            }
173
174            debug!(
175                "pattern binding {} is assigned to {} with type {:?}",
176                ident,
177                self.fcx.ty_to_string(*self.fcx.locals.borrow().get(&p.hir_id).unwrap()),
178                var_ty
179            );
180        }
181        let old_outermost_fn_param_pat = self.outermost_fn_param_pat.take();
182        intravisit::walk_pat(self, p);
183        self.outermost_fn_param_pat = old_outermost_fn_param_pat;
184    }
185
186    // Don't descend into the bodies of nested closures.
187    fn visit_fn(
188        &mut self,
189        _: intravisit::FnKind<'tcx>,
190        _: &'tcx hir::FnDecl<'tcx>,
191        _: hir::BodyId,
192        _: Span,
193        _: LocalDefId,
194    ) {
195    }
196}