@@ -245,7 +245,7 @@ pub(crate) fn class_symbol<'db>(
245
245
scope,
246
246
symbol,
247
247
RequiresExplicitReExport :: No ,
248
- ConsideredDefinitions :: AllLiveAtUse ,
248
+ ConsideredDefinitions :: EndOfScope ,
249
249
) ;
250
250
251
251
if symbol_and_quals. is_class_var ( ) {
@@ -262,12 +262,7 @@ pub(crate) fn class_symbol<'db>(
262
262
// Otherwise, we need to check if the symbol has bindings
263
263
let use_def = use_def_map ( db, scope) ;
264
264
let bindings = use_def. end_of_scope_bindings ( symbol) ;
265
- let inferred = place_from_bindings_impl (
266
- db,
267
- bindings,
268
- RequiresExplicitReExport :: No ,
269
- ConsideredDefinitions :: AllLiveAtUse ,
270
- ) ;
265
+ let inferred = place_from_bindings_impl ( db, bindings, RequiresExplicitReExport :: No ) ;
271
266
272
267
// TODO: we should not need to calculate inferred type second time. This is a temporary
273
268
// solution until the notion of Boundness and Declaredness is split. See #16036, #16264
@@ -361,7 +356,7 @@ pub(crate) fn imported_symbol<'db>(
361
356
global_scope ( db, file) ,
362
357
name,
363
358
requires_explicit_reexport,
364
- ConsideredDefinitions :: AllLiveAtUse ,
359
+ ConsideredDefinitions :: EndOfScope ,
365
360
)
366
361
. or_fall_back_to ( db, || {
367
362
if name == "__getattr__" {
@@ -391,7 +386,7 @@ pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> PlaceAndQua
391
386
global_scope ( db, file) ,
392
387
symbol,
393
388
RequiresExplicitReExport :: Yes ,
394
- ConsideredDefinitions :: AllLiveAtUse ,
389
+ ConsideredDefinitions :: EndOfScope ,
395
390
)
396
391
. or_fall_back_to ( db, || {
397
392
// We're looking up in the builtins namespace and not the module, so we should
@@ -462,14 +457,8 @@ fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option<ScopeId<'_
462
457
pub ( super ) fn place_from_bindings < ' db > (
463
458
db : & ' db dyn Db ,
464
459
bindings_with_constraints : BindingWithConstraintsIterator < ' _ , ' db > ,
465
- considered_definitions : ConsideredDefinitions , // TODO: remove this and always use `LiveBindingsAtUse` here?
466
460
) -> Place < ' db > {
467
- place_from_bindings_impl (
468
- db,
469
- bindings_with_constraints,
470
- RequiresExplicitReExport :: No ,
471
- considered_definitions,
472
- )
461
+ place_from_bindings_impl ( db, bindings_with_constraints, RequiresExplicitReExport :: No )
473
462
}
474
463
475
464
/// Build a declared type from a [`DeclarationsIterator`].
@@ -483,14 +472,8 @@ pub(super) fn place_from_bindings<'db>(
483
472
pub ( crate ) fn place_from_declarations < ' db > (
484
473
db : & ' db dyn Db ,
485
474
declarations : DeclarationsIterator < ' _ , ' db > ,
486
- considered_definitions : ConsideredDefinitions ,
487
475
) -> PlaceFromDeclarationsResult < ' db > {
488
- place_from_declarations_impl (
489
- db,
490
- declarations,
491
- RequiresExplicitReExport :: No ,
492
- considered_definitions,
493
- )
476
+ place_from_declarations_impl ( db, declarations, RequiresExplicitReExport :: No )
494
477
}
495
478
496
479
/// The result of looking up a declared type from declarations; see [`place_from_declarations`].
@@ -653,20 +636,15 @@ fn place_by_id<'db>(
653
636
// on inference from bindings.
654
637
655
638
let declarations = match considered_definitions {
656
- ConsideredDefinitions :: AllLiveAtUse => use_def. end_of_scope_declarations ( place_id) ,
657
- ConsideredDefinitions :: AllReachable => use_def. reachable_declarations ( place_id) ,
639
+ ConsideredDefinitions :: EndOfScope => use_def. end_of_scope_declarations ( place_id) ,
640
+ ConsideredDefinitions :: AllReachable => use_def. all_reachable_declarations ( place_id) ,
658
641
} ;
659
642
660
- let declared = place_from_declarations_impl (
661
- db,
662
- declarations,
663
- requires_explicit_reexport,
664
- considered_definitions,
665
- ) ;
643
+ let declared = place_from_declarations_impl ( db, declarations, requires_explicit_reexport) ;
666
644
667
645
let all_considered_bindings = || match considered_definitions {
668
- ConsideredDefinitions :: AllLiveAtUse => use_def. end_of_scope_bindings ( place_id) ,
669
- ConsideredDefinitions :: AllReachable => use_def. reachable_bindings ( place_id) ,
646
+ ConsideredDefinitions :: EndOfScope => use_def. end_of_scope_bindings ( place_id) ,
647
+ ConsideredDefinitions :: AllReachable => use_def. all_reachable_bindings ( place_id) ,
670
648
} ;
671
649
672
650
match declared {
@@ -683,12 +661,7 @@ fn place_by_id<'db>(
683
661
qualifiers,
684
662
} ) => {
685
663
let bindings = all_considered_bindings ( ) ;
686
- let inferred = place_from_bindings_impl (
687
- db,
688
- bindings,
689
- requires_explicit_reexport,
690
- considered_definitions,
691
- ) ;
664
+ let inferred = place_from_bindings_impl ( db, bindings, requires_explicit_reexport) ;
692
665
693
666
let place = match inferred {
694
667
// Place is possibly undeclared and definitely unbound
@@ -713,12 +686,7 @@ fn place_by_id<'db>(
713
686
qualifiers : _,
714
687
} ) => {
715
688
let bindings = all_considered_bindings ( ) ;
716
- let inferred = place_from_bindings_impl (
717
- db,
718
- bindings,
719
- requires_explicit_reexport,
720
- considered_definitions,
721
- ) ;
689
+ let inferred = place_from_bindings_impl ( db, bindings, requires_explicit_reexport) ;
722
690
723
691
// `__slots__` is a symbol with special behavior in Python's runtime. It can be
724
692
// modified externally, but those changes do not take effect. We therefore issue
@@ -842,10 +810,10 @@ fn place_from_bindings_impl<'db>(
842
810
db : & ' db dyn Db ,
843
811
bindings_with_constraints : BindingWithConstraintsIterator < ' _ , ' db > ,
844
812
requires_explicit_reexport : RequiresExplicitReExport ,
845
- considered_definitions : ConsideredDefinitions ,
846
813
) -> Place < ' db > {
847
814
let predicates = bindings_with_constraints. predicates ;
848
815
let reachability_constraints = bindings_with_constraints. reachability_constraints ;
816
+ let considered_definitions = bindings_with_constraints. boundness_analysis ;
849
817
let mut bindings_with_constraints = bindings_with_constraints. peekable ( ) ;
850
818
851
819
let is_non_exported = |binding : Definition < ' db > | {
@@ -865,7 +833,7 @@ fn place_from_bindings_impl<'db>(
865
833
// Evaluate this lazily because we don't always need it (for example, if there are no visible
866
834
// bindings at all, we don't need it), and it can cause us to evaluate reachability constraint
867
835
// expressions, which is extra work and can lead to cycles.
868
- let unbound_reachability = || {
836
+ let unbound_visibility = || {
869
837
unbound_reachability_constraint. map ( |reachability_constraint| {
870
838
reachability_constraints. evaluate ( db, predicates, reachability_constraint)
871
839
} )
@@ -945,7 +913,7 @@ fn place_from_bindings_impl<'db>(
945
913
// return `Never` in this case, because we will union the types of all bindings, and
946
914
// `Never` will be eliminated automatically.
947
915
948
- if unbound_reachability ( ) . is_none_or ( Truthiness :: is_always_false) {
916
+ if unbound_visibility ( ) . is_none_or ( Truthiness :: is_always_false) {
949
917
return Some ( Type :: Never ) ;
950
918
}
951
919
return None ;
@@ -958,23 +926,16 @@ fn place_from_bindings_impl<'db>(
958
926
959
927
if let Some ( first) = types. next ( ) {
960
928
let boundness = match considered_definitions {
961
- ConsideredDefinitions :: AllReachable => {
962
- // TODO: explain why we do this
963
- Boundness :: Bound
964
- }
965
- ConsideredDefinitions :: AllLiveAtUse => {
966
- // If we have at least one binding, the implicit `unbound` binding should not be
967
- // definitely visible.
968
- match unbound_reachability ( ) {
969
- Some ( Truthiness :: AlwaysTrue ) => {
970
- unreachable ! (
971
- "If we have at least one binding, the implicit `unbound` binding should not be definitely visible"
972
- )
973
- }
974
- Some ( Truthiness :: AlwaysFalse ) | None => Boundness :: Bound ,
975
- Some ( Truthiness :: Ambiguous ) => Boundness :: PossiblyUnbound ,
929
+ BoundnessAnalysis :: AlwaysBound => Boundness :: Bound ,
930
+ BoundnessAnalysis :: BasedOnUnboundVisibility => match unbound_visibility ( ) {
931
+ Some ( Truthiness :: AlwaysTrue ) => {
932
+ unreachable ! (
933
+ "If we have at least one binding, the implicit `unbound` binding should not be definitely visible"
934
+ )
976
935
}
977
- }
936
+ Some ( Truthiness :: AlwaysFalse ) | None => Boundness :: Bound ,
937
+ Some ( Truthiness :: Ambiguous ) => Boundness :: PossiblyUnbound ,
938
+ } ,
978
939
} ;
979
940
980
941
let ty = if let Some ( second) = types. next ( ) {
@@ -1079,10 +1040,10 @@ fn place_from_declarations_impl<'db>(
1079
1040
db : & ' db dyn Db ,
1080
1041
declarations : DeclarationsIterator < ' _ , ' db > ,
1081
1042
requires_explicit_reexport : RequiresExplicitReExport ,
1082
- considered_definitions : ConsideredDefinitions ,
1083
1043
) -> PlaceFromDeclarationsResult < ' db > {
1084
1044
let predicates = declarations. predicates ;
1085
1045
let reachability_constraints = declarations. reachability_constraints ;
1046
+ let boundness_analysis = declarations. boundness_analysis ;
1086
1047
let mut declarations = declarations. peekable ( ) ;
1087
1048
1088
1049
let is_non_exported = |declaration : Definition < ' db > | {
@@ -1136,9 +1097,9 @@ fn place_from_declarations_impl<'db>(
1136
1097
return Err ( ( declared, conflicting) ) ;
1137
1098
}
1138
1099
1139
- let boundness = match considered_definitions {
1140
- ConsideredDefinitions :: AllReachable => Boundness :: Bound ,
1141
- ConsideredDefinitions :: AllLiveAtUse => match undeclared_reachability {
1100
+ let boundness = match boundness_analysis {
1101
+ BoundnessAnalysis :: AlwaysBound => Boundness :: Bound ,
1102
+ BoundnessAnalysis :: BasedOnUnboundVisibility => match undeclared_reachability {
1142
1103
Truthiness :: AlwaysTrue => {
1143
1104
unreachable ! (
1144
1105
"If we have at least one declaration, the implicit `unbound` binding should not be definitely visible"
@@ -1180,7 +1141,7 @@ mod implicit_globals {
1180
1141
use ruff_python_ast as ast;
1181
1142
1182
1143
use crate :: db:: Db ;
1183
- use crate :: place:: { ConsideredDefinitions , PlaceAndQualifiers } ;
1144
+ use crate :: place:: PlaceAndQualifiers ;
1184
1145
use crate :: semantic_index:: place:: PlaceExpr ;
1185
1146
use crate :: semantic_index:: { self , place_table, use_def_map} ;
1186
1147
use crate :: types:: { KnownClass , Type } ;
@@ -1209,7 +1170,6 @@ mod implicit_globals {
1209
1170
place_from_declarations (
1210
1171
db,
1211
1172
use_def_map ( db, module_type_scope) . end_of_scope_declarations ( place_id) ,
1212
- ConsideredDefinitions :: AllLiveAtUse ,
1213
1173
)
1214
1174
}
1215
1175
@@ -1329,10 +1289,46 @@ impl RequiresExplicitReExport {
1329
1289
}
1330
1290
}
1331
1291
1292
+ /// Specifies which definitions should be considered when looking up a place.
1293
+ ///
1294
+ /// In the example below, the `EndOfScope` variant would consider the `x = 2` and `x = 3` definitions,
1295
+ /// while the `AllReachable` variant would also consider the `x = 1` definition.
1296
+ /// ```py
1297
+ /// def _():
1298
+ /// x = 1
1299
+ ///
1300
+ /// x = 2
1301
+ ///
1302
+ /// if flag():
1303
+ /// x = 3
1304
+ /// ```
1332
1305
#[ derive( Copy , Clone , Debug , PartialEq , Eq , Hash , salsa:: Update ) ]
1333
1306
pub ( crate ) enum ConsideredDefinitions {
1307
+ /// Consider only the definitions that are "live" at the end of the scope, i.e. those
1308
+ /// that have not been shadowed or deleted.
1309
+ EndOfScope ,
1310
+ /// Consider all definitions that are reachable from the start of the scope.
1334
1311
AllReachable ,
1335
- AllLiveAtUse ,
1312
+ }
1313
+
1314
+ /// Specifies how the boundness of a place should be determined.
1315
+ #[ derive( Copy , Clone , Debug , PartialEq , Eq , Hash , salsa:: Update ) ]
1316
+ pub ( crate ) enum BoundnessAnalysis {
1317
+ /// The place is always considered bound.
1318
+ AlwaysBound ,
1319
+ /// The boundness of the place is determined based on the visibility of the implicit
1320
+ /// `unbound` binding. In the example below, when analyzing the visibility of the
1321
+ /// `x = <unbound>` binding from the position of the end of the scope, it would be
1322
+ /// `Truthiness::Ambiguous`, because it could either be visible or not, depending on the
1323
+ /// `flag()` return value. This would result in a `Boundness::PossiblyUnbound` for `x`.
1324
+ ///
1325
+ /// ```py
1326
+ /// x = <unbound>
1327
+ ///
1328
+ /// if flag():
1329
+ /// x = 1
1330
+ /// ```
1331
+ BasedOnUnboundVisibility ,
1336
1332
}
1337
1333
1338
1334
/// Computes a possibly-widened type `Unknown | T_inferred` from the inferred type `T_inferred`
0 commit comments