Skip to content

Commit ad6ef57

Browse files
committed
Untrack symbol_by_id
1 parent fb09d63 commit ad6ef57

File tree

7 files changed

+141
-62
lines changed

7 files changed

+141
-62
lines changed

crates/red_knot_python_semantic/src/semantic_index/builder.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,12 +346,14 @@ impl<'db> SemanticIndexBuilder<'db> {
346346
// SAFETY: `definition_node` is guaranteed to be a child of `self.module`
347347
let kind = unsafe { definition_node.into_owned(self.module.clone()) };
348348
let category = kind.category();
349+
let is_reexported = kind.is_reexported();
349350
let definition = Definition::new(
350351
self.db,
351352
self.file,
352353
self.current_scope(),
353354
symbol,
354355
kind,
356+
is_reexported,
355357
countme::Count::default(),
356358
);
357359

crates/red_knot_python_semantic/src/semantic_index/definition.rs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,23 @@ pub struct Definition<'db> {
3333
/// The symbol defined.
3434
pub(crate) symbol: ScopedSymbolId,
3535

36+
/// WARNING: Only access this field when doing type inference for the same
37+
/// file as where `Definition` is defined to avoid cross-file query dependencies.
3638
#[no_eq]
3739
#[return_ref]
3840
#[tracked]
3941
pub(crate) kind: DefinitionKind<'db>,
4042

43+
/// This is a dedicated field to avoid accessing `kind` to compute this value.
44+
pub(crate) is_reexported: bool,
45+
4146
count: countme::Count<Definition<'static>>,
4247
}
4348

4449
impl<'db> Definition<'db> {
4550
pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
4651
self.file_scope(db).to_scope_id(db, self.file(db))
4752
}
48-
49-
pub(crate) fn category(self, db: &'db dyn Db) -> DefinitionCategory {
50-
self.kind(db).category()
51-
}
52-
53-
pub(crate) fn is_declaration(self, db: &'db dyn Db) -> bool {
54-
self.kind(db).category().is_declaration()
55-
}
56-
57-
pub(crate) fn is_binding(self, db: &'db dyn Db) -> bool {
58-
self.kind(db).category().is_binding()
59-
}
60-
61-
pub(crate) fn is_reexported(self, db: &'db dyn Db) -> bool {
62-
self.kind(db).is_reexported()
63-
}
6453
}
6554

6655
#[derive(Copy, Clone, Debug)]

crates/red_knot_python_semantic/src/symbol.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option<ScopeId<'_
293293
/// together with boundness information in a [`Symbol`].
294294
///
295295
/// The type will be a union if there are multiple bindings with different types.
296-
pub(crate) fn symbol_from_bindings<'db>(
296+
pub(super) fn symbol_from_bindings<'db>(
297297
db: &'db dyn Db,
298298
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
299299
) -> Symbol<'db> {
@@ -369,7 +369,6 @@ fn symbol_impl<'db>(
369369
name: &str,
370370
requires_explicit_reexport: RequiresExplicitReExport,
371371
) -> Symbol<'db> {
372-
#[salsa::tracked]
373372
fn symbol_by_id<'db>(
374373
db: &'db dyn Db,
375374
scope: ScopeId<'db>,

