Compare commits
5 Commits
editables-
...
charlie/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fdde079a8 | ||
|
|
ca22248628 | ||
|
|
d8cf8ac2ef | ||
|
|
1c7b84059e | ||
|
|
f82bb67555 |
75
analyze.py
Normal file
75
analyze.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import sys
|
||||
|
||||
source_counts = []
|
||||
lines_counts = []
|
||||
nodes_counts = []
|
||||
scopes_counts = []
|
||||
bindings_counts = []
|
||||
definitions_counts = []
|
||||
resolved_references_counts = []
|
||||
unresolved_references_counts = []
|
||||
globals_counts = []
|
||||
resolved_names_counts = []
|
||||
shadowed_bindings_counts = []
|
||||
|
||||
with open(sys.argv[1], 'r') as fp:
|
||||
for line in fp:
|
||||
if line.startswith('source'):
|
||||
source_counts.append(int(line.split()[1]))
|
||||
if line.startswith('lines'):
|
||||
lines_counts.append(int(line.split()[1]))
|
||||
if line.startswith('nodes'):
|
||||
nodes_counts.append(int(line.split()[1]))
|
||||
if line.startswith('scopes'):
|
||||
scopes_counts.append(int(line.split()[1]))
|
||||
if line.startswith('bindings'):
|
||||
bindings_counts.append(int(line.split()[1]))
|
||||
if line.startswith('definitions'):
|
||||
definitions_counts.append(int(line.split()[1]))
|
||||
if line.startswith('resolved_references'):
|
||||
resolved_references_counts.append(int(line.split()[1]))
|
||||
if line.startswith('unresolved_references'):
|
||||
unresolved_references_counts.append(int(line.split()[1]))
|
||||
if line.startswith('globals'):
|
||||
globals_counts.append(int(line.split()[1]))
|
||||
if line.startswith('resolved_names'):
|
||||
resolved_names_counts.append(int(line.split()[1]))
|
||||
if line.startswith('shadowed_bindings'):
|
||||
shadowed_bindings_counts.append(int(line.split()[1]))
|
||||
|
||||
# Each line represents a file.
|
||||
# Let's compute (e.g.) the average number of bindings per line.
|
||||
print('Average number of nodes per file:', sum(nodes_counts) / len(lines_counts))
|
||||
print('Average number of scopes per file:', sum(scopes_counts) / len(lines_counts))
|
||||
print('Average number of bindings per file:', sum(bindings_counts) / len(lines_counts))
|
||||
print('Average number of definitions per file:', sum(definitions_counts) / len(lines_counts))
|
||||
print('Average number of resolved references per file:', sum(resolved_references_counts) / len(lines_counts))
|
||||
print('Average number of unresolved references per file:', sum(unresolved_references_counts) / len(lines_counts))
|
||||
print('Average number of globals per file:', sum(globals_counts) / len(lines_counts))
|
||||
print('Average number of resolved names per file:', sum(resolved_names_counts) / len(lines_counts))
|
||||
print('Average number of shadowed bindings per file:', sum(shadowed_bindings_counts) / len(lines_counts))
|
||||
|
||||
print()
|
||||
|
||||
print('Max nodes per file:', max(nodes_counts))
|
||||
print('Max scopes per file:', max(scopes_counts))
|
||||
print('Max bindings per file:', max(bindings_counts))
|
||||
print('Max definitions per file:', max(definitions_counts))
|
||||
print('Max resolved references per file:', max(resolved_references_counts))
|
||||
print('Max unresolved references per file:', max(unresolved_references_counts))
|
||||
print('Max globals per file:', max(globals_counts))
|
||||
print('Max resolved names per file:', max(resolved_names_counts))
|
||||
print('Max shadowed bindings per file:', max(shadowed_bindings_counts))
|
||||
|
||||
print()
|
||||
|
||||
# Let's compute (e.g.) the average number of bindings per line.
|
||||
print('Average number of nodes per byte:', sum(nodes_counts) / sum(source_counts))
|
||||
print('Average number of scopes per byte:', sum(scopes_counts) / sum(source_counts))
|
||||
print('Average number of bindings per byte:', sum(bindings_counts) / sum(source_counts))
|
||||
print('Average number of definitions per byte:', sum(definitions_counts) / sum(source_counts))
|
||||
print('Average number of resolved references per byte:', sum(resolved_references_counts) / sum(source_counts))
|
||||
print('Average number of unresolved references per byte:', sum(unresolved_references_counts) / sum(source_counts))
|
||||
print('Average number of globals per byte:', sum(globals_counts) / sum(source_counts))
|
||||
print('Average number of resolved names per byte:', sum(resolved_names_counts) / sum(source_counts))
|
||||
print('Average number of shadowed bindings per byte:', sum(shadowed_bindings_counts) / sum(source_counts))
|
||||
@@ -233,10 +233,16 @@ impl ModuleResolutionPathBuf {
|
||||
ModuleResolutionPathRef::from(self).is_directory(search_path, resolver)
|
||||
}
|
||||
|
||||
pub(crate) fn is_site_packages(&self) -> bool {
|
||||
#[must_use]
|
||||
pub(crate) const fn is_site_packages(&self) -> bool {
|
||||
matches!(self.0, ModuleResolutionPathBufInner::SitePackages(_))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) const fn is_standard_library(&self) -> bool {
|
||||
matches!(self.0, ModuleResolutionPathBufInner::StandardLibrary(_))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn with_pyi_extension(&self) -> Self {
|
||||
ModuleResolutionPathRef::from(self).with_pyi_extension()
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::borrow::Cow;
|
||||
use std::iter::FusedIterator;
|
||||
use std::sync::Arc;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
|
||||
use ruff_db::files::{File, FilePath};
|
||||
@@ -442,6 +443,52 @@ pub(crate) mod internal {
|
||||
}
|
||||
}
|
||||
|
||||
/// Modules that are builtin to the Python interpreter itself.
|
||||
///
|
||||
/// When these module names are imported, standard module resolution is bypassed:
|
||||
/// the module name always resolves to the stdlib module,
|
||||
/// even if there's a module of the same name in the workspace root
|
||||
/// (which would normally result in the stdlib module being overridden).
|
||||
///
|
||||
/// TODO(Alex): write a script to generate this list,
|
||||
/// similar to what we do in `crates/ruff_python_stdlib/src/sys.rs`
|
||||
static BUILTIN_MODULES: Lazy<FxHashSet<&str>> = Lazy::new(|| {
|
||||
const BUILTIN_MODULE_NAMES: &[&str] = &[
|
||||
"_abc",
|
||||
"_ast",
|
||||
"_codecs",
|
||||
"_collections",
|
||||
"_functools",
|
||||
"_imp",
|
||||
"_io",
|
||||
"_locale",
|
||||
"_operator",
|
||||
"_signal",
|
||||
"_sre",
|
||||
"_stat",
|
||||
"_string",
|
||||
"_symtable",
|
||||
"_thread",
|
||||
"_tokenize",
|
||||
"_tracemalloc",
|
||||
"_typing",
|
||||
"_warnings",
|
||||
"_weakref",
|
||||
"atexit",
|
||||
"builtins",
|
||||
"errno",
|
||||
"faulthandler",
|
||||
"gc",
|
||||
"itertools",
|
||||
"marshal",
|
||||
"posix",
|
||||
"pwd",
|
||||
"sys",
|
||||
"time",
|
||||
];
|
||||
BUILTIN_MODULE_NAMES.iter().copied().collect()
|
||||
});
|
||||
|
||||
/// Given a module name and a list of search paths in which to lookup modules,
|
||||
/// attempt to resolve the module name
|
||||
fn resolve_name(
|
||||
@@ -450,8 +497,12 @@ fn resolve_name(
|
||||
) -> Option<(Arc<ModuleResolutionPathBuf>, File, ModuleKind)> {
|
||||
let resolver_settings = module_resolution_settings(db);
|
||||
let resolver_state = ResolverState::new(db, resolver_settings.target_version());
|
||||
let is_builtin_module = BUILTIN_MODULES.contains(&name.as_str());
|
||||
|
||||
for search_path in resolver_settings.search_paths(db) {
|
||||
if is_builtin_module && !search_path.is_standard_library() {
|
||||
continue;
|
||||
}
|
||||
let mut components = name.components();
|
||||
let module_name = components.next_back()?;
|
||||
|
||||
@@ -629,6 +680,40 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtins_vendored() {
|
||||
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
|
||||
.with_vendored_typeshed()
|
||||
.with_src_files(&[("builtins.py", "FOOOO = 42")])
|
||||
.build();
|
||||
|
||||
let builtins_module_name = ModuleName::new_static("builtins").unwrap();
|
||||
let builtins = resolve_module(&db, builtins_module_name).expect("builtins to resolve");
|
||||
|
||||
assert_eq!(builtins.file().path(&db), &stdlib.join("builtins.pyi"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtins_custom() {
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
stdlib_files: &[("builtins.pyi", "def min(a, b): ...")],
|
||||
versions: "builtins: 3.8-",
|
||||
};
|
||||
|
||||
const SRC: &[FileSpec] = &[("builtins.py", "FOOOO = 42")];
|
||||
|
||||
let TestCase { db, stdlib, .. } = TestCaseBuilder::new()
|
||||
.with_src_files(SRC)
|
||||
.with_custom_typeshed(TYPESHED)
|
||||
.with_target_version(TargetVersion::Py38)
|
||||
.build();
|
||||
|
||||
let builtins_module_name = ModuleName::new_static("builtins").unwrap();
|
||||
let builtins = resolve_module(&db, builtins_module_name).expect("builtins to resolve");
|
||||
|
||||
assert_eq!(builtins.file().path(&db), &stdlib.join("builtins.pyi"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdlib() {
|
||||
const TYPESHED: MockedTypeshed = MockedTypeshed {
|
||||
@@ -1603,28 +1688,4 @@ not_a_directory
|
||||
ModuleResolutionPathBuf::editable_installation_root(db.system(), "/src").unwrap()
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_duplicate_editable_search_paths_added() {
|
||||
let TestCase { mut db, .. } = TestCaseBuilder::new()
|
||||
.with_site_packages_files(&[("_foo.pth", "/x"), ("_bar.pth", "/x")])
|
||||
.build();
|
||||
|
||||
db.write_file("/x/foo.py", "").unwrap();
|
||||
|
||||
let search_paths: Vec<&SearchPathRoot> =
|
||||
module_resolution_settings(&db).search_paths(&db).collect();
|
||||
|
||||
let editable_install =
|
||||
ModuleResolutionPathBuf::editable_installation_root(db.system(), "/x").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
search_paths
|
||||
.iter()
|
||||
.filter(|path| ****path == editable_install)
|
||||
.count(),
|
||||
1,
|
||||
"Unexpected search paths: {search_paths:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
16
crates/red_knot_python_semantic/src/builtins.rs
Normal file
16
crates/red_knot_python_semantic/src/builtins.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use red_knot_module_resolver::{resolve_module, ModuleName};
|
||||
|
||||
use crate::semantic_index::global_scope;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::Db;
|
||||
|
||||
/// Salsa query to get the builtins scope.
|
||||
///
|
||||
/// Can return None if a custom typeshed is used that is missing `builtins.pyi`.
|
||||
#[salsa::tracked]
|
||||
pub(crate) fn builtins_scope(db: &dyn Db) -> Option<ScopeId<'_>> {
|
||||
let builtins_name =
|
||||
ModuleName::new_static("builtins").expect("Expected 'builtins' to be a valid module name");
|
||||
let builtins_file = resolve_module(db.upcast(), builtins_name)?.file();
|
||||
Some(global_scope(db, builtins_file))
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use salsa::DbWithJar;
|
||||
use red_knot_module_resolver::Db as ResolverDb;
|
||||
use ruff_db::{Db as SourceDb, Upcast};
|
||||
|
||||
use crate::builtins::builtins_scope;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
@@ -28,6 +29,7 @@ pub struct Jar(
|
||||
infer_definition_types,
|
||||
infer_expression_types,
|
||||
infer_scope_types,
|
||||
builtins_scope,
|
||||
);
|
||||
|
||||
/// Database giving access to semantic information about a Python program.
|
||||
|
||||
@@ -6,6 +6,7 @@ pub use db::{Db, Jar};
|
||||
pub use semantic_model::{HasTy, SemanticModel};
|
||||
|
||||
pub mod ast_node_ref;
|
||||
mod builtins;
|
||||
mod db;
|
||||
mod node_key;
|
||||
pub mod semantic_index;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::name::Name;
|
||||
|
||||
use crate::builtins::builtins_scope;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId};
|
||||
use crate::semantic_index::{global_scope, symbol_table, use_def_map};
|
||||
@@ -47,6 +48,15 @@ pub(crate) fn global_symbol_ty_by_name<'db>(db: &'db dyn Db, file: File, name: &
|
||||
symbol_ty_by_name(db, global_scope(db, file), name)
|
||||
}
|
||||
|
||||
/// Shorthand for `symbol_ty` that looks up a symbol in the builtins.
|
||||
///
|
||||
/// Returns `None` if the builtins module isn't available for some reason.
|
||||
pub(crate) fn builtins_symbol_ty_by_name<'db>(db: &'db dyn Db, name: &str) -> Type<'db> {
|
||||
builtins_scope(db)
|
||||
.map(|builtins| symbol_ty_by_name(db, builtins, name))
|
||||
.unwrap_or(Type::Unbound)
|
||||
}
|
||||
|
||||
/// Infer the type of a [`Definition`].
|
||||
pub(crate) fn definition_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> Type<'db> {
|
||||
let inference = infer_definition_types(db, definition);
|
||||
|
||||
@@ -29,13 +29,9 @@ impl Display for DisplayType<'_> {
|
||||
write!(f, "<module '{:?}'>", file.path(self.db.upcast()))
|
||||
}
|
||||
// TODO functions and classes should display using a fully qualified name
|
||||
Type::Class(class) => {
|
||||
f.write_str("Literal[")?;
|
||||
f.write_str(&class.name(self.db))?;
|
||||
f.write_str("]")
|
||||
}
|
||||
Type::Class(class) => write!(f, "Literal[{}]", class.name(self.db)),
|
||||
Type::Instance(class) => f.write_str(&class.name(self.db)),
|
||||
Type::Function(function) => f.write_str(&function.name(self.db)),
|
||||
Type::Function(function) => write!(f, "Literal[{}]", function.name(self.db)),
|
||||
Type::Union(union) => union.display(self.db).fmt(f),
|
||||
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
||||
Type::IntLiteral(n) => write!(f, "Literal[{n}]"),
|
||||
|
||||
@@ -29,15 +29,16 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{ExprContext, TypeParams};
|
||||
|
||||
use crate::builtins::builtins_scope;
|
||||
use crate::semantic_index::ast_ids::{HasScopedAstId, HasScopedUseId, ScopedExpressionId};
|
||||
use crate::semantic_index::definition::{Definition, DefinitionKind, DefinitionNodeKey};
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::semantic_index::symbol::NodeWithScopeKind;
|
||||
use crate::semantic_index::symbol::{NodeWithScopeRef, ScopeId};
|
||||
use crate::semantic_index::symbol::{FileScopeId, NodeWithScopeKind, NodeWithScopeRef, ScopeId};
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::types::{
|
||||
definitions_ty, global_symbol_ty_by_name, ClassType, FunctionType, Name, Type, UnionTypeBuilder,
|
||||
builtins_symbol_ty_by_name, definitions_ty, global_symbol_ty_by_name, ClassType, FunctionType,
|
||||
Name, Type, UnionTypeBuilder,
|
||||
};
|
||||
use crate::Db;
|
||||
|
||||
@@ -46,9 +47,9 @@ use crate::Db;
|
||||
/// scope.
|
||||
#[salsa::tracked(return_ref)]
|
||||
pub(crate) fn infer_scope_types<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> TypeInference<'db> {
|
||||
let _span = tracing::trace_span!("infer_scope_types", ?scope).entered();
|
||||
|
||||
let file = scope.file(db);
|
||||
let _span = tracing::trace_span!("infer_scope_types", ?scope, ?file).entered();
|
||||
|
||||
// Using the index here is fine because the code below depends on the AST anyway.
|
||||
// The isolation of the query is by the return inferred types.
|
||||
let index = semantic_index(db, file);
|
||||
@@ -63,9 +64,10 @@ pub(crate) fn infer_definition_types<'db>(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
) -> TypeInference<'db> {
|
||||
let _span = tracing::trace_span!("infer_definition_types", ?definition).entered();
|
||||
let file = definition.file(db);
|
||||
let _span = tracing::trace_span!("infer_definition_types", ?definition, ?file,).entered();
|
||||
|
||||
let index = semantic_index(db, definition.file(db));
|
||||
let index = semantic_index(db, file);
|
||||
|
||||
TypeInferenceBuilder::new(db, InferenceRegion::Definition(definition), index).finish()
|
||||
}
|
||||
@@ -80,9 +82,10 @@ pub(crate) fn infer_expression_types<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
) -> TypeInference<'db> {
|
||||
let _span = tracing::trace_span!("infer_expression_types", ?expression).entered();
|
||||
let file = expression.file(db);
|
||||
let _span = tracing::trace_span!("infer_expression_types", ?expression, ?file).entered();
|
||||
|
||||
let index = semantic_index(db, expression.file(db));
|
||||
let index = semantic_index(db, file);
|
||||
|
||||
TypeInferenceBuilder::new(db, InferenceRegion::Expression(expression), index).finish()
|
||||
}
|
||||
@@ -684,7 +687,18 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let symbol = symbols.symbol_by_name(id).unwrap();
|
||||
if !symbol.is_defined() || !self.scope.is_function_like(self.db) {
|
||||
// implicit global
|
||||
Some(global_symbol_ty_by_name(self.db, self.file, id))
|
||||
let mut unbound_ty = if file_scope_id == FileScopeId::global() {
|
||||
Type::Unbound
|
||||
} else {
|
||||
global_symbol_ty_by_name(self.db, self.file, id)
|
||||
};
|
||||
// fallback to builtins
|
||||
if matches!(unbound_ty, Type::Unbound)
|
||||
&& Some(self.scope) != builtins_scope(self.db)
|
||||
{
|
||||
unbound_ty = builtins_symbol_ty_by_name(self.db, id);
|
||||
}
|
||||
Some(unbound_ty)
|
||||
} else {
|
||||
Some(Type::Unbound)
|
||||
}
|
||||
@@ -790,6 +804,7 @@ mod tests {
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
use ruff_python_ast::name::Name;
|
||||
|
||||
use crate::builtins::builtins_scope;
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::semantic_index;
|
||||
@@ -817,6 +832,23 @@ mod tests {
|
||||
db
|
||||
}
|
||||
|
||||
fn setup_db_with_custom_typeshed(typeshed: &str) -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
Program::new(
|
||||
&db,
|
||||
TargetVersion::Py38,
|
||||
SearchPathSettings {
|
||||
extra_paths: Vec::new(),
|
||||
workspace_root: SystemPathBuf::from("/src"),
|
||||
site_packages: None,
|
||||
custom_typeshed: Some(SystemPathBuf::from(typeshed)),
|
||||
},
|
||||
);
|
||||
|
||||
db
|
||||
}
|
||||
|
||||
fn assert_public_ty(db: &TestDb, file_name: &str, symbol_name: &str, expected: &str) {
|
||||
let file = system_path_to_file(db, file_name).expect("Expected file to exist.");
|
||||
|
||||
@@ -1368,6 +1400,80 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_symbol_vendored_stdlib() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_file("/src/a.py", "c = copyright")?;
|
||||
|
||||
assert_public_ty(&db, "/src/a.py", "c", "Literal[copyright]");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_symbol_custom_stdlib() -> anyhow::Result<()> {
|
||||
let mut db = setup_db_with_custom_typeshed("/typeshed");
|
||||
|
||||
db.write_files([
|
||||
("/src/a.py", "c = copyright"),
|
||||
(
|
||||
"/typeshed/stdlib/builtins.pyi",
|
||||
"def copyright() -> None: ...",
|
||||
),
|
||||
("/typeshed/stdlib/VERSIONS", "builtins: 3.8-"),
|
||||
])?;
|
||||
|
||||
assert_public_ty(&db, "/src/a.py", "c", "Literal[copyright]");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_global_later_defined() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_file("/src/a.py", "x = foo; foo = 1")?;
|
||||
|
||||
assert_public_ty(&db, "/src/a.py", "x", "Unbound");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_builtin_later_defined() -> anyhow::Result<()> {
|
||||
let mut db = setup_db_with_custom_typeshed("/typeshed");
|
||||
|
||||
db.write_files([
|
||||
("/src/a.py", "x = foo"),
|
||||
("/typeshed/stdlib/builtins.pyi", "foo = bar; bar = 1"),
|
||||
("/typeshed/stdlib/VERSIONS", "builtins: 3.8-"),
|
||||
])?;
|
||||
|
||||
assert_public_ty(&db, "/src/a.py", "x", "Unbound");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_builtins() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_file("/src/a.py", "import builtins; x = builtins.copyright")?;
|
||||
|
||||
assert_public_ty(&db, "/src/a.py", "x", "Literal[copyright]");
|
||||
// imported builtins module is the same file as the implicit builtins
|
||||
let file = system_path_to_file(&db, "/src/a.py").expect("Expected file to exist.");
|
||||
let builtins_ty = global_symbol_ty_by_name(&db, file, "builtins");
|
||||
let Type::Module(builtins_file) = builtins_ty else {
|
||||
panic!("Builtins are not a module?");
|
||||
};
|
||||
let implicit_builtins_file = builtins_scope(&db).expect("builtins to exist").file(&db);
|
||||
assert_eq!(builtins_file, implicit_builtins_file);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn first_public_def<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> {
|
||||
let scope = global_scope(db, file);
|
||||
*use_def_map(db, scope)
|
||||
|
||||
@@ -137,7 +137,7 @@ fn benchmark_incremental(criterion: &mut Criterion) {
|
||||
|
||||
case.fs
|
||||
.write_file(
|
||||
SystemPath::new("/src/foo.py"),
|
||||
SystemPath::new("/src/bar.py"),
|
||||
format!("{BAR_CODE}\n# A comment\n"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -22,6 +22,10 @@ impl<I: Idx, T> IndexVec<I, T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.raw.reserve(additional)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -1914,6 +1914,10 @@ impl<'a> Checker<'a> {
|
||||
binding_id
|
||||
}
|
||||
|
||||
fn reserve(&mut self) {
|
||||
self.semantic.reserve(self.locator.contents().len());
|
||||
}
|
||||
|
||||
fn bind_builtins(&mut self) {
|
||||
for builtin in PYTHON_BUILTINS
|
||||
.iter()
|
||||
@@ -2411,6 +2415,7 @@ pub(crate) fn check_ast(
|
||||
cell_offsets,
|
||||
notebook_index,
|
||||
);
|
||||
checker.reserve();
|
||||
checker.bind_builtins();
|
||||
|
||||
// Iterate over the AST.
|
||||
@@ -2436,5 +2441,29 @@ pub(crate) fn check_ast(
|
||||
checker.analyze.scopes.push(ScopeId::global());
|
||||
analyze::deferred_scopes(&mut checker);
|
||||
|
||||
// println!("source: {:?}", checker.locator.contents().len());
|
||||
// println!("nodes: {:?}", checker.semantic().nodes.len());
|
||||
// println!("lines: {:?}", checker.locator.contents().lines().count());
|
||||
// println!("scopes: {:?}", checker.semantic.scopes.len());
|
||||
// println!("bindings: {:?}", checker.semantic.bindings.len());
|
||||
// println!("definitions: {:?}", checker.semantic.definitions.len());
|
||||
// println!(
|
||||
// "resolved_references: {:?}",
|
||||
// checker.semantic.resolved_references.len()
|
||||
// );
|
||||
// println!(
|
||||
// "unresolved_references: {:?}",
|
||||
// checker.semantic.unresolved_references.len()
|
||||
// );
|
||||
// println!("globals: {:?}", checker.semantic.globals.len());
|
||||
// println!(
|
||||
// "resolved_names: {:?}",
|
||||
// checker.semantic.resolved_names.len()
|
||||
// );
|
||||
// println!(
|
||||
// "shadowed_bindings: {:?}",
|
||||
// checker.semantic.shadowed_bindings.len()
|
||||
// );
|
||||
|
||||
checker.diagnostics
|
||||
}
|
||||
|
||||
@@ -357,6 +357,10 @@ pub struct BindingId;
|
||||
pub struct Bindings<'a>(IndexVec<BindingId, Binding<'a>>);
|
||||
|
||||
impl<'a> Bindings<'a> {
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.0.reserve(additional)
|
||||
}
|
||||
|
||||
/// Pushes a new [`Binding`] and returns its [`BindingId`].
|
||||
pub fn push(&mut self, binding: Binding<'a>) -> BindingId {
|
||||
self.0.push(binding)
|
||||
|
||||
@@ -187,6 +187,10 @@ impl<'a> Definition<'a> {
|
||||
pub struct Definitions<'a>(IndexVec<DefinitionId, Definition<'a>>);
|
||||
|
||||
impl<'a> Definitions<'a> {
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.0.reserve(additional)
|
||||
}
|
||||
|
||||
pub fn for_module(definition: Module<'a>) -> Self {
|
||||
Self(IndexVec::from_raw(vec![Definition::Module(definition)]))
|
||||
}
|
||||
|
||||
@@ -16,13 +16,21 @@ use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
|
||||
pub struct GlobalsId;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct GlobalsArena<'a>(IndexVec<GlobalsId, Globals<'a>>);
|
||||
pub struct GlobalsArena<'a>(IndexVec<GlobalsId, Globals<'a>>);
|
||||
|
||||
impl<'a> GlobalsArena<'a> {
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.0.reserve(additional)
|
||||
}
|
||||
|
||||
/// Inserts a new set of global names into the global names arena and returns its unique id.
|
||||
pub(crate) fn push(&mut self, globals: Globals<'a>) -> GlobalsId {
|
||||
self.0.push(globals)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Index<GlobalsId> for GlobalsArena<'a> {
|
||||
|
||||
@@ -33,7 +33,7 @@ pub struct SemanticModel<'a> {
|
||||
module: Module<'a>,
|
||||
|
||||
/// Stack of all AST nodes in the program.
|
||||
nodes: Nodes<'a>,
|
||||
pub nodes: Nodes<'a>,
|
||||
|
||||
/// The ID of the current AST node.
|
||||
node_id: Option<NodeId>,
|
||||
@@ -58,13 +58,13 @@ pub struct SemanticModel<'a> {
|
||||
pub bindings: Bindings<'a>,
|
||||
|
||||
/// Stack of all references created in any scope, at any point in execution.
|
||||
resolved_references: ResolvedReferences,
|
||||
pub resolved_references: ResolvedReferences,
|
||||
|
||||
/// Stack of all unresolved references created in any scope, at any point in execution.
|
||||
unresolved_references: UnresolvedReferences,
|
||||
pub unresolved_references: UnresolvedReferences,
|
||||
|
||||
/// Arena of global bindings.
|
||||
globals: GlobalsArena<'a>,
|
||||
pub globals: GlobalsArena<'a>,
|
||||
|
||||
/// Map from binding ID to binding ID that it shadows (in another scope).
|
||||
///
|
||||
@@ -129,7 +129,7 @@ pub struct SemanticModel<'a> {
|
||||
|
||||
/// Map from [`ast::ExprName`] node (represented as a [`NameId`]) to the [`Binding`] to which
|
||||
/// it resolved (represented as a [`BindingId`]).
|
||||
resolved_names: FxHashMap<NameId, BindingId>,
|
||||
pub resolved_names: FxHashMap<NameId, BindingId>,
|
||||
}
|
||||
|
||||
impl<'a> SemanticModel<'a> {
|
||||
@@ -159,6 +159,37 @@ impl<'a> SemanticModel<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self, capacity: usize) {
|
||||
const NODES_PER_BYTE: f64 = 0.0735463782722034;
|
||||
const SCOPES_PER_BYTE: f64 = 0.003789696886327717;
|
||||
const BINDINGS_PER_BYTE: f64 = 0.033044323498783695;
|
||||
const DEFINITIONS_PER_BYTE: f64 = 0.001704180043788056;
|
||||
const RESOLVED_REFERENCES_PER_BYTE: f64 = 0.01967324945679754;
|
||||
const UNRESOLVED_REFERENCES_PER_BYTE: f64 = 0.0001548020396830927;
|
||||
const GLOBALS_PER_BYTE: f64 = 3.8755626455402864e-06;
|
||||
const RESOLVED_NAMES_PER_BYTE: f64 = 0.019565557549871618;
|
||||
const SHADOWED_BINDINGS_PER_BYTE: f64 = 0.00017560708107067872;
|
||||
|
||||
self.nodes
|
||||
.reserve((NODES_PER_BYTE * capacity as f64) as usize);
|
||||
self.scopes
|
||||
.reserve((SCOPES_PER_BYTE * capacity as f64) as usize);
|
||||
self.bindings
|
||||
.reserve((BINDINGS_PER_BYTE * capacity as f64) as usize);
|
||||
self.definitions
|
||||
.reserve((DEFINITIONS_PER_BYTE * capacity as f64) as usize);
|
||||
self.resolved_references
|
||||
.reserve((RESOLVED_REFERENCES_PER_BYTE * capacity as f64) as usize);
|
||||
self.unresolved_references
|
||||
.reserve((UNRESOLVED_REFERENCES_PER_BYTE * capacity as f64) as usize);
|
||||
self.globals
|
||||
.reserve((GLOBALS_PER_BYTE * capacity as f64) as usize);
|
||||
self.resolved_names
|
||||
.reserve((RESOLVED_NAMES_PER_BYTE * capacity as f64) as usize);
|
||||
self.shadowed_bindings
|
||||
.reserve((SHADOWED_BINDINGS_PER_BYTE * capacity as f64) as usize);
|
||||
}
|
||||
|
||||
/// Return the [`Binding`] for the given [`BindingId`].
|
||||
#[inline]
|
||||
pub fn binding(&self, id: BindingId) -> &Binding<'a> {
|
||||
@@ -2328,7 +2359,7 @@ impl Ranged for ImportedName {
|
||||
/// A unique identifier for an [`ast::ExprName`]. No two names can even appear at the same location
|
||||
/// in the source code, so the starting offset is a cheap and sufficient unique identifier.
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
struct NameId(TextSize);
|
||||
pub struct NameId(TextSize);
|
||||
|
||||
impl From<&ast::ExprName> for NameId {
|
||||
fn from(name: &ast::ExprName) -> Self {
|
||||
|
||||
@@ -47,6 +47,14 @@ impl<'a> Nodes<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.nodes.reserve(additional)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.nodes.len()
|
||||
}
|
||||
|
||||
/// Return the [`NodeId`] of the parent node.
|
||||
#[inline]
|
||||
pub fn parent_id(&self, node_id: NodeId) -> Option<NodeId> {
|
||||
|
||||
@@ -114,9 +114,13 @@ pub struct ResolvedReferenceId;
|
||||
|
||||
/// The references of a program indexed by [`ResolvedReferenceId`].
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ResolvedReferences(IndexVec<ResolvedReferenceId, ResolvedReference>);
|
||||
pub struct ResolvedReferences(IndexVec<ResolvedReferenceId, ResolvedReference>);
|
||||
|
||||
impl ResolvedReferences {
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.0.reserve(additional)
|
||||
}
|
||||
|
||||
/// Pushes a new [`ResolvedReference`] and returns its [`ResolvedReferenceId`].
|
||||
pub(crate) fn push(
|
||||
&mut self,
|
||||
@@ -195,9 +199,13 @@ bitflags! {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct UnresolvedReferences(Vec<UnresolvedReference>);
|
||||
pub struct UnresolvedReferences(Vec<UnresolvedReference>);
|
||||
|
||||
impl UnresolvedReferences {
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.0.reserve(additional)
|
||||
}
|
||||
|
||||
/// Pushes a new [`UnresolvedReference`].
|
||||
pub(crate) fn push(
|
||||
&mut self,
|
||||
|
||||
@@ -203,6 +203,10 @@ impl ScopeId {
|
||||
pub struct Scopes<'a>(IndexVec<ScopeId, Scope<'a>>);
|
||||
|
||||
impl<'a> Scopes<'a> {
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.0.reserve(additional)
|
||||
}
|
||||
|
||||
/// Returns a reference to the global scope
|
||||
pub(crate) fn global(&self) -> &Scope<'a> {
|
||||
&self[ScopeId::global()]
|
||||
|
||||
@@ -74,13 +74,13 @@ pub struct Options {
|
||||
)]
|
||||
pub extend: Option<String>,
|
||||
|
||||
/// The style in which violation messages should be formatted: `"full"`
|
||||
/// (shows source), `"concise"` (default), `"grouped"` (group messages by file), `"json"`
|
||||
/// The style in which violation messages should be formatted: `"full"` (default)
|
||||
/// (shows source), `"concise"`, `"grouped"` (group messages by file), `"json"`
|
||||
/// (machine-readable), `"junit"` (machine-readable XML), `"github"` (GitHub
|
||||
/// Actions annotations), `"gitlab"` (GitLab CI code quality report),
|
||||
/// `"pylint"` (Pylint text format) or `"azure"` (Azure Pipeline logging commands).
|
||||
#[option(
|
||||
default = r#""concise""#,
|
||||
default = r#""full""#,
|
||||
value_type = r#""full" | "concise" | "grouped" | "json" | "junit" | "github" | "gitlab" | "pylint" | "azure""#,
|
||||
example = r#"
|
||||
# Group violations by containing file.
|
||||
|
||||
2
ruff.schema.json
generated
2
ruff.schema.json
generated
@@ -531,7 +531,7 @@
|
||||
}
|
||||
},
|
||||
"output-format": {
|
||||
"description": "The style in which violation messages should be formatted: `\"full\"` (shows source), `\"concise\"` (default), `\"grouped\"` (group messages by file), `\"json\"` (machine-readable), `\"junit\"` (machine-readable XML), `\"github\"` (GitHub Actions annotations), `\"gitlab\"` (GitLab CI code quality report), `\"pylint\"` (Pylint text format) or `\"azure\"` (Azure Pipeline logging commands).",
|
||||
"description": "The style in which violation messages should be formatted: `\"full\"` (default) (shows source), `\"concise\"`, `\"grouped\"` (group messages by file), `\"json\"` (machine-readable), `\"junit\"` (machine-readable XML), `\"github\"` (GitHub Actions annotations), `\"gitlab\"` (GitLab CI code quality report), `\"pylint\"` (Pylint text format) or `\"azure\"` (Azure Pipeline logging commands).",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/OutputFormat"
|
||||
|
||||
Reference in New Issue
Block a user