@@ -297,6 +297,7 @@ pub(crate) struct UseDefMap<'db> {
297
297
/// [`SymbolBindings`] reaching a [`ScopedUseId`].
298
298
bindings_by_use : IndexVec < ScopedUseId , SymbolBindings > ,
299
299
300
+ /// Tracks whether or not a given use of a symbol is reachable from the start of the scope.
300
301
reachability_by_use : IndexVec < ScopedUseId , ScopedVisibilityConstraintId > ,
301
302
302
303
/// If the definition is a binding (only) -- `x = 1` for example -- then we need
@@ -347,6 +348,17 @@ impl<'db> UseDefMap<'db> {
347
348
self . bindings_iterator ( & self . bindings_by_use [ use_id] )
348
349
}
349
350
351
+ /// Returns true if a given 'use' of a symbol is reachable from the start of the scope.
352
+ /// For example, in the following code, use `2` is reachable, but `1` and `3` are not:
353
+ /// ```py
354
+ /// def f():
355
+ /// x = 1
356
+ /// if False:
357
+ /// x # 1
358
+ /// x # 2
359
+ /// return
360
+ /// x # 3
361
+ /// ```
350
362
pub ( crate ) fn is_symbol_use_reachable ( & self , db : & dyn crate :: Db , use_id : ScopedUseId ) -> bool {
351
363
!self
352
364
. visibility_constraints
@@ -560,17 +572,55 @@ pub(super) struct UseDefMapBuilder<'db> {
560
572
pub ( super ) visibility_constraints : VisibilityConstraintsBuilder ,
561
573
562
574
/// A constraint which describes the visibility of the unbound/undeclared state, i.e.
563
- /// whether or not the start of the scope is visible. This is important for cases like
564
- /// `if True: x = 1; use(x)` where we need to hide the implicit "x = unbound" binding
565
- /// in the "else" branch.
575
+ /// whether or not a use of a symbol at the current point in control flow would see
576
+ /// the fake "x = <unbound>" binding at the start of the scope. This is important for
577
+ /// cases like the following, where we need to hide the implicit unbound binding in
578
+ /// the "else" branch:
579
+ /// ```py
580
+ /// # x = <unbound>
581
+ ///
582
+ /// if True:
583
+ /// x = 1
584
+ ///
585
+ /// use(x) # the `x = <unbound>` binding is not visible here
586
+ /// ```
566
587
pub ( super ) scope_start_visibility : ScopedVisibilityConstraintId ,
567
588
568
589
/// Live bindings at each so-far-recorded use.
569
590
bindings_by_use : IndexVec < ScopedUseId , SymbolBindings > ,
570
591
571
- /// Whether or not the scope start is visible for a given use of a symbol.
592
+ /// Tracks whether or not the scope start is visible at the current point in control flow.
593
+ /// This is subtly different from `scope_start_visibility`, as we apply these constraints
594
+ /// at the beginnging of a branch. Visibility constraints, on the other hand, need to be
595
+ /// applied at the end of a branch, as we apply them retroactively to all live bindings:
596
+ /// ```py
597
+ /// y = 1
598
+ ///
599
+ /// if test:
600
+ /// # we record a reachability constraint of [test] here,
601
+ /// # so that it can affect the use of `x`:
602
+ ///
603
+ /// x # we store a reachability constraint of [test] for this use of `x`
604
+ ///
605
+ /// y = 2
606
+ ///
607
+ /// # we record a visibility constraint of [test] here, which retroactively affects
608
+ /// # the `y = 1` and the `y = 2` binding.
609
+ /// else:
610
+ /// # we record a reachability constraint of [~test] here.
611
+ ///
612
+ /// pass
613
+ ///
614
+ /// # we record a visibility constraint of [~test] here, which retroactively affects
615
+ /// # the `y = 1` binding.
616
+ ///
617
+ /// use(y)
618
+ /// ```
619
+ /// Depending on the value of `test`, the `y = 1`, `y = 2`, or both bindings may be visible.
620
+ /// The use of `x` is recorded with a reachability constraint of `[test]`.
572
621
reachability : ScopedVisibilityConstraintId ,
573
622
623
+ /// Tracks whether or not a given use of a symbol is reachable from the start of the scope.
574
624
reachability_by_use : IndexVec < ScopedUseId , ScopedVisibilityConstraintId > ,
575
625
576
626
/// Live declarations for each so-far-recorded binding.
0 commit comments