crates/red_knot_python_semantic/src/types.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ pub(crate) use self::diagnostic::register_lints;
1616
pub use self::diagnostic::{TypeCheckDiagnostic, TypeCheckDiagnostics};
1717
pub(crate) use self::display::TypeArrayDisplay;
1818
pub(crate) use self::infer::{
19-
infer_deferred_types, infer_definition_types, infer_expression_types, infer_scope_types,
19+
infer_deferred_types, infer_definition_types, infer_expression_type, infer_expression_types,
20+
infer_scope_types,
2021
};
2122
pub use self::narrow::KnownConstraintFunction;
2223
pub(crate) use self::signatures::Signature;
@@ -26,7 +27,6 @@ use crate::module_resolver::{file_to_module, resolve_module, KnownModule};
2627
use crate::semantic_index::ast_ids::HasScopedExpressionId;
2728
use crate::semantic_index::attribute_assignment::AttributeAssignment;
2829
use crate::semantic_index::definition::Definition;
29-
use crate::semantic_index::expression::Expression;
3030
use crate::semantic_index::symbol::ScopeId;
3131
use crate::semantic_index::{
3232
attribute_assignments, imported_modules, semantic_index, symbol_table, use_def_map,
@@ -3818,16 +3818,6 @@ impl<'db> Class<'db> {
38183818
name: &str,
38193819
inferred_type_from_class_body: Option<Type<'db>>,
38203820
) -> Symbol<'db> {
3821-
// We use a separate salsa query here to prevent unrelated changes in the AST of an external
3822-
// file from triggering re-evaluations of downstream queries.
3823-
// See the `dependency_implicit_instance_attribute` test for more information.
3824-
#[salsa::tracked]
3825-
fn infer_expression_type<'db>(db: &'db dyn Db, expression: Expression<'db>) -> Type<'db> {
3826-
let inference = infer_expression_types(db, expression);
3827-
let expr_scope = expression.scope(db);
3828-
inference.expression_type(expression.node_ref(db).scoped_expression_id(db, expr_scope))
3829-
}
3830-
38313821
// If we do not see any declarations of an attribute, neither in the class body nor in
38323822
// any method, we build a union of `Unknown` with the inferred types of all bindings of
38333823
// that attribute. We include `Unknown` in that union to account for the fact that the

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ fn infer_definition_types_cycle_recovery<'db>(
118118
) -> TypeInference<'db> {
119119
tracing::trace!("infer_definition_types_cycle_recovery");
120120
let mut inference = TypeInference::empty(input.scope(db));
121-
let category = input.category(db);
121+
let category = input.kind(db).category();
122122
if category.is_declaration() {
123123
inference
124124
.declarations
@@ -198,6 +198,36 @@ pub(crate) fn infer_expression_types<'db>(
198198
TypeInferenceBuilder::new(db, InferenceRegion::Expression(expression), index).finish()
199199
}
200200

201+
/// Infers the type of an `expression` that is guaranteed to be in the same file as the calling query.
202+
///
203+
/// This is a small helper around [`infer_expression_types()`] to reduce the boilerplate.
204+
/// Use [`infer_expression_type()`] if it isn't guaranteed that `expression` is in the same file to
205+
/// avoid cross-file query dependencies.
206+
pub(super) fn infer_same_file_expression_type<'db>(
207+
db: &'db dyn Db,
208+
expression: Expression<'db>,
209+
) -> Type<'db> {
210+
let inference = infer_expression_types(db, expression);
211+
let scope = expression.scope(db);
212+
inference.expression_type(expression.node_ref(db).scoped_expression_id(db, scope))
213+
}
214+
215+
/// Infers the type of an expression where the expression might come from another file.
216+
///
217+
/// Use this over [`infer_expression_types`] if the expression might come from another file than the
218+
/// enclosing query to avoid cross-file query dependencies.
219+
///
220+
/// Use [`infer_same_file_expression_type`] if it is guaranteed that `expression` is in the same
221+
/// to avoid unnecessary salsa ingredients. This is normally the case inside the `TypeInferenceBuilder`.
222+
#[salsa::tracked]
223+
pub(crate) fn infer_expression_type<'db>(
224+
db: &'db dyn Db,
225+
expression: Expression<'db>,
226+
) -> Type<'db> {
227+
// It's okay to call the "same file" version here because we're inside a salsa query.
228+
infer_same_file_expression_type(db, expression)
229+
}
230+
201231
/// Infer the types for an [`Unpack`] operation.
202232
///
203233
/// This infers the expression type and performs structural match against the target expression
@@ -870,7 +900,7 @@ impl<'db> TypeInferenceBuilder<'db> {
870900
}
871901

