Compare commits

..

3 Commits

Author SHA1 Message Date
David Peter
fee980a1ba Make infer_local_place_load a query 2025-09-02 09:21:56 +02:00
David Peter
f25b3e2254 Remove projects 2025-09-01 17:38:24 +02:00
David Peter
c6b661f374 [ty] Experiment: make infer_expression_types/infer_definition_types non-queries 2025-09-01 16:47:53 +02:00
10 changed files with 383 additions and 372 deletions

View File

@@ -19,12 +19,6 @@ class Identity:
reveal_type(Identity[0]) # revealed: str
```
`__class_getitem__` is implicitly a classmethod, so it can be called like this:
```py
reveal_type(Identity.__class_getitem__(0)) # revealed: str
```
## Class getitem union
```py

View File

@@ -182,7 +182,7 @@ impl<'db> DunderAllNamesCollector<'db> {
///
/// This function panics if `expr` was not marked as a standalone expression during semantic indexing.
fn standalone_expression_type(&self, expr: &ast::Expr) -> Type<'db> {
infer_expression_types(self.db, self.index.expression(expr), false).expression_type(expr)
infer_expression_types(self.db, self.index.expression(expr)).expression_type(expr)
}
/// Evaluate the given expression and return its truthiness.

View File

@@ -42,7 +42,7 @@ fn ast_ids<'db>(db: &'db dyn Db, scope: ScopeId) -> &'db AstIds {
/// Uniquely identifies a use of a name in a [`crate::semantic_index::FileScopeId`].
#[newtype_index]
#[derive(get_size2::GetSize)]
#[derive(get_size2::GetSize, salsa::Update)]
pub struct ScopedUseId;
pub trait HasScopedUseId {

View File

@@ -328,10 +328,10 @@ fn singleton_to_type(db: &dyn Db, singleton: ruff_python_ast::Singleton) -> Type
fn pattern_kind_to_type<'db>(db: &'db dyn Db, kind: &PatternPredicateKind<'db>) -> Type<'db> {
match kind {
PatternPredicateKind::Singleton(singleton) => singleton_to_type(db, *singleton),
PatternPredicateKind::Value(value) => infer_expression_type(db, *value, false),
PatternPredicateKind::Value(value) => infer_expression_type(db, *value),
PatternPredicateKind::Class(class_expr, kind) => {
if kind.is_irrefutable() {
infer_expression_type(db, *class_expr, false)
infer_expression_type(db, *class_expr)
.to_instance(db)
.unwrap_or(Type::Never)
} else {
@@ -718,7 +718,7 @@ impl ReachabilityConstraints {
) -> Truthiness {
match predicate_kind {
PatternPredicateKind::Value(value) => {
let value_ty = infer_expression_type(db, *value, false);
let value_ty = infer_expression_type(db, *value);
if subject_ty.is_single_valued(db) {
Truthiness::from(subject_ty.is_equivalent_to(db, value_ty))
@@ -769,7 +769,7 @@ impl ReachabilityConstraints {
truthiness
}
PatternPredicateKind::Class(class_expr, kind) => {
let class_ty = infer_expression_type(db, *class_expr, false).to_instance(db);
let class_ty = infer_expression_type(db, *class_expr).to_instance(db);
class_ty.map_or(Truthiness::Ambiguous, |class_ty| {
if subject_ty.is_subtype_of(db, class_ty) {
@@ -797,7 +797,7 @@ impl ReachabilityConstraints {
}
fn analyze_single_pattern_predicate(db: &dyn Db, predicate: PatternPredicate) -> Truthiness {
let subject_ty = infer_expression_type(db, predicate.subject(db), false);
let subject_ty = infer_expression_type(db, predicate.subject(db));
let narrowed_subject_ty = IntersectionBuilder::new(db)
.add_positive(subject_ty)
@@ -837,7 +837,7 @@ impl ReachabilityConstraints {
// selection algorithm).
// Avoiding this on the happy-path is important because these constraints can be
// very large in number, since we add them on all statement level function calls.
let ty = infer_expression_type(db, callable, false);
let ty = infer_expression_type(db, callable);
// Short-circuit for well known types that are known not to return `Never` when called.
// Without the short-circuit, we've seen that threads keep blocking each other
@@ -875,7 +875,7 @@ impl ReachabilityConstraints {
} else if all_overloads_return_never {
Truthiness::AlwaysTrue
} else {
let call_expr_ty = infer_expression_type(db, call_expr, false);
let call_expr_ty = infer_expression_type(db, call_expr);
if call_expr_ty.is_equivalent_to(db, Type::Never) {
Truthiness::AlwaysTrue
} else {

View File

@@ -3337,12 +3337,7 @@ impl<'db> Type<'db> {
name: Name,
policy: MemberLookupPolicy,
) -> PlaceAndQualifiers<'db> {
let _span = tracing::trace_span!(
"member_lookup_with_policy",
ty = self.display(db).to_string(),
?name
)
.entered();
let _span = tracing::trace_span!("member_lookup_with_policy: {}", ?name).entered();
if name == "__class__" {
return Place::bound(self.dunder_class(db)).into();
}
@@ -10428,7 +10423,11 @@ static_assertions::assert_eq_size!(Type, [u8; 16]);
pub(crate) mod tests {
use super::*;
use crate::db::tests::{TestDbBuilder, setup_db};
use crate::place::{typing_extensions_symbol, typing_symbol};
use crate::place::{global_symbol, typing_extensions_symbol, typing_symbol};
use ruff_db::files::system_path_to_file;
use ruff_db::parsed::parsed_module;
use ruff_db::system::DbWithWritableSystem as _;
use ruff_db::testing::assert_function_query_was_not_run;
use ruff_python_ast::PythonVersion;
use test_case::test_case;
@@ -10469,62 +10468,62 @@ pub(crate) mod tests {
/// Inferring the result of a call-expression shouldn't need to re-run after
/// a trivial change to the function's file (e.g. by adding a docstring to the function).
// #[test]
// fn call_type_doesnt_rerun_when_only_callee_changed() -> anyhow::Result<()> {
// let mut db = setup_db();
#[test]
fn call_type_doesnt_rerun_when_only_callee_changed() -> anyhow::Result<()> {
let mut db = setup_db();
// db.write_dedented(
// "src/foo.py",
// r#"
// def foo() -> int:
// return 5
// "#,
// )?;
// db.write_dedented(
// "src/bar.py",
// r#"
// from foo import foo
db.write_dedented(
"src/foo.py",
r#"
def foo() -> int:
return 5
"#,
)?;
db.write_dedented(
"src/bar.py",
r#"
from foo import foo
// a = foo()
// "#,
// )?;
a = foo()
"#,
)?;
// let bar = system_path_to_file(&db, "src/bar.py")?;
// let a = global_symbol(&db, bar, "a").place;
let bar = system_path_to_file(&db, "src/bar.py")?;
let a = global_symbol(&db, bar, "a").place;
// assert_eq!(
// a.expect_type(),
// UnionType::from_elements(&db, [Type::unknown(), KnownClass::Int.to_instance(&db)])
// );
assert_eq!(
a.expect_type(),
UnionType::from_elements(&db, [Type::unknown(), KnownClass::Int.to_instance(&db)])
);
// // Add a docstring to foo to trigger a re-run.
// // The bar-call site of foo should not be re-run because of that
// db.write_dedented(
// "src/foo.py",
// r#"
// def foo() -> int:
// "Computes a value"
// return 5
// "#,
// )?;
// db.clear_salsa_events();
// Add a docstring to foo to trigger a re-run.
// The bar-call site of foo should not be re-run because of that
db.write_dedented(
"src/foo.py",
r#"
def foo() -> int:
"Computes a value"
return 5
"#,
)?;
db.clear_salsa_events();
// let a = global_symbol(&db, bar, "a").place;
let a = global_symbol(&db, bar, "a").place;
// assert_eq!(
// a.expect_type(),
// UnionType::from_elements(&db, [Type::unknown(), KnownClass::Int.to_instance(&db)])
// );
// let events = db.take_salsa_events();
assert_eq!(
a.expect_type(),
UnionType::from_elements(&db, [Type::unknown(), KnownClass::Int.to_instance(&db)])
);
let events = db.take_salsa_events();
// let module = parsed_module(&db, bar).load(&db);
// let call = &*module.syntax().body[1].as_assign_stmt().unwrap().value;
// let foo_call = semantic_index(&db, bar).expression(call);
let module = parsed_module(&db, bar).load(&db);
let call = &*module.syntax().body[1].as_assign_stmt().unwrap().value;
let foo_call = semantic_index(&db, bar).expression(call);
// assert_function_query_was_not_run(&db, infer_expression_types, foo_call, &events, false);
assert_function_query_was_not_run(&db, infer_expression_types, foo_call, &events);
// Ok(())
// }
Ok(())
}
/// All other tests also make sure that `Type::Todo` works as expected. This particular
/// test makes sure that we handle `Todo` types correctly, even if they originate from

View File

@@ -2816,6 +2816,8 @@ impl<'db> ClassLiteral<'db> {
name: String,
target_method_decorator: MethodDecorator,
) -> PlaceAndQualifiers<'db> {
let _span =
tracing::trace_span!("implicit_attribute_inner", ?class_body_scope, ?name).entered();
// If we do not see any declarations of an attribute, neither in the class body nor in
// any method, we build a union of `Unknown` with the inferred types of all bindings of
// that attribute. We include `Unknown` in that union to account for the fact that the
@@ -2882,7 +2884,7 @@ impl<'db> ClassLiteral<'db> {
// `self.SOME_CONSTANT: Final = 1`, infer the type from the value
// on the right-hand side.
let inferred_ty = infer_expression_type(db, index.expression(value), true);
let inferred_ty = infer_expression_type(db, index.expression(value));
return Place::bound(inferred_ty).with_qualifiers(all_qualifiers);
}
@@ -2968,7 +2970,6 @@ impl<'db> ClassLiteral<'db> {
let inferred_ty = infer_expression_type(
db,
index.expression(assign.value(&module)),
true,
);
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
@@ -2996,7 +2997,6 @@ impl<'db> ClassLiteral<'db> {
let iterable_ty = infer_expression_type(
db,
index.expression(for_stmt.iterable(&module)),
true,
);
// TODO: Potential diagnostics resulting from the iterable are currently not reported.
let inferred_ty =
@@ -3027,7 +3027,6 @@ impl<'db> ClassLiteral<'db> {
let context_ty = infer_expression_type(
db,
index.expression(with_item.context_expr(&module)),
true,
);
let inferred_ty = if with_item.is_async() {
context_ty.aenter(db)
@@ -3061,7 +3060,6 @@ impl<'db> ClassLiteral<'db> {
let iterable_ty = infer_expression_type(
db,
index.expression(comprehension.iterable(&module)),
true,
);
// TODO: Potential diagnostics resulting from the iterable are currently not reported.
let inferred_ty =

View File

@@ -725,10 +725,7 @@ impl<'db> FunctionType<'db> {
/// classmethod.
pub(crate) fn is_classmethod(self, db: &'db dyn Db) -> bool {
self.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|| matches!(
self.name(db).as_str(),
"__init_subclass__" | "__class_getitem__"
)
|| self.name(db) == "__init_subclass__"
}
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.

View File

@@ -83,7 +83,7 @@ use crate::semantic_index::definition::{
};
use crate::semantic_index::expression::{Expression, ExpressionKind};
use crate::semantic_index::narrowing_constraints::ConstraintKey;
use crate::semantic_index::place::{PlaceExpr, PlaceExprRef};
use crate::semantic_index::place::{PlaceExpr, PlaceExprRef, ScopedPlaceId};
use crate::semantic_index::scope::{
FileScopeId, NodeWithScopeKind, NodeWithScopeRef, ScopeId, ScopeKind,
};
@@ -137,7 +137,7 @@ use crate::types::{
use crate::unpack::{EvaluationMode, Unpack, UnpackPosition};
use crate::util::diagnostics::format_enumeration;
use crate::util::subscript::{PyIndex, PySlice};
use crate::{Db, FxOrderSet, Program};
use crate::{Db, FxOrderSet, Program, db};
/// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope.
/// Use when checking a scope, or needing to provide a type for an arbitrary expression in the
@@ -171,7 +171,6 @@ fn scope_cycle_initial<'db>(_db: &'db dyn Db, scope: ScopeId<'db>) -> ScopeInfer
/// Infer all types for a [`Definition`] (including sub-expressions).
/// Use when resolving a place use or public type of a place.
#[salsa::tracked(returns(ref), cycle_fn=definition_cycle_recover, cycle_initial=definition_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn infer_definition_types<'db>(
db: &'db dyn Db,
definition: Definition<'db>,
@@ -252,11 +251,9 @@ fn deferred_cycle_initial<'db>(
/// Use rarely; only for cases where we'd otherwise risk double-inferring an expression: RHS of an
/// assignment, which might be unpacking/multi-target and thus part of multiple definitions, or a
/// type narrowing guard expression (e.g. if statement test node).
#[salsa::tracked(returns(ref), cycle_fn=expression_cycle_recover, cycle_initial=expression_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn infer_expression_types<'db>(
db: &'db dyn Db,
expression: Expression<'db>,
_break_cycle: bool,
) -> ExpressionInference<'db> {
let file = expression.file(db);
let module = parsed_module(db, file).load(db);
@@ -279,7 +276,6 @@ fn expression_cycle_recover<'db>(
_value: &ExpressionInference<'db>,
_count: u32,
_expression: Expression<'db>,
_break_cycle: bool,
) -> salsa::CycleRecoveryAction<ExpressionInference<'db>> {
salsa::CycleRecoveryAction::Iterate
}
@@ -287,7 +283,6 @@ fn expression_cycle_recover<'db>(
fn expression_cycle_initial<'db>(
db: &'db dyn Db,
expression: Expression<'db>,
_break_cycle: bool,
) -> ExpressionInference<'db> {
ExpressionInference::cycle_fallback(expression.scope(db))
}
@@ -301,9 +296,8 @@ pub(super) fn infer_same_file_expression_type<'db>(
db: &'db dyn Db,
expression: Expression<'db>,
parsed: &ParsedModuleRef,
break_cycle: bool,
) -> Type<'db> {
let inference = infer_expression_types(db, expression, break_cycle);
let inference = infer_expression_types(db, expression);
inference.expression_type(expression.node_ref(db, parsed))
}
@@ -318,33 +312,19 @@ pub(super) fn infer_same_file_expression_type<'db>(
pub(crate) fn infer_expression_type<'db>(
db: &'db dyn Db,
expression: Expression<'db>,
break_cycle: bool,
) -> Type<'db> {
let file = expression.file(db);
let module = parsed_module(db, file).load(db);
// It's okay to call the "same file" version here because we're inside a salsa query.
infer_same_file_expression_type(db, expression, &module, break_cycle)
infer_same_file_expression_type(db, expression, &module)
}
// #[salsa::tracked(cycle_fn=single_expression_cycle_recover, cycle_initial=single_expression_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
// pub(crate) fn infer_expression_type_query<'db>(
// db: &'db dyn Db,
// expression: Expression<'db>,
// ) -> Type<'db> {
// let file = expression.file(db);
// let module = parsed_module(db, file).load(db);
// // It's okay to call the "same file" version here because we're inside a salsa query.
// infer_same_file_expression_type(db, expression, &module)
// }
fn single_expression_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &Type<'db>,
_count: u32,
_expression: Expression<'db>,
_break_cycle: bool,
) -> salsa::CycleRecoveryAction<Type<'db>> {
salsa::CycleRecoveryAction::Iterate
}
@@ -352,7 +332,6 @@ fn single_expression_cycle_recover<'db>(
fn single_expression_cycle_initial<'db>(
_db: &'db dyn Db,
_expression: Expression<'db>,
_break_cycle: bool,
) -> Type<'db> {
Type::Never
}
@@ -366,7 +345,7 @@ pub(crate) fn static_expression_truthiness<'db>(
db: &'db dyn Db,
expression: Expression<'db>,
) -> Truthiness {
let inference = infer_expression_types(db, expression, false);
let inference = infer_expression_types(db, expression);
if !inference.all_places_definitely_bound() {
return Truthiness::Ambiguous;
@@ -5654,8 +5633,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
expression: &ast::Expr,
standalone_expression: Expression<'db>,
) -> Type<'db> {
let types = infer_expression_types(self.db(), standalone_expression, false);
self.extend_expression(types);
let types = infer_expression_types(self.db(), standalone_expression);
self.extend_expression(&types);
// Instead of calling `self.expression_type(expr)` after extending here, we get
// the result from `types` directly because we might be in cycle recovery where
@@ -6088,7 +6067,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
builder.db(),
builder.index.expression(iter_expr),
builder.module(),
false,
)
} else {
builder.infer_standalone_expression(iter_expr)
@@ -6111,7 +6089,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let mut infer_iterable_type = || {
let expression = self.index.expression(iterable);
let result = infer_expression_types(self.db(), expression, false);
let result = infer_expression_types(self.db(), expression);
// Two things are different if it's the first comprehension:
// (1) We must lookup the `ScopedExpressionId` of the iterable expression in the outer scope,
@@ -6122,7 +6100,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if comprehension.is_first() && target.is_name_expr() {
result.expression_type(iterable)
} else {
self.extend_expression_unchecked(result);
self.extend_expression_unchecked(&result);
result.expression_type(iterable)
}
};
@@ -6817,37 +6795,27 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
expr: PlaceExprRef,
expr_ref: ast::ExprRef,
) -> (Place<'db>, Option<ScopedUseId>) {
if expr_ref
.as_name_expr()
.is_some_and(|name| name.is_invalid())
{
return (Place::Unbound, None);
}
let db = self.db();
let scope = self.scope();
let file_scope_id = scope.file_scope_id(db);
let place_table = self.index.place_table(file_scope_id);
let use_def = self.index.use_def_map(file_scope_id);
// If we're inferring types of deferred expressions, look them up from end-of-scope.
if self.is_deferred() {
let place = if let Some(place_id) = place_table.place_id(expr) {
place_from_bindings(db, use_def.all_reachable_bindings(place_id))
} else {
assert!(
self.deferred_state.in_string_annotation(),
"Expected the place table to create a place for every valid PlaceExpr node"
);
Place::Unbound
};
(place, None)
let is_deferred = self.is_deferred();
let deferred_state = self.deferred_state;
let use_id = if is_deferred {
None
} else {
if expr_ref
.as_name_expr()
.is_some_and(|name| name.is_invalid())
{
return (Place::Unbound, None);
}
Some(expr_ref.scoped_use_id(db, scope))
};
let use_id = expr_ref.scoped_use_id(db, scope);
let place = place_from_bindings(db, use_def.bindings_at_use(use_id));
let place_table = self.index.place_table(scope.file_scope_id(db));
let place_id = place_table.place_id(expr);
(place, Some(use_id))
}
infer_local_place_load(db, scope, deferred_state, place_id, use_id)
}
/// Infer the type of a place expression from definitions, assuming a load context.
@@ -7361,6 +7329,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
fn infer_attribute_expression(&mut self, attribute: &ast::ExprAttribute) -> Type<'db> {
let _span = tracing::debug_span!("infer_attribute_expression", ?attribute).entered();
let ast::ExprAttribute {
value,
attr: _,
@@ -9223,7 +9193,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
match ty.try_call(db, &CallArguments::positional([slice_ty])) {
match ty.try_call(db, &CallArguments::positional([value_ty, slice_ty])) {
Ok(bindings) => return bindings.return_type(db),
Err(CallError(_, bindings)) => {
if let Some(builder) =
@@ -11316,6 +11286,60 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
}
fn infer_local_place_load_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &(Place<'db>, Option<ScopedUseId>),
_count: u32,
_scope: ScopeId<'db>,
_deferred_state: DeferredExpressionState,
_place_id: Option<ScopedPlaceId>,
_use_id: Option<ScopedUseId>,
) -> salsa::CycleRecoveryAction<(Place<'db>, Option<ScopedUseId>)> {
salsa::CycleRecoveryAction::Iterate
}
fn infer_local_place_load_cycle_initial<'db>(
_db: &'db dyn Db,
_scope: ScopeId<'db>,
_deferred_state: DeferredExpressionState,
_place_id: Option<ScopedPlaceId>,
_use_id: Option<ScopedUseId>,
) -> (Place<'db>, Option<ScopedUseId>) {
(Place::Unbound, None)
}
#[salsa::tracked(cycle_fn=infer_local_place_load_cycle_recover, cycle_initial=infer_local_place_load_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
fn infer_local_place_load<'db>(
db: &'db dyn Db,
scope: ScopeId<'db>,
deferred_state: DeferredExpressionState,
place_id: Option<ScopedPlaceId>,
use_id: Option<ScopedUseId>,
) -> (Place<'db>, Option<ScopedUseId>) {
let file_scope_id = scope.file_scope_id(db);
let index = semantic_index(db, scope.file(db));
let use_def = index.use_def_map(file_scope_id);
if let Some(use_id) = use_id {
// Non-deferred load
let place = place_from_bindings(db, use_def.bindings_at_use(use_id));
(place, Some(use_id))
} else {
// If we're inferring types of deferred expressions, look them up from end-of-scope.
let place = if let Some(place_id) = place_id {
place_from_bindings(db, use_def.all_reachable_bindings(place_id))
} else {
assert!(
deferred_state.in_string_annotation(),
"Expected the place table to create a place for every valid PlaceExpr node"
);
Place::Unbound
};
(place, None)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum GenericContextError {
/// It's invalid to subscript `Generic` or `Protocol` with this type
@@ -11340,7 +11364,7 @@ impl GenericContextError {
}
/// The deferred state of a specific expression in an inference region.
#[derive(Default, Debug, Clone, Copy)]
#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq)]
enum DeferredExpressionState {
/// The expression is not deferred.
#[default]
@@ -11687,7 +11711,7 @@ mod tests {
use ruff_db::diagnostic::Diagnostic;
use ruff_db::files::{File, system_path_to_file};
use ruff_db::system::DbWithWritableSystem as _;
use ruff_db::testing::assert_function_query_was_not_run;
use ruff_db::testing::{assert_function_query_was_not_run, assert_function_query_was_run};
use super::*;
@@ -12069,267 +12093,267 @@ mod tests {
Ok(())
}
// #[test]
// fn dependency_implicit_instance_attribute() -> anyhow::Result<()> {
// fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
// let file_main = system_path_to_file(db, "/src/main.py").unwrap();
// let ast = parsed_module(db, file_main).load(db);
// // Get the second statement in `main.py` (x = …) and extract the expression
// // node on the right-hand side:
// let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value;
#[test]
fn dependency_implicit_instance_attribute() -> anyhow::Result<()> {
fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
let file_main = system_path_to_file(db, "/src/main.py").unwrap();
let ast = parsed_module(db, file_main).load(db);
// Get the second statement in `main.py` (x = …) and extract the expression
// node on the right-hand side:
let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value;
// let index = semantic_index(db, file_main);
// index.expression(x_rhs_node.as_ref())
// }
let index = semantic_index(db, file_main);
index.expression(x_rhs_node.as_ref())
}
// let mut db = setup_db();
let mut db = setup_db();
// db.write_dedented(
// "/src/mod.py",
// r#"
// class C:
// def f(self):
// self.attr: int | None = None
// "#,
// )?;
// db.write_dedented(
// "/src/main.py",
// r#"
// from mod import C
// x = C().attr
// "#,
// )?;
db.write_dedented(
"/src/mod.py",
r#"
class C:
def f(self):
self.attr: int | None = None
"#,
)?;
db.write_dedented(
"/src/main.py",
r#"
from mod import C
x = C().attr
"#,
)?;
// let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
// // Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
// db.write_dedented(
// "/src/mod.py",
// r#"
// class C:
// def f(self):
// self.attr: str | None = None
// "#,
// )?;
// Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
db.write_dedented(
"/src/mod.py",
r#"
class C:
def f(self):
self.attr: str | None = None
"#,
)?;
// let events = {
// db.clear_salsa_events();
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
// db.take_salsa_events()
// };
// assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
let events = {
db.clear_salsa_events();
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
db.take_salsa_events()
};
assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
// // Add a comment; this should not trigger the type of `x` to be re-inferred
// db.write_dedented(
// "/src/mod.py",
// r#"
// class C:
// def f(self):
// # a comment!
// self.attr: str | None = None
// "#,
// )?;
// Add a comment; this should not trigger the type of `x` to be re-inferred
db.write_dedented(
"/src/mod.py",
r#"
class C:
def f(self):
# a comment!
self.attr: str | None = None
"#,
)?;
// let events = {
// db.clear_salsa_events();
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
// db.take_salsa_events()
// };
let events = {
db.clear_salsa_events();
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
db.take_salsa_events()
};
// assert_function_query_was_not_run(
// &db,
// infer_expression_types,
// x_rhs_expression(&db),
// &events,
// );
assert_function_query_was_not_run(
&db,
infer_expression_types,
x_rhs_expression(&db),
&events,
);
// Ok(())
// }
Ok(())
}
// /// This test verifies that changing a class's declaration in a non-meaningful way (e.g. by adding a comment)
// /// doesn't trigger type inference for expressions that depend on the class's members.
// #[test]
// fn dependency_own_instance_member() -> anyhow::Result<()> {
// fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
// let file_main = system_path_to_file(db, "/src/main.py").unwrap();
// let ast = parsed_module(db, file_main).load(db);
// // Get the second statement in `main.py` (x = …) and extract the expression
// // node on the right-hand side:
// let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value;
/// This test verifies that changing a class's declaration in a non-meaningful way (e.g. by adding a comment)
/// doesn't trigger type inference for expressions that depend on the class's members.
#[test]
fn dependency_own_instance_member() -> anyhow::Result<()> {
fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
let file_main = system_path_to_file(db, "/src/main.py").unwrap();
let ast = parsed_module(db, file_main).load(db);
// Get the second statement in `main.py` (x = …) and extract the expression
// node on the right-hand side:
let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value;
// let index = semantic_index(db, file_main);
// index.expression(x_rhs_node.as_ref())
// }
let index = semantic_index(db, file_main);
index.expression(x_rhs_node.as_ref())
}
// let mut db = setup_db();
let mut db = setup_db();
// db.write_dedented(
// "/src/mod.py",
// r#"
// class C:
// if random.choice([True, False]):
// attr: int = 42
// else:
// attr: None = None
// "#,
// )?;
// db.write_dedented(
// "/src/main.py",
// r#"
// from mod import C
// x = C().attr
// "#,
// )?;
db.write_dedented(
"/src/mod.py",
r#"
class C:
if random.choice([True, False]):
attr: int = 42
else:
attr: None = None
"#,
)?;
db.write_dedented(
"/src/main.py",
r#"
from mod import C
x = C().attr
"#,
)?;
// let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
// // Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
// db.write_dedented(
// "/src/mod.py",
// r#"
// class C:
// if random.choice([True, False]):
// attr: str = "42"
// else:
// attr: None = None
// "#,
// )?;
// Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
db.write_dedented(
"/src/mod.py",
r#"
class C:
if random.choice([True, False]):
attr: str = "42"
else:
attr: None = None
"#,
)?;
// let events = {
// db.clear_salsa_events();
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
// db.take_salsa_events()
// };
// assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
let events = {
db.clear_salsa_events();
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
db.take_salsa_events()
};
assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
// // Add a comment; this should not trigger the type of `x` to be re-inferred
// db.write_dedented(
// "/src/mod.py",
// r#"
// class C:
// # comment
// if random.choice([True, False]):
// attr: str = "42"
// else:
// attr: None = None
// "#,
// )?;
// Add a comment; this should not trigger the type of `x` to be re-inferred
db.write_dedented(
"/src/mod.py",
r#"
class C:
# comment
if random.choice([True, False]):
attr: str = "42"
else:
attr: None = None
"#,
)?;
// let events = {
// db.clear_salsa_events();
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
// db.take_salsa_events()
// };
let events = {
db.clear_salsa_events();
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
db.take_salsa_events()
};
// assert_function_query_was_not_run(
// &db,
// infer_expression_types,
// x_rhs_expression(&db),
// &events,
// );
assert_function_query_was_not_run(
&db,
infer_expression_types,
x_rhs_expression(&db),
&events,
);
// Ok(())
// }
Ok(())
}
// #[test]
// fn dependency_implicit_class_member() -> anyhow::Result<()> {
// fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
// let file_main = system_path_to_file(db, "/src/main.py").unwrap();
// let ast = parsed_module(db, file_main).load(db);
// // Get the third statement in `main.py` (x = …) and extract the expression
// // node on the right-hand side:
// let x_rhs_node = &ast.syntax().body[2].as_assign_stmt().unwrap().value;
#[test]
fn dependency_implicit_class_member() -> anyhow::Result<()> {
fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
let file_main = system_path_to_file(db, "/src/main.py").unwrap();
let ast = parsed_module(db, file_main).load(db);
// Get the third statement in `main.py` (x = …) and extract the expression
// node on the right-hand side:
let x_rhs_node = &ast.syntax().body[2].as_assign_stmt().unwrap().value;
// let index = semantic_index(db, file_main);
// index.expression(x_rhs_node.as_ref())
// }
let index = semantic_index(db, file_main);
index.expression(x_rhs_node.as_ref())
}
// let mut db = setup_db();
let mut db = setup_db();
// db.write_dedented(
// "/src/mod.py",
// r#"
// class C:
// def __init__(self):
// self.instance_attr: str = "24"
db.write_dedented(
"/src/mod.py",
r#"
class C:
def __init__(self):
self.instance_attr: str = "24"
// @classmethod
// def method(cls):
// cls.class_attr: int = 42
// "#,
// )?;
// db.write_dedented(
// "/src/main.py",
// r#"
// from mod import C
// C.method()
// x = C().class_attr
// "#,
// )?;
@classmethod
def method(cls):
cls.class_attr: int = 42
"#,
)?;
db.write_dedented(
"/src/main.py",
r#"
from mod import C
C.method()
x = C().class_attr
"#,
)?;
// let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int");
let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int");
// // Change the type of `class_attr` to `str`; this should trigger the type of `x` to be re-inferred
// db.write_dedented(
// "/src/mod.py",
// r#"
// class C:
// def __init__(self):
// self.instance_attr: str = "24"
// Change the type of `class_attr` to `str`; this should trigger the type of `x` to be re-inferred
db.write_dedented(
"/src/mod.py",
r#"
class C:
def __init__(self):
self.instance_attr: str = "24"
// @classmethod
// def method(cls):
// cls.class_attr: str = "42"
// "#,
// )?;
@classmethod
def method(cls):
cls.class_attr: str = "42"
"#,
)?;
// let events = {
// db.clear_salsa_events();
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str");
// db.take_salsa_events()
// };
// assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
let events = {
db.clear_salsa_events();
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str");
db.take_salsa_events()
};
assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
// // Add a comment; this should not trigger the type of `x` to be re-inferred
// db.write_dedented(
// "/src/mod.py",
// r#"
// class C:
// def __init__(self):
// self.instance_attr: str = "24"
// Add a comment; this should not trigger the type of `x` to be re-inferred
db.write_dedented(
"/src/mod.py",
r#"
class C:
def __init__(self):
self.instance_attr: str = "24"
// @classmethod
// def method(cls):
// # comment
// cls.class_attr: str = "42"
// "#,
// )?;
@classmethod
def method(cls):
# comment
cls.class_attr: str = "42"
"#,
)?;
// let events = {
// db.clear_salsa_events();
// let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
// assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str");
// db.take_salsa_events()
// };
let events = {
db.clear_salsa_events();
let attr_ty = global_symbol(&db, file_main, "x").place.expect_type();
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str");
db.take_salsa_events()
};
// assert_function_query_was_not_run(
// &db,
// infer_expression_types,
// x_rhs_expression(&db),
// &events,
// );
assert_function_query_was_not_run(
&db,
infer_expression_types,
x_rhs_expression(&db),
&events,
);
// Ok(())
// }
Ok(())
}
}

View File

@@ -709,7 +709,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
return None;
}
let inference = infer_expression_types(self.db, expression, false);
let inference = infer_expression_types(self.db, expression);
let comparator_tuples = std::iter::once(&**left)
.chain(comparators)
@@ -799,7 +799,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
expression: Expression<'db>,
is_positive: bool,
) -> Option<NarrowingConstraints<'db>> {
let inference = infer_expression_types(self.db, expression, false);
let inference = infer_expression_types(self.db, expression);
let callable_ty = inference.expression_type(&*expr_call.func);
@@ -921,8 +921,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
let subject = place_expr(subject.node_ref(self.db, self.module))?;
let place = self.expect_place(&subject);
let ty = infer_same_file_expression_type(self.db, cls, self.module, false)
.to_instance(self.db)?;
let ty = infer_same_file_expression_type(self.db, cls, self.module).to_instance(self.db)?;
Some(NarrowingConstraints::from_iter([(place, ty)]))
}
@@ -935,7 +934,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
let subject = place_expr(subject.node_ref(self.db, self.module))?;
let place = self.expect_place(&subject);
let ty = infer_same_file_expression_type(self.db, value, self.module, false);
let ty = infer_same_file_expression_type(self.db, value, self.module);
Some(NarrowingConstraints::from_iter([(place, ty)]))
}
@@ -964,7 +963,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
expression: Expression<'db>,
is_positive: bool,
) -> Option<NarrowingConstraints<'db>> {
let inference = infer_expression_types(self.db, expression, false);
let inference = infer_expression_types(self.db, expression);
let mut sub_constraints = expr_bool_op
.values
.iter()

View File

@@ -48,7 +48,7 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
"Unpacking target must be a list or tuple expression"
);
let value_type = infer_expression_types(self.db(), value.expression(), false)
let value_type = infer_expression_types(self.db(), value.expression())
.expression_type(value.expression().node_ref(self.db(), self.module()));
let value_type = match value.kind() {