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