872902
fn add_binding(&mut self, node: AnyNodeRef, binding: Definition<'db>, ty: Type<'db>) {
873-
debug_assert!(binding.is_binding(self.db()));
903+
debug_assert!(binding.kind(self.db()).category().is_binding());
874904
let use_def = self.index.use_def_map(binding.file_scope(self.db()));
875905
let declarations = use_def.declarations_at_binding(binding);
876906
let mut bound_ty = ty;
@@ -905,7 +935,7 @@ impl<'db> TypeInferenceBuilder<'db> {
905935
declaration: Definition<'db>,
906936
ty: TypeAndQualifiers<'db>,
907937
) {
908-
debug_assert!(declaration.is_declaration(self.db()));
938+
debug_assert!(declaration.kind(self.db()).category().is_declaration());
909939
let use_def = self.index.use_def_map(declaration.file_scope(self.db()));
910940
let prior_bindings = use_def.bindings_at_declaration(declaration);
911941
// unbound_ty is Never because for this check we don't care about unbound
@@ -935,8 +965,8 @@ impl<'db> TypeInferenceBuilder<'db> {
935965
definition: Definition<'db>,
936966
declared_and_inferred_ty: &DeclaredAndInferredType<'db>,
937967
) {
938-
debug_assert!(definition.is_binding(self.db()));
939-
debug_assert!(definition.is_declaration(self.db()));
968+
debug_assert!(definition.kind(self.db()).category().is_binding());
969+
debug_assert!(definition.kind(self.db()).category().is_declaration());
940970

941971
let (declared_ty, inferred_ty) = match *declared_and_inferred_ty {
942972
DeclaredAndInferredType::AreTheSame(ty) => (ty.into(), ty),
@@ -6626,4 +6656,92 @@ mod tests {
66266656

66276657
Ok(())
66286658
}
6659+
6660+
/// This test verifies that queries
6661+
#[test]
6662+
fn dependency_own_instance_member() -> anyhow::Result<()> {
6663+
fn x_rhs_expression(db: &TestDb) -> Expression<'_> {
6664+
let file_main = system_path_to_file(db, "/src/main.py").unwrap();
6665+
let ast = parsed_module(db, file_main);
6666+
// Get the second statement in `main.py` (x = …) and extract the expression
6667+
// node on the right-hand side:
6668+
let x_rhs_node = &ast.syntax().body[1].as_assign_stmt().unwrap().value;
6669+
6670+
let index = semantic_index(db, file_main);
6671+
index.expression(x_rhs_node.as_ref())
6672+
}
6673+
6674+
let mut db = setup_db();
6675+
6676+
db.write_dedented(
6677+
"/src/mod.py",
6678+
r#"
6679+
class C:
6680+
if random.choice([True, False]):
6681+
attr: int = 42
6682+
else:
6683+
attr: None = None
6684+
"#,
6685+
)?;
6686+
db.write_dedented(
6687+
"/src/main.py",
6688+
r#"
6689+
from mod import C
6690+
x = C().attr
6691+
"#,
6692+
)?;
6693+
6694+
let file_main = system_path_to_file(&db, "/src/main.py").unwrap();
6695+
let attr_ty = global_symbol(&db, file_main, "x").expect_type();
6696+
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | int | None");
6697+
6698+
// Change the type of `attr` to `str | None`; this should trigger the type of `x` to be re-inferred
6699+
db.write_dedented(
6700+
"/src/mod.py",
6701+
r#"
6702+
class C:
6703+
if random.choice([True, False]):
6704+
attr: str = "42"
6705+
else:
6706+
attr: None = None
6707+
"#,
6708+
)?;
6709+
6710+
let events = {
6711+
db.clear_salsa_events();
6712+
let attr_ty = global_symbol(&db, file_main, "x").expect_type();
6713+
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
6714+
db.take_salsa_events()
6715+
};
6716+
assert_function_query_was_run(&db, infer_expression_types, x_rhs_expression(&db), &events);
6717+
6718+
// Add a comment; this should not trigger the type of `x` to be re-inferred
6719+
db.write_dedented(
6720+
"/src/mod.py",
6721+
r#"
6722+
class C:
6723+
# comment
6724+
if random.choice([True, False]):
6725+
attr: str = "42"
6726+
else:
6727+
attr: None = None
6728+
"#,
6729+
)?;
6730+
6731+
let events = {
6732+
db.clear_salsa_events();
6733+
let attr_ty = global_symbol(&db, file_main, "x").expect_type();
6734+
assert_eq!(attr_ty.display(&db).to_string(), "Unknown | str | None");
6735+
db.take_salsa_events()
6736+
};
6737+
6738+
assert_function_query_was_not_run(
6739+
&db,
6740+
infer_expression_types,
6741+
x_rhs_expression(&db),
6742+
&events,
6743+
);
6744+
6745+
Ok(())
6746+
}
66296747
}

crates/red_knot_python_semantic/src/types/narrow.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::semantic_index::definition::Definition;
66
use crate::semantic_index::expression::Expression;
77
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
88
use crate::semantic_index::symbol_table;
9+
use crate::types::infer::infer_same_file_expression_type;
910
use crate::types::{
1011
infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass, KnownFunction,
1112
SubclassOfType, Truthiness, Type, UnionBuilder,
@@ -497,11 +498,8 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
497498
if let Some(ast::ExprName { id, .. }) = subject.node_ref(self.db).as_name_expr() {
498499
// SAFETY: we should always have a symbol for every Name node.
499500
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
500-
let scope = self.scope();
501-
let inference = infer_expression_types(self.db, cls);
502-
let ty = inference
503-
.expression_type(cls.node_ref(self.db).scoped_expression_id(self.db, scope))
504-
.to_instance(self.db);
501+
let ty = infer_same_file_expression_type(self.db, cls).to_instance(self.db);
502+
505503
let mut constraints = NarrowingConstraints::default();
506504
constraints.insert(symbol, ty);
507505
Some(constraints)

crates/red_knot_python_semantic/src/visibility_constraints.rs

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,8 @@ use std::cmp::Ordering;
178178
use ruff_index::{Idx, IndexVec};
179179
use rustc_hash::FxHashMap;
180180

181-
use crate::semantic_index::{
182-
ast_ids::HasScopedExpressionId,
183-
constraint::{Constraint, ConstraintNode, PatternConstraintKind},
184-
};
185-
use crate::types::{infer_expression_types, Truthiness};
181+
use crate::semantic_index::constraint::{Constraint, ConstraintNode, PatternConstraintKind};
182+
use crate::types::{infer_expression_type, Truthiness};
186183
use crate::Db;
187184

188185
/// A ternary formula that defines under what conditions a binding is visible. (A ternary formula
@@ -617,28 +614,14 @@ impl<'db> VisibilityConstraints<'db> {
617614
fn analyze_single(db: &dyn Db, constraint: &Constraint) -> Truthiness {
618615
match constraint.node {
619616
ConstraintNode::Expression(test_expr) => {
620-
let inference = infer_expression_types(db, test_expr);
621-
let scope = test_expr.scope(db);
622-
let ty = inference
623-
.expression_type(test_expr.node_ref(db).scoped_expression_id(db, scope));
624-
617+
let ty = infer_expression_type(db, test_expr);
625618
ty.bool(db).negate_if(!constraint.is_positive)
626619
}
627620
ConstraintNode::Pattern(inner) => match inner.kind(db) {
628621
PatternConstraintKind::Value(value, guard) => {
629622
let subject_expression = inner.subject(db);
630-
let inference = infer_expression_types(db, subject_expression);
631-
let scope = subject_expression.scope(db);
632-
let subject_ty = inference.expression_type(
633-
subject_expression
634-
.node_ref(db)
635-
.scoped_expression_id(db, scope),
636-
);
637-
638-
let inference = infer_expression_types(db, *value);
639-
let scope = value.scope(db);
640-
let value_ty = inference
641-
.expression_type(value.node_ref(db).scoped_expression_id(db, scope));
623+
let subject_ty = infer_expression_type(db, subject_expression);
624+
let value_ty = infer_expression_type(db, *value);
642625

643626
if subject_ty.is_single_valued(db) {
644627
let truthiness =

0 commit comments

Comments
 (0)