Compare commits
1 Commits
0.6.4
...
dhruv/rest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dc0a523de |
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,34 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.4
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-builtins`\] Use dynamic builtins list based on Python version ([#13172](https://github.com/astral-sh/ruff/pull/13172))
|
||||
- \[`pydoclint`\] Permit yielding `None` in `DOC402` and `DOC403` ([#13148](https://github.com/astral-sh/ruff/pull/13148))
|
||||
- \[`pylint`\] Update diagnostic message for `PLW3201` ([#13194](https://github.com/astral-sh/ruff/pull/13194))
|
||||
- \[`ruff`\] Implement `post-init-default` (`RUF033`) ([#13192](https://github.com/astral-sh/ruff/pull/13192))
|
||||
- \[`ruff`\] Implement useless if-else (`RUF034`) ([#13218](https://github.com/astral-sh/ruff/pull/13218))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-pyi`\] Respect `pep8_naming.classmethod-decorators` settings when determining if a method is a classmethod in `custom-type-var-return-type` (`PYI019`) ([#13162](https://github.com/astral-sh/ruff/pull/13162))
|
||||
- \[`flake8-pyi`\] Teach various rules that annotations might be stringized ([#12951](https://github.com/astral-sh/ruff/pull/12951))
|
||||
- \[`pylint`\] Avoid `no-self-use` for `attrs`-style validators ([#13166](https://github.com/astral-sh/ruff/pull/13166))
|
||||
- \[`pylint`\] Recurse into subscript subexpressions when searching for list/dict lookups (`PLR1733`, `PLR1736`) ([#13186](https://github.com/astral-sh/ruff/pull/13186))
|
||||
- \[`pyupgrade`\] Detect `aiofiles.open` calls in `UP015` ([#13173](https://github.com/astral-sh/ruff/pull/13173))
|
||||
- \[`pyupgrade`\] Mark `sys.version_info[0] < 3` and similar comparisons as outdated (`UP036`) ([#13175](https://github.com/astral-sh/ruff/pull/13175))
|
||||
|
||||
### CLI
|
||||
|
||||
- Enrich messages of SARIF results ([#13180](https://github.com/astral-sh/ruff/pull/13180))
|
||||
- Handle singular case for incompatible rules warning in `ruff format` output ([#13212](https://github.com/astral-sh/ruff/pull/13212))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`pydocstyle`\] Improve heuristics for detecting Google-style docstrings ([#13142](https://github.com/astral-sh/ruff/pull/13142))
|
||||
- \[`refurb`\] Treat `sep` arguments with effects as unsafe removals (`FURB105`) ([#13165](https://github.com/astral-sh/ruff/pull/13165))
|
||||
|
||||
## 0.6.3
|
||||
|
||||
### Preview features
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2091,7 +2091,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.6.4"
|
||||
version = "0.6.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2284,7 +2284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.6.4"
|
||||
version = "0.6.3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2604,7 +2604,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.6.4"
|
||||
version = "0.6.3"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
|
||||
@@ -136,8 +136,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.6.4/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.6.4/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.6.3/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.6.3/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -170,7 +170,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.6.4
|
||||
rev: v0.6.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
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 crate::module_name::ModuleName;
|
||||
use crate::module_resolver::resolve_module;
|
||||
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, builtins_name)?.file();
|
||||
Some(global_scope(db, builtins_file))
|
||||
}
|
||||
@@ -10,6 +10,7 @@ pub use python_version::PythonVersion;
|
||||
pub use semantic_model::{HasTy, SemanticModel};
|
||||
|
||||
pub mod ast_node_ref;
|
||||
mod builtins;
|
||||
mod db;
|
||||
mod module_name;
|
||||
mod module_resolver;
|
||||
@@ -19,7 +20,6 @@ mod python_version;
|
||||
pub mod semantic_index;
|
||||
mod semantic_model;
|
||||
pub(crate) mod site_packages;
|
||||
mod stdlib;
|
||||
pub mod types;
|
||||
|
||||
type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
@@ -193,11 +193,8 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
countme::Count::default(),
|
||||
);
|
||||
|
||||
let existing_definition = self
|
||||
.definitions_by_node
|
||||
self.definitions_by_node
|
||||
.insert(definition_node.key(), definition);
|
||||
debug_assert_eq!(existing_definition, None);
|
||||
|
||||
self.current_use_def_map_mut()
|
||||
.record_definition(symbol, definition);
|
||||
|
||||
@@ -330,11 +327,10 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
// Insert a mapping from the parameter to the same definition.
|
||||
// This ensures that calling `HasTy::ty` on the inner parameter returns
|
||||
// a valid type (and doesn't panic)
|
||||
let existing_definition = self.definitions_by_node.insert(
|
||||
self.definitions_by_node.insert(
|
||||
DefinitionNodeRef::from(AnyParameterRef::Variadic(&with_default.parameter)).key(),
|
||||
definition,
|
||||
);
|
||||
debug_assert_eq!(existing_definition, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,7 +668,6 @@ where
|
||||
ForStmtDefinitionNodeRef {
|
||||
iterable: &node.iter,
|
||||
target: name_node,
|
||||
is_async: node.is_async,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -152,7 +152,6 @@ pub(crate) struct WithItemDefinitionNodeRef<'a> {
|
||||
pub(crate) struct ForStmtDefinitionNodeRef<'a> {
|
||||
pub(crate) iterable: &'a ast::Expr,
|
||||
pub(crate) target: &'a ast::ExprName,
|
||||
pub(crate) is_async: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -207,15 +206,12 @@ impl DefinitionNodeRef<'_> {
|
||||
DefinitionNodeRef::AugmentedAssignment(augmented_assignment) => {
|
||||
DefinitionKind::AugmentedAssignment(AstNodeRef::new(parsed, augmented_assignment))
|
||||
}
|
||||
DefinitionNodeRef::For(ForStmtDefinitionNodeRef {
|
||||
iterable,
|
||||
target,
|
||||
is_async,
|
||||
}) => DefinitionKind::For(ForStmtDefinitionKind {
|
||||
iterable: AstNodeRef::new(parsed.clone(), iterable),
|
||||
target: AstNodeRef::new(parsed, target),
|
||||
is_async,
|
||||
}),
|
||||
DefinitionNodeRef::For(ForStmtDefinitionNodeRef { iterable, target }) => {
|
||||
DefinitionKind::For(ForStmtDefinitionKind {
|
||||
iterable: AstNodeRef::new(parsed.clone(), iterable),
|
||||
target: AstNodeRef::new(parsed, target),
|
||||
})
|
||||
}
|
||||
DefinitionNodeRef::Comprehension(ComprehensionDefinitionNodeRef {
|
||||
iterable,
|
||||
target,
|
||||
@@ -269,7 +265,6 @@ impl DefinitionNodeRef<'_> {
|
||||
Self::For(ForStmtDefinitionNodeRef {
|
||||
iterable: _,
|
||||
target,
|
||||
is_async: _,
|
||||
}) => target.into(),
|
||||
Self::Comprehension(ComprehensionDefinitionNodeRef { target, .. }) => target.into(),
|
||||
Self::Parameter(node) => match node {
|
||||
@@ -393,7 +388,6 @@ impl WithItemDefinitionKind {
|
||||
pub struct ForStmtDefinitionKind {
|
||||
iterable: AstNodeRef<ast::Expr>,
|
||||
target: AstNodeRef<ast::ExprName>,
|
||||
is_async: bool,
|
||||
}
|
||||
|
||||
impl ForStmtDefinitionKind {
|
||||
@@ -404,10 +398,6 @@ impl ForStmtDefinitionKind {
|
||||
pub(crate) fn target(&self) -> &ast::ExprName {
|
||||
self.target.node()
|
||||
}
|
||||
|
||||
pub(crate) fn is_async(&self) -> bool {
|
||||
self.is_async
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{resolve_module, Module};
|
||||
use crate::semantic_index::ast_ids::HasScopedAstId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::types::{definition_ty, global_symbol_ty, infer_scope_types, Type};
|
||||
use crate::types::{definition_ty, global_symbol_ty_by_name, infer_scope_types, Type};
|
||||
use crate::Db;
|
||||
|
||||
pub struct SemanticModel<'db> {
|
||||
@@ -40,7 +40,7 @@ impl<'db> SemanticModel<'db> {
|
||||
}
|
||||
|
||||
pub fn global_symbol_ty(&self, module: &Module, symbol_name: &str) -> Type<'db> {
|
||||
global_symbol_ty(self.db, module.file(), symbol_name)
|
||||
global_symbol_ty_by_name(self.db, module.file(), symbol_name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::resolve_module;
|
||||
use crate::semantic_index::global_scope;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::types::{global_symbol_ty, Type};
|
||||
use crate::Db;
|
||||
|
||||
/// Enumeration of various core stdlib modules, for which we have dedicated Salsa queries.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum CoreStdlibModule {
|
||||
Builtins,
|
||||
Types,
|
||||
Typeshed,
|
||||
}
|
||||
|
||||
impl CoreStdlibModule {
|
||||
fn name(self) -> ModuleName {
|
||||
let module_name = match self {
|
||||
Self::Builtins => "builtins",
|
||||
Self::Types => "types",
|
||||
Self::Typeshed => "_typeshed",
|
||||
};
|
||||
ModuleName::new_static(module_name)
|
||||
.unwrap_or_else(|| panic!("{module_name} should be a valid module name!"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in a given core module
|
||||
///
|
||||
/// Returns `Unbound` if the given core module cannot be resolved for some reason
|
||||
fn core_module_symbol_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
core_module: CoreStdlibModule,
|
||||
symbol: &str,
|
||||
) -> Type<'db> {
|
||||
resolve_module(db, core_module.name())
|
||||
.map(|module| global_symbol_ty(db, module.file(), symbol))
|
||||
.unwrap_or(Type::Unbound)
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in the builtins namespace.
|
||||
///
|
||||
/// Returns `Unbound` if the `builtins` module isn't available for some reason.
|
||||
#[inline]
|
||||
pub(crate) fn builtins_symbol_ty<'db>(db: &'db dyn Db, symbol: &str) -> Type<'db> {
|
||||
core_module_symbol_ty(db, CoreStdlibModule::Builtins, symbol)
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in the `types` module namespace.
|
||||
///
|
||||
/// Returns `Unbound` if the `types` module isn't available for some reason.
|
||||
#[inline]
|
||||
pub(crate) fn types_symbol_ty<'db>(db: &'db dyn Db, symbol: &str) -> Type<'db> {
|
||||
core_module_symbol_ty(db, CoreStdlibModule::Types, symbol)
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in the `_typeshed` module namespace.
|
||||
///
|
||||
/// Returns `Unbound` if the `_typeshed` module isn't available for some reason.
|
||||
#[inline]
|
||||
pub(crate) fn typeshed_symbol_ty<'db>(db: &'db dyn Db, symbol: &str) -> Type<'db> {
|
||||
core_module_symbol_ty(db, CoreStdlibModule::Typeshed, symbol)
|
||||
}
|
||||
|
||||
/// Get the scope of a core stdlib module.
|
||||
///
|
||||
/// Can return `None` if a custom typeshed is used that is missing the core module in question.
|
||||
fn core_module_scope(db: &dyn Db, core_module: CoreStdlibModule) -> Option<ScopeId<'_>> {
|
||||
resolve_module(db, core_module.name()).map(|module| global_scope(db, module.file()))
|
||||
}
|
||||
|
||||
/// Get the `builtins` module scope.
|
||||
///
|
||||
/// Can return `None` if a custom typeshed is used that is missing `builtins.pyi`.
|
||||
pub(crate) fn builtins_module_scope(db: &dyn Db) -> Option<ScopeId<'_>> {
|
||||
core_module_scope(db, CoreStdlibModule::Builtins)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use infer::TypeInferenceBuilder;
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
use crate::builtins::builtins_scope;
|
||||
use crate::semantic_index::ast_ids::HasScopedAstId;
|
||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId};
|
||||
@@ -9,7 +9,6 @@ use crate::semantic_index::{
|
||||
global_scope, semantic_index, symbol_table, use_def_map, DefinitionWithConstraints,
|
||||
DefinitionWithConstraintsIterator,
|
||||
};
|
||||
use crate::stdlib::{builtins_symbol_ty, types_symbol_ty, typeshed_symbol_ty};
|
||||
use crate::types::narrow::narrowing_constraint;
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
@@ -41,7 +40,7 @@ pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics {
|
||||
}
|
||||
|
||||
/// Infer the public type of a symbol (its type as seen from outside its scope).
|
||||
pub(crate) fn symbol_ty_by_id<'db>(
|
||||
pub(crate) fn symbol_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
scope: ScopeId<'db>,
|
||||
symbol: ScopedSymbolId,
|
||||
@@ -59,17 +58,30 @@ pub(crate) fn symbol_ty_by_id<'db>(
|
||||
}
|
||||
|
||||
/// Shorthand for `symbol_ty` that takes a symbol name instead of an ID.
|
||||
pub(crate) fn symbol_ty<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Type<'db> {
|
||||
pub(crate) fn symbol_ty_by_name<'db>(
|
||||
db: &'db dyn Db,
|
||||
scope: ScopeId<'db>,
|
||||
name: &str,
|
||||
) -> Type<'db> {
|
||||
let table = symbol_table(db, scope);
|
||||
table
|
||||
.symbol_id_by_name(name)
|
||||
.map(|symbol| symbol_ty_by_id(db, scope, symbol))
|
||||
.map(|symbol| symbol_ty(db, scope, symbol))
|
||||
.unwrap_or(Type::Unbound)
|
||||
}
|
||||
|
||||
/// Shorthand for `symbol_ty` that looks up a module-global symbol by name in a file.
|
||||
pub(crate) fn global_symbol_ty<'db>(db: &'db dyn Db, file: File, name: &str) -> Type<'db> {
|
||||
symbol_ty(db, global_scope(db, file), name)
|
||||
pub(crate) fn global_symbol_ty_by_name<'db>(db: &'db dyn Db, file: File, name: &str) -> Type<'db> {
|
||||
symbol_ty_by_name(db, global_scope(db, file), name)
|
||||
}
|
||||
|
||||
/// Shorthand for `symbol_ty` that looks up a symbol in the builtins.
|
||||
///
|
||||
/// Returns `Unbound` 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`].
|
||||
@@ -294,9 +306,13 @@ impl<'db> Type<'db> {
|
||||
pub fn replace_unbound_with(&self, db: &'db dyn Db, replacement: Type<'db>) -> Type<'db> {
|
||||
match self {
|
||||
Type::Unbound => replacement,
|
||||
Type::Union(union) => {
|
||||
union.map(db, |element| element.replace_unbound_with(db, replacement))
|
||||
}
|
||||
Type::Union(union) => union
|
||||
.elements(db)
|
||||
.into_iter()
|
||||
.fold(UnionBuilder::new(db), |builder, ty| {
|
||||
builder.add(ty.replace_unbound_with(db, replacement))
|
||||
})
|
||||
.build(),
|
||||
ty => *ty,
|
||||
}
|
||||
}
|
||||
@@ -315,7 +331,7 @@ impl<'db> Type<'db> {
|
||||
/// us to explicitly consider whether to handle an error or propagate
|
||||
/// it up the call stack.
|
||||
#[must_use]
|
||||
pub fn member(&self, db: &'db dyn Db, name: &str) -> Type<'db> {
|
||||
pub fn member(&self, db: &'db dyn Db, name: &ast::name::Name) -> Type<'db> {
|
||||
match self {
|
||||
Type::Any => Type::Any,
|
||||
Type::Never => {
|
||||
@@ -332,13 +348,19 @@ impl<'db> Type<'db> {
|
||||
// TODO: attribute lookup on function type
|
||||
Type::Unknown
|
||||
}
|
||||
Type::Module(file) => global_symbol_ty(db, *file, name),
|
||||
Type::Module(file) => global_symbol_ty_by_name(db, *file, name),
|
||||
Type::Class(class) => class.class_member(db, name),
|
||||
Type::Instance(_) => {
|
||||
// TODO MRO? get_own_instance_member, get_instance_member
|
||||
Type::Unknown
|
||||
}
|
||||
Type::Union(union) => union.map(db, |element| element.member(db, name)),
|
||||
Type::Union(union) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.fold(UnionBuilder::new(db), |builder, element_ty| {
|
||||
builder.add(element_ty.member(db, name))
|
||||
})
|
||||
.build(),
|
||||
Type::Intersection(_) => {
|
||||
// TODO perform the get_member on each type in the intersection
|
||||
// TODO return the intersection of those results
|
||||
@@ -393,52 +415,6 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the type of an object that is iterated over in some way,
|
||||
/// return the type of objects that are yielded by that iteration.
|
||||
///
|
||||
/// E.g., for the following loop, given the type of `x`, infer the type of `y`:
|
||||
/// ```python
|
||||
/// for y in x:
|
||||
/// pass
|
||||
/// ```
|
||||
fn iterate(&self, db: &'db dyn Db) -> IterationOutcome<'db> {
|
||||
// `self` represents the type of the iterable;
|
||||
// `__iter__` and `__next__` are both looked up on the class of the iterable:
|
||||
let iterable_meta_type = self.to_meta_type(db);
|
||||
|
||||
let dunder_iter_method = iterable_meta_type.member(db, "__iter__");
|
||||
if !dunder_iter_method.is_unbound() {
|
||||
let Some(iterator_ty) = dunder_iter_method.call(db) else {
|
||||
return IterationOutcome::NotIterable {
|
||||
not_iterable_ty: *self,
|
||||
};
|
||||
};
|
||||
|
||||
let dunder_next_method = iterator_ty.to_meta_type(db).member(db, "__next__");
|
||||
return dunder_next_method
|
||||
.call(db)
|
||||
.map(|element_ty| IterationOutcome::Iterable { element_ty })
|
||||
.unwrap_or(IterationOutcome::NotIterable {
|
||||
not_iterable_ty: *self,
|
||||
});
|
||||
}
|
||||
|
||||
// Although it's not considered great practice,
|
||||
// classes that define `__getitem__` are also iterable,
|
||||
// even if they do not define `__iter__`.
|
||||
//
|
||||
// TODO(Alex) this is only valid if the `__getitem__` method is annotated as
|
||||
// accepting `int` or `SupportsIndex`
|
||||
let dunder_get_item_method = iterable_meta_type.member(db, "__getitem__");
|
||||
|
||||
dunder_get_item_method
|
||||
.call(db)
|
||||
.map(|element_ty| IterationOutcome::Iterable { element_ty })
|
||||
.unwrap_or(IterationOutcome::NotIterable {
|
||||
not_iterable_ty: *self,
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_instance(&self) -> Type<'db> {
|
||||
match self {
|
||||
@@ -448,56 +424,6 @@ impl<'db> Type<'db> {
|
||||
_ => Type::Unknown, // TODO type errors
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a type that is assumed to represent an instance of a class,
|
||||
/// return a type that represents that class itself.
|
||||
#[must_use]
|
||||
pub fn to_meta_type(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
Type::Unbound => Type::Unbound,
|
||||
Type::Never => Type::Never,
|
||||
Type::Instance(class) => Type::Class(*class),
|
||||
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
|
||||
Type::BooleanLiteral(_) => builtins_symbol_ty(db, "bool"),
|
||||
Type::BytesLiteral(_) => builtins_symbol_ty(db, "bytes"),
|
||||
Type::IntLiteral(_) => builtins_symbol_ty(db, "int"),
|
||||
Type::Function(_) => types_symbol_ty(db, "FunctionType"),
|
||||
Type::Module(_) => types_symbol_ty(db, "ModuleType"),
|
||||
Type::None => typeshed_symbol_ty(db, "NoneType"),
|
||||
// TODO not accurate if there's a custom metaclass...
|
||||
Type::Class(_) => builtins_symbol_ty(db, "type"),
|
||||
// TODO can we do better here? `type[LiteralString]`?
|
||||
Type::StringLiteral(_) | Type::LiteralString => builtins_symbol_ty(db, "str"),
|
||||
// TODO: `type[Any]`?
|
||||
Type::Any => Type::Any,
|
||||
// TODO: `type[Unknown]`?
|
||||
Type::Unknown => Type::Unknown,
|
||||
// TODO intersections
|
||||
Type::Intersection(_) => Type::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum IterationOutcome<'db> {
|
||||
Iterable { element_ty: Type<'db> },
|
||||
NotIterable { not_iterable_ty: Type<'db> },
|
||||
}
|
||||
|
||||
impl<'db> IterationOutcome<'db> {
|
||||
fn unwrap_with_diagnostic(
|
||||
self,
|
||||
iterable_node: ast::AnyNodeRef,
|
||||
inference_builder: &mut TypeInferenceBuilder<'db>,
|
||||
) -> Type<'db> {
|
||||
match self {
|
||||
Self::Iterable { element_ty } => element_ty,
|
||||
Self::NotIterable { not_iterable_ty } => {
|
||||
inference_builder.not_iterable_diagnostic(iterable_node, not_iterable_ty);
|
||||
Type::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned]
|
||||
@@ -578,7 +504,7 @@ impl<'db> ClassType<'db> {
|
||||
/// Returns the class member of this class named `name`.
|
||||
///
|
||||
/// The member resolves to a member of the class itself or any of its bases.
|
||||
pub fn class_member(self, db: &'db dyn Db, name: &str) -> Type<'db> {
|
||||
pub fn class_member(self, db: &'db dyn Db, name: &ast::name::Name) -> Type<'db> {
|
||||
let member = self.own_class_member(db, name);
|
||||
if !member.is_unbound() {
|
||||
return member;
|
||||
@@ -588,12 +514,12 @@ impl<'db> ClassType<'db> {
|
||||
}
|
||||
|
||||
/// Returns the inferred type of the class member named `name`.
|
||||
pub fn own_class_member(self, db: &'db dyn Db, name: &str) -> Type<'db> {
|
||||
pub fn own_class_member(self, db: &'db dyn Db, name: &ast::name::Name) -> Type<'db> {
|
||||
let scope = self.body_scope(db);
|
||||
symbol_ty(db, scope, name)
|
||||
symbol_ty_by_name(db, scope, name)
|
||||
}
|
||||
|
||||
pub fn inherited_class_member(self, db: &'db dyn Db, name: &str) -> Type<'db> {
|
||||
pub fn inherited_class_member(self, db: &'db dyn Db, name: &ast::name::Name) -> Type<'db> {
|
||||
for base in self.bases(db) {
|
||||
let member = base.member(db, name);
|
||||
if !member.is_unbound() {
|
||||
@@ -616,21 +542,6 @@ impl<'db> UnionType<'db> {
|
||||
pub fn contains(&self, db: &'db dyn Db, ty: Type<'db>) -> bool {
|
||||
self.elements(db).contains(&ty)
|
||||
}
|
||||
|
||||
/// Apply a transformation function to all elements of the union,
|
||||
/// and create a new union from the resulting set of types
|
||||
pub fn map(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
mut transform_fn: impl FnMut(&Type<'db>) -> Type<'db>,
|
||||
) -> Type<'db> {
|
||||
self.elements(db)
|
||||
.into_iter()
|
||||
.fold(UnionBuilder::new(db), |builder, element| {
|
||||
builder.add(transform_fn(element))
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned]
|
||||
@@ -777,114 +688,4 @@ mod tests {
|
||||
&["Object of type 'Literal[123]' is not callable"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_iterable() {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
nonsense = 123
|
||||
for x in nonsense:
|
||||
pass
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||
let a_file_diagnostics = super::check_types(&db, a_file);
|
||||
assert_diagnostic_messages(
|
||||
&a_file_diagnostics,
|
||||
&["Object of type 'Literal[123]' is not iterable"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_iteration_protocol_takes_precedence_over_old_style() {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
class NotIterable:
|
||||
def __getitem__(self, key: int) -> int:
|
||||
return 42
|
||||
|
||||
__iter__ = None
|
||||
|
||||
for x in NotIterable():
|
||||
pass
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||
let a_file_diagnostics = super::check_types(&db, a_file);
|
||||
assert_diagnostic_messages(
|
||||
&a_file_diagnostics,
|
||||
&["Object of type 'NotIterable' is not iterable"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn starred_expressions_must_be_iterable() {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
class NotIterable: pass
|
||||
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator:
|
||||
|
||||
x = [*NotIterable()]
|
||||
y = [*Iterable()]
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||
let a_file_diagnostics = super::check_types(&db, a_file);
|
||||
assert_diagnostic_messages(
|
||||
&a_file_diagnostics,
|
||||
&["Object of type 'NotIterable' is not iterable"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yield_from_expression_must_be_iterable() {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
class NotIterable: pass
|
||||
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator:
|
||||
|
||||
def generator_function():
|
||||
yield from Iterable()
|
||||
yield from NotIterable()
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||
let a_file_diagnostics = super::check_types(&db, a_file);
|
||||
assert_diagnostic_messages(
|
||||
&a_file_diagnostics,
|
||||
&["Object of type 'NotIterable' is not iterable"],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,13 @@
|
||||
//! * No type in an intersection can be a supertype of any other type in the intersection (just
|
||||
//! eliminate the supertype from the intersection).
|
||||
//! * An intersection containing two non-overlapping types should simplify to [`Type::Never`].
|
||||
use crate::types::{builtins_symbol_ty, IntersectionType, Type, UnionType};
|
||||
|
||||
use crate::types::{IntersectionType, Type, UnionType};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use ordermap::set::MutableValues;
|
||||
|
||||
use super::builtins_symbol_ty_by_name;
|
||||
|
||||
pub(crate) struct UnionBuilder<'db> {
|
||||
elements: FxOrderSet<Type<'db>>,
|
||||
db: &'db dyn Db,
|
||||
@@ -65,7 +68,7 @@ impl<'db> UnionBuilder<'db> {
|
||||
if let Some(true_index) = self.elements.get_index_of(&Type::BooleanLiteral(true)) {
|
||||
if self.elements.contains(&Type::BooleanLiteral(false)) {
|
||||
*self.elements.get_index_mut2(true_index).unwrap() =
|
||||
builtins_symbol_ty(self.db, "bool");
|
||||
builtins_symbol_ty_by_name(self.db, "bool");
|
||||
self.elements.remove(&Type::BooleanLiteral(false));
|
||||
}
|
||||
}
|
||||
@@ -275,7 +278,7 @@ mod tests {
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::types::builtins_symbol_ty;
|
||||
use crate::types::builtins_symbol_ty_by_name;
|
||||
use crate::ProgramSettings;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
|
||||
@@ -348,7 +351,7 @@ mod tests {
|
||||
#[test]
|
||||
fn build_union_bool() {
|
||||
let db = setup_db();
|
||||
let bool_ty = builtins_symbol_ty(&db, "bool");
|
||||
let bool_ty = builtins_symbol_ty_by_name(&db, "bool");
|
||||
|
||||
let t0 = Type::BooleanLiteral(true);
|
||||
let t1 = Type::BooleanLiteral(true);
|
||||
|
||||
@@ -236,7 +236,9 @@ mod tests {
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::types::{global_symbol_ty, BytesLiteralType, StringLiteralType, Type, UnionBuilder};
|
||||
use crate::types::{
|
||||
global_symbol_ty_by_name, BytesLiteralType, StringLiteralType, Type, UnionBuilder,
|
||||
};
|
||||
use crate::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
@@ -281,16 +283,16 @@ mod tests {
|
||||
let vec: Vec<Type<'_>> = vec![
|
||||
Type::Unknown,
|
||||
Type::IntLiteral(-1),
|
||||
global_symbol_ty(&db, mod_file, "A"),
|
||||
global_symbol_ty_by_name(&db, mod_file, "A"),
|
||||
Type::StringLiteral(StringLiteralType::new(&db, Box::from("A"))),
|
||||
Type::BytesLiteral(BytesLiteralType::new(&db, Box::from([0]))),
|
||||
Type::BytesLiteral(BytesLiteralType::new(&db, Box::from([7]))),
|
||||
Type::IntLiteral(0),
|
||||
Type::IntLiteral(1),
|
||||
Type::StringLiteral(StringLiteralType::new(&db, Box::from("B"))),
|
||||
global_symbol_ty(&db, mod_file, "foo"),
|
||||
global_symbol_ty(&db, mod_file, "bar"),
|
||||
global_symbol_ty(&db, mod_file, "B"),
|
||||
global_symbol_ty_by_name(&db, mod_file, "foo"),
|
||||
global_symbol_ty_by_name(&db, mod_file, "bar"),
|
||||
global_symbol_ty_by_name(&db, mod_file, "B"),
|
||||
Type::BooleanLiteral(true),
|
||||
Type::None,
|
||||
];
|
||||
|
||||
@@ -37,6 +37,7 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext, UnaryOp};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::builtins::builtins_scope;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{file_to_module, resolve_module};
|
||||
use crate::semantic_index::ast_ids::{HasScopedAstId, HasScopedUseId, ScopedExpressionId};
|
||||
@@ -45,11 +46,11 @@ use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::semantic_index::symbol::{NodeWithScopeKind, NodeWithScopeRef, ScopeId};
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::stdlib::builtins_module_scope;
|
||||
use crate::types::diagnostic::{TypeCheckDiagnostic, TypeCheckDiagnostics};
|
||||
use crate::types::{
|
||||
builtins_symbol_ty, definitions_ty, global_symbol_ty, symbol_ty, BytesLiteralType, ClassType,
|
||||
FunctionType, StringLiteralType, Type, UnionBuilder,
|
||||
builtins_symbol_ty_by_name, definitions_ty, global_symbol_ty_by_name, symbol_ty,
|
||||
symbol_ty_by_name, BytesLiteralType, ClassType, FunctionType, StringLiteralType, Type,
|
||||
UnionBuilder,
|
||||
};
|
||||
use crate::Db;
|
||||
|
||||
@@ -243,7 +244,7 @@ impl<'db> TypeInference<'db> {
|
||||
/// Similarly, when we encounter a standalone-inferable expression (right-hand side of an
|
||||
/// assignment, type narrowing guard), we use the [`infer_expression_types()`] query to ensure we
|
||||
/// don't infer its types more than once.
|
||||
pub(super) struct TypeInferenceBuilder<'db> {
|
||||
struct TypeInferenceBuilder<'db> {
|
||||
db: &'db dyn Db,
|
||||
index: &'db SemanticIndex<'db>,
|
||||
region: InferenceRegion<'db>,
|
||||
@@ -394,7 +395,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_for_statement_definition(
|
||||
for_statement_definition.target(),
|
||||
for_statement_definition.iterable(),
|
||||
for_statement_definition.is_async(),
|
||||
definition,
|
||||
);
|
||||
}
|
||||
@@ -1030,23 +1030,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_body(orelse);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
|
||||
pub(super) fn not_iterable_diagnostic(&mut self, node: AnyNodeRef, not_iterable_ty: Type<'db>) {
|
||||
self.add_diagnostic(
|
||||
node,
|
||||
"not-iterable",
|
||||
format_args!(
|
||||
"Object of type '{}' is not iterable",
|
||||
not_iterable_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn infer_for_statement_definition(
|
||||
&mut self,
|
||||
target: &ast::ExprName,
|
||||
iterable: &ast::Expr,
|
||||
is_async: bool,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
let expression = self.index.expression(iterable);
|
||||
@@ -1056,14 +1043,18 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.types
|
||||
.expression_ty(iterable.scoped_ast_id(self.db, self.scope));
|
||||
|
||||
let loop_var_value_ty = if is_async {
|
||||
// TODO(Alex): async iterables/iterators!
|
||||
Type::Unknown
|
||||
} else {
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
.unwrap_with_diagnostic(iterable.into(), self)
|
||||
};
|
||||
// TODO(Alex): only a valid iterable if the *type* of `iterable_ty` has an `__iter__`
|
||||
// member (dunders are never looked up on an instance)
|
||||
let _dunder_iter_ty = iterable_ty.member(self.db, &ast::name::Name::from("__iter__"));
|
||||
|
||||
// TODO(Alex):
|
||||
// - infer the return type of the `__iter__` method, which gives us the iterator
|
||||
// - lookup the `__next__` method on the iterator
|
||||
// - infer the return type of the iterator's `__next__` method,
|
||||
// which gives us the type of the variable being bound here
|
||||
// (...or the type of the object being unpacked into multiple definitions, if it's something like
|
||||
// `for k, v in d.items(): ...`)
|
||||
let loop_var_value_ty = Type::Unknown;
|
||||
|
||||
self.types
|
||||
.expressions
|
||||
@@ -1409,9 +1400,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
ast::Number::Int(n) => n
|
||||
.as_i64()
|
||||
.map(Type::IntLiteral)
|
||||
.unwrap_or_else(|| builtins_symbol_ty(self.db, "int").to_instance()),
|
||||
ast::Number::Float(_) => builtins_symbol_ty(self.db, "float").to_instance(),
|
||||
ast::Number::Complex { .. } => builtins_symbol_ty(self.db, "complex").to_instance(),
|
||||
.unwrap_or_else(|| builtins_symbol_ty_by_name(self.db, "int").to_instance()),
|
||||
ast::Number::Float(_) => builtins_symbol_ty_by_name(self.db, "float").to_instance(),
|
||||
ast::Number::Complex { .. } => {
|
||||
builtins_symbol_ty_by_name(self.db, "complex").to_instance()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1489,11 +1482,12 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
fn infer_ellipsis_literal_expression(
|
||||
&mut self,
|
||||
_literal: &ast::ExprEllipsisLiteral,
|
||||
) -> Type<'db> {
|
||||
builtins_symbol_ty(self.db, "Ellipsis")
|
||||
builtins_symbol_ty_by_name(self.db, "Ellipsis")
|
||||
}
|
||||
|
||||
fn infer_tuple_expression(&mut self, tuple: &ast::ExprTuple) -> Type<'db> {
|
||||
@@ -1509,7 +1503,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
// TODO generic
|
||||
builtins_symbol_ty(self.db, "tuple").to_instance()
|
||||
builtins_symbol_ty_by_name(self.db, "tuple").to_instance()
|
||||
}
|
||||
|
||||
fn infer_list_expression(&mut self, list: &ast::ExprList) -> Type<'db> {
|
||||
@@ -1524,7 +1518,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
// TODO generic
|
||||
builtins_symbol_ty(self.db, "list").to_instance()
|
||||
builtins_symbol_ty_by_name(self.db, "list").to_instance()
|
||||
}
|
||||
|
||||
fn infer_set_expression(&mut self, set: &ast::ExprSet) -> Type<'db> {
|
||||
@@ -1535,7 +1529,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
// TODO generic
|
||||
builtins_symbol_ty(self.db, "set").to_instance()
|
||||
builtins_symbol_ty_by_name(self.db, "set").to_instance()
|
||||
}
|
||||
|
||||
fn infer_dict_expression(&mut self, dict: &ast::ExprDict) -> Type<'db> {
|
||||
@@ -1547,7 +1541,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
// TODO generic
|
||||
builtins_symbol_ty(self.db, "dict").to_instance()
|
||||
builtins_symbol_ty_by_name(self.db, "dict").to_instance()
|
||||
}
|
||||
|
||||
/// Infer the type of the `iter` expression of the first comprehension.
|
||||
@@ -1823,10 +1817,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
ctx: _,
|
||||
} = starred;
|
||||
|
||||
let iterable_ty = self.infer_expression(value);
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
.unwrap_with_diagnostic(value.as_ref().into(), self);
|
||||
self.infer_expression(value);
|
||||
|
||||
// TODO
|
||||
Type::Unknown
|
||||
@@ -1844,12 +1835,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
fn infer_yield_from_expression(&mut self, yield_from: &ast::ExprYieldFrom) -> Type<'db> {
|
||||
let ast::ExprYieldFrom { range: _, value } = yield_from;
|
||||
|
||||
let iterable_ty = self.infer_expression(value);
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
.unwrap_with_diagnostic(value.as_ref().into(), self);
|
||||
self.infer_expression(value);
|
||||
|
||||
// TODO get type from `ReturnType` of generator
|
||||
// TODO get type from awaitable
|
||||
Type::Unknown
|
||||
}
|
||||
|
||||
@@ -1865,17 +1853,15 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
/// Look up a name reference that isn't bound in the local scope.
|
||||
fn lookup_name(&self, name: &ast::name::Name) -> Type<'db> {
|
||||
let file_scope_id = self.scope.file_scope_id(self.db);
|
||||
let is_defined = self
|
||||
.index
|
||||
.symbol_table(file_scope_id)
|
||||
let symbols = self.index.symbol_table(file_scope_id);
|
||||
let symbol = symbols
|
||||
.symbol_by_name(name)
|
||||
.expect("Symbol table should create a symbol for every Name node")
|
||||
.is_defined();
|
||||
.expect("Expected the symbol table to create a symbol for every Name node");
|
||||
|
||||
// In function-like scopes, any local variable (symbol that is defined in this
|
||||
// scope) can only have a definition in this scope, or be undefined; it never references
|
||||
// another scope. (At runtime, it would use the `LOAD_FAST` opcode.)
|
||||
if !is_defined || !self.scope.is_function_like(self.db) {
|
||||
if !symbol.is_defined() || !self.scope.is_function_like(self.db) {
|
||||
// Walk up parent scopes looking for a possible enclosing scope that may have a
|
||||
// definition of this name visible to us (would be `LOAD_DEREF` at runtime.)
|
||||
for (enclosing_scope_file_id, _) in self.index.ancestor_scopes(file_scope_id) {
|
||||
@@ -1898,7 +1884,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// runtime, it is the scope that creates the cell for our closure.) If the name
|
||||
// isn't bound in that scope, we should get an unbound name, not continue
|
||||
// falling back to other scopes / globals / builtins.
|
||||
return symbol_ty(self.db, enclosing_scope_id, name);
|
||||
return symbol_ty_by_name(self.db, enclosing_scope_id, name);
|
||||
}
|
||||
}
|
||||
// No nonlocal binding, check module globals. Avoid infinite recursion if `self.scope`
|
||||
@@ -1906,11 +1892,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let ty = if file_scope_id.is_global() {
|
||||
Type::Unbound
|
||||
} else {
|
||||
global_symbol_ty(self.db, self.file, name)
|
||||
global_symbol_ty_by_name(self.db, self.file, name)
|
||||
};
|
||||
// Fallback to builtins (without infinite recursion if we're already in builtins.)
|
||||
if ty.may_be_unbound(self.db) && Some(self.scope) != builtins_module_scope(self.db) {
|
||||
ty.replace_unbound_with(self.db, builtins_symbol_ty(self.db, name))
|
||||
if ty.may_be_unbound(self.db) && Some(self.scope) != builtins_scope(self.db) {
|
||||
ty.replace_unbound_with(self.db, builtins_symbol_ty_by_name(self.db, name))
|
||||
} else {
|
||||
ty
|
||||
}
|
||||
@@ -1923,27 +1909,20 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let ast::ExprName { range: _, id, ctx } = name;
|
||||
let file_scope_id = self.scope.file_scope_id(self.db);
|
||||
|
||||
// if we're inferring types of deferred expressions, always treat them as public symbols
|
||||
if self.is_deferred() {
|
||||
let symbols = self.index.symbol_table(file_scope_id);
|
||||
let symbol = symbols
|
||||
.symbol_id_by_name(id)
|
||||
.expect("Expected the symbol table to create a symbol for every Name node");
|
||||
return symbol_ty(self.db, self.scope, symbol);
|
||||
}
|
||||
|
||||
match ctx {
|
||||
ExprContext::Load => {
|
||||
let use_def = self.index.use_def_map(file_scope_id);
|
||||
let symbol = self
|
||||
.index
|
||||
.symbol_table(file_scope_id)
|
||||
.symbol_id_by_name(id)
|
||||
.expect("Expected the symbol table to create a symbol for every Name node");
|
||||
// if we're inferring types of deferred expressions, always treat them as public symbols
|
||||
let (definitions, may_be_unbound) = if self.is_deferred() {
|
||||
(
|
||||
use_def.public_definitions(symbol),
|
||||
use_def.public_may_be_unbound(symbol),
|
||||
)
|
||||
} else {
|
||||
let use_id = name.scoped_use_id(self.db, self.scope);
|
||||
(
|
||||
use_def.use_definitions(use_id),
|
||||
use_def.use_may_be_unbound(use_id),
|
||||
)
|
||||
};
|
||||
let use_id = name.scoped_use_id(self.db, self.scope);
|
||||
let may_be_unbound = use_def.use_may_be_unbound(use_id);
|
||||
|
||||
let unbound_ty = if may_be_unbound {
|
||||
Some(self.lookup_name(id))
|
||||
@@ -1951,7 +1930,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
None
|
||||
};
|
||||
|
||||
definitions_ty(self.db, definitions, unbound_ty)
|
||||
definitions_ty(self.db, use_def.use_definitions(use_id), unbound_ty)
|
||||
}
|
||||
ExprContext::Store | ExprContext::Del => Type::None,
|
||||
ExprContext::Invalid => Type::Unknown,
|
||||
@@ -2007,22 +1986,22 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Add) => n
|
||||
.checked_add(m)
|
||||
.map(Type::IntLiteral)
|
||||
.unwrap_or_else(|| builtins_symbol_ty(self.db, "int").to_instance()),
|
||||
.unwrap_or_else(|| builtins_symbol_ty_by_name(self.db, "int").to_instance()),
|
||||
|
||||
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Sub) => n
|
||||
.checked_sub(m)
|
||||
.map(Type::IntLiteral)
|
||||
.unwrap_or_else(|| builtins_symbol_ty(self.db, "int").to_instance()),
|
||||
.unwrap_or_else(|| builtins_symbol_ty_by_name(self.db, "int").to_instance()),
|
||||
|
||||
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Mult) => n
|
||||
.checked_mul(m)
|
||||
.map(Type::IntLiteral)
|
||||
.unwrap_or_else(|| builtins_symbol_ty(self.db, "int").to_instance()),
|
||||
.unwrap_or_else(|| builtins_symbol_ty_by_name(self.db, "int").to_instance()),
|
||||
|
||||
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Div) => n
|
||||
.checked_div(m)
|
||||
.map(Type::IntLiteral)
|
||||
.unwrap_or_else(|| builtins_symbol_ty(self.db, "int").to_instance()),
|
||||
.unwrap_or_else(|| builtins_symbol_ty_by_name(self.db, "int").to_instance()),
|
||||
|
||||
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Mod) => n
|
||||
.checked_rem(m)
|
||||
@@ -2401,14 +2380,14 @@ 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::program::{Program, SearchPathSettings};
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::FileScopeId;
|
||||
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
|
||||
use crate::stdlib::builtins_module_scope;
|
||||
use crate::types::{global_symbol_ty, infer_definition_types, symbol_ty};
|
||||
use crate::types::{global_symbol_ty_by_name, infer_definition_types, symbol_ty_by_name};
|
||||
use crate::{HasTy, ProgramSettings, SemanticModel};
|
||||
|
||||
use super::TypeInferenceBuilder;
|
||||
@@ -2461,7 +2440,7 @@ mod tests {
|
||||
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.");
|
||||
|
||||
let ty = global_symbol_ty(db, file, symbol_name);
|
||||
let ty = global_symbol_ty_by_name(db, file, symbol_name);
|
||||
assert_eq!(ty.display(db).to_string(), expected);
|
||||
}
|
||||
|
||||
@@ -2486,7 +2465,7 @@ mod tests {
|
||||
assert_eq!(scope.name(db), *expected_scope_name);
|
||||
}
|
||||
|
||||
let ty = symbol_ty(db, scope, symbol_name);
|
||||
let ty = symbol_ty_by_name(db, scope, symbol_name);
|
||||
assert_eq!(ty.display(db).to_string(), expected);
|
||||
}
|
||||
|
||||
@@ -2690,7 +2669,7 @@ mod tests {
|
||||
)?;
|
||||
|
||||
let mod_file = system_path_to_file(&db, "src/mod.py").expect("Expected file to exist.");
|
||||
let ty = global_symbol_ty(&db, mod_file, "Sub");
|
||||
let ty = global_symbol_ty_by_name(&db, mod_file, "Sub");
|
||||
|
||||
let class = ty.expect_class();
|
||||
|
||||
@@ -2717,7 +2696,7 @@ mod tests {
|
||||
)?;
|
||||
|
||||
let mod_file = system_path_to_file(&db, "src/mod.py").unwrap();
|
||||
let ty = global_symbol_ty(&db, mod_file, "C");
|
||||
let ty = global_symbol_ty_by_name(&db, mod_file, "C");
|
||||
let class_id = ty.expect_class();
|
||||
let member_ty = class_id.class_member(&db, &Name::new_static("f"));
|
||||
let func = member_ty.expect_function();
|
||||
@@ -2921,7 +2900,7 @@ mod tests {
|
||||
db.write_file("src/a.py", "def example() -> int: return 42")?;
|
||||
|
||||
let mod_file = system_path_to_file(&db, "src/a.py").unwrap();
|
||||
let function = global_symbol_ty(&db, mod_file, "example").expect_function();
|
||||
let function = global_symbol_ty_by_name(&db, mod_file, "example").expect_function();
|
||||
let returns = function.return_type(&db);
|
||||
assert_eq!(returns.display(&db).to_string(), "int");
|
||||
|
||||
@@ -2996,108 +2975,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_for_loop() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
class IntIterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class IntIterable:
|
||||
def __iter__(self) -> IntIterator:
|
||||
return IntIterator()
|
||||
|
||||
for x in IntIterable():
|
||||
pass
|
||||
",
|
||||
)?;
|
||||
|
||||
assert_public_ty(&db, "src/a.py", "x", "int");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_loop_with_old_style_iteration_protocol() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
class OldStyleIterable:
|
||||
def __getitem__(self, key: int) -> int:
|
||||
return 42
|
||||
|
||||
for x in OldStyleIterable():
|
||||
pass
|
||||
",
|
||||
)?;
|
||||
|
||||
assert_public_ty(&db, "src/a.py", "x", "int");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This tests that we understand that `async` for loops
|
||||
/// do not work according to the synchronous iteration protocol
|
||||
#[test]
|
||||
fn invalid_async_for_loop() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
async def foo():
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
async for x in Iterator():
|
||||
pass
|
||||
",
|
||||
)?;
|
||||
|
||||
// TODO(Alex) async iterables/iterators!
|
||||
assert_scope_ty(&db, "src/a.py", &["foo"], "x", "Unknown");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_async_for_loop() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
async def foo():
|
||||
class IntAsyncIterator:
|
||||
async def __anext__(self) -> int:
|
||||
return 42
|
||||
|
||||
class IntAsyncIterable:
|
||||
def __aiter__(self) -> IntAsyncIterator:
|
||||
return IntAsyncIterator()
|
||||
|
||||
async for x in IntAsyncIterable():
|
||||
pass
|
||||
",
|
||||
)?;
|
||||
|
||||
// TODO(Alex) async iterables/iterators!
|
||||
assert_scope_ty(&db, "src/a.py", &["foo"], "x", "Unknown");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_constructor_call_expression() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
@@ -3440,7 +3317,7 @@ mod tests {
|
||||
)?;
|
||||
|
||||
let a = system_path_to_file(&db, "src/a.py").expect("Expected file to exist.");
|
||||
let c_ty = global_symbol_ty(&db, a, "C");
|
||||
let c_ty = global_symbol_ty_by_name(&db, a, "C");
|
||||
let c_class = c_ty.expect_class();
|
||||
let mut c_bases = c_class.bases(&db);
|
||||
let b_ty = c_bases.next().unwrap();
|
||||
@@ -3477,8 +3354,8 @@ mod tests {
|
||||
.unwrap()
|
||||
.0
|
||||
.to_scope_id(&db, file);
|
||||
let y_ty = symbol_ty(&db, function_scope, "y");
|
||||
let x_ty = symbol_ty(&db, function_scope, "x");
|
||||
let y_ty = symbol_ty_by_name(&db, function_scope, "y");
|
||||
let x_ty = symbol_ty_by_name(&db, function_scope, "x");
|
||||
|
||||
assert_eq!(y_ty.display(&db).to_string(), "Unbound");
|
||||
assert_eq!(x_ty.display(&db).to_string(), "Literal[2]");
|
||||
@@ -3508,8 +3385,8 @@ mod tests {
|
||||
.unwrap()
|
||||
.0
|
||||
.to_scope_id(&db, file);
|
||||
let y_ty = symbol_ty(&db, function_scope, "y");
|
||||
let x_ty = symbol_ty(&db, function_scope, "x");
|
||||
let y_ty = symbol_ty_by_name(&db, function_scope, "y");
|
||||
let x_ty = symbol_ty_by_name(&db, function_scope, "x");
|
||||
|
||||
assert_eq!(x_ty.display(&db).to_string(), "Unbound");
|
||||
assert_eq!(y_ty.display(&db).to_string(), "Literal[1]");
|
||||
@@ -3539,7 +3416,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.0
|
||||
.to_scope_id(&db, file);
|
||||
let y_ty = symbol_ty(&db, function_scope, "y");
|
||||
let y_ty = symbol_ty_by_name(&db, function_scope, "y");
|
||||
|
||||
assert_eq!(
|
||||
y_ty.display(&db).to_string(),
|
||||
@@ -3573,8 +3450,8 @@ mod tests {
|
||||
.unwrap()
|
||||
.0
|
||||
.to_scope_id(&db, file);
|
||||
let y_ty = symbol_ty(&db, class_scope, "y");
|
||||
let x_ty = symbol_ty(&db, class_scope, "x");
|
||||
let y_ty = symbol_ty_by_name(&db, class_scope, "y");
|
||||
let x_ty = symbol_ty_by_name(&db, class_scope, "x");
|
||||
|
||||
assert_eq!(x_ty.display(&db).to_string(), "Unbound | Literal[2]");
|
||||
assert_eq!(y_ty.display(&db).to_string(), "Literal[1]");
|
||||
@@ -3667,11 +3544,9 @@ mod tests {
|
||||
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(&db, file, "builtins");
|
||||
let builtins_ty = global_symbol_ty_by_name(&db, file, "builtins");
|
||||
let builtins_file = builtins_ty.expect_module();
|
||||
let implicit_builtins_file = builtins_module_scope(&db)
|
||||
.expect("builtins module should exist")
|
||||
.file(&db);
|
||||
let implicit_builtins_file = builtins_scope(&db).expect("builtins to exist").file(&db);
|
||||
assert_eq!(builtins_file, implicit_builtins_file);
|
||||
|
||||
Ok(())
|
||||
@@ -3695,24 +3570,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deferred_annotation_builtin() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
db.write_file("/src/a.pyi", "class C(object): pass")?;
|
||||
let file = system_path_to_file(&db, "/src/a.pyi").unwrap();
|
||||
let ty = global_symbol_ty(&db, file, "C");
|
||||
|
||||
let base = ty
|
||||
.expect_class()
|
||||
.bases(&db)
|
||||
.next()
|
||||
.expect("there should be at least one base");
|
||||
|
||||
assert_eq!(base.display(&db).to_string(), "Literal[object]");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn narrow_not_none() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
@@ -3993,7 +3850,7 @@ mod tests {
|
||||
])?;
|
||||
|
||||
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||
let x_ty = global_symbol_ty(&db, a, "x");
|
||||
let x_ty = global_symbol_ty_by_name(&db, a, "x");
|
||||
|
||||
assert_eq!(x_ty.display(&db).to_string(), "Literal[10]");
|
||||
|
||||
@@ -4002,7 +3859,7 @@ mod tests {
|
||||
|
||||
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||
|
||||
let x_ty_2 = global_symbol_ty(&db, a, "x");
|
||||
let x_ty_2 = global_symbol_ty_by_name(&db, a, "x");
|
||||
|
||||
assert_eq!(x_ty_2.display(&db).to_string(), "Literal[20]");
|
||||
|
||||
@@ -4019,7 +3876,7 @@ mod tests {
|
||||
])?;
|
||||
|
||||
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||
let x_ty = global_symbol_ty(&db, a, "x");
|
||||
let x_ty = global_symbol_ty_by_name(&db, a, "x");
|
||||
|
||||
assert_eq!(x_ty.display(&db).to_string(), "Literal[10]");
|
||||
|
||||
@@ -4029,7 +3886,7 @@ mod tests {
|
||||
|
||||
db.clear_salsa_events();
|
||||
|
||||
let x_ty_2 = global_symbol_ty(&db, a, "x");
|
||||
let x_ty_2 = global_symbol_ty_by_name(&db, a, "x");
|
||||
|
||||
assert_eq!(x_ty_2.display(&db).to_string(), "Literal[10]");
|
||||
|
||||
@@ -4055,7 +3912,7 @@ mod tests {
|
||||
])?;
|
||||
|
||||
let a = system_path_to_file(&db, "/src/a.py").unwrap();
|
||||
let x_ty = global_symbol_ty(&db, a, "x");
|
||||
let x_ty = global_symbol_ty_by_name(&db, a, "x");
|
||||
|
||||
assert_eq!(x_ty.display(&db).to_string(), "Literal[10]");
|
||||
|
||||
@@ -4065,7 +3922,7 @@ mod tests {
|
||||
|
||||
db.clear_salsa_events();
|
||||
|
||||
let x_ty_2 = global_symbol_ty(&db, a, "x");
|
||||
let x_ty_2 = global_symbol_ty_by_name(&db, a, "x");
|
||||
|
||||
assert_eq!(x_ty_2.display(&db).to_string(), "Literal[10]");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.6.4"
|
||||
version = "0.6.3"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.6.4"
|
||||
version = "0.6.3"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -94,9 +94,6 @@ class Apples:
|
||||
def __mro_entries__(self, bases):
|
||||
pass
|
||||
|
||||
# Removed with Python 3
|
||||
def __unicode__(self):
|
||||
pass
|
||||
|
||||
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
|
||||
...
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# Valid
|
||||
x = 1 if True else 2
|
||||
|
||||
# Invalid
|
||||
x = 1 if True else 1
|
||||
|
||||
# Invalid
|
||||
x = "a" if True else "a"
|
||||
|
||||
# Invalid
|
||||
x = 0.1 if False else 0.1
|
||||
@@ -1404,9 +1404,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::IfExpInsteadOfOrOperator) {
|
||||
refurb::rules::if_exp_instead_of_or_operator(checker, if_exp);
|
||||
}
|
||||
if checker.enabled(Rule::UselessIfElse) {
|
||||
ruff::rules::useless_if_else(checker, if_exp);
|
||||
}
|
||||
}
|
||||
Expr::ListComp(
|
||||
comp @ ast::ExprListComp {
|
||||
|
||||
@@ -961,7 +961,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "031") => (RuleGroup::Preview, rules::ruff::rules::IncorrectlyParenthesizedTupleInSubscript),
|
||||
(Ruff, "032") => (RuleGroup::Preview, rules::ruff::rules::DecimalFromFloatLiteral),
|
||||
(Ruff, "033") => (RuleGroup::Preview, rules::ruff::rules::PostInitDefault),
|
||||
(Ruff, "034") => (RuleGroup::Preview, rules::ruff::rules::UselessIfElse),
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ use crate::checkers::ast::Checker;
|
||||
use crate::rules::pylint::helpers::is_known_dunder_method;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for dunder methods that have no special meaning in Python 3.
|
||||
/// Checks for misspelled and unknown dunder names in method definitions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Misspelled or no longer supported dunder name methods may cause your code to not function
|
||||
/// Misspelled dunder name methods may cause your code to not function
|
||||
/// as expected.
|
||||
///
|
||||
/// Since dunder methods are associated with customizing the behavior
|
||||
@@ -51,7 +51,7 @@ impl Violation for BadDunderMethodName {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let BadDunderMethodName { name } = self;
|
||||
format!("Dunder method `{name}` has no special meaning in Python 3")
|
||||
format!("Bad or misspelled dunder method name `{name}`")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
bad_dunder_method_name.py:5:9: PLW3201 Dunder method `_init_` has no special meaning in Python 3
|
||||
bad_dunder_method_name.py:5:9: PLW3201 Bad or misspelled dunder method name `_init_`
|
||||
|
|
||||
4 | class Apples:
|
||||
5 | def _init_(self): # [bad-dunder-name]
|
||||
@@ -9,7 +9,7 @@ bad_dunder_method_name.py:5:9: PLW3201 Dunder method `_init_` has no special mea
|
||||
6 | pass
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:8:9: PLW3201 Dunder method `__hello__` has no special meaning in Python 3
|
||||
bad_dunder_method_name.py:8:9: PLW3201 Bad or misspelled dunder method name `__hello__`
|
||||
|
|
||||
6 | pass
|
||||
7 |
|
||||
@@ -18,7 +18,7 @@ bad_dunder_method_name.py:8:9: PLW3201 Dunder method `__hello__` has no special
|
||||
9 | print("hello")
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:11:9: PLW3201 Dunder method `__init_` has no special meaning in Python 3
|
||||
bad_dunder_method_name.py:11:9: PLW3201 Bad or misspelled dunder method name `__init_`
|
||||
|
|
||||
9 | print("hello")
|
||||
10 |
|
||||
@@ -28,7 +28,7 @@ bad_dunder_method_name.py:11:9: PLW3201 Dunder method `__init_` has no special m
|
||||
13 | pass
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:15:9: PLW3201 Dunder method `_init_` has no special meaning in Python 3
|
||||
bad_dunder_method_name.py:15:9: PLW3201 Bad or misspelled dunder method name `_init_`
|
||||
|
|
||||
13 | pass
|
||||
14 |
|
||||
@@ -38,7 +38,7 @@ bad_dunder_method_name.py:15:9: PLW3201 Dunder method `_init_` has no special me
|
||||
17 | pass
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:19:9: PLW3201 Dunder method `___neg__` has no special meaning in Python 3
|
||||
bad_dunder_method_name.py:19:9: PLW3201 Bad or misspelled dunder method name `___neg__`
|
||||
|
|
||||
17 | pass
|
||||
18 |
|
||||
@@ -48,7 +48,7 @@ bad_dunder_method_name.py:19:9: PLW3201 Dunder method `___neg__` has no special
|
||||
21 | pass
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:23:9: PLW3201 Dunder method `__inv__` has no special meaning in Python 3
|
||||
bad_dunder_method_name.py:23:9: PLW3201 Bad or misspelled dunder method name `__inv__`
|
||||
|
|
||||
21 | pass
|
||||
22 |
|
||||
@@ -58,10 +58,4 @@ bad_dunder_method_name.py:23:9: PLW3201 Dunder method `__inv__` has no special m
|
||||
25 | pass
|
||||
|
|
||||
|
||||
bad_dunder_method_name.py:98:9: PLW3201 Dunder method `__unicode__` has no special meaning in Python 3
|
||||
|
|
||||
97 | # Removed with Python 3
|
||||
98 | def __unicode__(self):
|
||||
| ^^^^^^^^^^^ PLW3201
|
||||
99 | pass
|
||||
|
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ mod tests {
|
||||
#[test_case(Rule::AssertWithPrintMessage, Path::new("RUF030.py"))]
|
||||
#[test_case(Rule::IncorrectlyParenthesizedTupleInSubscript, Path::new("RUF031.py"))]
|
||||
#[test_case(Rule::DecimalFromFloatLiteral, Path::new("RUF032.py"))]
|
||||
#[test_case(Rule::UselessIfElse, Path::new("RUF034.py"))]
|
||||
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101.py"))]
|
||||
#[test_case(Rule::PostInitDefault, Path::new("RUF033.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
|
||||
@@ -29,7 +29,6 @@ pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
|
||||
pub(crate) use unnecessary_key_check::*;
|
||||
pub(crate) use unused_async::*;
|
||||
pub(crate) use unused_noqa::*;
|
||||
pub(crate) use useless_if_else::*;
|
||||
pub(crate) use zip_instead_of_pairwise::*;
|
||||
|
||||
mod ambiguous_unicode_character;
|
||||
@@ -67,7 +66,6 @@ mod unnecessary_iterable_allocation_for_first_element;
|
||||
mod unnecessary_key_check;
|
||||
mod unused_async;
|
||||
mod unused_noqa;
|
||||
mod useless_if_else;
|
||||
mod zip_instead_of_pairwise;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for useless if-else conditions with identical arms.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Useless if-else conditions add unnecessary complexity to the code without
|
||||
/// providing any logical benefit.
|
||||
///
|
||||
/// Assigning the value directly is clearer and more explicit, and
|
||||
/// should be preferred.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// # Bad
|
||||
/// foo = x if y else x
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// # Good
|
||||
/// foo = x
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct UselessIfElse;
|
||||
|
||||
impl Violation for UselessIfElse {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Useless if-else condition")
|
||||
}
|
||||
}
|
||||
|
||||
/// RUF031
|
||||
pub(crate) fn useless_if_else(checker: &mut Checker, if_expr: &ast::ExprIf) {
|
||||
let ast::ExprIf {
|
||||
body,
|
||||
orelse,
|
||||
range,
|
||||
..
|
||||
} = if_expr;
|
||||
|
||||
// Skip if the body and orelse are not the same
|
||||
if ComparableExpr::from(body) != ComparableExpr::from(orelse) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(UselessIfElse, *range));
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF034.py:5:5: RUF034 Useless if-else condition
|
||||
|
|
||||
4 | # Invalid
|
||||
5 | x = 1 if True else 1
|
||||
| ^^^^^^^^^^^^^^^^ RUF034
|
||||
6 |
|
||||
7 | # Invalid
|
||||
|
|
||||
|
||||
RUF034.py:8:5: RUF034 Useless if-else condition
|
||||
|
|
||||
7 | # Invalid
|
||||
8 | x = "a" if True else "a"
|
||||
| ^^^^^^^^^^^^^^^^^^^^ RUF034
|
||||
9 |
|
||||
10 | # Invalid
|
||||
|
|
||||
|
||||
RUF034.py:11:5: RUF034 Useless if-else condition
|
||||
|
|
||||
10 | # Invalid
|
||||
11 | x = 0.1 if False else 0.1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ RUF034
|
||||
|
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.6.4"
|
||||
version = "0.6.3"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -78,7 +78,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.6.4
|
||||
rev: v0.6.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -91,7 +91,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.6.4
|
||||
rev: v0.6.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -105,7 +105,7 @@ To run the hooks over Jupyter Notebooks too, add `jupyter` to the list of allowe
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.6.4
|
||||
rev: v0.6.3
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -109,3 +109,16 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Omits the nav title "Ruff" entirely unless on a small screen, in which case
|
||||
the nav title is needed for backwards navigation in the collapsible
|
||||
nav variant.
|
||||
|
||||
See https://github.com/astral-sh/uv/issues/5130 */
|
||||
.md-nav__title {
|
||||
display: none;
|
||||
}
|
||||
@media screen and (max-width: 1219px) {
|
||||
.md-nav__title {
|
||||
display: flex ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@ theme:
|
||||
logo: assets/bolt.svg
|
||||
favicon: assets/favicon.ico
|
||||
features:
|
||||
- navigation.instant
|
||||
- navigation.instant.prefetch
|
||||
- navigation.tracking
|
||||
- content.code.annotate
|
||||
- toc.integrate
|
||||
- toc.follow
|
||||
- navigation.path
|
||||
- navigation.top
|
||||
- content.code.copy
|
||||
- content.tabs.link
|
||||
- navigation.footer
|
||||
- navigation.instant
|
||||
- navigation.instant.prefetch
|
||||
- navigation.path
|
||||
- navigation.top
|
||||
- navigation.tracking
|
||||
- toc.follow
|
||||
palette:
|
||||
# Note: Using the system theme works with the insiders version
|
||||
# https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#automatic-light-dark-mode
|
||||
@@ -71,6 +71,15 @@ not_in_nav: |
|
||||
extra:
|
||||
analytics:
|
||||
provider: fathom
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/astral-sh/ruff
|
||||
- icon: fontawesome/brands/discord
|
||||
link: https://discord.com/invite/astral-sh
|
||||
- icon: fontawesome/brands/python
|
||||
link: https://pypi.org/project/ruff/
|
||||
- icon: fontawesome/brands/x-twitter
|
||||
link: https://x.com/astral_sh
|
||||
validation:
|
||||
omitted_files: warn
|
||||
absolute_links: warn
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.6.4"
|
||||
version = "0.6.3"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
|
||||
readme = "README.md"
|
||||
|
||||
1
ruff.schema.json
generated
1
ruff.schema.json
generated
@@ -3740,7 +3740,6 @@
|
||||
"RUF031",
|
||||
"RUF032",
|
||||
"RUF033",
|
||||
"RUF034",
|
||||
"RUF1",
|
||||
"RUF10",
|
||||
"RUF100",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "scripts"
|
||||
version = "0.6.4"
|
||||
version = "0.6.3"
|
||||
description = ""
|
||||
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]
|
||||
|
||||
|
||||
@@ -28,8 +28,16 @@ class Section(NamedTuple):
|
||||
|
||||
SECTIONS: list[Section] = [
|
||||
Section("Overview", "index.md", generated=True),
|
||||
Section("Tutorial", "tutorial.md", generated=False),
|
||||
Section("Installing Ruff", "installation.md", generated=False),
|
||||
Section(
|
||||
"Getting Started",
|
||||
"",
|
||||
generated=False,
|
||||
subsections=[
|
||||
Section("Tutorial", "tutorial.md", generated=False),
|
||||
Section("Installation", "installation.md", generated=False),
|
||||
Section("Configuration", "configuration.md", generated=False),
|
||||
],
|
||||
),
|
||||
Section("The Ruff Linter", "linter.md", generated=False),
|
||||
Section("The Ruff Formatter", "formatter.md", generated=False),
|
||||
Section(
|
||||
@@ -44,11 +52,17 @@ SECTIONS: list[Section] = [
|
||||
Section("Migrating from ruff-lsp", "editors/migration.md", generated=False),
|
||||
],
|
||||
),
|
||||
Section("Configuring Ruff", "configuration.md", generated=False),
|
||||
Section("Preview", "preview.md", generated=False),
|
||||
Section("Rules", "rules.md", generated=True),
|
||||
Section("Settings", "settings.md", generated=True),
|
||||
Section("Versioning", "versioning.md", generated=False),
|
||||
Section(
|
||||
"Reference",
|
||||
"",
|
||||
generated=False,
|
||||
subsections=[
|
||||
Section("Preview", "preview.md", generated=False),
|
||||
Section("Rules", "rules.md", generated=True),
|
||||
Section("Settings", "settings.md", generated=True),
|
||||
Section("Versioning", "versioning.md", generated=False),
|
||||
],
|
||||
),
|
||||
Section("Integrations", "integrations.md", generated=False),
|
||||
Section("FAQ", "faq.md", generated=False),
|
||||
Section("Contributing", "contributing.md", generated=True),
|
||||
@@ -125,8 +139,14 @@ def main() -> None:
|
||||
|
||||
Path("docs").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
section_queue = SECTIONS.copy()
|
||||
|
||||
# Split the README.md into sections.
|
||||
for title, filename, generated, _ in SECTIONS:
|
||||
while section_queue:
|
||||
title, filename, generated, sub_sections = section_queue.pop(0)
|
||||
if sub_sections is not None:
|
||||
section_queue.extend(sub_sections)
|
||||
|
||||
if not generated:
|
||||
continue
|
||||
|
||||
|
||||
@@ -204,11 +204,6 @@ class Venv:
|
||||
"--python",
|
||||
self.python,
|
||||
"--quiet",
|
||||
# We pass `--exclude-newer` to ensure that type-checking of one of
|
||||
# our projects isn't unexpectedly broken by a change in the
|
||||
# annotations of one of that project's dependencies
|
||||
"--exclude-newer",
|
||||
"2024-09-03T00:00:00Z",
|
||||
*dependencies,
|
||||
]
|
||||
|
||||
|
||||
@@ -15,11 +15,7 @@ class Project(typing.NamedTuple):
|
||||
revision: str
|
||||
|
||||
dependencies: list[str]
|
||||
"""List of type checking dependencies.
|
||||
|
||||
Dependencies are pinned using a `--exclude-newer` flag when installing them
|
||||
into the virtual environment; see the `Venv.install()` method for details.
|
||||
"""
|
||||
"""List of type checking dependencies"""
|
||||
|
||||
include: list[str] = []
|
||||
"""The directories and files to check. If empty, checks the current directory"""
|
||||
@@ -100,7 +96,7 @@ ALL = [
|
||||
Project(
|
||||
name="black",
|
||||
repository="https://github.com/psf/black",
|
||||
revision="ac28187bf4a4ac159651c73d3a50fe6d0f653eac",
|
||||
revision="c20423249e9d8dfb8581eebbfc67a13984ee45e9",
|
||||
include=["src"],
|
||||
dependencies=[
|
||||
"aiohttp",
|
||||
|
||||
Reference in New Issue
Block a user