mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 22:54:42 +00:00
[ty] Evaluate reachability of non-definitely-bound to Ambiguous (#19579)
## Summary closes https://github.com/astral-sh/ty/issues/692 If the expression (or any child expressions) is not definitely bound the reachability constraint evaluation is determined as ambiguous. This fixes the infinite cycles panic in the following code: ```py from typing import Literal class Toggle: def __init__(self: "Toggle"): if not self.x: self.x: Literal[True] = True ``` Credit of this solution is for David. ## Test Plan - Added a test case with too many cycle iterations panic. - Previous tests. --------- Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
parent
18eaa659c1
commit
d9aaacd01f
8 changed files with 153 additions and 27 deletions
|
@ -2288,6 +2288,26 @@ class H:
|
||||||
self.x = other.x or self.x
|
self.x = other.x or self.x
|
||||||
```
|
```
|
||||||
|
|
||||||
|
An attribute definition can be guarded by a condition involving that attribute. This is a regression
|
||||||
|
test for <https://github.com/astral-sh/ty/issues/692>:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
def check(x) -> Literal[False]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
class Toggle:
|
||||||
|
def __init__(self: "Toggle"):
|
||||||
|
if not self.x:
|
||||||
|
self.x: Literal[True] = True
|
||||||
|
if check(self.y):
|
||||||
|
self.y = True
|
||||||
|
|
||||||
|
reveal_type(Toggle().x) # revealed: Literal[True]
|
||||||
|
reveal_type(Toggle().y) # revealed: Unknown | Literal[True]
|
||||||
|
```
|
||||||
|
|
||||||
### Builtin types attributes
|
### Builtin types attributes
|
||||||
|
|
||||||
This test can probably be removed eventually, but we currently include it because we do not yet
|
This test can probably be removed eventually, but we currently include it because we do not yet
|
||||||
|
|
|
@ -1564,6 +1564,24 @@ if True:
|
||||||
from module import symbol
|
from module import symbol
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Non-definitely bound symbols in conditions
|
||||||
|
|
||||||
|
When a non-definitely bound symbol is used as a (part of a) condition, we always infer an ambiguous
|
||||||
|
truthiness. If we didn't do that, `x` would be considered definitely bound in the following example:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def _(flag: bool):
|
||||||
|
if flag:
|
||||||
|
ALWAYS_TRUE_IF_BOUND = True
|
||||||
|
|
||||||
|
# error: [possibly-unresolved-reference] "Name `ALWAYS_TRUE_IF_BOUND` used when possibly not defined"
|
||||||
|
if True and ALWAYS_TRUE_IF_BOUND:
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
# error: [possibly-unresolved-reference] "Name `x` used when possibly not defined"
|
||||||
|
x
|
||||||
|
```
|
||||||
|
|
||||||
## Unreachable code
|
## Unreachable code
|
||||||
|
|
||||||
A closely related feature is the ability to detect unreachable code. For example, we do not emit a
|
A closely related feature is the ability to detect unreachable code. For example, we do not emit a
|
||||||
|
|
|
@ -135,6 +135,10 @@ impl<'db> Place<'db> {
|
||||||
Place::Unbound => Place::Unbound,
|
Place::Unbound => Place::Unbound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn is_definitely_bound(&self) -> bool {
|
||||||
|
matches!(self, Place::Type(_, Boundness::Bound))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> From<LookupResult<'db>> for PlaceAndQualifiers<'db> {
|
impl<'db> From<LookupResult<'db>> for PlaceAndQualifiers<'db> {
|
||||||
|
|
|
@ -209,6 +209,7 @@ use crate::semantic_index::predicate::{
|
||||||
};
|
};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
IntersectionBuilder, Truthiness, Type, UnionBuilder, UnionType, infer_expression_type,
|
IntersectionBuilder, Truthiness, Type, UnionBuilder, UnionType, infer_expression_type,
|
||||||
|
static_expression_truthiness,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A ternary formula that defines under what conditions a binding is visible. (A ternary formula
|
/// A ternary formula that defines under what conditions a binding is visible. (A ternary formula
|
||||||
|
@ -821,8 +822,7 @@ impl ReachabilityConstraints {
|
||||||
fn analyze_single(db: &dyn Db, predicate: &Predicate) -> Truthiness {
|
fn analyze_single(db: &dyn Db, predicate: &Predicate) -> Truthiness {
|
||||||
match predicate.node {
|
match predicate.node {
|
||||||
PredicateNode::Expression(test_expr) => {
|
PredicateNode::Expression(test_expr) => {
|
||||||
let ty = infer_expression_type(db, test_expr);
|
static_expression_truthiness(db, test_expr).negate_if(!predicate.is_positive)
|
||||||
ty.bool(db).negate_if(!predicate.is_positive)
|
|
||||||
}
|
}
|
||||||
PredicateNode::ReturnsNever(CallableAndCallExpr {
|
PredicateNode::ReturnsNever(CallableAndCallExpr {
|
||||||
callable,
|
callable,
|
||||||
|
|
|
@ -598,7 +598,7 @@ impl<'db> UseDefMap<'db> {
|
||||||
.is_always_false()
|
.is_always_false()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_declaration_reachable(
|
pub(crate) fn declaration_reachability(
|
||||||
&self,
|
&self,
|
||||||
db: &dyn crate::Db,
|
db: &dyn crate::Db,
|
||||||
declaration: &DeclarationWithConstraint<'db>,
|
declaration: &DeclarationWithConstraint<'db>,
|
||||||
|
@ -610,7 +610,7 @@ impl<'db> UseDefMap<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_binding_reachable(
|
pub(crate) fn binding_reachability(
|
||||||
&self,
|
&self,
|
||||||
db: &dyn crate::Db,
|
db: &dyn crate::Db,
|
||||||
binding: &BindingWithConstraints<'_, 'db>,
|
binding: &BindingWithConstraints<'_, 'db>,
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub use self::diagnostic::TypeCheckDiagnostics;
|
||||||
pub(crate) use self::diagnostic::register_lints;
|
pub(crate) use self::diagnostic::register_lints;
|
||||||
pub(crate) use self::infer::{
|
pub(crate) use self::infer::{
|
||||||
infer_deferred_types, infer_definition_types, infer_expression_type, infer_expression_types,
|
infer_deferred_types, infer_definition_types, infer_expression_type, infer_expression_types,
|
||||||
infer_scope_types,
|
infer_scope_types, static_expression_truthiness,
|
||||||
};
|
};
|
||||||
pub(crate) use self::signatures::{CallableSignature, Signature};
|
pub(crate) use self::signatures::{CallableSignature, Signature};
|
||||||
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
|
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
|
||||||
|
@ -6910,6 +6910,10 @@ bitflags! {
|
||||||
const NOT_REQUIRED = 1 << 4;
|
const NOT_REQUIRED = 1 << 4;
|
||||||
/// `typing_extensions.ReadOnly`
|
/// `typing_extensions.ReadOnly`
|
||||||
const READ_ONLY = 1 << 5;
|
const READ_ONLY = 1 << 5;
|
||||||
|
/// An implicit instance attribute which is possibly unbound according
|
||||||
|
/// to local control flow within the method it is defined in. This flag
|
||||||
|
/// overrules the `Boundness` information on `PlaceAndQualifiers`.
|
||||||
|
const POSSIBLY_UNBOUND_IMPLICIT_ATTRIBUTE = 1 << 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8620,7 +8624,7 @@ impl TypeRelation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, get_size2::GetSize)]
|
||||||
pub enum Truthiness {
|
pub enum Truthiness {
|
||||||
/// For an object `x`, `bool(x)` will always return `True`
|
/// For an object `x`, `bool(x)` will always return `True`
|
||||||
AlwaysTrue,
|
AlwaysTrue,
|
||||||
|
|
|
@ -2736,17 +2736,20 @@ impl<'db> ClassLiteral<'db> {
|
||||||
// self.name: <annotation>
|
// self.name: <annotation>
|
||||||
// self.name: <annotation> = …
|
// self.name: <annotation> = …
|
||||||
|
|
||||||
if use_def_map(db, method_scope)
|
let reachability = use_def_map(db, method_scope)
|
||||||
.is_declaration_reachable(db, &attribute_declaration)
|
.declaration_reachability(db, &attribute_declaration);
|
||||||
.is_always_false()
|
|
||||||
{
|
if reachability.is_always_false() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let annotation = declaration_type(db, declaration);
|
let annotation = declaration_type(db, declaration);
|
||||||
let annotation =
|
let mut annotation =
|
||||||
Place::bound(annotation.inner).with_qualifiers(annotation.qualifiers);
|
Place::bound(annotation.inner).with_qualifiers(annotation.qualifiers);
|
||||||
|
|
||||||
|
if reachability.is_ambiguous() {
|
||||||
|
annotation.qualifiers |= TypeQualifiers::POSSIBLY_UNBOUND_IMPLICIT_ATTRIBUTE;
|
||||||
|
}
|
||||||
if let Some(all_qualifiers) = annotation.is_bare_final() {
|
if let Some(all_qualifiers) = annotation.is_bare_final() {
|
||||||
if let Some(value) = assignment.value(&module) {
|
if let Some(value) = assignment.value(&module) {
|
||||||
// If we see an annotated assignment with a bare `Final` as in
|
// If we see an annotated assignment with a bare `Final` as in
|
||||||
|
@ -2789,7 +2792,7 @@ impl<'db> ClassLiteral<'db> {
|
||||||
.all_reachable_symbol_bindings(method_place)
|
.all_reachable_symbol_bindings(method_place)
|
||||||
.find_map(|bind| {
|
.find_map(|bind| {
|
||||||
(bind.binding.is_defined_and(|def| def == method))
|
(bind.binding.is_defined_and(|def| def == method))
|
||||||
.then(|| class_map.is_binding_reachable(db, &bind))
|
.then(|| class_map.binding_reachability(db, &bind))
|
||||||
})
|
})
|
||||||
.unwrap_or(Truthiness::AlwaysFalse)
|
.unwrap_or(Truthiness::AlwaysFalse)
|
||||||
} else {
|
} else {
|
||||||
|
@ -2818,12 +2821,16 @@ impl<'db> ClassLiteral<'db> {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
match method_map
|
match method_map
|
||||||
.is_binding_reachable(db, &attribute_assignment)
|
.binding_reachability(db, &attribute_assignment)
|
||||||
.and(is_method_reachable)
|
.and(is_method_reachable)
|
||||||
{
|
{
|
||||||
Truthiness::AlwaysTrue | Truthiness::Ambiguous => {
|
Truthiness::AlwaysTrue => {
|
||||||
is_attribute_bound = true;
|
is_attribute_bound = true;
|
||||||
}
|
}
|
||||||
|
Truthiness::Ambiguous => {
|
||||||
|
is_attribute_bound = true;
|
||||||
|
qualifiers |= TypeQualifiers::POSSIBLY_UNBOUND_IMPLICIT_ATTRIBUTE;
|
||||||
|
}
|
||||||
Truthiness::AlwaysFalse => {
|
Truthiness::AlwaysFalse => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2834,7 +2841,7 @@ impl<'db> ClassLiteral<'db> {
|
||||||
// TODO: this is incomplete logic since the attributes bound after termination are considered reachable.
|
// TODO: this is incomplete logic since the attributes bound after termination are considered reachable.
|
||||||
let unbound_reachability = unbound_binding
|
let unbound_reachability = unbound_binding
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| method_map.is_binding_reachable(db, binding))
|
.map(|binding| method_map.binding_reachability(db, binding))
|
||||||
.unwrap_or(Truthiness::AlwaysFalse);
|
.unwrap_or(Truthiness::AlwaysFalse);
|
||||||
|
|
||||||
if unbound_reachability
|
if unbound_reachability
|
||||||
|
|
|
@ -337,6 +337,45 @@ fn single_expression_cycle_initial<'db>(
|
||||||
Type::Never
|
Type::Never
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the statically-known truthiness of a given expression.
|
||||||
|
///
|
||||||
|
/// Returns [`Truthiness::Ambiguous`] in case any non-definitely bound places
|
||||||
|
/// were encountered while inferring the type of the expression.
|
||||||
|
#[salsa::tracked(cycle_fn=static_expression_truthiness_cycle_recover, cycle_initial=static_expression_truthiness_cycle_initial, heap_size=get_size2::GetSize::get_heap_size)]
|
||||||
|
pub(crate) fn static_expression_truthiness<'db>(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
expression: Expression<'db>,
|
||||||
|
) -> Truthiness {
|
||||||
|
let inference = infer_expression_types(db, expression);
|
||||||
|
|
||||||
|
if !inference.all_places_definitely_bound() {
|
||||||
|
return Truthiness::Ambiguous;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = expression.file(db);
|
||||||
|
let module = parsed_module(db, file).load(db);
|
||||||
|
let node = expression.node_ref(db, &module);
|
||||||
|
|
||||||
|
inference.expression_type(node).bool(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
fn static_expression_truthiness_cycle_recover<'db>(
|
||||||
|
_db: &'db dyn Db,
|
||||||
|
_value: &Truthiness,
|
||||||
|
_count: u32,
|
||||||
|
_expression: Expression<'db>,
|
||||||
|
) -> salsa::CycleRecoveryAction<Truthiness> {
|
||||||
|
salsa::CycleRecoveryAction::Iterate
|
||||||
|
}
|
||||||
|
|
||||||
|
fn static_expression_truthiness_cycle_initial<'db>(
|
||||||
|
_db: &'db dyn Db,
|
||||||
|
_expression: Expression<'db>,
|
||||||
|
) -> Truthiness {
|
||||||
|
Truthiness::Ambiguous
|
||||||
|
}
|
||||||
|
|
||||||
/// Infer the types for an [`Unpack`] operation.
|
/// Infer the types for an [`Unpack`] operation.
|
||||||
///
|
///
|
||||||
/// This infers the expression type and performs structural match against the target expression
|
/// This infers the expression type and performs structural match against the target expression
|
||||||
|
@ -657,6 +696,9 @@ struct ExpressionInferenceExtra<'db> {
|
||||||
///
|
///
|
||||||
/// Falls back to `Type::Never` if an expression is missing.
|
/// Falls back to `Type::Never` if an expression is missing.
|
||||||
cycle_fallback: bool,
|
cycle_fallback: bool,
|
||||||
|
|
||||||
|
/// `true` if all places in this expression are definitely bound
|
||||||
|
all_definitely_bound: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> ExpressionInference<'db> {
|
impl<'db> ExpressionInference<'db> {
|
||||||
|
@ -665,6 +707,7 @@ impl<'db> ExpressionInference<'db> {
|
||||||
Self {
|
Self {
|
||||||
extra: Some(Box::new(ExpressionInferenceExtra {
|
extra: Some(Box::new(ExpressionInferenceExtra {
|
||||||
cycle_fallback: true,
|
cycle_fallback: true,
|
||||||
|
all_definitely_bound: true,
|
||||||
..ExpressionInferenceExtra::default()
|
..ExpressionInferenceExtra::default()
|
||||||
})),
|
})),
|
||||||
expressions: FxHashMap::default(),
|
expressions: FxHashMap::default(),
|
||||||
|
@ -698,6 +741,14 @@ impl<'db> ExpressionInference<'db> {
|
||||||
fn fallback_type(&self) -> Option<Type<'db>> {
|
fn fallback_type(&self) -> Option<Type<'db>> {
|
||||||
self.is_cycle_callback().then_some(Type::Never)
|
self.is_cycle_callback().then_some(Type::Never)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if all places in this expression are definitely bound.
|
||||||
|
pub(crate) fn all_places_definitely_bound(&self) -> bool {
|
||||||
|
self.extra
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.all_definitely_bound)
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the intersection type is on the left or right side of the comparison.
|
/// Whether the intersection type is on the left or right side of the comparison.
|
||||||
|
@ -847,6 +898,9 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> {
|
||||||
///
|
///
|
||||||
/// This is used only when constructing a cycle-recovery `TypeInference`.
|
/// This is used only when constructing a cycle-recovery `TypeInference`.
|
||||||
cycle_fallback: bool,
|
cycle_fallback: bool,
|
||||||
|
|
||||||
|
/// `true` if all places in this expression are definitely bound
|
||||||
|
all_definitely_bound: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
@ -880,6 +934,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
deferred: VecSet::default(),
|
deferred: VecSet::default(),
|
||||||
undecorated_type: None,
|
undecorated_type: None,
|
||||||
cycle_fallback: false,
|
cycle_fallback: false,
|
||||||
|
all_definitely_bound: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6614,7 +6669,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let (resolved, constraint_keys) =
|
let (resolved, constraint_keys) =
|
||||||
self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node));
|
self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node));
|
||||||
|
|
||||||
resolved
|
let resolved_after_fallback = resolved
|
||||||
// Not found in the module's explicitly declared global symbols?
|
// Not found in the module's explicitly declared global symbols?
|
||||||
// Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc.
|
// Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc.
|
||||||
// These are looked up as attributes on `types.ModuleType`.
|
// These are looked up as attributes on `types.ModuleType`.
|
||||||
|
@ -6650,8 +6705,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
} else {
|
} else {
|
||||||
Place::Unbound.into()
|
Place::Unbound.into()
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.unwrap_with_diagnostic(|lookup_error| match lookup_error {
|
|
||||||
|
if !resolved_after_fallback.place.is_definitely_bound() {
|
||||||
|
self.all_definitely_bound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ty =
|
||||||
|
resolved_after_fallback.unwrap_with_diagnostic(|lookup_error| match lookup_error {
|
||||||
LookupError::Unbound(qualifiers) => {
|
LookupError::Unbound(qualifiers) => {
|
||||||
self.report_unresolved_reference(name_node);
|
self.report_unresolved_reference(name_node);
|
||||||
TypeAndQualifiers::new(Type::unknown(), qualifiers)
|
TypeAndQualifiers::new(Type::unknown(), qualifiers)
|
||||||
|
@ -6662,8 +6723,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
type_when_bound
|
type_when_bound
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.inner_type()
|
|
||||||
|
ty.inner_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_local_place_load(
|
fn infer_local_place_load(
|
||||||
|
@ -7093,7 +7155,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn narrow_expr_with_applicable_constraints<'r>(
|
fn narrow_expr_with_applicable_constraints<'r>(
|
||||||
&self,
|
&mut self,
|
||||||
target: impl Into<ast::ExprRef<'r>>,
|
target: impl Into<ast::ExprRef<'r>>,
|
||||||
target_ty: Type<'db>,
|
target_ty: Type<'db>,
|
||||||
constraint_keys: &[(FileScopeId, ConstraintKey)],
|
constraint_keys: &[(FileScopeId, ConstraintKey)],
|
||||||
|
@ -7136,11 +7198,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
assigned_type = Some(ty);
|
assigned_type = Some(ty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let fallback_place = value_type.member(db, &attr.id);
|
||||||
|
if !fallback_place.place.is_definitely_bound()
|
||||||
|
|| fallback_place
|
||||||
|
.qualifiers
|
||||||
|
.contains(TypeQualifiers::POSSIBLY_UNBOUND_IMPLICIT_ATTRIBUTE)
|
||||||
|
{
|
||||||
|
self.all_definitely_bound = false;
|
||||||
|
}
|
||||||
|
|
||||||
let resolved_type = value_type
|
let resolved_type =
|
||||||
.member(db, &attr.id)
|
fallback_place.map_type(|ty| {
|
||||||
.map_type(|ty| self.narrow_expr_with_applicable_constraints(attribute, ty, &constraint_keys))
|
self.narrow_expr_with_applicable_constraints(attribute, ty, &constraint_keys)
|
||||||
.unwrap_with_diagnostic(|lookup_error| match lookup_error {
|
}).unwrap_with_diagnostic(|lookup_error| match lookup_error {
|
||||||
LookupError::Unbound(_) => {
|
LookupError::Unbound(_) => {
|
||||||
let report_unresolved_attribute = self.is_reachable(attribute);
|
let report_unresolved_attribute = self.is_reachable(attribute);
|
||||||
|
|
||||||
|
@ -9248,6 +9318,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
declarations,
|
declarations,
|
||||||
deferred,
|
deferred,
|
||||||
cycle_fallback,
|
cycle_fallback,
|
||||||
|
all_definitely_bound,
|
||||||
|
|
||||||
// Ignored; only relevant to definition regions
|
// Ignored; only relevant to definition regions
|
||||||
undecorated_type: _,
|
undecorated_type: _,
|
||||||
|
@ -9274,7 +9345,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
);
|
);
|
||||||
|
|
||||||
let extra =
|
let extra =
|
||||||
(cycle_fallback || !bindings.is_empty() || !diagnostics.is_empty()).then(|| {
|
(cycle_fallback || !bindings.is_empty() || !diagnostics.is_empty() || !all_definitely_bound).then(|| {
|
||||||
if bindings.len() > 20 {
|
if bindings.len() > 20 {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Inferred expression region `{:?}` contains {} bindings. Lookups by linear scan might be slow.",
|
"Inferred expression region `{:?}` contains {} bindings. Lookups by linear scan might be slow.",
|
||||||
|
@ -9287,6 +9358,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
bindings: bindings.into_boxed_slice(),
|
bindings: bindings.into_boxed_slice(),
|
||||||
diagnostics,
|
diagnostics,
|
||||||
cycle_fallback,
|
cycle_fallback,
|
||||||
|
all_definitely_bound,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9312,7 +9384,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
deferred,
|
deferred,
|
||||||
cycle_fallback,
|
cycle_fallback,
|
||||||
undecorated_type,
|
undecorated_type,
|
||||||
|
all_definitely_bound: _,
|
||||||
// builder only state
|
// builder only state
|
||||||
typevar_binding_context: _,
|
typevar_binding_context: _,
|
||||||
deferred_state: _,
|
deferred_state: _,
|
||||||
|
@ -9379,6 +9451,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
deferred: _,
|
deferred: _,
|
||||||
bindings: _,
|
bindings: _,
|
||||||
declarations: _,
|
declarations: _,
|
||||||
|
all_definitely_bound: _,
|
||||||
|
|
||||||
// Ignored; only relevant to definition regions
|
// Ignored; only relevant to definition regions
|
||||||
undecorated_type: _,
|
undecorated_type: _,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue