Skip to main content

hir_def/expr_store/
scope.rs

1//! Name resolution for expressions.
2use std::mem;
3
4use base_db::SourceDatabase;
5use hir_expand::{MacroDefId, name::Name};
6use la_arena::{Arena, ArenaMap, Idx, IdxRange, RawIdx};
7
8use crate::{
9    BlockId, DefWithBodyId, ExpressionStoreOwnerId, GenericDefId, VariantId,
10    expr_store::{Body, ExpressionStore, HygieneId, StoreVisitor, StoreVisitorExt, body::Param},
11    hir::{
12        Binding, BindingId, Expr, ExprId, Item, LabelId, Pat, PatId, Statement,
13        generics::GenericParams,
14    },
15    signatures::VariantFields,
16    type_ref::TypeRefId,
17};
18
19pub type ScopeId = Idx<ScopeData>;
20
21#[derive(Debug, PartialEq, Eq)]
22pub struct ExprScopes {
23    scopes: Arena<ScopeData>,
24    scope_entries: Arena<ScopeEntry>,
25    scope_by_expr: ArenaMap<ExprId, ScopeId>,
26}
27
28#[derive(Debug, PartialEq, Eq)]
29pub struct ScopeEntry {
30    name: Name,
31    hygiene: HygieneId,
32    binding: BindingId,
33}
34
35impl ScopeEntry {
36    pub fn name(&self) -> &Name {
37        &self.name
38    }
39
40    pub(crate) fn hygiene(&self) -> HygieneId {
41        self.hygiene
42    }
43
44    pub fn binding(&self) -> BindingId {
45        self.binding
46    }
47}
48
49#[derive(Debug, PartialEq, Eq)]
50pub struct ScopeData {
51    parent: Option<ScopeId>,
52    kind: ScopeKind,
53    entries: IdxRange<ScopeEntry>,
54}
55
56#[derive(Debug, PartialEq, Eq)]
57enum ScopeKind {
58    None,
59    Block { id: BlockId, label: Option<LabelId> },
60    Label(LabelId),
61    MacroDef(Box<MacroDefId>),
62}
63
64#[salsa::tracked]
65impl ExprScopes {
66    #[salsa::tracked(returns(ref))]
67    pub fn body_expr_scopes(db: &dyn SourceDatabase, def: DefWithBodyId) -> ExprScopes {
68        let body = Body::of(db, def);
69        let mut scopes = ExprScopes::new_body(body);
70        scopes.shrink_to_fit();
71        scopes
72    }
73
74    #[salsa::tracked(returns(ref))]
75    pub fn sig_expr_scopes(db: &dyn SourceDatabase, def: GenericDefId) -> ExprScopes {
76        let (_, store) = GenericParams::with_store(db, def);
77        let roots = store.expr_roots();
78        let mut scopes = ExprScopes::new_store(store, roots);
79        scopes.shrink_to_fit();
80        scopes
81    }
82
83    #[salsa::tracked(returns(ref))]
84    pub fn variant_scopes(db: &dyn SourceDatabase, def: VariantId) -> ExprScopes {
85        let fields = VariantFields::of(db, def);
86        let roots = fields.store.expr_roots();
87        let mut scopes = ExprScopes::new_store(&fields.store, roots);
88        scopes.shrink_to_fit();
89        scopes
90    }
91}
92
93impl ExprScopes {
94    #[inline]
95    pub fn of(db: &dyn SourceDatabase, def: impl Into<ExpressionStoreOwnerId>) -> &ExprScopes {
96        match def.into() {
97            ExpressionStoreOwnerId::Body(def) => Self::body_expr_scopes(db, def),
98            ExpressionStoreOwnerId::Signature(def) => Self::sig_expr_scopes(db, def),
99            ExpressionStoreOwnerId::VariantFields(variant_id) => {
100                Self::variant_scopes(db, variant_id)
101            }
102        }
103    }
104
105    pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
106        &self.scope_entries[self.scopes[scope].entries.clone()]
107    }
108
109    /// If `scope` refers to a block expression scope, returns the corresponding `BlockId`.
110    pub fn block(&self, scope: ScopeId) -> Option<BlockId> {
111        match self.scopes[scope].kind {
112            ScopeKind::Block { id, label: _ } => Some(id),
113            ScopeKind::None | ScopeKind::Label(_) | ScopeKind::MacroDef(_) => None,
114        }
115    }
116
117    /// If `scope` refers to a macro def scope, returns the corresponding `MacroId`.
118    #[expect(
119        clippy::borrowed_box,
120        reason = "If we return `&MacroDefId` we need to move it, this way we just clone the `Box`."
121    )]
122    pub fn macro_def(&self, scope: ScopeId) -> Option<&Box<MacroDefId>> {
123        match &self.scopes[scope].kind {
124            ScopeKind::MacroDef(macro_def) => Some(macro_def),
125            ScopeKind::None | ScopeKind::Block { id: _, label: _ } | ScopeKind::Label(_) => None,
126        }
127    }
128
129    /// If `scope` refers to a labeled expression scope, returns the corresponding `Label`.
130    pub fn label(&self, scope: ScopeId) -> Option<LabelId> {
131        match &self.scopes[scope].kind {
132            &ScopeKind::Block { id: _, label } => label,
133            &ScopeKind::Label(label) => Some(label),
134            ScopeKind::None | ScopeKind::MacroDef(_) => None,
135        }
136    }
137
138    /// Returns the scopes in ascending order.
139    pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ {
140        std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
141    }
142
143    pub fn resolve_name_in_scope(&self, scope: ScopeId, name: &Name) -> Option<&ScopeEntry> {
144        self.scope_chain(Some(scope))
145            .find_map(|scope| self.entries(scope).iter().find(|it| it.name == *name))
146    }
147
148    pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> {
149        self.scope_by_expr.get(expr).copied()
150    }
151
152    pub fn scope_by_expr(&self) -> &ArenaMap<ExprId, ScopeId> {
153        &self.scope_by_expr
154    }
155}
156
157fn empty_entries(idx: usize) -> IdxRange<ScopeEntry> {
158    IdxRange::new(Idx::from_raw(RawIdx::from(idx as u32))..Idx::from_raw(RawIdx::from(idx as u32)))
159}
160
161impl ExprScopes {
162    fn new_body(body: &Body) -> ExprScopes {
163        let mut scopes = ExprScopes {
164            scopes: Arena::default(),
165            scope_entries: Arena::default(),
166            scope_by_expr: ArenaMap::with_capacity(
167                body.expr_only.as_ref().map_or(0, |it| it.exprs.len()),
168            ),
169        };
170        let root = scopes.root_scope();
171        if let Some(Param { formal: self_param, user_written: _ }) = body.self_param {
172            scopes.add_bindings(body, root, self_param, body.binding_hygiene(self_param));
173        }
174        let mut visitor =
175            ExprScopeVisitor { store: body, scopes: &mut scopes, scope: root, const_scope: root };
176        body.params.iter().for_each(|param| visitor.on_pat(param.formal));
177        visitor.on_expr(body.root_expr());
178        scopes
179    }
180
181    fn new_store(store: &ExpressionStore, roots: impl IntoIterator<Item = ExprId>) -> ExprScopes {
182        let mut scopes = ExprScopes {
183            scopes: Arena::default(),
184            scope_entries: Arena::default(),
185            scope_by_expr: ArenaMap::with_capacity(
186                store.expr_only.as_ref().map_or(0, |it| it.exprs.len()),
187            ),
188        };
189        let root = scopes.root_scope();
190        for root_expr in roots {
191            let scope = scopes.new_scope(root);
192            ExprScopeVisitor { store, scopes: &mut scopes, scope, const_scope: scope }
193                .on_expr(root_expr);
194        }
195        scopes
196    }
197
198    fn root_scope(&mut self) -> ScopeId {
199        self.scopes.alloc(ScopeData {
200            parent: None,
201            kind: ScopeKind::None,
202            entries: empty_entries(self.scope_entries.len()),
203        })
204    }
205
206    fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
207        self.scopes.alloc(ScopeData {
208            parent: Some(parent),
209            kind: ScopeKind::None,
210            entries: empty_entries(self.scope_entries.len()),
211        })
212    }
213
214    fn new_labeled_scope(&mut self, parent: ScopeId, label: Option<LabelId>) -> ScopeId {
215        let kind = match label {
216            Some(label) => ScopeKind::Label(label),
217            None => ScopeKind::None,
218        };
219        self.scopes.alloc(ScopeData {
220            parent: Some(parent),
221            kind,
222            entries: empty_entries(self.scope_entries.len()),
223        })
224    }
225
226    fn new_block_scope(
227        &mut self,
228        parent: ScopeId,
229        block: Option<BlockId>,
230        label: Option<LabelId>,
231    ) -> ScopeId {
232        let kind = match (block, label) {
233            (Some(id), label) => ScopeKind::Block { id, label },
234            (None, Some(label)) => ScopeKind::Label(label),
235            (None, None) => ScopeKind::None,
236        };
237        self.scopes.alloc(ScopeData {
238            parent: Some(parent),
239            kind,
240            entries: empty_entries(self.scope_entries.len()),
241        })
242    }
243
244    fn new_macro_def_scope(&mut self, parent: ScopeId, macro_id: Box<MacroDefId>) -> ScopeId {
245        self.scopes.alloc(ScopeData {
246            parent: Some(parent),
247            kind: ScopeKind::MacroDef(macro_id),
248            entries: empty_entries(self.scope_entries.len()),
249        })
250    }
251
252    fn add_bindings(
253        &mut self,
254        store: &ExpressionStore,
255        scope: ScopeId,
256        binding: BindingId,
257        hygiene: HygieneId,
258    ) {
259        let Binding { name, .. } = &store[binding];
260        let entry = self.scope_entries.alloc(ScopeEntry { name: name.clone(), binding, hygiene });
261        self.scopes[scope].entries =
262            IdxRange::new_inclusive(self.scopes[scope].entries.start()..=entry);
263    }
264
265    fn set_scope(&mut self, node: ExprId, scope: ScopeId) {
266        self.scope_by_expr.insert(node, scope);
267    }
268
269    fn shrink_to_fit(&mut self) {
270        let ExprScopes { scopes, scope_entries, scope_by_expr } = self;
271        scopes.shrink_to_fit();
272        scope_entries.shrink_to_fit();
273        scope_by_expr.shrink_to_fit();
274    }
275}
276
277struct ExprScopeVisitor<'a> {
278    store: &'a ExpressionStore,
279    scopes: &'a mut ExprScopes,
280    scope: ScopeId,
281    const_scope: ScopeId,
282}
283
284impl ExprScopeVisitor<'_> {
285    fn with_scope(&mut self, scope: ScopeId, f: impl FnOnce(&mut Self)) {
286        let old_scope = mem::replace(&mut self.scope, scope);
287        f(self);
288        self.scope = old_scope;
289    }
290
291    fn visit_block(
292        &mut self,
293        expr: ExprId,
294        id: Option<BlockId>,
295        statements: &[Statement],
296        tail: Option<ExprId>,
297        label: Option<LabelId>,
298    ) {
299        let scope = self.scopes.new_block_scope(self.scope, id, label);
300        self.with_scope(scope, |this| {
301            let old_const_scope = if id.is_some() {
302                let const_scope = this.scopes.new_block_scope(this.const_scope, id, None);
303                mem::replace(&mut this.const_scope, const_scope)
304            } else {
305                // We don't need to allocate a new scope, since only items matter to us.
306                this.const_scope
307            };
308            // Overwrite the old scope for the block expr, so that every block scope can be found
309            // via the block itthis (important for blocks that only contain items, no expressions).
310            this.scopes.set_scope(expr, this.scope);
311
312            for stmt in statements {
313                match stmt {
314                    Statement::Let { pat, initializer, else_branch, type_ref } => {
315                        this.on_type_opt(*type_ref);
316                        this.on_expr_opt(*initializer);
317                        this.on_expr_opt(*else_branch);
318                        this.scope = this.scopes.new_scope(this.scope);
319                        this.on_pat(*pat);
320                    }
321                    Statement::Expr { expr, has_semi: _ } => this.on_expr(*expr),
322                    Statement::Item(Item::MacroDef(macro_id)) => {
323                        this.scope = this.scopes.new_macro_def_scope(this.scope, macro_id.clone());
324                        this.const_scope =
325                            this.scopes.new_macro_def_scope(this.const_scope, macro_id.clone());
326                    }
327                    Statement::Item(Item::Other) => (),
328                }
329            }
330            this.on_expr_opt(tail);
331
332            this.const_scope = old_const_scope;
333        });
334    }
335}
336
337impl StoreVisitor for ExprScopeVisitor<'_> {
338    fn on_expr(&mut self, expr: ExprId) {
339        self.scopes.set_scope(expr, self.scope);
340        match &self.store[expr] {
341            Expr::Block { statements, tail, id, label, unsafe_: _ } => {
342                self.visit_block(expr, *id, statements, *tail, *label);
343            }
344            Expr::Loop { body, label, source: _ } => {
345                let scope = self.scopes.new_labeled_scope(self.scope, *label);
346                self.with_scope(scope, |this| this.on_expr(*body));
347            }
348            Expr::Closure { args, arg_types, ret_type, body, capture_by: _, closure_kind: _ } => {
349                arg_types.iter().for_each(|type_ref| self.on_type_opt(*type_ref));
350                self.on_type_opt(*ret_type);
351                let scope = self.scopes.new_scope(self.scope);
352                self.with_scope(scope, |this| {
353                    this.on_pats(args);
354                    this.on_expr(*body);
355                });
356            }
357            Expr::Match { expr, arms } => {
358                self.on_expr(*expr);
359                for arm in arms.iter() {
360                    let scope = self.scopes.new_scope(self.scope);
361                    self.with_scope(scope, |this| {
362                        this.on_pat(arm.pat);
363                        this.on_expr_opt(arm.guard);
364                        this.on_expr(arm.expr);
365                    });
366                }
367            }
368            &Expr::If { condition, then_branch, else_branch } => {
369                let then_branch_scope = self.scopes.new_scope(self.scope);
370                self.with_scope(then_branch_scope, |this| {
371                    this.on_expr(condition);
372                    this.on_expr(then_branch);
373                });
374                self.on_expr_opt(else_branch);
375            }
376            &Expr::Let { pat, expr } => {
377                self.on_expr(expr);
378                self.scope = self.scopes.new_scope(self.scope);
379                self.on_pat(pat);
380            }
381            _ => self.store.visit_expr_children(expr, self),
382        }
383    }
384
385    fn on_anon_const_expr(&mut self, expr: ExprId) {
386        self.with_scope(self.const_scope, |this| this.on_expr(expr));
387    }
388
389    fn on_pat(&mut self, pat: PatId) {
390        if let Pat::Bind { id, .. } = self.store[pat] {
391            self.scopes.add_bindings(self.store, self.scope, id, self.store.binding_hygiene(id));
392        }
393
394        self.store.visit_pat_children(pat, self);
395    }
396
397    fn on_type(&mut self, ty: TypeRefId) {
398        self.with_scope(self.const_scope, |this| self.store.visit_type_ref_children(ty, this));
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use hir_expand::{InFile, name::AsName};
405    use span::FileId;
406    use syntax::{AstNode, algo::find_node_at_offset, ast};
407    use test_fixture::WithFixture;
408    use test_utils::{assert_eq_text, extract_offset};
409
410    use crate::{
411        DefWithBodyId, FunctionId, ModuleDefId,
412        expr_store::{Body, scope::ExprScopes},
413        nameres::crate_def_map,
414        test_db::TestDB,
415    };
416
417    fn find_function(db: &TestDB, file_id: FileId) -> FunctionId {
418        let krate = db.test_crate();
419        let crate_def_map = crate_def_map(db, krate);
420
421        let module = crate_def_map.modules_for_file(db, file_id).next().unwrap();
422        let (_, def) = crate_def_map[module].scope.entries().next().unwrap();
423        match def.take_values().unwrap() {
424            ModuleDefId::FunctionId(it) => it,
425            _ => panic!(),
426        }
427    }
428
429    fn do_check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: &[&str]) {
430        let (offset, code) = extract_offset(ra_fixture);
431        let code = {
432            let mut buf = String::new();
433            let off: usize = offset.into();
434            buf.push_str(&code[..off]);
435            buf.push_str("$0marker");
436            buf.push_str(&code[off..]);
437            buf
438        };
439
440        let (db, position) = TestDB::with_position(&code);
441        let editioned_file_id = position.file_id;
442        let offset = position.offset;
443
444        let (file_id, _) = editioned_file_id.unpack(&db);
445
446        let file_syntax = editioned_file_id.parse(&db).syntax_node();
447        let marker: Option<ast::PathExpr> = find_node_at_offset(&file_syntax, offset);
448        let function = find_function(&db, file_id);
449
450        let scopes = ExprScopes::of(&db, DefWithBodyId::from(function));
451        let (body, source_map) = Body::with_source_map(&db, function.into());
452
453        let expr_id = marker
454            .and_then(|marker| {
455                source_map
456                    .node_expr(InFile { file_id: editioned_file_id.into(), value: &marker.into() })
457                    .and_then(|expr| expr.as_expr())
458            })
459            .or_else(|| {
460                body.exprs().find_map(|(expr, value)| {
461                    let crate::hir::Expr::Path(path) = value else { return None };
462                    path.mod_path()
463                        .and_then(|path| path.as_ident())
464                        .is_some_and(|name| name.as_str() == "marker")
465                        .then_some(expr)
466                })
467            })
468            .unwrap();
469        let scope = scopes.scope_for(expr_id);
470
471        let actual = scopes
472            .scope_chain(scope)
473            .flat_map(|scope| scopes.entries(scope))
474            .map(|it| it.name().as_str())
475            .collect::<Vec<_>>()
476            .join("\n");
477        let expected = expected.join("\n");
478        assert_eq_text!(&expected, &actual);
479    }
480
481    #[test]
482    fn type_anon_const_scope() {
483        do_check(
484            r#"
485fn f(param: usize) {
486    let local = 0;
487    let _: [(); $0] = [];
488}
489"#,
490            &["param"],
491        );
492    }
493
494    #[test]
495    fn pattern_type_expr_scope() {
496        do_check(
497            r#"
498fn f(param: usize) {
499    let local = 0;
500    let _: builtin#pattern_type (usize is 0..=$0) = 0;
501}
502"#,
503            &["param"],
504        );
505    }
506
507    #[test]
508    fn closure_pattern_type_expr_scope() {
509        do_check(
510            r#"
511fn f(param: usize) {
512    let local = 0;
513    let _ = |_: builtin#pattern_type (usize is 0..=$0)| {};
514}
515"#,
516            &["param"],
517        );
518    }
519
520    #[test]
521    fn array_repeat_expr_scope() {
522        do_check(
523            r#"
524fn f(param: usize) {
525    let local = 0;
526    let _ = [(); $0];
527}
528"#,
529            &["param"],
530        );
531    }
532
533    #[test]
534    fn inline_const_expr_scope() {
535        do_check(
536            r#"
537fn f(param: usize) {
538    let local = 0;
539    let _ = const { $0 };
540}
541"#,
542            &["param"],
543        );
544    }
545
546    #[test]
547    fn test_lambda_scope() {
548        do_check(
549            r"
550            fn quux(foo: i32) {
551                let f = |bar, baz: i32| {
552                    $0
553                };
554            }",
555            &["bar", "baz", "foo"],
556        );
557    }
558
559    #[test]
560    fn test_call_scope() {
561        do_check(
562            r"
563            fn quux() {
564                f(|x| $0 );
565            }",
566            &["x"],
567        );
568    }
569
570    #[test]
571    fn test_method_call_scope() {
572        do_check(
573            r"
574            fn quux() {
575                z.f(|x| $0 );
576            }",
577            &["x"],
578        );
579    }
580
581    #[test]
582    fn test_loop_scope() {
583        do_check(
584            r"
585            fn quux() {
586                loop {
587                    let x = ();
588                    $0
589                };
590            }",
591            &["x"],
592        );
593    }
594
595    #[test]
596    fn test_match() {
597        do_check(
598            r"
599            fn quux() {
600                match () {
601                    Some(x) => {
602                        $0
603                    }
604                };
605            }",
606            &["x"],
607        );
608    }
609
610    #[test]
611    fn test_shadow_variable() {
612        do_check(
613            r"
614            fn foo(x: String) {
615                let x : &str = &x$0;
616            }",
617            &["x"],
618        );
619    }
620
621    #[test]
622    fn test_bindings_after_at() {
623        do_check(
624            r"
625fn foo() {
626    match Some(()) {
627        opt @ Some(unit) => {
628            $0
629        }
630        _ => {}
631    }
632}
633",
634            &["opt", "unit"],
635        );
636    }
637
638    #[test]
639    fn macro_inner_item() {
640        do_check(
641            r"
642            macro_rules! mac {
643                () => {{
644                    fn inner() {}
645                    inner();
646                }};
647            }
648
649            fn foo() {
650                mac!();
651                $0
652            }
653        ",
654            &[],
655        );
656    }
657
658    #[test]
659    fn broken_inner_item() {
660        do_check(
661            r"
662            fn foo() {
663                trait {}
664                $0
665            }
666        ",
667            &[],
668        );
669    }
670
671    fn do_check_local_name(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected_offset: u32) {
672        let (db, position) = TestDB::with_position(ra_fixture);
673        let editioned_file_id = position.file_id;
674        let offset = position.offset;
675
676        let (file_id, _) = editioned_file_id.unpack(&db);
677
678        let file = editioned_file_id.parse(&db).ok().unwrap();
679        let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into())
680            .expect("failed to find a name at the target offset");
681        let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), offset).unwrap();
682
683        let function = find_function(&db, file_id);
684
685        let scopes = ExprScopes::body_expr_scopes(&db, DefWithBodyId::from(function));
686        let (_, source_map) = Body::with_source_map(&db, function.into());
687
688        let expr_scope = {
689            let expr_ast = name_ref.syntax().ancestors().find_map(ast::Expr::cast).unwrap();
690            let expr_id = source_map
691                .node_expr(InFile { file_id: editioned_file_id.into(), value: &expr_ast })
692                .unwrap()
693                .as_expr()
694                .unwrap();
695            scopes.scope_for(expr_id).unwrap()
696        };
697
698        let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap();
699        let pat_src =
700            source_map.pat_syntax(source_map.patterns_for_binding(resolved.binding())[0]).unwrap();
701
702        let local_name = pat_src.value.syntax_node_ptr().to_node(file.syntax());
703        assert_eq!(local_name.text_range(), expected_name.syntax().text_range());
704    }
705
706    #[test]
707    fn test_resolve_local_name() {
708        do_check_local_name(
709            r#"
710fn foo(x: i32, y: u32) {
711    {
712        let z = x * 2;
713    }
714    {
715        let t = x$0 * 3;
716    }
717}
718"#,
719            7,
720        );
721    }
722
723    #[test]
724    fn test_resolve_local_name_declaration() {
725        do_check_local_name(
726            r#"
727fn foo(x: String) {
728    let x : &str = &x$0;
729}
730"#,
731            7,
732        );
733    }
734
735    #[test]
736    fn test_resolve_local_name_shadow() {
737        do_check_local_name(
738            r"
739fn foo(x: String) {
740    let x : &str = &x;
741    x$0
742}
743",
744            28,
745        );
746    }
747
748    #[test]
749    fn ref_patterns_contribute_bindings() {
750        do_check_local_name(
751            r"
752fn foo() {
753    if let Some(&from) = bar() {
754        from$0;
755    }
756}
757",
758            28,
759        );
760    }
761
762    #[test]
763    fn while_let_adds_binding() {
764        do_check_local_name(
765            r#"
766fn test() {
767    let foo: Option<f32> = None;
768    while let Option::Some(spam) = foo {
769        spam$0
770    }
771}
772"#,
773            75,
774        );
775        do_check_local_name(
776            r#"
777fn test() {
778    let foo: Option<f32> = None;
779    while (((let Option::Some(_) = foo))) && let Option::Some(spam) = foo {
780        spam$0
781    }
782}
783"#,
784            107,
785        );
786    }
787
788    #[test]
789    fn match_guard_if_let() {
790        do_check_local_name(
791            r#"
792fn test() {
793    let foo: Option<f32> = None;
794    match foo {
795        _ if let Option::Some(spam) = foo => spam$0,
796    }
797}
798"#,
799            93,
800        );
801    }
802
803    #[test]
804    fn let_chains_can_reference_previous_lets() {
805        do_check_local_name(
806            r#"
807fn test() {
808    let foo: Option<i32> = None;
809    if let Some(spam) = foo && spa$0m > 1 && let Some(spam) = foo && spam > 1 {}
810}
811"#,
812            61,
813        );
814        do_check_local_name(
815            r#"
816fn test() {
817    let foo: Option<i32> = None;
818    if let Some(spam) = foo && spam > 1 && let Some(spam) = foo && sp$0am > 1 {}
819}
820"#,
821            100,
822        );
823    }
824}