@@ -337,6 +337,45 @@ fn single_expression_cycle_initial<'db>(
337
337
Type::Never
338
338
}
339
339
340
+ /// Returns the statically-known truthiness of a given expression.
341
+ ///
342
+ /// Returns [`Truthiness::Ambiguous`] in case any non-definitely bound places
343
+ /// were encountered while inferring the type of the expression.
344
+ #[salsa::tracked(cycle_fn=static_expression_truthiness_cycle_recover, cycle_initial=static_expression_truthiness_cycle_initial, heap_size=get_size2::GetSize::get_heap_size)]
345
+ pub(crate) fn static_expression_truthiness<'db>(
346
+ db: &'db dyn Db,
347
+ expression: Expression<'db>,
348
+ ) -> Truthiness {
349
+ let inference = infer_expression_types(db, expression);
350
+
351
+ if !inference.all_places_definitely_bound() {
352
+ return Truthiness::Ambiguous;
353
+ }
354
+
355
+ let file = expression.file(db);
356
+ let module = parsed_module(db, file).load(db);
357
+ let node = expression.node_ref(db, &module);
358
+
359
+ inference.expression_type(node).bool(db)
360
+ }
361
+
362
+ #[expect(clippy::trivially_copy_pass_by_ref)]
363
+ fn static_expression_truthiness_cycle_recover<'db>(
364
+ _db: &'db dyn Db,
365
+ _value: &Truthiness,
366
+ _count: u32,
367
+ _expression: Expression<'db>,
368
+ ) -> salsa::CycleRecoveryAction<Truthiness> {
369
+ salsa::CycleRecoveryAction::Iterate
370
+ }
371
+
372
+ fn static_expression_truthiness_cycle_initial<'db>(
373
+ _db: &'db dyn Db,
374
+ _expression: Expression<'db>,
375
+ ) -> Truthiness {
376
+ Truthiness::Ambiguous
377
+ }
378
+
340
379
/// Infer the types for an [`Unpack`] operation.
341
380
///
342
381
/// This infers the expression type and performs structural match against the target expression
@@ -657,6 +696,9 @@ struct ExpressionInferenceExtra<'db> {
657
696
///
658
697
/// Falls back to `Type::Never` if an expression is missing.
659
698
cycle_fallback: bool,
699
+
700
+ /// `true` if all places in this expression are definitely bound
701
+ all_definitely_bound: bool,
660
702
}
661
703
662
704
impl<'db> ExpressionInference<'db> {
@@ -665,6 +707,7 @@ impl<'db> ExpressionInference<'db> {
665
707
Self {
666
708
extra: Some(Box::new(ExpressionInferenceExtra {
667
709
cycle_fallback: true,
710
+ all_definitely_bound: true,
668
711
..ExpressionInferenceExtra::default()
669
712
})),
670
713
expressions: FxHashMap::default(),
@@ -698,6 +741,14 @@ impl<'db> ExpressionInference<'db> {
698
741
fn fallback_type(&self) -> Option<Type<'db>> {
699
742
self.is_cycle_callback().then_some(Type::Never)
700
743
}
744
+
745
+ /// Returns true if all places in this expression are definitely bound.
746
+ pub(crate) fn all_places_definitely_bound(&self) -> bool {
747
+ self.extra
748
+ .as_ref()
749
+ .map(|e| e.all_definitely_bound)
750
+ .unwrap_or(true)
751
+ }
701
752
}
702
753
703
754
/// Whether the intersection type is on the left or right side of the comparison.
@@ -847,6 +898,9 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> {
847
898
///
848
899
/// This is used only when constructing a cycle-recovery `TypeInference`.
849
900
cycle_fallback: bool,
901
+
902
+ /// `true` if all places in this expression are definitely bound
903
+ all_definitely_bound: bool,
850
904
}
851
905
852
906
impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
@@ -880,6 +934,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
880
934
deferred: VecSet::default(),
881
935
undecorated_type: None,
882
936
cycle_fallback: false,
937
+ all_definitely_bound: true,
883
938
}
884
939
}
885
940
@@ -6614,7 +6669,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
6614
6669
let (resolved, constraint_keys) =
6615
6670
self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node));
6616
6671
6617
- resolved
6672
+ let resolved_after_fallback = resolved
6618
6673
// Not found in the module's explicitly declared global symbols?
6619
6674
// Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc.
6620
6675
// These are looked up as attributes on `types.ModuleType`.
@@ -6650,8 +6705,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
6650
6705
} else {
6651
6706
Place::Unbound.into()
6652
6707
}
6653
- } )
6654
- . unwrap_with_diagnostic ( |lookup_error| match lookup_error {
6708
+ });
6709
+
6710
+ if !resolved_after_fallback.place.is_definitely_bound() {
6711
+ self.all_definitely_bound = false;
6712
+ }
6713
+
6714
+ let ty =
6715
+ resolved_after_fallback.unwrap_with_diagnostic(|lookup_error| match lookup_error {
6655
6716
LookupError::Unbound(qualifiers) => {
6656
6717
self.report_unresolved_reference(name_node);
6657
6718
TypeAndQualifiers::new(Type::unknown(), qualifiers)
@@ -6662,8 +6723,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
6662
6723
}
6663
6724
type_when_bound
6664
6725
}
6665
- } )
6666
- . inner_type ( )
6726
+ });
6727
+
6728
+ ty.inner_type()
6667
6729
}
6668
6730
6669
6731
fn infer_local_place_load(
@@ -7093,7 +7155,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
7093
7155
}
7094
7156
7095
7157
fn narrow_expr_with_applicable_constraints<'r>(
7096
- & self ,
7158
+ &mut self,
7097
7159
target: impl Into<ast::ExprRef<'r>>,
7098
7160
target_ty: Type<'db>,
7099
7161
constraint_keys: &[(FileScopeId, ConstraintKey)],
@@ -7136,11 +7198,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
7136
7198
assigned_type = Some(ty);
7137
7199
}
7138
7200
}
7201
+ let fallback_place = value_type.member(db, &attr.id);
7202
+ if !fallback_place.place.is_definitely_bound()
7203
+ || fallback_place
7204
+ .qualifiers
7205
+ .contains(TypeQualifiers::POSSIBLY_UNBOUND_IMPLICIT_ATTRIBUTE)
7206
+ {
7207
+ self.all_definitely_bound = false;
7208
+ }
7139
7209
7140
- let resolved_type = value_type
7141
- . member ( db , & attr . id )
7142
- . map_type ( |ty| self . narrow_expr_with_applicable_constraints ( attribute, ty, & constraint_keys) )
7143
- . unwrap_with_diagnostic ( |lookup_error| match lookup_error {
7210
+ let resolved_type =
7211
+ fallback_place.map_type(|ty| {
7212
+ self.narrow_expr_with_applicable_constraints(attribute, ty, &constraint_keys)
7213
+ }) .unwrap_with_diagnostic(|lookup_error| match lookup_error {
7144
7214
LookupError::Unbound(_) => {
7145
7215
let report_unresolved_attribute = self.is_reachable(attribute);
7146
7216
@@ -9248,6 +9318,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
9248
9318
declarations,
9249
9319
deferred,
9250
9320
cycle_fallback,
9321
+ all_definitely_bound,
9251
9322
9252
9323
// Ignored; only relevant to definition regions
9253
9324
undecorated_type: _,
@@ -9274,7 +9345,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
9274
9345
);
9275
9346
9276
9347
let extra =
9277
- ( cycle_fallback || !bindings. is_empty ( ) || !diagnostics. is_empty ( ) ) . then ( || {
9348
+ (cycle_fallback || !bindings.is_empty() || !diagnostics.is_empty() || !all_definitely_bound ).then(|| {
9278
9349
if bindings.len() > 20 {
9279
9350
tracing::debug!(
9280
9351
"Inferred expression region `{:?}` contains {} bindings. Lookups by linear scan might be slow.",
@@ -9287,6 +9358,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
9287
9358
bindings: bindings.into_boxed_slice(),
9288
9359
diagnostics,
9289
9360
cycle_fallback,
9361
+ all_definitely_bound,
9290
9362
})
9291
9363
});
9292
9364
@@ -9312,7 +9384,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
9312
9384
deferred,
9313
9385
cycle_fallback,
9314
9386
undecorated_type,
9315
-
9387
+ all_definitely_bound: _,
9316
9388
// builder only state
9317
9389
typevar_binding_context: _,
9318
9390
deferred_state: _,
@@ -9379,6 +9451,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
9379
9451
deferred: _,
9380
9452
bindings: _,
9381
9453
declarations: _,
9454
+ all_definitely_bound: _,
9382
9455
9383
9456
// Ignored; only relevant to definition regions
9384
9457
undecorated_type: _,
0 commit comments