@@ -480,11 +480,83 @@ pub struct LocalDecl<'tcx> {
480480 /// Source info of the local.
481481 pub source_info : SourceInfo ,
482482
483- /// The *lexical * visibility scope the local is defined
483+ /// The *syntactic * visibility scope the local is defined
484484 /// in. If the local was defined in a let-statement, this
485485 /// is *within* the let-statement, rather than outside
486486 /// of it.
487- pub lexical_scope : VisibilityScope ,
487+ ///
488+ /// This is needed because visibility scope of locals within a let-statement
489+ /// is weird.
490+ ///
491+ /// The reason is that we want the local to be *within* the let-statement
492+ /// for lint purposes, but we want the local to be *after* the let-statement
493+ /// for names-in-scope purposes.
494+ ///
495+ /// That's it, if we have a let-statement like the one in this
496+ /// function:
497+ /// ```
498+ /// fn foo(x: &str) {
499+ /// #[allow(unused_mut)]
500+ /// let mut x: u32 = { // <- one unused mut
501+ /// let mut y: u32 = x.parse().unwrap();
502+ /// y + 2
503+ /// };
504+ /// drop(x);
505+ /// }
506+ /// ```
507+ ///
508+ /// Then, from a lint point of view, the declaration of `x: u32`
509+ /// (and `y: u32`) are within the `#[allow(unused_mut)]` scope - the
510+ /// lint scopes are the same as the AST/HIR nesting.
511+ ///
512+ /// However, from a name lookup point of view, the scopes look more like
513+ /// as if the let-statements were `match` expressions:
514+ ///
515+ /// ```
516+ /// fn foo(x: &str) {
517+ /// match {
518+ /// match x.parse().unwrap() {
519+ /// y => y + 2
520+ /// }
521+ /// } {
522+ /// x => drop(x)
523+ /// };
524+ /// }
525+ /// ```
526+ ///
527+ /// We care about the name-lookup scopes for debuginfo - if the
528+ /// debuginfo instruction pointer is at the call to `x.parse()`, we
529+ /// want `x` to refer to `x: &str`, but if it is at the call to
530+ /// `drop(x)`, we want it to refer to `x: u32`.
531+ ///
532+ /// To allow both uses to work, we need to have more than a single scope
533+ /// for a local. We have the `syntactic_scope` represent the
534+ /// "syntactic" lint scope (with a variable being under its let
535+ /// block) while the source-info scope represents the "local variable"
536+ /// scope (where the "rest" of a block is under all prior let-statements).
537+ ///
538+ /// The end result looks like this:
539+ ///
540+ /// ROOT SCOPE
541+ /// │{ argument x: &str }
542+ /// │
543+ /// │ │{ #[allow(unused_mut] } // this is actually split into 2 scopes
544+ /// │ │ // in practice because I'm lazy.
545+ /// │ │
546+ /// │ │← x.syntactic_scope
547+ /// │ │← `x.parse().unwrap()`
548+ /// │ │
549+ /// │ │ │← y.syntactic_scope
550+ /// │ │
551+ /// │ │ │{ let y: u32 }
552+ /// │ │ │
553+ /// │ │ │← y.source_info.scope
554+ /// │ │ │← `y + 2`
555+ /// │
556+ /// │ │{ let x: u32 }
557+ /// │ │← x.source_info.scope
558+ /// │ │← `drop(x)` // this accesses `x: u32`
559+ pub syntactic_scope : VisibilityScope ,
488560}
489561
490562impl < ' tcx > LocalDecl < ' tcx > {
@@ -499,7 +571,7 @@ impl<'tcx> LocalDecl<'tcx> {
499571 span,
500572 scope : ARGUMENT_VISIBILITY_SCOPE
501573 } ,
502- lexical_scope : ARGUMENT_VISIBILITY_SCOPE ,
574+ syntactic_scope : ARGUMENT_VISIBILITY_SCOPE ,
503575 internal : false ,
504576 is_user_variable : false
505577 }
@@ -516,7 +588,7 @@ impl<'tcx> LocalDecl<'tcx> {
516588 span,
517589 scope : ARGUMENT_VISIBILITY_SCOPE
518590 } ,
519- lexical_scope : ARGUMENT_VISIBILITY_SCOPE ,
591+ syntactic_scope : ARGUMENT_VISIBILITY_SCOPE ,
520592 internal : true ,
521593 is_user_variable : false
522594 }
@@ -534,7 +606,7 @@ impl<'tcx> LocalDecl<'tcx> {
534606 span,
535607 scope : ARGUMENT_VISIBILITY_SCOPE
536608 } ,
537- lexical_scope : ARGUMENT_VISIBILITY_SCOPE ,
609+ syntactic_scope : ARGUMENT_VISIBILITY_SCOPE ,
538610 internal : false ,
539611 name : None , // FIXME maybe we do want some name here?
540612 is_user_variable : false
0 commit comments