Skip to content

Commit 22733cb

Browse files
authored
red-knot(Salsa): Types without refinements (#11899)
1 parent a26bd01 commit 22733cb

File tree

13 files changed

+2168
-146
lines changed

13 files changed

+2168
-146
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ruff_python_semantic/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ ruff_text_size = { workspace = true }
2020

2121
bitflags = { workspace = true }
2222
is-macro = { workspace = true }
23+
indexmap = { workspace = true, optional = true }
2324
salsa = { workspace = true, optional = true }
2425
smallvec = { workspace = true, optional = true }
2526
smol_str = { workspace = true }
@@ -36,4 +37,4 @@ tempfile = { workspace = true }
3637
workspace = true
3738

3839
[features]
39-
red_knot = ["dep:salsa", "dep:tracing", "dep:hashbrown", "dep:smallvec"]
40+
red_knot = ["dep:salsa", "dep:tracing", "dep:hashbrown", "dep:smallvec", "dep:indexmap"]

crates/ruff_python_semantic/src/db.rs

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,41 @@ use crate::module::resolver::{
66
file_to_module, internal::ModuleNameIngredient, internal::ModuleResolverSearchPaths,
77
resolve_module_query,
88
};
9-
10-
use crate::red_knot::semantic_index::symbol::ScopeId;
11-
use crate::red_knot::semantic_index::{scopes_map, semantic_index, symbol_table};
9+
use crate::red_knot::semantic_index::symbol::{
10+
public_symbols_map, scopes_map, PublicSymbolId, ScopeId,
11+
};
12+
use crate::red_knot::semantic_index::{root_scope, semantic_index, symbol_table};
13+
use crate::red_knot::types::{infer_types, public_symbol_ty};
1214

1315
#[salsa::jar(db=Db)]
1416
pub struct Jar(
1517
ModuleNameIngredient,
1618
ModuleResolverSearchPaths,
1719
ScopeId,
20+
PublicSymbolId,
1821
symbol_table,
1922
resolve_module_query,
2023
file_to_module,
2124
scopes_map,
25+
root_scope,
2226
semantic_index,
27+
infer_types,
28+
public_symbol_ty,
29+
public_symbols_map,
2330
);
2431

2532
/// Database giving access to semantic information about a Python program.
2633
pub trait Db: SourceDb + DbWithJar<Jar> + Upcast<dyn SourceDb> {}
2734

2835
#[cfg(test)]
2936
pub(crate) mod tests {
37+
use std::fmt::Formatter;
38+
use std::marker::PhantomData;
3039
use std::sync::Arc;
3140

32-
use salsa::DebugWithDb;
41+
use salsa::ingredient::Ingredient;
42+
use salsa::storage::HasIngredientsFor;
43+
use salsa::{AsId, DebugWithDb};
3344

3445
use ruff_db::file_system::{FileSystem, MemoryFileSystem, OsFileSystem};
3546
use ruff_db::vfs::Vfs;
@@ -86,7 +97,7 @@ pub(crate) mod tests {
8697
///
8798
/// ## Panics
8899
/// If there are any pending salsa snapshots.
89-
pub(crate) fn take_sale_events(&mut self) -> Vec<salsa::Event> {
100+
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
90101
let inner = Arc::get_mut(&mut self.events).expect("no pending salsa snapshots");
91102

92103
let events = inner.get_mut().unwrap();
@@ -98,7 +109,7 @@ pub(crate) mod tests {
98109
/// ## Panics
99110
/// If there are any pending salsa snapshots.
100111
pub(crate) fn clear_salsa_events(&mut self) {
101-
self.take_sale_events();
112+
self.take_salsa_events();
102113
}
103114
}
104115

@@ -150,4 +161,106 @@ pub(crate) mod tests {
150161
#[allow(unused)]
151162
Os(OsFileSystem),
152163
}
164+
165+
pub(crate) fn assert_will_run_function_query<C, Db, Jar>(
166+
db: &Db,
167+
to_function: impl FnOnce(&C) -> &salsa::function::FunctionIngredient<C>,
168+
key: C::Key,
169+
events: &[salsa::Event],
170+
) where
171+
C: salsa::function::Configuration<Jar = Jar>
172+
+ salsa::storage::IngredientsFor<Jar = Jar, Ingredients = C>,
173+
Jar: HasIngredientsFor<C>,
174+
Db: salsa::DbWithJar<Jar>,
175+
C::Key: AsId,
176+
{
177+
will_run_function_query(db, to_function, key, events, true);
178+
}
179+
180+
pub(crate) fn assert_will_not_run_function_query<C, Db, Jar>(
181+
db: &Db,
182+
to_function: impl FnOnce(&C) -> &salsa::function::FunctionIngredient<C>,
183+
key: C::Key,
184+
events: &[salsa::Event],
185+
) where
186+
C: salsa::function::Configuration<Jar = Jar>
187+
+ salsa::storage::IngredientsFor<Jar = Jar, Ingredients = C>,
188+
Jar: HasIngredientsFor<C>,
189+
Db: salsa::DbWithJar<Jar>,
190+
C::Key: AsId,
191+
{
192+
will_run_function_query(db, to_function, key, events, false);
193+
}
194+
195+
fn will_run_function_query<C, Db, Jar>(
196+
db: &Db,
197+
to_function: impl FnOnce(&C) -> &salsa::function::FunctionIngredient<C>,
198+
key: C::Key,
199+
events: &[salsa::Event],
200+
should_run: bool,
201+
) where
202+
C: salsa::function::Configuration<Jar = Jar>
203+
+ salsa::storage::IngredientsFor<Jar = Jar, Ingredients = C>,
204+
Jar: HasIngredientsFor<C>,
205+
Db: salsa::DbWithJar<Jar>,
206+
C::Key: AsId,
207+
{
208+
let (jar, _) =
209+
<_ as salsa::storage::HasJar<<C as salsa::storage::IngredientsFor>::Jar>>::jar(db);
210+
let ingredient = jar.ingredient();
211+
212+
let function_ingredient = to_function(ingredient);
213+
214+
let ingredient_index =
215+
<salsa::function::FunctionIngredient<C> as Ingredient<Db>>::ingredient_index(
216+
function_ingredient,
217+
);
218+
219+
let did_run = events.iter().any(|event| {
220+
if let salsa::EventKind::WillExecute { database_key } = event.kind {
221+
database_key.ingredient_index() == ingredient_index
222+
&& database_key.key_index() == key.as_id()
223+
} else {
224+
false
225+
}
226+
});
227+
228+
if should_run && !did_run {
229+
panic!(
230+
"Expected query {:?} to run but it didn't",
231+
DebugIdx {
232+
db: PhantomData::<Db>,
233+
value_id: key.as_id(),
234+
ingredient: function_ingredient,
235+
}
236+
);
237+
} else if !should_run && did_run {
238+
panic!(
239+
"Expected query {:?} not to run but it did",
240+
DebugIdx {
241+
db: PhantomData::<Db>,
242+
value_id: key.as_id(),
243+
ingredient: function_ingredient,
244+
}
245+
);
246+
}
247+
}
248+
249+
struct DebugIdx<'a, I, Db>
250+
where
251+
I: Ingredient<Db>,
252+
{
253+
value_id: salsa::Id,
254+
ingredient: &'a I,
255+
db: PhantomData<Db>,
256+
}
257+
258+
impl<'a, I, Db> std::fmt::Debug for DebugIdx<'a, I, Db>
259+
where
260+
I: Ingredient<Db>,
261+
{
262+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
263+
self.ingredient.fmt_index(Some(self.value_id), f)
264+
}
265+
}
153266
}

crates/ruff_python_semantic/src/module/resolver.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,7 @@ mod tests {
886886
let foo_module2 = resolve_module(&db, foo_module_name);
887887

888888
assert!(!db
889-
.take_sale_events()
889+
.take_salsa_events()
890890
.iter()
891891
.any(|event| { matches!(event.kind, salsa::EventKind::WillExecute { .. }) }));
892892

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
use rustc_hash::FxHasher;
2+
use std::hash::BuildHasherDefault;
3+
14
pub mod ast_node_ref;
25
mod node_key;
36
pub mod semantic_index;
7+
pub mod types;
8+
pub(crate) type FxIndexSet<V> = indexmap::set::IndexSet<V, BuildHasherDefault<FxHasher>>;

crates/ruff_python_semantic/src/red_knot/semantic_index.rs

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ use ruff_index::{IndexSlice, IndexVec};
99
use ruff_python_ast as ast;
1010

1111
use crate::red_knot::node_key::NodeKey;
12-
use crate::red_knot::semantic_index::ast_ids::AstIds;
12+
use crate::red_knot::semantic_index::ast_ids::{AstId, AstIds, ScopeClassId, ScopeFunctionId};
1313
use crate::red_knot::semantic_index::builder::SemanticIndexBuilder;
1414
use crate::red_knot::semantic_index::symbol::{
15-
FileScopeId, PublicSymbolId, Scope, ScopeId, ScopeSymbolId, ScopesMap, SymbolTable,
15+
FileScopeId, PublicSymbolId, Scope, ScopeId, ScopeKind, ScopedSymbolId, SymbolTable,
1616
};
1717
use crate::Db;
1818

@@ -21,7 +21,7 @@ mod builder;
2121
pub mod definition;
2222
pub mod symbol;
2323

24-
type SymbolMap = hashbrown::HashMap<ScopeSymbolId, (), ()>;
24+
type SymbolMap = hashbrown::HashMap<ScopedSymbolId, (), ()>;
2525

2626
/// Returns the semantic index for `file`.
2727
///
@@ -42,33 +42,22 @@ pub(crate) fn semantic_index(db: &dyn Db, file: VfsFile) -> SemanticIndex {
4242
pub(crate) fn symbol_table(db: &dyn Db, scope: ScopeId) -> Arc<SymbolTable> {
4343
let index = semantic_index(db, scope.file(db));
4444

45-
index.symbol_table(scope.scope_id(db))
46-
}
47-
48-
/// Returns a mapping from file specific [`FileScopeId`] to a program-wide unique [`ScopeId`].
49-
#[salsa::tracked(return_ref)]
50-
pub(crate) fn scopes_map(db: &dyn Db, file: VfsFile) -> ScopesMap {
51-
let index = semantic_index(db, file);
52-
53-
let scopes: IndexVec<_, _> = index
54-
.scopes
55-
.indices()
56-
.map(|id| ScopeId::new(db, file, id))
57-
.collect();
58-
59-
ScopesMap::new(scopes)
45+
index.symbol_table(scope.file_scope_id(db))
6046
}
6147

6248
/// Returns the root scope of `file`.
63-
pub fn root_scope(db: &dyn Db, file: VfsFile) -> ScopeId {
49+
#[salsa::tracked]
50+
pub(crate) fn root_scope(db: &dyn Db, file: VfsFile) -> ScopeId {
6451
FileScopeId::root().to_scope_id(db, file)
6552
}
6653

6754
/// Returns the symbol with the given name in `file`'s public scope or `None` if
6855
/// no symbol with the given name exists.
69-
pub fn global_symbol(db: &dyn Db, file: VfsFile, name: &str) -> Option<PublicSymbolId> {
56+
pub fn public_symbol(db: &dyn Db, file: VfsFile, name: &str) -> Option<PublicSymbolId> {
7057
let root_scope = root_scope(db, file);
71-
root_scope.symbol(db, name)
58+
let symbol_table = symbol_table(db, root_scope);
59+
let local = symbol_table.symbol_id_by_name(name)?;
60+
Some(local.to_public_symbol(db, file))
7261
}
7362

7463
/// The symbol tables for an entire file.
@@ -90,14 +79,17 @@ pub struct SemanticIndex {
9079
/// Note: We should not depend on this map when analysing other files or
9180
/// changing a file invalidates all dependents.
9281
ast_ids: IndexVec<FileScopeId, AstIds>,
82+
83+
/// Map from scope to the node that introduces the scope.
84+
scope_nodes: IndexVec<FileScopeId, NodeWithScopeId>,
9385
}
9486

9587
impl SemanticIndex {
9688
/// Returns the symbol table for a specific scope.
9789
///
9890
/// Use the Salsa cached [`symbol_table`] query if you only need the
9991
/// symbol table for a single scope.
100-
fn symbol_table(&self, scope_id: FileScopeId) -> Arc<SymbolTable> {
92+
pub(super) fn symbol_table(&self, scope_id: FileScopeId) -> Arc<SymbolTable> {
10193
self.symbol_tables[scope_id].clone()
10294
}
10395

@@ -152,6 +144,10 @@ impl SemanticIndex {
152144
pub(crate) fn ancestor_scopes(&self, scope: FileScopeId) -> AncestorsIter {
153145
AncestorsIter::new(self, scope)
154146
}
147+
148+
pub(crate) fn scope_node(&self, scope_id: FileScopeId) -> NodeWithScopeId {
149+
self.scope_nodes[scope_id]
150+
}
155151
}
156152

157153
/// ID that uniquely identifies an expression inside a [`Scope`].
@@ -246,6 +242,28 @@ impl<'a> Iterator for ChildrenIter<'a> {
246242
}
247243
}
248244

245+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
246+
pub(crate) enum NodeWithScopeId {
247+
Module,
248+
Class(AstId<ScopeClassId>),
249+
ClassTypeParams(AstId<ScopeClassId>),
250+
Function(AstId<ScopeFunctionId>),
251+
FunctionTypeParams(AstId<ScopeFunctionId>),
252+
}
253+
254+
impl NodeWithScopeId {
255+
fn scope_kind(self) -> ScopeKind {
256+
match self {
257+
NodeWithScopeId::Module => ScopeKind::Module,
258+
NodeWithScopeId::Class(_) => ScopeKind::Class,
259+
NodeWithScopeId::Function(_) => ScopeKind::Function,
260+
NodeWithScopeId::ClassTypeParams(_) | NodeWithScopeId::FunctionTypeParams(_) => {
261+
ScopeKind::Annotation
262+
}
263+
}
264+
}
265+
}
266+
249267
impl FusedIterator for ChildrenIter<'_> {}
250268

251269
#[cfg(test)]
@@ -583,19 +601,14 @@ class C[T]:
583601
let TestCase { db, file } = test_case("x = 1;\ndef test():\n y = 4");
584602

585603
let index = semantic_index(&db, file);
586-
let root_table = index.symbol_table(FileScopeId::root());
587604
let parsed = parsed_module(&db, file);
588605
let ast = parsed.syntax();
589606

590-
let x_sym = root_table
591-
.symbol_by_name("x")
592-
.expect("x symbol should exist");
593-
594607
let x_stmt = ast.body[0].as_assign_stmt().unwrap();
595608
let x = &x_stmt.targets[0];
596609

597610
assert_eq!(index.expression_scope(x).kind(), ScopeKind::Module);
598-
assert_eq!(index.expression_scope_id(x), x_sym.scope());
611+
assert_eq!(index.expression_scope_id(x), FileScopeId::root());
599612

600613
let def = ast.body[1].as_function_def_stmt().unwrap();
601614
let y_stmt = def.body[0].as_assign_stmt().unwrap();

0 commit comments

Comments
 (0)