1use 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 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 #[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 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 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 this.const_scope
307 };
308 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}