Compare commits
52 Commits
0.6.3
...
dhruv/rest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dc0a523de | ||
|
|
e1e9143c47 | ||
|
|
3c4ec82aee | ||
|
|
29c36a56b2 | ||
|
|
dfee65882b | ||
|
|
50c8ee5175 | ||
|
|
c2aac5f826 | ||
|
|
387af831f9 | ||
|
|
9d517061f2 | ||
|
|
facf6febf0 | ||
|
|
46e687e8d1 | ||
|
|
599103c933 | ||
|
|
54df960a4a | ||
|
|
3463683632 | ||
|
|
6b973b2556 | ||
|
|
c0e2c13d0d | ||
|
|
591a7a152c | ||
|
|
b7c7b4b387 | ||
|
|
ea0246c51a | ||
|
|
0f85769976 | ||
|
|
47f0b45be3 | ||
|
|
f4bed22b05 | ||
|
|
17eb65b26f | ||
|
|
9986397d56 | ||
|
|
58c641c92f | ||
|
|
227fa4e035 | ||
|
|
2b21b77ee6 | ||
|
|
ba272b093c | ||
|
|
8972e5d175 | ||
|
|
9ac2e61bad | ||
|
|
6deb056117 | ||
|
|
c4aad4b161 | ||
|
|
3abd5c08a5 | ||
|
|
2014cba87f | ||
|
|
5661353334 | ||
|
|
dd5d0d523c | ||
|
|
1be8c2e340 | ||
|
|
52d8847b60 | ||
|
|
d3b6e8f58b | ||
|
|
bf620dcb38 | ||
|
|
fae0573817 | ||
|
|
0c23b868dc | ||
|
|
3ceedf76b8 | ||
|
|
828871dc5c | ||
|
|
ee21fc7fd8 | ||
|
|
28ab5f4065 | ||
|
|
a73bebcf15 | ||
|
|
34dafb67a2 | ||
|
|
f8656ff35e | ||
|
|
34b4732c46 | ||
|
|
ce68f1cc1b | ||
|
|
281e6d9791 |
9
.github/workflows/publish-docs.yml
vendored
9
.github/workflows/publish-docs.yml
vendored
@@ -34,10 +34,10 @@ jobs:
|
||||
- name: "Set docs version"
|
||||
run: |
|
||||
version="${{ (inputs.plan != '' && fromJson(inputs.plan).announcement_tag) || inputs.ref }}"
|
||||
# if version is missing, exit with error
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "Can't build docs without a version."
|
||||
exit 1
|
||||
# if version is missing, use 'latest'
|
||||
if [ -z "$version" ]; then
|
||||
echo "Using 'latest' as version"
|
||||
version="latest"
|
||||
fi
|
||||
|
||||
# Use version as display name for now
|
||||
@@ -145,6 +145,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
|
||||
run: |
|
||||
branch_name="${{ env.branch_name }}"
|
||||
|
||||
# auto-merge the PR if the build was triggered by a release. Manual builds should be reviewed by a human.
|
||||
# give the PR a few seconds to be created before trying to auto-merge it
|
||||
sleep 10
|
||||
|
||||
@@ -45,7 +45,7 @@ repos:
|
||||
)$
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.24.1
|
||||
rev: v1.24.3
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -59,7 +59,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.6.2
|
||||
rev: v0.6.3
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
|
||||
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -1803,9 +1803,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-junit"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfc1a6a5406a114913df2df8507998c755311b55b78584bed5f6e88f6417c4d4"
|
||||
checksum = "62ffd2f9a162cfae131bed6d9d1ed60adced33be340a94f96952897d7cb0c240"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"indexmap",
|
||||
@@ -1818,9 +1818,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -1997,6 +1997,7 @@ dependencies = [
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ pep440_rs = { version = "0.6.0", features = ["serde"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
proc-macro2 = { version = "1.0.79" }
|
||||
pyproject-toml = { version = "0.9.0" }
|
||||
quick-junit = { version = "0.4.0" }
|
||||
quick-junit = { version = "0.5.0" }
|
||||
quote = { version = "1.0.23" }
|
||||
rand = { version = "0.8.5" }
|
||||
rayon = { version = "1.10.0" }
|
||||
|
||||
@@ -31,10 +31,10 @@ impl<T> AstNodeRef<T> {
|
||||
/// which the `AstNodeRef` belongs.
|
||||
///
|
||||
/// ## Safety
|
||||
///
|
||||
/// Dereferencing the `node` can result in undefined behavior if `parsed` isn't the
|
||||
/// [`ParsedModule`] to which `node` belongs. It's the caller's responsibility to ensure that
|
||||
/// the invariant `node belongs to parsed` is upheld.
|
||||
|
||||
pub(super) unsafe fn new(parsed: ParsedModule, node: &T) -> Self {
|
||||
Self {
|
||||
_parsed: parsed,
|
||||
|
||||
@@ -23,3 +23,4 @@ pub(crate) mod site_packages;
|
||||
pub mod types;
|
||||
|
||||
type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>;
|
||||
type FxOrderMap<K, V> = ordermap::map::OrderMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
@@ -666,7 +666,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
fn comprehension_scope() {
|
||||
let TestCase { db, file } = test_case(
|
||||
"
|
||||
[x for x in iter1]
|
||||
[x for x, y in iter1]
|
||||
",
|
||||
);
|
||||
|
||||
@@ -690,7 +690,22 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
|
||||
let comprehension_symbol_table = index.symbol_table(comprehension_scope_id);
|
||||
|
||||
assert_eq!(names(&comprehension_symbol_table), vec!["x"]);
|
||||
assert_eq!(names(&comprehension_symbol_table), vec!["x", "y"]);
|
||||
|
||||
let use_def = index.use_def_map(comprehension_scope_id);
|
||||
for name in ["x", "y"] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
comprehension_symbol_table
|
||||
.symbol_id_by_name(name)
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::Comprehension(_)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Test case to validate that the `x` variable used in the comprehension is referencing the
|
||||
@@ -730,8 +745,8 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
let DefinitionKind::Comprehension(comprehension) = definition.node(&db) else {
|
||||
panic!("expected generator definition")
|
||||
};
|
||||
let ast::Comprehension { target, .. } = comprehension.node();
|
||||
let name = target.as_name_expr().unwrap().id().as_str();
|
||||
let target = comprehension.target();
|
||||
let name = target.id().as_str();
|
||||
|
||||
assert_eq!(name, "x");
|
||||
assert_eq!(target.range(), TextRange::new(23.into(), 24.into()));
|
||||
@@ -1073,7 +1088,7 @@ def x():
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_stmt_symbols() {
|
||||
fn match_stmt() {
|
||||
let TestCase { db, file } = test_case(
|
||||
"
|
||||
match subject:
|
||||
@@ -1087,13 +1102,73 @@ match subject:
|
||||
",
|
||||
);
|
||||
|
||||
let global_table = symbol_table(&db, global_scope(&db, file));
|
||||
let global_scope_id = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, global_scope_id);
|
||||
|
||||
assert!(global_table.symbol_by_name("Foo").unwrap().is_used());
|
||||
assert_eq!(
|
||||
names(&global_table),
|
||||
vec!["subject", "a", "b", "c", "d", "f", "e", "h", "g", "Foo", "i", "j", "k", "l"]
|
||||
vec!["subject", "a", "b", "c", "d", "e", "f", "g", "h", "Foo", "i", "j", "k", "l"]
|
||||
);
|
||||
|
||||
let use_def = use_def_map(&db, global_scope_id);
|
||||
for (name, expected_index) in [
|
||||
("a", 0),
|
||||
("b", 0),
|
||||
("c", 1),
|
||||
("d", 2),
|
||||
("e", 0),
|
||||
("f", 1),
|
||||
("g", 0),
|
||||
("h", 1),
|
||||
("i", 0),
|
||||
("j", 1),
|
||||
("k", 0),
|
||||
("l", 1),
|
||||
] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
global_table.symbol_id_by_name(name).expect("symbol exists"),
|
||||
)
|
||||
.expect("Expected with item definition for {name}");
|
||||
if let DefinitionKind::MatchPattern(pattern) = definition.node(&db) {
|
||||
assert_eq!(pattern.index(), expected_index);
|
||||
} else {
|
||||
panic!("Expected match pattern definition for {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_match_case() {
|
||||
let TestCase { db, file } = test_case(
|
||||
"
|
||||
match 1:
|
||||
case first:
|
||||
match 2:
|
||||
case second:
|
||||
pass
|
||||
",
|
||||
);
|
||||
|
||||
let global_scope_id = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, global_scope_id);
|
||||
|
||||
assert_eq!(names(&global_table), vec!["first", "second"]);
|
||||
|
||||
let use_def = use_def_map(&db, global_scope_id);
|
||||
for (name, expected_index) in [("first", 0), ("second", 0)] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
global_table.symbol_id_by_name(name).expect("symbol exists"),
|
||||
)
|
||||
.expect("Expected with item definition for {name}");
|
||||
if let DefinitionKind::MatchPattern(pattern) = definition.node(&db) {
|
||||
assert_eq!(pattern.index(), expected_index);
|
||||
} else {
|
||||
panic!("Expected match pattern definition for {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder};
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::Db;
|
||||
|
||||
use super::definition::WithItemDefinitionNodeRef;
|
||||
use super::definition::{MatchPatternDefinitionNodeRef, WithItemDefinitionNodeRef};
|
||||
|
||||
pub(super) struct SemanticIndexBuilder<'db> {
|
||||
// Builder state
|
||||
@@ -36,6 +36,8 @@ pub(super) struct SemanticIndexBuilder<'db> {
|
||||
scope_stack: Vec<FileScopeId>,
|
||||
/// The assignment we're currently visiting.
|
||||
current_assignment: Option<CurrentAssignment<'db>>,
|
||||
/// The match case we're currently visiting.
|
||||
current_match_case: Option<CurrentMatchCase<'db>>,
|
||||
/// Flow states at each `break` in the current loop.
|
||||
loop_break_states: Vec<FlowSnapshot>,
|
||||
|
||||
@@ -59,6 +61,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
module: parsed,
|
||||
scope_stack: Vec::new(),
|
||||
current_assignment: None,
|
||||
current_match_case: None,
|
||||
loop_break_states: vec![],
|
||||
|
||||
scopes: IndexVec::new(),
|
||||
@@ -282,6 +285,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
|
||||
// The `iter` of the first generator is evaluated in the outer scope, while all subsequent
|
||||
// nodes are evaluated in the inner scope.
|
||||
self.add_standalone_expression(&generator.iter);
|
||||
self.visit_expr(&generator.iter);
|
||||
self.push_scope(scope);
|
||||
|
||||
@@ -297,6 +301,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
}
|
||||
|
||||
for generator in generators_iter {
|
||||
self.add_standalone_expression(&generator.iter);
|
||||
self.visit_expr(&generator.iter);
|
||||
|
||||
self.current_assignment = Some(CurrentAssignment::Comprehension {
|
||||
@@ -600,6 +605,17 @@ where
|
||||
self.visit_body(body);
|
||||
self.visit_body(orelse);
|
||||
}
|
||||
ast::Stmt::Match(ast::StmtMatch {
|
||||
subject,
|
||||
cases,
|
||||
range: _,
|
||||
}) => {
|
||||
self.add_standalone_expression(subject);
|
||||
self.visit_expr(subject);
|
||||
for case in cases {
|
||||
self.visit_match_case(case);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
@@ -664,7 +680,11 @@ where
|
||||
Some(CurrentAssignment::Comprehension { node, first }) => {
|
||||
self.add_definition(
|
||||
symbol,
|
||||
ComprehensionDefinitionNodeRef { node, first },
|
||||
ComprehensionDefinitionNodeRef {
|
||||
iterable: &node.iter,
|
||||
target: name_node,
|
||||
first,
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::WithItem(with_item)) => {
|
||||
@@ -794,7 +814,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_parameters(&mut self, parameters: &'ast ruff_python_ast::Parameters) {
|
||||
fn visit_parameters(&mut self, parameters: &'ast ast::Parameters) {
|
||||
// Intentionally avoid walking default expressions, as we handle them in the enclosing
|
||||
// scope.
|
||||
for parameter in parameters.iter().map(ast::AnyParameterRef::as_parameter) {
|
||||
@@ -802,23 +822,58 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_match_case(&mut self, match_case: &'ast ast::MatchCase) {
|
||||
debug_assert!(self.current_match_case.is_none());
|
||||
self.current_match_case = Some(CurrentMatchCase::new(&match_case.pattern));
|
||||
self.visit_pattern(&match_case.pattern);
|
||||
self.current_match_case = None;
|
||||
|
||||
if let Some(expr) = &match_case.guard {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.visit_body(&match_case.body);
|
||||
}
|
||||
|
||||
fn visit_pattern(&mut self, pattern: &'ast ast::Pattern) {
|
||||
if let ast::Pattern::MatchAs(ast::PatternMatchAs {
|
||||
name: Some(name), ..
|
||||
})
|
||||
| ast::Pattern::MatchStar(ast::PatternMatchStar {
|
||||
if let ast::Pattern::MatchStar(ast::PatternMatchStar {
|
||||
name: Some(name),
|
||||
range: _,
|
||||
}) = pattern
|
||||
{
|
||||
let symbol = self.add_or_update_symbol(name.id().clone(), SymbolFlags::IS_DEFINED);
|
||||
let state = self.current_match_case.as_ref().unwrap();
|
||||
self.add_definition(
|
||||
symbol,
|
||||
MatchPatternDefinitionNodeRef {
|
||||
pattern: state.pattern,
|
||||
identifier: name,
|
||||
index: state.index,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
walk_pattern(self, pattern);
|
||||
|
||||
if let ast::Pattern::MatchAs(ast::PatternMatchAs {
|
||||
name: Some(name), ..
|
||||
})
|
||||
| ast::Pattern::MatchMapping(ast::PatternMatchMapping {
|
||||
rest: Some(name), ..
|
||||
}) = pattern
|
||||
{
|
||||
// TODO(dhruvmanila): Add definition
|
||||
self.add_or_update_symbol(name.id.clone(), SymbolFlags::IS_DEFINED);
|
||||
let symbol = self.add_or_update_symbol(name.id().clone(), SymbolFlags::IS_DEFINED);
|
||||
let state = self.current_match_case.as_ref().unwrap();
|
||||
self.add_definition(
|
||||
symbol,
|
||||
MatchPatternDefinitionNodeRef {
|
||||
pattern: state.pattern,
|
||||
identifier: name,
|
||||
index: state.index,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
walk_pattern(self, pattern);
|
||||
self.current_match_case.as_mut().unwrap().index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,3 +926,27 @@ impl<'a> From<&'a ast::WithItem> for CurrentAssignment<'a> {
|
||||
Self::WithItem(value)
|
||||
}
|
||||
}
|
||||
|
||||
struct CurrentMatchCase<'a> {
|
||||
/// The pattern that's part of the current match case.
|
||||
pattern: &'a ast::Pattern,
|
||||
|
||||
/// The index of the sub-pattern that's being currently visited within the pattern.
|
||||
///
|
||||
/// For example:
|
||||
/// ```py
|
||||
/// match subject:
|
||||
/// case a as b: ...
|
||||
/// case [a, b]: ...
|
||||
/// case a | b: ...
|
||||
/// ```
|
||||
///
|
||||
/// In all of the above cases, the index would be 0 for `a` and 1 for `b`.
|
||||
index: u32,
|
||||
}
|
||||
|
||||
impl<'a> CurrentMatchCase<'a> {
|
||||
fn new(pattern: &'a ast::Pattern) -> Self {
|
||||
Self { pattern, index: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ pub(crate) enum DefinitionNodeRef<'a> {
|
||||
Comprehension(ComprehensionDefinitionNodeRef<'a>),
|
||||
Parameter(ast::AnyParameterRef<'a>),
|
||||
WithItem(WithItemDefinitionNodeRef<'a>),
|
||||
MatchPattern(MatchPatternDefinitionNodeRef<'a>),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::StmtFunctionDef> for DefinitionNodeRef<'a> {
|
||||
@@ -123,6 +124,12 @@ impl<'a> From<ast::AnyParameterRef<'a>> for DefinitionNodeRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<MatchPatternDefinitionNodeRef<'a>> for DefinitionNodeRef<'a> {
|
||||
fn from(node: MatchPatternDefinitionNodeRef<'a>) -> Self {
|
||||
Self::MatchPattern(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct ImportFromDefinitionNodeRef<'a> {
|
||||
pub(crate) node: &'a ast::StmtImportFrom,
|
||||
@@ -149,10 +156,22 @@ pub(crate) struct ForStmtDefinitionNodeRef<'a> {
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct ComprehensionDefinitionNodeRef<'a> {
|
||||
pub(crate) node: &'a ast::Comprehension,
|
||||
pub(crate) iterable: &'a ast::Expr,
|
||||
pub(crate) target: &'a ast::ExprName,
|
||||
pub(crate) first: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct MatchPatternDefinitionNodeRef<'a> {
|
||||
/// The outermost pattern node in which the identifier being defined occurs.
|
||||
pub(crate) pattern: &'a ast::Pattern,
|
||||
/// The identifier being defined.
|
||||
pub(crate) identifier: &'a ast::Identifier,
|
||||
/// The index of the identifier in the pattern when visiting the `pattern` node in evaluation
|
||||
/// order.
|
||||
pub(crate) index: u32,
|
||||
}
|
||||
|
||||
impl DefinitionNodeRef<'_> {
|
||||
#[allow(unsafe_code)]
|
||||
pub(super) unsafe fn into_owned(self, parsed: ParsedModule) -> DefinitionKind {
|
||||
@@ -193,12 +212,15 @@ impl DefinitionNodeRef<'_> {
|
||||
target: AstNodeRef::new(parsed, target),
|
||||
})
|
||||
}
|
||||
DefinitionNodeRef::Comprehension(ComprehensionDefinitionNodeRef { node, first }) => {
|
||||
DefinitionKind::Comprehension(ComprehensionDefinitionKind {
|
||||
node: AstNodeRef::new(parsed, node),
|
||||
first,
|
||||
})
|
||||
}
|
||||
DefinitionNodeRef::Comprehension(ComprehensionDefinitionNodeRef {
|
||||
iterable,
|
||||
target,
|
||||
first,
|
||||
}) => DefinitionKind::Comprehension(ComprehensionDefinitionKind {
|
||||
iterable: AstNodeRef::new(parsed.clone(), iterable),
|
||||
target: AstNodeRef::new(parsed, target),
|
||||
first,
|
||||
}),
|
||||
DefinitionNodeRef::Parameter(parameter) => match parameter {
|
||||
ast::AnyParameterRef::Variadic(parameter) => {
|
||||
DefinitionKind::Parameter(AstNodeRef::new(parsed, parameter))
|
||||
@@ -213,6 +235,15 @@ impl DefinitionNodeRef<'_> {
|
||||
target: AstNodeRef::new(parsed, target),
|
||||
})
|
||||
}
|
||||
DefinitionNodeRef::MatchPattern(MatchPatternDefinitionNodeRef {
|
||||
pattern,
|
||||
identifier,
|
||||
index,
|
||||
}) => DefinitionKind::MatchPattern(MatchPatternDefinitionKind {
|
||||
pattern: AstNodeRef::new(parsed.clone(), pattern),
|
||||
identifier: AstNodeRef::new(parsed, identifier),
|
||||
index,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,12 +266,15 @@ impl DefinitionNodeRef<'_> {
|
||||
iterable: _,
|
||||
target,
|
||||
}) => target.into(),
|
||||
Self::Comprehension(ComprehensionDefinitionNodeRef { node, first: _ }) => node.into(),
|
||||
Self::Comprehension(ComprehensionDefinitionNodeRef { target, .. }) => target.into(),
|
||||
Self::Parameter(node) => match node {
|
||||
ast::AnyParameterRef::Variadic(parameter) => parameter.into(),
|
||||
ast::AnyParameterRef::NonVariadic(parameter) => parameter.into(),
|
||||
},
|
||||
Self::WithItem(WithItemDefinitionNodeRef { node: _, target }) => target.into(),
|
||||
Self::MatchPattern(MatchPatternDefinitionNodeRef { identifier, .. }) => {
|
||||
identifier.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,17 +294,41 @@ pub enum DefinitionKind {
|
||||
Parameter(AstNodeRef<ast::Parameter>),
|
||||
ParameterWithDefault(AstNodeRef<ast::ParameterWithDefault>),
|
||||
WithItem(WithItemDefinitionKind),
|
||||
MatchPattern(MatchPatternDefinitionKind),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct MatchPatternDefinitionKind {
|
||||
pattern: AstNodeRef<ast::Pattern>,
|
||||
identifier: AstNodeRef<ast::Identifier>,
|
||||
index: u32,
|
||||
}
|
||||
|
||||
impl MatchPatternDefinitionKind {
|
||||
pub(crate) fn pattern(&self) -> &ast::Pattern {
|
||||
self.pattern.node()
|
||||
}
|
||||
|
||||
pub(crate) fn index(&self) -> u32 {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComprehensionDefinitionKind {
|
||||
node: AstNodeRef<ast::Comprehension>,
|
||||
iterable: AstNodeRef<ast::Expr>,
|
||||
target: AstNodeRef<ast::ExprName>,
|
||||
first: bool,
|
||||
}
|
||||
|
||||
impl ComprehensionDefinitionKind {
|
||||
pub(crate) fn node(&self) -> &ast::Comprehension {
|
||||
self.node.node()
|
||||
pub(crate) fn iterable(&self) -> &ast::Expr {
|
||||
self.iterable.node()
|
||||
}
|
||||
|
||||
pub(crate) fn target(&self) -> &ast::ExprName {
|
||||
self.target.node()
|
||||
}
|
||||
|
||||
pub(crate) fn is_first(&self) -> bool {
|
||||
@@ -393,12 +451,6 @@ impl From<&ast::StmtFor> for DefinitionNodeKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::Comprehension> for DefinitionNodeKey {
|
||||
fn from(node: &ast::Comprehension) -> Self {
|
||||
Self(NodeKey::from_node(node))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::Parameter> for DefinitionNodeKey {
|
||||
fn from(node: &ast::Parameter) -> Self {
|
||||
Self(NodeKey::from_node(node))
|
||||
@@ -410,3 +462,9 @@ impl From<&ast::ParameterWithDefault> for DefinitionNodeKey {
|
||||
Self(NodeKey::from_node(node))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::Identifier> for DefinitionNodeKey {
|
||||
fn from(identifier: &ast::Identifier) -> Self {
|
||||
Self(NodeKey::from_node(identifier))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,10 @@ impl FileScopeId {
|
||||
FileScopeId::from_u32(0)
|
||||
}
|
||||
|
||||
pub fn is_global(self) -> bool {
|
||||
self == FileScopeId::global()
|
||||
}
|
||||
|
||||
pub fn to_scope_id(self, db: &dyn Db, file: File) -> ScopeId<'_> {
|
||||
let index = semantic_index(db, file);
|
||||
index.scope_ids_by_scope[self]
|
||||
|
||||
@@ -152,9 +152,9 @@ pub(crate) fn definitions_ty<'db>(
|
||||
);
|
||||
let mut all_types = unbound_ty.into_iter().chain(def_types);
|
||||
|
||||
let Some(first) = all_types.next() else {
|
||||
panic!("definitions_ty should never be called with zero definitions and no unbound_ty.")
|
||||
};
|
||||
let first = all_types
|
||||
.next()
|
||||
.expect("definitions_ty should never be called with zero definitions and no unbound_ty.");
|
||||
|
||||
if let Some(second) = all_types.next() {
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
@@ -171,7 +171,7 @@ pub(crate) fn definitions_ty<'db>(
|
||||
}
|
||||
|
||||
/// Unique ID for a type.
|
||||
#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Type<'db> {
|
||||
/// the dynamic type: a statically-unknown set of values
|
||||
Any,
|
||||
@@ -216,14 +216,82 @@ impl<'db> Type<'db> {
|
||||
matches!(self, Type::Unbound)
|
||||
}
|
||||
|
||||
pub const fn is_unknown(&self) -> bool {
|
||||
matches!(self, Type::Unknown)
|
||||
}
|
||||
|
||||
pub const fn is_never(&self) -> bool {
|
||||
matches!(self, Type::Never)
|
||||
}
|
||||
|
||||
pub const fn into_class_type(self) -> Option<ClassType<'db>> {
|
||||
match self {
|
||||
Type::Class(class_type) => Some(class_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_class(self) -> ClassType<'db> {
|
||||
self.into_class_type()
|
||||
.expect("Expected a Type::Class variant")
|
||||
}
|
||||
|
||||
pub const fn into_module_type(self) -> Option<File> {
|
||||
match self {
|
||||
Type::Module(file) => Some(file),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_module(self) -> File {
|
||||
self.into_module_type()
|
||||
.expect("Expected a Type::Module variant")
|
||||
}
|
||||
|
||||
pub const fn into_union_type(self) -> Option<UnionType<'db>> {
|
||||
match self {
|
||||
Type::Union(union_type) => Some(union_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_union(self) -> UnionType<'db> {
|
||||
self.into_union_type()
|
||||
.expect("Expected a Type::Union variant")
|
||||
}
|
||||
|
||||
pub const fn into_intersection_type(self) -> Option<IntersectionType<'db>> {
|
||||
match self {
|
||||
Type::Intersection(intersection_type) => Some(intersection_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_intersection(self) -> IntersectionType<'db> {
|
||||
self.into_intersection_type()
|
||||
.expect("Expected a Type::Intersection variant")
|
||||
}
|
||||
|
||||
pub const fn into_function_type(self) -> Option<FunctionType<'db>> {
|
||||
match self {
|
||||
Type::Function(function_type) => Some(function_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_function(self) -> FunctionType<'db> {
|
||||
self.into_function_type()
|
||||
.expect("Expected a Type::Function variant")
|
||||
}
|
||||
|
||||
pub const fn into_int_literal_type(self) -> Option<i64> {
|
||||
match self {
|
||||
Type::IntLiteral(value) => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_int_literal(self) -> i64 {
|
||||
self.into_int_literal_type()
|
||||
.expect("Expected a Type::IntLiteral variant")
|
||||
}
|
||||
|
||||
pub fn may_be_unbound(&self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
Type::Unbound => true,
|
||||
@@ -320,8 +388,35 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the type resulting from calling an object of this type.
|
||||
///
|
||||
/// Returns `None` if `self` is not a callable type.
|
||||
#[must_use]
|
||||
pub fn instance(&self) -> Type<'db> {
|
||||
pub fn call(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Type::Function(function_type) => Some(function_type.return_type(db)),
|
||||
|
||||
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||
Type::Class(class) => Some(Type::Instance(*class)),
|
||||
|
||||
// TODO: handle classes which implement the Callable protocol
|
||||
Type::Instance(_instance_ty) => Some(Type::Unknown),
|
||||
|
||||
// `Any` is callable, and its return type is also `Any`.
|
||||
Type::Any => Some(Type::Any),
|
||||
|
||||
Type::Unknown => Some(Type::Unknown),
|
||||
|
||||
// TODO: union and intersection types, if they reduce to `Callable`
|
||||
Type::Union(_) => Some(Type::Unknown),
|
||||
Type::Intersection(_) => Some(Type::Unknown),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_instance(&self) -> Type<'db> {
|
||||
match self {
|
||||
Type::Any => Type::Any,
|
||||
Type::Unknown => Type::Unknown,
|
||||
@@ -334,8 +429,11 @@ impl<'db> Type<'db> {
|
||||
#[salsa::interned]
|
||||
pub struct FunctionType<'db> {
|
||||
/// name of the function at definition
|
||||
#[return_ref]
|
||||
pub name: ast::name::Name,
|
||||
|
||||
definition: Definition<'db>,
|
||||
|
||||
/// types of all decorators on this function
|
||||
decorators: Vec<Type<'db>>,
|
||||
}
|
||||
@@ -344,11 +442,42 @@ impl<'db> FunctionType<'db> {
|
||||
pub fn has_decorator(self, db: &dyn Db, decorator: Type<'_>) -> bool {
|
||||
self.decorators(db).contains(&decorator)
|
||||
}
|
||||
|
||||
/// inferred return type for this function
|
||||
pub fn return_type(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
let definition = self.definition(db);
|
||||
let DefinitionKind::Function(function_stmt_node) = definition.node(db) else {
|
||||
panic!("Function type definition must have `DefinitionKind::Function`")
|
||||
};
|
||||
|
||||
// TODO if a function `bar` is decorated by `foo`,
|
||||
// where `foo` is annotated as returning a type `X` that is a subtype of `Callable`,
|
||||
// we need to infer the return type from `X`'s return annotation
|
||||
// rather than from `bar`'s return annotation
|
||||
// in order to determine the type that `bar` returns
|
||||
if !function_stmt_node.decorator_list.is_empty() {
|
||||
return Type::Unknown;
|
||||
}
|
||||
|
||||
function_stmt_node
|
||||
.returns
|
||||
.as_ref()
|
||||
.map(|returns| {
|
||||
if function_stmt_node.is_async {
|
||||
// TODO: generic `types.CoroutineType`!
|
||||
Type::Unknown
|
||||
} else {
|
||||
definition_expression_ty(db, definition, returns.as_ref())
|
||||
}
|
||||
})
|
||||
.unwrap_or(Type::Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned]
|
||||
pub struct ClassType<'db> {
|
||||
/// Name of the class at definition
|
||||
#[return_ref]
|
||||
pub name: ast::name::Name,
|
||||
|
||||
definition: Definition<'db>,
|
||||
@@ -405,6 +534,7 @@ impl<'db> ClassType<'db> {
|
||||
#[salsa::interned]
|
||||
pub struct UnionType<'db> {
|
||||
/// The union type includes values in any of these types.
|
||||
#[return_ref]
|
||||
elements: FxOrderSet<Type<'db>>,
|
||||
}
|
||||
|
||||
@@ -417,6 +547,7 @@ impl<'db> UnionType<'db> {
|
||||
#[salsa::interned]
|
||||
pub struct IntersectionType<'db> {
|
||||
/// The intersection type includes only values in all of these types.
|
||||
#[return_ref]
|
||||
positive: FxOrderSet<Type<'db>>,
|
||||
|
||||
/// The intersection type does not include any value in any of these types.
|
||||
@@ -424,6 +555,7 @@ pub struct IntersectionType<'db> {
|
||||
/// Negation types aren't expressible in annotations, and are most likely to arise from type
|
||||
/// narrowing along with intersections (e.g. `if not isinstance(...)`), so we represent them
|
||||
/// directly in intersections rather than as a separate type.
|
||||
#[return_ref]
|
||||
negative: FxOrderSet<Type<'db>>,
|
||||
}
|
||||
|
||||
@@ -535,4 +667,25 @@ mod tests {
|
||||
let b_file_diagnostics = super::check_types(&db, b_file);
|
||||
assert_eq!(&*b_file_diagnostics, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_callable() {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
nonsense = 123
|
||||
x = nonsense()
|
||||
",
|
||||
)
|
||||
.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 callable"],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,12 @@
|
||||
//! * 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::{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>>,
|
||||
@@ -45,7 +49,7 @@ impl<'db> UnionBuilder<'db> {
|
||||
pub(crate) fn add(mut self, ty: Type<'db>) -> Self {
|
||||
match ty {
|
||||
Type::Union(union) => {
|
||||
self.elements.extend(&union.elements(self.db));
|
||||
self.elements.extend(union.elements(self.db));
|
||||
}
|
||||
Type::Never => {}
|
||||
_ => {
|
||||
@@ -56,11 +60,36 @@ impl<'db> UnionBuilder<'db> {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Type<'db> {
|
||||
/// Performs the following normalizations:
|
||||
/// - Replaces `Literal[True,False]` with `bool`.
|
||||
/// - TODO For enums `E` with members `X1`,...,`Xn`, replaces
|
||||
/// `Literal[E.X1,...,E.Xn]` with `E`.
|
||||
fn simplify(&mut self) {
|
||||
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_by_name(self.db, "bool");
|
||||
self.elements.remove(&Type::BooleanLiteral(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(mut self) -> Type<'db> {
|
||||
match self.elements.len() {
|
||||
0 => Type::Never,
|
||||
1 => self.elements[0],
|
||||
_ => Type::Union(UnionType::new(self.db, self.elements)),
|
||||
_ => {
|
||||
self.simplify();
|
||||
|
||||
match self.elements.len() {
|
||||
0 => Type::Never,
|
||||
1 => self.elements[0],
|
||||
_ => {
|
||||
self.elements.shrink_to_fit();
|
||||
Type::Union(UnionType::new(self.db, self.elements))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,25 +276,48 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
mod tests {
|
||||
use super::{IntersectionBuilder, IntersectionType, Type, UnionBuilder, UnionType};
|
||||
use crate::db::tests::TestDb;
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
TestDb::new()
|
||||
}
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::types::builtins_symbol_ty_by_name;
|
||||
use crate::ProgramSettings;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
|
||||
impl<'db> UnionType<'db> {
|
||||
fn elements_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
|
||||
self.elements(db).into_iter().collect()
|
||||
self.elements(db).into_iter().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
let src_root = SystemPathBuf::from("/src");
|
||||
db.memory_file_system()
|
||||
.create_directory_all(&src_root)
|
||||
.unwrap();
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
&ProgramSettings {
|
||||
target_version: PythonVersion::default(),
|
||||
search_paths: SearchPathSettings::new(src_root),
|
||||
},
|
||||
)
|
||||
.expect("Valid search path settings");
|
||||
|
||||
db
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_union() {
|
||||
let db = setup_db();
|
||||
let t0 = Type::IntLiteral(0);
|
||||
let t1 = Type::IntLiteral(1);
|
||||
let Type::Union(union) = UnionBuilder::new(&db).add(t0).add(t1).build() else {
|
||||
panic!("expected a union");
|
||||
};
|
||||
let union = UnionBuilder::new(&db)
|
||||
.add(t0)
|
||||
.add(t1)
|
||||
.build()
|
||||
.expect_union();
|
||||
|
||||
assert_eq!(union.elements_vec(&db), &[t0, t1]);
|
||||
}
|
||||
@@ -296,6 +348,34 @@ mod tests {
|
||||
assert_eq!(ty, t0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_union_bool() {
|
||||
let db = setup_db();
|
||||
let bool_ty = builtins_symbol_ty_by_name(&db, "bool");
|
||||
|
||||
let t0 = Type::BooleanLiteral(true);
|
||||
let t1 = Type::BooleanLiteral(true);
|
||||
let t2 = Type::BooleanLiteral(false);
|
||||
let t3 = Type::IntLiteral(17);
|
||||
|
||||
let union = UnionBuilder::new(&db)
|
||||
.add(t0)
|
||||
.add(t1)
|
||||
.add(t3)
|
||||
.build()
|
||||
.expect_union();
|
||||
assert_eq!(union.elements_vec(&db), &[t0, t3]);
|
||||
let union = UnionBuilder::new(&db)
|
||||
.add(t0)
|
||||
.add(t1)
|
||||
.add(t2)
|
||||
.add(t3)
|
||||
.build()
|
||||
.expect_union();
|
||||
|
||||
assert_eq!(union.elements_vec(&db), &[bool_ty, t3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_union_flatten() {
|
||||
let db = setup_db();
|
||||
@@ -303,20 +383,22 @@ mod tests {
|
||||
let t1 = Type::IntLiteral(1);
|
||||
let t2 = Type::IntLiteral(2);
|
||||
let u1 = UnionBuilder::new(&db).add(t0).add(t1).build();
|
||||
let Type::Union(union) = UnionBuilder::new(&db).add(u1).add(t2).build() else {
|
||||
panic!("expected a union");
|
||||
};
|
||||
let union = UnionBuilder::new(&db)
|
||||
.add(u1)
|
||||
.add(t2)
|
||||
.build()
|
||||
.expect_union();
|
||||
|
||||
assert_eq!(union.elements_vec(&db), &[t0, t1, t2]);
|
||||
}
|
||||
|
||||
impl<'db> IntersectionType<'db> {
|
||||
fn pos_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
|
||||
self.positive(db).into_iter().collect()
|
||||
self.positive(db).into_iter().copied().collect()
|
||||
}
|
||||
|
||||
fn neg_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
|
||||
self.negative(db).into_iter().collect()
|
||||
self.negative(db).into_iter().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,16 +407,14 @@ mod tests {
|
||||
let db = setup_db();
|
||||
let t0 = Type::IntLiteral(0);
|
||||
let ta = Type::Any;
|
||||
let Type::Intersection(inter) = IntersectionBuilder::new(&db)
|
||||
let intersection = IntersectionBuilder::new(&db)
|
||||
.add_positive(ta)
|
||||
.add_negative(t0)
|
||||
.build()
|
||||
else {
|
||||
panic!("expected to be an intersection");
|
||||
};
|
||||
.expect_intersection();
|
||||
|
||||
assert_eq!(inter.pos_vec(&db), &[ta]);
|
||||
assert_eq!(inter.neg_vec(&db), &[t0]);
|
||||
assert_eq!(intersection.pos_vec(&db), &[ta]);
|
||||
assert_eq!(intersection.neg_vec(&db), &[t0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -347,16 +427,14 @@ mod tests {
|
||||
.add_positive(ta)
|
||||
.add_negative(t1)
|
||||
.build();
|
||||
let Type::Intersection(inter) = IntersectionBuilder::new(&db)
|
||||
let intersection = IntersectionBuilder::new(&db)
|
||||
.add_positive(t2)
|
||||
.add_positive(i0)
|
||||
.build()
|
||||
else {
|
||||
panic!("expected to be an intersection");
|
||||
};
|
||||
.expect_intersection();
|
||||
|
||||
assert_eq!(inter.pos_vec(&db), &[t2, ta]);
|
||||
assert_eq!(inter.neg_vec(&db), &[t1]);
|
||||
assert_eq!(intersection.pos_vec(&db), &[t2, ta]);
|
||||
assert_eq!(intersection.neg_vec(&db), &[t1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -369,16 +447,14 @@ mod tests {
|
||||
.add_positive(ta)
|
||||
.add_negative(t1)
|
||||
.build();
|
||||
let Type::Intersection(inter) = IntersectionBuilder::new(&db)
|
||||
let intersection = IntersectionBuilder::new(&db)
|
||||
.add_positive(t2)
|
||||
.add_negative(i0)
|
||||
.build()
|
||||
else {
|
||||
panic!("expected to be an intersection");
|
||||
};
|
||||
.expect_intersection();
|
||||
|
||||
assert_eq!(inter.pos_vec(&db), &[t2, t1]);
|
||||
assert_eq!(inter.neg_vec(&db), &[ta]);
|
||||
assert_eq!(intersection.pos_vec(&db), &[t2, t1]);
|
||||
assert_eq!(intersection.neg_vec(&db), &[ta]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -389,13 +465,11 @@ mod tests {
|
||||
let ta = Type::Any;
|
||||
let u0 = UnionBuilder::new(&db).add(t0).add(t1).build();
|
||||
|
||||
let Type::Union(union) = IntersectionBuilder::new(&db)
|
||||
let union = IntersectionBuilder::new(&db)
|
||||
.add_positive(ta)
|
||||
.add_positive(u0)
|
||||
.build()
|
||||
else {
|
||||
panic!("expected a union");
|
||||
};
|
||||
.expect_union();
|
||||
let [Type::Intersection(i0), Type::Intersection(i1)] = union.elements_vec(&db)[..] else {
|
||||
panic!("expected a union of two intersections");
|
||||
};
|
||||
|
||||
@@ -6,12 +6,16 @@ use ruff_python_ast::str::Quote;
|
||||
use ruff_python_literal::escape::AsciiEscape;
|
||||
|
||||
use crate::types::{IntersectionType, Type, UnionType};
|
||||
use crate::Db;
|
||||
use crate::{Db, FxOrderMap};
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
pub fn display(&'db self, db: &'db dyn Db) -> DisplayType<'db> {
|
||||
DisplayType { ty: self, db }
|
||||
}
|
||||
|
||||
fn representation(&'db self, db: &'db dyn Db) -> DisplayRepresentation<'db> {
|
||||
DisplayRepresentation { db, ty: self }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
@@ -21,6 +25,39 @@ pub struct DisplayType<'db> {
|
||||
}
|
||||
|
||||
impl Display for DisplayType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let representation = self.ty.representation(self.db);
|
||||
if matches!(
|
||||
self.ty,
|
||||
Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::Class(_)
|
||||
| Type::Function(_)
|
||||
) {
|
||||
write!(f, "Literal[{representation}]",)
|
||||
} else {
|
||||
representation.fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DisplayType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the string representation of a type, which is the value displayed either as
|
||||
/// `Literal[<repr>]` or `Literal[<repr1>, <repr2>]` for literal types or as `<repr>` for
|
||||
/// non literals
|
||||
struct DisplayRepresentation<'db> {
|
||||
ty: &'db Type<'db>,
|
||||
db: &'db dyn Db,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DisplayRepresentation<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self.ty {
|
||||
Type::Any => f.write_str("Any"),
|
||||
@@ -32,39 +69,27 @@ impl Display for DisplayType<'_> {
|
||||
write!(f, "<module '{:?}'>", file.path(self.db))
|
||||
}
|
||||
// TODO functions and classes should display using a fully qualified name
|
||||
Type::Class(class) => write!(f, "Literal[{}]", class.name(self.db)),
|
||||
Type::Instance(class) => f.write_str(&class.name(self.db)),
|
||||
Type::Function(function) => write!(f, "Literal[{}]", function.name(self.db)),
|
||||
Type::Class(class) => f.write_str(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::Union(union) => union.display(self.db).fmt(f),
|
||||
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
||||
Type::IntLiteral(n) => write!(f, "Literal[{n}]"),
|
||||
Type::BooleanLiteral(boolean) => {
|
||||
write!(f, "Literal[{}]", if *boolean { "True" } else { "False" })
|
||||
Type::IntLiteral(n) => write!(f, "{n}"),
|
||||
Type::BooleanLiteral(boolean) => f.write_str(if *boolean { "True" } else { "False" }),
|
||||
Type::StringLiteral(string) => {
|
||||
write!(f, r#""{}""#, string.value(self.db).replace('"', r#"\""#))
|
||||
}
|
||||
Type::StringLiteral(string) => write!(
|
||||
f,
|
||||
r#"Literal["{}"]"#,
|
||||
string.value(self.db).replace('"', r#"\""#)
|
||||
),
|
||||
Type::LiteralString => write!(f, "LiteralString"),
|
||||
Type::LiteralString => f.write_str("LiteralString"),
|
||||
Type::BytesLiteral(bytes) => {
|
||||
let escape =
|
||||
AsciiEscape::with_preferred_quote(bytes.value(self.db).as_ref(), Quote::Double);
|
||||
|
||||
f.write_str("Literal[")?;
|
||||
escape.bytes_repr().write(f)?;
|
||||
f.write_str("]")
|
||||
escape.bytes_repr().write(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DisplayType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> UnionType<'db> {
|
||||
fn display(&'db self, db: &'db dyn Db) -> DisplayUnionType<'db> {
|
||||
DisplayUnionType { db, ty: self }
|
||||
@@ -78,45 +103,58 @@ struct DisplayUnionType<'db> {
|
||||
|
||||
impl Display for DisplayUnionType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let union = self.ty;
|
||||
let elements = self.ty.elements(self.db);
|
||||
|
||||
let (int_literals, other_types): (Vec<Type>, Vec<Type>) = union
|
||||
.elements(self.db)
|
||||
.iter()
|
||||
.copied()
|
||||
.partition(|ty| matches!(ty, Type::IntLiteral(_)));
|
||||
// Group literal types by kind.
|
||||
let mut grouped_literals = FxOrderMap::default();
|
||||
|
||||
for element in elements {
|
||||
if let Ok(literal_kind) = LiteralTypeKind::try_from(*element) {
|
||||
grouped_literals
|
||||
.entry(literal_kind)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(*element);
|
||||
}
|
||||
}
|
||||
|
||||
let mut first = true;
|
||||
if !int_literals.is_empty() {
|
||||
f.write_str("Literal[")?;
|
||||
let mut nums: Vec<_> = int_literals
|
||||
.into_iter()
|
||||
.filter_map(|ty| {
|
||||
if let Type::IntLiteral(n) = ty {
|
||||
Some(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
nums.sort_unstable();
|
||||
for num in nums {
|
||||
|
||||
// Print all types, but write all literals together (while preserving their position).
|
||||
for ty in elements {
|
||||
if let Ok(literal_kind) = LiteralTypeKind::try_from(*ty) {
|
||||
let Some(mut literals) = grouped_literals.remove(&literal_kind) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !first {
|
||||
f.write_str(", ")?;
|
||||
f.write_str(" | ")?;
|
||||
};
|
||||
|
||||
f.write_str("Literal[")?;
|
||||
|
||||
if literal_kind == LiteralTypeKind::IntLiteral {
|
||||
literals.sort_unstable_by_key(|ty| ty.expect_int_literal());
|
||||
}
|
||||
write!(f, "{num}")?;
|
||||
first = false;
|
||||
|
||||
for (i, literal_ty) in literals.iter().enumerate() {
|
||||
if i > 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
literal_ty.representation(self.db).fmt(f)?;
|
||||
}
|
||||
f.write_str("]")?;
|
||||
} else {
|
||||
if !first {
|
||||
f.write_str(" | ")?;
|
||||
};
|
||||
|
||||
ty.display(self.db).fmt(f)?;
|
||||
}
|
||||
f.write_str("]")?;
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
for ty in other_types {
|
||||
if !first {
|
||||
f.write_str(" | ")?;
|
||||
};
|
||||
first = false;
|
||||
write!(f, "{}", ty.display(self.db))?;
|
||||
}
|
||||
debug_assert!(grouped_literals.is_empty());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -128,6 +166,30 @@ impl std::fmt::Debug for DisplayUnionType<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
enum LiteralTypeKind {
|
||||
Class,
|
||||
Function,
|
||||
IntLiteral,
|
||||
StringLiteral,
|
||||
BytesLiteral,
|
||||
}
|
||||
|
||||
impl TryFrom<Type<'_>> for LiteralTypeKind {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Type<'_>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Type::Class(_) => Ok(Self::Class),
|
||||
Type::Function(_) => Ok(Self::Function),
|
||||
Type::IntLiteral(_) => Ok(Self::IntLiteral),
|
||||
Type::StringLiteral(_) => Ok(Self::StringLiteral),
|
||||
Type::BytesLiteral(_) => Ok(Self::BytesLiteral),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> IntersectionType<'db> {
|
||||
fn display(&'db self, db: &'db dyn Db) -> DisplayIntersectionType<'db> {
|
||||
DisplayIntersectionType { db, ty: self }
|
||||
@@ -167,3 +229,91 @@ impl std::fmt::Debug for DisplayIntersectionType<'_> {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::types::{
|
||||
global_symbol_ty_by_name, BytesLiteralType, StringLiteralType, Type, UnionBuilder,
|
||||
};
|
||||
use crate::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
let src_root = SystemPathBuf::from("/src");
|
||||
db.memory_file_system()
|
||||
.create_directory_all(&src_root)
|
||||
.unwrap();
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
&ProgramSettings {
|
||||
target_version: PythonVersion::default(),
|
||||
search_paths: SearchPathSettings::new(src_root),
|
||||
},
|
||||
)
|
||||
.expect("Valid search path settings");
|
||||
|
||||
db
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_condense_literal_display_by_type() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/main.py",
|
||||
"
|
||||
def foo(x: int) -> int:
|
||||
return x + 1
|
||||
|
||||
def bar(s: str) -> str:
|
||||
return s
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
",
|
||||
)?;
|
||||
let mod_file = system_path_to_file(&db, "src/main.py").expect("Expected file to exist.");
|
||||
|
||||
let vec: Vec<Type<'_>> = vec![
|
||||
Type::Unknown,
|
||||
Type::IntLiteral(-1),
|
||||
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_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,
|
||||
];
|
||||
let builder = vec.iter().fold(UnionBuilder::new(&db), |builder, literal| {
|
||||
builder.add(*literal)
|
||||
});
|
||||
let union = builder.build().expect_union();
|
||||
let display = format!("{}", union.display(&db));
|
||||
assert_eq!(
|
||||
display,
|
||||
concat!(
|
||||
"Unknown | ",
|
||||
"Literal[-1, 0, 1] | ",
|
||||
"Literal[A, B] | ",
|
||||
"Literal[\"A\", \"B\"] | ",
|
||||
"Literal[b\"\\x00\", b\"\\x07\"] | ",
|
||||
"Literal[foo, bar] | ",
|
||||
"Literal[True] | ",
|
||||
"None"
|
||||
)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
1ace5718deaf3041f8e3d1dc9c9e8a8e830e517f
|
||||
23d867efb2df6de5600f64656f1aa8a83e06109e
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import sys
|
||||
import typing_extensions
|
||||
from typing import Any, ClassVar, Generic, Literal, TypedDict, overload
|
||||
from typing_extensions import Unpack
|
||||
from typing_extensions import Self, Unpack
|
||||
|
||||
PyCF_ONLY_AST: Literal[1024]
|
||||
PyCF_TYPE_COMMENTS: Literal[4096]
|
||||
@@ -34,6 +34,9 @@ class AST:
|
||||
if sys.version_info >= (3, 13):
|
||||
_field_types: ClassVar[dict[str, Any]]
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self) -> Self: ...
|
||||
|
||||
class mod(AST): ...
|
||||
class type_ignore(AST): ...
|
||||
|
||||
@@ -44,6 +47,9 @@ class TypeIgnore(type_ignore):
|
||||
tag: str
|
||||
def __init__(self, lineno: int, tag: str) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, lineno: int = ..., tag: str = ...) -> Self: ...
|
||||
|
||||
class FunctionType(mod):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("argtypes", "returns")
|
||||
@@ -57,6 +63,9 @@ class FunctionType(mod):
|
||||
else:
|
||||
def __init__(self, argtypes: list[expr], returns: expr) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, argtypes: list[expr] = ..., returns: expr = ...) -> Self: ...
|
||||
|
||||
class Module(mod):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("body", "type_ignores")
|
||||
@@ -67,6 +76,9 @@ class Module(mod):
|
||||
else:
|
||||
def __init__(self, body: list[stmt], type_ignores: list[TypeIgnore]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, body: list[stmt] = ..., type_ignores: list[TypeIgnore] = ...) -> Self: ...
|
||||
|
||||
class Interactive(mod):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("body",)
|
||||
@@ -76,12 +88,18 @@ class Interactive(mod):
|
||||
else:
|
||||
def __init__(self, body: list[stmt]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, body: list[stmt] = ...) -> Self: ...
|
||||
|
||||
class Expression(mod):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("body",)
|
||||
body: expr
|
||||
def __init__(self, body: expr) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, body: expr = ...) -> Self: ...
|
||||
|
||||
class stmt(AST):
|
||||
lineno: int
|
||||
col_offset: int
|
||||
@@ -89,6 +107,9 @@ class stmt(AST):
|
||||
end_col_offset: int | None
|
||||
def __init__(self, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class FunctionDef(stmt):
|
||||
if sys.version_info >= (3, 12):
|
||||
__match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment", "type_params")
|
||||
@@ -152,6 +173,19 @@ class FunctionDef(stmt):
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
name: _Identifier = ...,
|
||||
args: arguments = ...,
|
||||
body: list[stmt] = ...,
|
||||
decorator_list: list[expr] = ...,
|
||||
returns: expr | None = ...,
|
||||
type_comment: str | None = ...,
|
||||
type_params: list[type_param] = ...,
|
||||
) -> Self: ...
|
||||
|
||||
class AsyncFunctionDef(stmt):
|
||||
if sys.version_info >= (3, 12):
|
||||
__match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment", "type_params")
|
||||
@@ -215,6 +249,19 @@ class AsyncFunctionDef(stmt):
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
name: _Identifier = ...,
|
||||
args: arguments = ...,
|
||||
body: list[stmt],
|
||||
decorator_list: list[expr],
|
||||
returns: expr | None,
|
||||
type_comment: str | None,
|
||||
type_params: list[type_param],
|
||||
) -> Self: ...
|
||||
|
||||
class ClassDef(stmt):
|
||||
if sys.version_info >= (3, 12):
|
||||
__match_args__ = ("name", "bases", "keywords", "body", "decorator_list", "type_params")
|
||||
@@ -260,12 +307,28 @@ class ClassDef(stmt):
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
name: _Identifier,
|
||||
bases: list[expr],
|
||||
keywords: list[keyword],
|
||||
body: list[stmt],
|
||||
decorator_list: list[expr],
|
||||
type_params: list[type_param],
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
class Return(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("value",)
|
||||
value: expr | None
|
||||
def __init__(self, value: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, value: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Delete(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("targets",)
|
||||
@@ -275,6 +338,9 @@ class Delete(stmt):
|
||||
else:
|
||||
def __init__(self, targets: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, targets: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Assign(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("targets", "value", "type_comment")
|
||||
@@ -295,6 +361,11 @@ class Assign(stmt):
|
||||
self, targets: list[expr], value: expr, type_comment: str | None = None, **kwargs: Unpack[_Attributes]
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, targets: list[expr] = ..., value: expr = ..., type_comment: str | None = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class AugAssign(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("target", "op", "value")
|
||||
@@ -305,6 +376,16 @@ class AugAssign(stmt):
|
||||
self, target: Name | Attribute | Subscript, op: operator, value: expr, **kwargs: Unpack[_Attributes]
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
target: Name | Attribute | Subscript = ...,
|
||||
op: operator = ...,
|
||||
value: expr = ...,
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
class AnnAssign(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("target", "annotation", "value", "simple")
|
||||
@@ -332,6 +413,17 @@ class AnnAssign(stmt):
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
target: Name | Attribute | Subscript = ...,
|
||||
annotation: expr = ...,
|
||||
value: expr | None = ...,
|
||||
simple: int = ...,
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
class For(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("target", "iter", "body", "orelse", "type_comment")
|
||||
@@ -361,6 +453,18 @@ class For(stmt):
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
target: expr = ...,
|
||||
iter: expr = ...,
|
||||
body: list[stmt] = ...,
|
||||
orelse: list[stmt] = ...,
|
||||
type_comment: str | None = ...,
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
class AsyncFor(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("target", "iter", "body", "orelse", "type_comment")
|
||||
@@ -390,6 +494,18 @@ class AsyncFor(stmt):
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
target: expr = ...,
|
||||
iter: expr = ...,
|
||||
body: list[stmt] = ...,
|
||||
orelse: list[stmt] = ...,
|
||||
type_comment: str | None = ...,
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
class While(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("test", "body", "orelse")
|
||||
@@ -403,6 +519,9 @@ class While(stmt):
|
||||
else:
|
||||
def __init__(self, test: expr, body: list[stmt], orelse: list[stmt], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, test: expr, body: list[stmt], orelse: list[stmt], **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class If(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("test", "body", "orelse")
|
||||
@@ -416,6 +535,11 @@ class If(stmt):
|
||||
else:
|
||||
def __init__(self, test: expr, body: list[stmt], orelse: list[stmt], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, test: expr = ..., body: list[stmt] = ..., orelse: list[stmt] = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class With(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("items", "body", "type_comment")
|
||||
@@ -435,6 +559,16 @@ class With(stmt):
|
||||
self, items: list[withitem], body: list[stmt], type_comment: str | None = None, **kwargs: Unpack[_Attributes]
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
items: list[withitem] = ...,
|
||||
body: list[stmt] = ...,
|
||||
type_comment: str | None = ...,
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
class AsyncWith(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("items", "body", "type_comment")
|
||||
@@ -454,6 +588,16 @@ class AsyncWith(stmt):
|
||||
self, items: list[withitem], body: list[stmt], type_comment: str | None = None, **kwargs: Unpack[_Attributes]
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
items: list[withitem] = ...,
|
||||
body: list[stmt] = ...,
|
||||
type_comment: str | None = ...,
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
class Raise(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("exc", "cause")
|
||||
@@ -461,6 +605,9 @@ class Raise(stmt):
|
||||
cause: expr | None
|
||||
def __init__(self, exc: expr | None = None, cause: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, exc: expr | None = ..., cause: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Try(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("body", "handlers", "orelse", "finalbody")
|
||||
@@ -487,6 +634,17 @@ class Try(stmt):
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
body: list[stmt] = ...,
|
||||
handlers: list[ExceptHandler] = ...,
|
||||
orelse: list[stmt] = ...,
|
||||
finalbody: list[stmt] = ...,
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
class TryStar(stmt):
|
||||
__match_args__ = ("body", "handlers", "orelse", "finalbody")
|
||||
@@ -513,6 +671,17 @@ if sys.version_info >= (3, 11):
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
body: list[stmt] = ...,
|
||||
handlers: list[ExceptHandler] = ...,
|
||||
orelse: list[stmt] = ...,
|
||||
finalbody: list[stmt] = ...,
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
class Assert(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("test", "msg")
|
||||
@@ -520,6 +689,9 @@ class Assert(stmt):
|
||||
msg: expr | None
|
||||
def __init__(self, test: expr, msg: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, test: expr, msg: expr | None, **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Import(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("names",)
|
||||
@@ -529,6 +701,9 @@ class Import(stmt):
|
||||
else:
|
||||
def __init__(self, names: list[alias], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, names: list[alias] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class ImportFrom(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("module", "names", "level")
|
||||
@@ -550,6 +725,11 @@ class ImportFrom(stmt):
|
||||
self, module: str | None = None, *, names: list[alias], level: int, **kwargs: Unpack[_Attributes]
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, module: str | None = ..., names: list[alias] = ..., level: int = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class Global(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("names",)
|
||||
@@ -559,6 +739,9 @@ class Global(stmt):
|
||||
else:
|
||||
def __init__(self, names: list[_Identifier], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, names: list[_Identifier], **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Nonlocal(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("names",)
|
||||
@@ -568,12 +751,18 @@ class Nonlocal(stmt):
|
||||
else:
|
||||
def __init__(self, names: list[_Identifier], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, names: list[_Identifier] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Expr(stmt):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("value",)
|
||||
value: expr
|
||||
def __init__(self, value: expr, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Pass(stmt): ...
|
||||
class Break(stmt): ...
|
||||
class Continue(stmt): ...
|
||||
@@ -585,6 +774,9 @@ class expr(AST):
|
||||
end_col_offset: int | None
|
||||
def __init__(self, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class BoolOp(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("op", "values")
|
||||
@@ -595,6 +787,9 @@ class BoolOp(expr):
|
||||
else:
|
||||
def __init__(self, op: boolop, values: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, op: boolop = ..., values: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class BinOp(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("left", "op", "right")
|
||||
@@ -603,6 +798,11 @@ class BinOp(expr):
|
||||
right: expr
|
||||
def __init__(self, left: expr, op: operator, right: expr, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, left: expr = ..., op: operator = ..., right: expr = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class UnaryOp(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("op", "operand")
|
||||
@@ -610,6 +810,9 @@ class UnaryOp(expr):
|
||||
operand: expr
|
||||
def __init__(self, op: unaryop, operand: expr, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, op: unaryop = ..., operand: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Lambda(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("args", "body")
|
||||
@@ -617,6 +820,9 @@ class Lambda(expr):
|
||||
body: expr
|
||||
def __init__(self, args: arguments, body: expr, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, args: arguments = ..., body: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class IfExp(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("test", "body", "orelse")
|
||||
@@ -625,6 +831,11 @@ class IfExp(expr):
|
||||
orelse: expr
|
||||
def __init__(self, test: expr, body: expr, orelse: expr, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, test: expr = ..., body: expr = ..., orelse: expr = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class Dict(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("keys", "values")
|
||||
@@ -635,6 +846,11 @@ class Dict(expr):
|
||||
else:
|
||||
def __init__(self, keys: list[expr | None], values: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, keys: list[expr | None] = ..., values: list[expr] = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class Set(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("elts",)
|
||||
@@ -644,6 +860,9 @@ class Set(expr):
|
||||
else:
|
||||
def __init__(self, elts: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, elts: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class ListComp(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("elt", "generators")
|
||||
@@ -654,6 +873,11 @@ class ListComp(expr):
|
||||
else:
|
||||
def __init__(self, elt: expr, generators: list[comprehension], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, elt: expr = ..., generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class SetComp(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("elt", "generators")
|
||||
@@ -664,6 +888,11 @@ class SetComp(expr):
|
||||
else:
|
||||
def __init__(self, elt: expr, generators: list[comprehension], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, elt: expr = ..., generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class DictComp(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("key", "value", "generators")
|
||||
@@ -677,6 +906,11 @@ class DictComp(expr):
|
||||
else:
|
||||
def __init__(self, key: expr, value: expr, generators: list[comprehension], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, key: expr = ..., value: expr = ..., generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class GeneratorExp(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("elt", "generators")
|
||||
@@ -687,24 +921,38 @@ class GeneratorExp(expr):
|
||||
else:
|
||||
def __init__(self, elt: expr, generators: list[comprehension], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, elt: expr = ..., generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class Await(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("value",)
|
||||
value: expr
|
||||
def __init__(self, value: expr, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Yield(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("value",)
|
||||
value: expr | None
|
||||
def __init__(self, value: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, value: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class YieldFrom(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("value",)
|
||||
value: expr
|
||||
def __init__(self, value: expr, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Compare(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("left", "ops", "comparators")
|
||||
@@ -718,6 +966,11 @@ class Compare(expr):
|
||||
else:
|
||||
def __init__(self, left: expr, ops: list[cmpop], comparators: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, left: expr = ..., ops: list[cmpop] = ..., comparators: list[expr] = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class Call(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("func", "args", "keywords")
|
||||
@@ -731,6 +984,11 @@ class Call(expr):
|
||||
else:
|
||||
def __init__(self, func: expr, args: list[expr], keywords: list[keyword], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, func: expr = ..., args: list[expr] = ..., keywords: list[keyword] = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class FormattedValue(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("value", "conversion", "format_spec")
|
||||
@@ -739,6 +997,11 @@ class FormattedValue(expr):
|
||||
format_spec: expr | None
|
||||
def __init__(self, value: expr, conversion: int, format_spec: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, value: expr = ..., conversion: int = ..., format_spec: expr | None = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class JoinedStr(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("values",)
|
||||
@@ -748,6 +1011,9 @@ class JoinedStr(expr):
|
||||
else:
|
||||
def __init__(self, values: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, values: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Constant(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("value", "kind")
|
||||
@@ -760,6 +1026,9 @@ class Constant(expr):
|
||||
|
||||
def __init__(self, value: Any, kind: str | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, value: Any = ..., kind: str | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class NamedExpr(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("target", "value")
|
||||
@@ -767,6 +1036,9 @@ class NamedExpr(expr):
|
||||
value: expr
|
||||
def __init__(self, target: Name, value: expr, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, target: Name = ..., value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Attribute(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("value", "attr", "ctx")
|
||||
@@ -775,6 +1047,11 @@ class Attribute(expr):
|
||||
ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__`
|
||||
def __init__(self, value: expr, attr: _Identifier, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, value: expr = ..., attr: _Identifier = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
_Slice: typing_extensions.TypeAlias = expr
|
||||
_SliceAttributes: typing_extensions.TypeAlias = _Attributes
|
||||
@@ -794,6 +1071,16 @@ class Slice(_Slice):
|
||||
self, lower: expr | None = None, upper: expr | None = None, step: expr | None = None, **kwargs: Unpack[_SliceAttributes]
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
lower: expr | None = ...,
|
||||
upper: expr | None = ...,
|
||||
step: expr | None = ...,
|
||||
**kwargs: Unpack[_SliceAttributes],
|
||||
) -> Self: ...
|
||||
|
||||
if sys.version_info < (3, 9):
|
||||
class ExtSlice(slice):
|
||||
dims: list[slice]
|
||||
@@ -811,6 +1098,11 @@ class Subscript(expr):
|
||||
ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__`
|
||||
def __init__(self, value: expr, slice: _Slice, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, value: expr = ..., slice: _Slice = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class Starred(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("value", "ctx")
|
||||
@@ -818,6 +1110,9 @@ class Starred(expr):
|
||||
ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__`
|
||||
def __init__(self, value: expr, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, value: expr = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Name(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("id", "ctx")
|
||||
@@ -825,6 +1120,9 @@ class Name(expr):
|
||||
ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__`
|
||||
def __init__(self, id: _Identifier, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, id: _Identifier = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class List(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("elts", "ctx")
|
||||
@@ -835,6 +1133,9 @@ class List(expr):
|
||||
else:
|
||||
def __init__(self, elts: list[expr], ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, elts: list[expr] = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class Tuple(expr):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("elts", "ctx")
|
||||
@@ -847,6 +1148,9 @@ class Tuple(expr):
|
||||
else:
|
||||
def __init__(self, elts: list[expr], ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, elts: list[expr] = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class expr_context(AST): ...
|
||||
|
||||
if sys.version_info < (3, 9):
|
||||
@@ -910,6 +1214,9 @@ class comprehension(AST):
|
||||
else:
|
||||
def __init__(self, target: expr, iter: expr, ifs: list[expr], is_async: int) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, target: expr = ..., iter: expr = ..., ifs: list[expr] = ..., is_async: int = ...) -> Self: ...
|
||||
|
||||
class excepthandler(AST):
|
||||
lineno: int
|
||||
col_offset: int
|
||||
@@ -917,6 +1224,11 @@ class excepthandler(AST):
|
||||
end_col_offset: int | None
|
||||
def __init__(self, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, lineno: int = ..., col_offset: int = ..., end_lineno: int | None = ..., end_col_offset: int | None = ...
|
||||
) -> Self: ...
|
||||
|
||||
class ExceptHandler(excepthandler):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("type", "name", "body")
|
||||
@@ -937,6 +1249,16 @@ class ExceptHandler(excepthandler):
|
||||
self, type: expr | None = None, name: _Identifier | None = None, *, body: list[stmt], **kwargs: Unpack[_Attributes]
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
type: expr | None = ...,
|
||||
name: _Identifier | None = ...,
|
||||
body: list[stmt] = ...,
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
class arguments(AST):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("posonlyargs", "args", "vararg", "kwonlyargs", "kw_defaults", "kwarg", "defaults")
|
||||
@@ -995,6 +1317,19 @@ class arguments(AST):
|
||||
defaults: list[expr],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
posonlyargs: list[arg] = ...,
|
||||
args: list[arg] = ...,
|
||||
vararg: arg | None = ...,
|
||||
kwonlyargs: list[arg] = ...,
|
||||
kw_defaults: list[expr | None] = ...,
|
||||
kwarg: arg | None = ...,
|
||||
defaults: list[expr] = ...,
|
||||
) -> Self: ...
|
||||
|
||||
class arg(AST):
|
||||
lineno: int
|
||||
col_offset: int
|
||||
@@ -1009,6 +1344,16 @@ class arg(AST):
|
||||
self, arg: _Identifier, annotation: expr | None = None, type_comment: str | None = None, **kwargs: Unpack[_Attributes]
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
arg: _Identifier = ...,
|
||||
annotation: expr | None = ...,
|
||||
type_comment: str | None = ...,
|
||||
**kwargs: Unpack[_Attributes],
|
||||
) -> Self: ...
|
||||
|
||||
class keyword(AST):
|
||||
lineno: int
|
||||
col_offset: int
|
||||
@@ -1023,6 +1368,9 @@ class keyword(AST):
|
||||
@overload
|
||||
def __init__(self, arg: _Identifier | None = None, *, value: expr, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, arg: _Identifier | None = ..., value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class alias(AST):
|
||||
lineno: int
|
||||
col_offset: int
|
||||
@@ -1034,6 +1382,9 @@ class alias(AST):
|
||||
asname: _Identifier | None
|
||||
def __init__(self, name: str, asname: _Identifier | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, name: str = ..., asname: _Identifier | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
|
||||
|
||||
class withitem(AST):
|
||||
if sys.version_info >= (3, 10):
|
||||
__match_args__ = ("context_expr", "optional_vars")
|
||||
@@ -1041,6 +1392,9 @@ class withitem(AST):
|
||||
optional_vars: expr | None
|
||||
def __init__(self, context_expr: expr, optional_vars: expr | None = None) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, context_expr: expr = ..., optional_vars: expr | None = ...) -> Self: ...
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
class Match(stmt):
|
||||
__match_args__ = ("subject", "cases")
|
||||
@@ -1051,6 +1405,11 @@ if sys.version_info >= (3, 10):
|
||||
else:
|
||||
def __init__(self, subject: expr, cases: list[match_case], **kwargs: Unpack[_Attributes]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, subject: expr = ..., cases: list[match_case] = ..., **kwargs: Unpack[_Attributes]
|
||||
) -> Self: ...
|
||||
|
||||
class pattern(AST):
|
||||
lineno: int
|
||||
col_offset: int
|
||||
@@ -1058,6 +1417,11 @@ if sys.version_info >= (3, 10):
|
||||
end_col_offset: int
|
||||
def __init__(self, **kwargs: Unpack[_Attributes[int]]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, lineno: int = ..., col_offset: int = ..., end_lineno: int = ..., end_col_offset: int = ...
|
||||
) -> Self: ...
|
||||
|
||||
# Without the alias, Pyright complains variables named pattern are recursively defined
|
||||
_Pattern: typing_extensions.TypeAlias = pattern
|
||||
|
||||
@@ -1074,16 +1438,25 @@ if sys.version_info >= (3, 10):
|
||||
@overload
|
||||
def __init__(self, pattern: _Pattern, guard: expr | None = None, *, body: list[stmt]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, pattern: _Pattern = ..., guard: expr | None = ..., body: list[stmt] = ...) -> Self: ...
|
||||
|
||||
class MatchValue(pattern):
|
||||
__match_args__ = ("value",)
|
||||
value: expr
|
||||
def __init__(self, value: expr, **kwargs: Unpack[_Attributes[int]]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ...
|
||||
|
||||
class MatchSingleton(pattern):
|
||||
__match_args__ = ("value",)
|
||||
value: Literal[True, False] | None
|
||||
def __init__(self, value: Literal[True, False] | None, **kwargs: Unpack[_Attributes[int]]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, value: Literal[True, False] | None = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ...
|
||||
|
||||
class MatchSequence(pattern):
|
||||
__match_args__ = ("patterns",)
|
||||
patterns: list[pattern]
|
||||
@@ -1092,11 +1465,17 @@ if sys.version_info >= (3, 10):
|
||||
else:
|
||||
def __init__(self, patterns: list[pattern], **kwargs: Unpack[_Attributes[int]]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ...
|
||||
|
||||
class MatchStar(pattern):
|
||||
__match_args__ = ("name",)
|
||||
name: _Identifier | None
|
||||
def __init__(self, name: _Identifier | None, **kwargs: Unpack[_Attributes[int]]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, name: _Identifier | None = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ...
|
||||
|
||||
class MatchMapping(pattern):
|
||||
__match_args__ = ("keys", "patterns", "rest")
|
||||
keys: list[expr]
|
||||
@@ -1119,6 +1498,16 @@ if sys.version_info >= (3, 10):
|
||||
**kwargs: Unpack[_Attributes[int]],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
keys: list[expr] = ...,
|
||||
patterns: list[pattern] = ...,
|
||||
rest: _Identifier | None = ...,
|
||||
**kwargs: Unpack[_Attributes[int]],
|
||||
) -> Self: ...
|
||||
|
||||
class MatchClass(pattern):
|
||||
__match_args__ = ("cls", "patterns", "kwd_attrs", "kwd_patterns")
|
||||
cls: expr
|
||||
@@ -1144,6 +1533,17 @@ if sys.version_info >= (3, 10):
|
||||
**kwargs: Unpack[_Attributes[int]],
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
cls: expr = ...,
|
||||
patterns: list[pattern] = ...,
|
||||
kwd_attrs: list[_Identifier] = ...,
|
||||
kwd_patterns: list[pattern] = ...,
|
||||
**kwargs: Unpack[_Attributes[int]],
|
||||
) -> Self: ...
|
||||
|
||||
class MatchAs(pattern):
|
||||
__match_args__ = ("pattern", "name")
|
||||
pattern: _Pattern | None
|
||||
@@ -1152,6 +1552,11 @@ if sys.version_info >= (3, 10):
|
||||
self, pattern: _Pattern | None = None, name: _Identifier | None = None, **kwargs: Unpack[_Attributes[int]]
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, pattern: _Pattern | None = ..., name: _Identifier | None = ..., **kwargs: Unpack[_Attributes[int]]
|
||||
) -> Self: ...
|
||||
|
||||
class MatchOr(pattern):
|
||||
__match_args__ = ("patterns",)
|
||||
patterns: list[pattern]
|
||||
@@ -1160,6 +1565,9 @@ if sys.version_info >= (3, 10):
|
||||
else:
|
||||
def __init__(self, patterns: list[pattern], **kwargs: Unpack[_Attributes[int]]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, *, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ...
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
class type_param(AST):
|
||||
lineno: int
|
||||
@@ -1168,6 +1576,9 @@ if sys.version_info >= (3, 12):
|
||||
end_col_offset: int
|
||||
def __init__(self, **kwargs: Unpack[_Attributes[int]]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(self, **kwargs: Unpack[_Attributes[int]]) -> Self: ...
|
||||
|
||||
class TypeVar(type_param):
|
||||
if sys.version_info >= (3, 13):
|
||||
__match_args__ = ("name", "bound", "default_value")
|
||||
@@ -1187,6 +1598,16 @@ if sys.version_info >= (3, 12):
|
||||
else:
|
||||
def __init__(self, name: _Identifier, bound: expr | None = None, **kwargs: Unpack[_Attributes[int]]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
name: _Identifier = ...,
|
||||
bound: expr | None = ...,
|
||||
default_value: expr | None = ...,
|
||||
**kwargs: Unpack[_Attributes[int]],
|
||||
) -> Self: ...
|
||||
|
||||
class ParamSpec(type_param):
|
||||
if sys.version_info >= (3, 13):
|
||||
__match_args__ = ("name", "default_value")
|
||||
@@ -1201,6 +1622,11 @@ if sys.version_info >= (3, 12):
|
||||
else:
|
||||
def __init__(self, name: _Identifier, **kwargs: Unpack[_Attributes[int]]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, name: _Identifier = ..., default_value: expr | None = ..., **kwargs: Unpack[_Attributes[int]]
|
||||
) -> Self: ...
|
||||
|
||||
class TypeVarTuple(type_param):
|
||||
if sys.version_info >= (3, 13):
|
||||
__match_args__ = ("name", "default_value")
|
||||
@@ -1215,6 +1641,11 @@ if sys.version_info >= (3, 12):
|
||||
else:
|
||||
def __init__(self, name: _Identifier, **kwargs: Unpack[_Attributes[int]]) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self, *, name: _Identifier = ..., default_value: expr | None = ..., **kwargs: Unpack[_Attributes[int]]
|
||||
) -> Self: ...
|
||||
|
||||
class TypeAlias(stmt):
|
||||
__match_args__ = ("name", "type_params", "value")
|
||||
name: Name
|
||||
@@ -1233,3 +1664,13 @@ if sys.version_info >= (3, 12):
|
||||
def __init__(
|
||||
self, name: Name, type_params: list[type_param], value: expr, **kwargs: Unpack[_Attributes[int]]
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def __replace__(
|
||||
self,
|
||||
*,
|
||||
name: Name = ...,
|
||||
type_params: list[type_param] = ...,
|
||||
value: expr = ...,
|
||||
**kwargs: Unpack[_Attributes[int]],
|
||||
) -> Self: ...
|
||||
|
||||
@@ -2,7 +2,7 @@ import sys
|
||||
from _typeshed import SupportsGetItem
|
||||
from collections.abc import Callable, Container, Iterable, MutableMapping, MutableSequence, Sequence
|
||||
from typing import Any, AnyStr, Generic, Protocol, SupportsAbs, SupportsIndex, TypeVar, final, overload
|
||||
from typing_extensions import ParamSpec, TypeAlias, TypeVarTuple, Unpack
|
||||
from typing_extensions import ParamSpec, TypeAlias, TypeIs, TypeVarTuple, Unpack
|
||||
|
||||
_R = TypeVar("_R")
|
||||
_T = TypeVar("_T")
|
||||
@@ -145,3 +145,7 @@ if sys.version_info >= (3, 11):
|
||||
def call(obj: Callable[_P, _R], /, *args: _P.args, **kwargs: _P.kwargs) -> _R: ...
|
||||
|
||||
def _compare_digest(a: AnyStr, b: AnyStr, /) -> bool: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
def is_none(a: object, /) -> TypeIs[None]: ...
|
||||
def is_not_none(a: _T | None, /) -> TypeIs[_T]: ...
|
||||
|
||||
@@ -28,9 +28,9 @@ S_IFDIR: Final = 0o040000
|
||||
|
||||
# These are 0 on systems that don't support the specific kind of file.
|
||||
# Example: Linux doesn't support door files, so S_IFDOOR is 0 on linux.
|
||||
S_IFDOOR: int
|
||||
S_IFPORT: int
|
||||
S_IFWHT: int
|
||||
S_IFDOOR: Final[int]
|
||||
S_IFPORT: Final[int]
|
||||
S_IFWHT: Final[int]
|
||||
|
||||
S_ISUID: Final = 0o4000
|
||||
S_ISGID: Final = 0o2000
|
||||
@@ -79,9 +79,9 @@ def S_ISWHT(mode: int, /) -> bool: ...
|
||||
def filemode(mode: int, /) -> str: ...
|
||||
|
||||
if sys.platform == "win32":
|
||||
IO_REPARSE_TAG_SYMLINK: int
|
||||
IO_REPARSE_TAG_MOUNT_POINT: int
|
||||
IO_REPARSE_TAG_APPEXECLINK: int
|
||||
IO_REPARSE_TAG_SYMLINK: Final = 0xA000000C
|
||||
IO_REPARSE_TAG_MOUNT_POINT: Final = 0xA0000003
|
||||
IO_REPARSE_TAG_APPEXECLINK: Final = 0x8000001B
|
||||
|
||||
if sys.platform == "win32":
|
||||
FILE_ATTRIBUTE_ARCHIVE: Final = 32
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import signal
|
||||
import sys
|
||||
from _typeshed import structseq
|
||||
from collections.abc import Callable
|
||||
@@ -16,16 +17,39 @@ class LockType:
|
||||
def acquire(self, blocking: bool = True, timeout: float = -1) -> bool: ...
|
||||
def release(self) -> None: ...
|
||||
def locked(self) -> bool: ...
|
||||
def acquire_lock(self, blocking: bool = True, timeout: float = -1) -> bool: ...
|
||||
def release_lock(self) -> None: ...
|
||||
def locked_lock(self) -> bool: ...
|
||||
def __enter__(self) -> bool: ...
|
||||
def __exit__(
|
||||
self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
||||
) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
@final
|
||||
class _ThreadHandle:
|
||||
ident: int
|
||||
|
||||
def join(self, timeout: float | None = None, /) -> None: ...
|
||||
def is_done(self) -> bool: ...
|
||||
def _set_done(self) -> None: ...
|
||||
|
||||
def start_joinable_thread(
|
||||
function: Callable[[], object], handle: _ThreadHandle | None = None, daemon: bool = True
|
||||
) -> _ThreadHandle: ...
|
||||
lock = LockType
|
||||
|
||||
@overload
|
||||
def start_new_thread(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts]], /) -> int: ...
|
||||
@overload
|
||||
def start_new_thread(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any], /) -> int: ...
|
||||
def interrupt_main() -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
def interrupt_main(signum: signal.Signals = ..., /) -> None: ...
|
||||
|
||||
else:
|
||||
def interrupt_main() -> None: ...
|
||||
|
||||
def exit() -> NoReturn: ...
|
||||
def allocate_lock() -> LockType: ...
|
||||
def get_ident() -> int: ...
|
||||
|
||||
@@ -106,8 +106,8 @@ EXCEPTION: Final = 8
|
||||
READABLE: Final = 2
|
||||
WRITABLE: Final = 4
|
||||
|
||||
TCL_VERSION: str
|
||||
TK_VERSION: str
|
||||
TCL_VERSION: Final[str]
|
||||
TK_VERSION: Final[str]
|
||||
|
||||
@final
|
||||
class TkttType:
|
||||
|
||||
@@ -33,7 +33,8 @@ from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet,
|
||||
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
|
||||
from types import CellType, CodeType, TracebackType
|
||||
|
||||
# mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping} are imported from collections.abc in builtins.pyi
|
||||
# mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping}
|
||||
# are imported from collections.abc in builtins.pyi
|
||||
from typing import ( # noqa: Y022
|
||||
IO,
|
||||
Any,
|
||||
@@ -967,7 +968,9 @@ class tuple(Sequence[_T_co]):
|
||||
if sys.version_info >= (3, 9):
|
||||
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
||||
|
||||
# Doesn't exist at runtime, but deleting this breaks mypy. See #2999
|
||||
# Doesn't exist at runtime, but deleting this breaks mypy and pyright. See:
|
||||
# https://github.com/python/typeshed/issues/7580
|
||||
# https://github.com/python/mypy/issues/8240
|
||||
@final
|
||||
@type_check_only
|
||||
class function:
|
||||
@@ -1084,7 +1087,8 @@ class dict(MutableMapping[_KT, _VT]):
|
||||
def keys(self) -> dict_keys[_KT, _VT]: ...
|
||||
def values(self) -> dict_values[_KT, _VT]: ...
|
||||
def items(self) -> dict_items[_KT, _VT]: ...
|
||||
# Signature of `dict.fromkeys` should be kept identical to `fromkeys` methods of `OrderedDict`/`ChainMap`/`UserDict` in `collections`
|
||||
# Signature of `dict.fromkeys` should be kept identical to
|
||||
# `fromkeys` methods of `OrderedDict`/`ChainMap`/`UserDict` in `collections`
|
||||
# TODO: the true signature of `dict.fromkeys` is not expressible in the current type system.
|
||||
# See #3800 & https://github.com/python/typing/issues/548#issuecomment-683336963.
|
||||
@classmethod
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from collections.abc import Callable
|
||||
from typing import IO, Any, Final
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
__all__ = ["Cmd"]
|
||||
|
||||
PROMPT: Final = "(Cmd) "
|
||||
IDENTCHARS: str # Too big to be `Literal`
|
||||
IDENTCHARS: Final[LiteralString] # Too big to be `Literal`
|
||||
|
||||
class Cmd:
|
||||
prompt: str
|
||||
|
||||
@@ -345,15 +345,15 @@ class _OrderedDictValuesView(ValuesView[_VT_co], Reversible[_VT_co]):
|
||||
# but they are not exposed anywhere)
|
||||
# pyright doesn't have a specific error code for subclassing error!
|
||||
@final
|
||||
class _odict_keys(dict_keys[_KT_co, _VT_co], Reversible[_KT_co]): # type: ignore[misc] # pyright: ignore
|
||||
class _odict_keys(dict_keys[_KT_co, _VT_co], Reversible[_KT_co]): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
|
||||
def __reversed__(self) -> Iterator[_KT_co]: ...
|
||||
|
||||
@final
|
||||
class _odict_items(dict_items[_KT_co, _VT_co], Reversible[tuple[_KT_co, _VT_co]]): # type: ignore[misc] # pyright: ignore
|
||||
class _odict_items(dict_items[_KT_co, _VT_co], Reversible[tuple[_KT_co, _VT_co]]): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
|
||||
def __reversed__(self) -> Iterator[tuple[_KT_co, _VT_co]]: ...
|
||||
|
||||
@final
|
||||
class _odict_values(dict_values[_KT_co, _VT_co], Reversible[_VT_co], Generic[_KT_co, _VT_co]): # type: ignore[misc] # pyright: ignore
|
||||
class _odict_values(dict_values[_KT_co, _VT_co], Reversible[_VT_co], Generic[_KT_co, _VT_co]): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
|
||||
def __reversed__(self) -> Iterator[_VT_co]: ...
|
||||
|
||||
class OrderedDict(dict[_KT, _VT], Reversible[_KT], Generic[_KT, _VT]):
|
||||
@@ -475,7 +475,8 @@ class ChainMap(MutableMapping[_KT, _VT]):
|
||||
def pop(self, key: _KT, default: _T) -> _VT | _T: ...
|
||||
def copy(self) -> Self: ...
|
||||
__copy__ = copy
|
||||
# All arguments to `fromkeys` are passed to `dict.fromkeys` at runtime, so the signature should be kept in line with `dict.fromkeys`.
|
||||
# All arguments to `fromkeys` are passed to `dict.fromkeys` at runtime,
|
||||
# so the signature should be kept in line with `dict.fromkeys`.
|
||||
@classmethod
|
||||
@overload
|
||||
def fromkeys(cls, iterable: Iterable[_T]) -> ChainMap[_T, Any | None]: ...
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import sys
|
||||
from typing import Final
|
||||
|
||||
if sys.platform != "win32":
|
||||
class _Method: ...
|
||||
METHOD_CRYPT: _Method
|
||||
METHOD_MD5: _Method
|
||||
METHOD_SHA256: _Method
|
||||
METHOD_SHA512: _Method
|
||||
METHOD_BLOWFISH: _Method
|
||||
METHOD_CRYPT: Final[_Method]
|
||||
METHOD_MD5: Final[_Method]
|
||||
METHOD_SHA256: Final[_Method]
|
||||
METHOD_SHA512: Final[_Method]
|
||||
METHOD_BLOWFISH: Final[_Method]
|
||||
methods: list[_Method]
|
||||
def mksalt(method: _Method | None = None, *, rounds: int | None = None) -> str: ...
|
||||
def crypt(word: str, salt: str | _Method | None = None) -> str: ...
|
||||
|
||||
@@ -185,3 +185,8 @@ if sys.version_info >= (3, 12):
|
||||
c_time_t: type[c_int32 | c_int64] # alias for one or the other at runtime
|
||||
|
||||
class py_object(_CanCastTo, _SimpleCData[_T]): ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
class c_float_complex(_SimpleCData[complex]): ...
|
||||
class c_double_complex(_SimpleCData[complex]): ...
|
||||
class c_longdouble_complex(_SimpleCData[complex]): ...
|
||||
|
||||
@@ -229,18 +229,17 @@ if sys.version_info >= (3, 9):
|
||||
else:
|
||||
class _InitVarMeta(type):
|
||||
# Not used, instead `InitVar.__class_getitem__` is called.
|
||||
# pyright ignore is needed because pyright (not unreasonably) thinks this
|
||||
# is an invalid use of InitVar.
|
||||
def __getitem__(self, params: Any) -> InitVar[Any]: ... # pyright: ignore
|
||||
# pyright (not unreasonably) thinks this is an invalid use of InitVar.
|
||||
def __getitem__(self, params: Any) -> InitVar[Any]: ... # pyright: ignore[reportInvalidTypeForm]
|
||||
|
||||
class InitVar(Generic[_T], metaclass=_InitVarMeta):
|
||||
type: Type[_T]
|
||||
def __init__(self, type: Type[_T]) -> None: ...
|
||||
if sys.version_info >= (3, 9):
|
||||
@overload
|
||||
def __class_getitem__(cls, type: Type[_T]) -> InitVar[_T]: ... # pyright: ignore
|
||||
def __class_getitem__(cls, type: Type[_T]) -> InitVar[_T]: ... # pyright: ignore[reportInvalidTypeForm]
|
||||
@overload
|
||||
def __class_getitem__(cls, type: Any) -> InitVar[Any]: ... # pyright: ignore
|
||||
def __class_getitem__(cls, type: Any) -> InitVar[Any]: ... # pyright: ignore[reportInvalidTypeForm]
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
def make_dataclass(
|
||||
|
||||
@@ -265,12 +265,12 @@ class datetime(date):
|
||||
def fromtimestamp(cls, timestamp: float, /, tz: _TzInfo | None = ...) -> Self: ...
|
||||
|
||||
@classmethod
|
||||
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .fromtimestamp(datetime.UTC)")
|
||||
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .fromtimestamp(datetime.timezone.utc)")
|
||||
def utcfromtimestamp(cls, t: float, /) -> Self: ...
|
||||
@classmethod
|
||||
def now(cls, tz: _TzInfo | None = None) -> Self: ...
|
||||
@classmethod
|
||||
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.UTC)")
|
||||
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.timezone.utc)")
|
||||
def utcnow(cls) -> Self: ...
|
||||
@classmethod
|
||||
def combine(cls, date: _Date, time: _Time, tzinfo: _TzInfo | None = ...) -> Self: ...
|
||||
|
||||
@@ -30,6 +30,7 @@ _CommandT = TypeVar("_CommandT", bound=Command)
|
||||
_Ts = TypeVarTuple("_Ts")
|
||||
|
||||
class Command:
|
||||
dry_run: Literal[0, 1] # Exposed from __getattr_. Same as Distribution.dry_run
|
||||
distribution: Distribution
|
||||
# Any to work around variance issues
|
||||
sub_commands: ClassVar[list[tuple[str, Callable[[Any], bool] | None]]]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, ClassVar, Literal
|
||||
from typing import Any, ClassVar, Final, Literal
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
from ..cmd import Command
|
||||
@@ -22,7 +22,7 @@ class SilentReporter(_Reporter):
|
||||
) -> None: ...
|
||||
def system_message(self, level, message, *children, **kwargs): ...
|
||||
|
||||
HAS_DOCUTILS: bool
|
||||
HAS_DOCUTILS: Final[bool]
|
||||
|
||||
class check(Command):
|
||||
description: str
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from _typeshed import StrOrBytesPath
|
||||
from collections.abc import Sequence
|
||||
from re import Pattern
|
||||
from typing import Any, ClassVar, Literal
|
||||
from typing import Any, ClassVar, Final, Literal
|
||||
|
||||
from ..ccompiler import CCompiler
|
||||
from ..cmd import Command
|
||||
|
||||
LANG_EXT: dict[str, str]
|
||||
LANG_EXT: Final[dict[str, str]]
|
||||
|
||||
class config(Command):
|
||||
description: str
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from typing import Any, ClassVar
|
||||
from typing import Any, ClassVar, Final, Literal
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
HAS_USER_SITE: bool
|
||||
SCHEME_KEYS: tuple[str, ...]
|
||||
INSTALL_SCHEMES: dict[str, dict[Any, Any]]
|
||||
HAS_USER_SITE: Final[bool]
|
||||
|
||||
SCHEME_KEYS: Final[tuple[Literal["purelib"], Literal["platlib"], Literal["headers"], Literal["scripts"], Literal["data"]]]
|
||||
INSTALL_SCHEMES: Final[dict[str, dict[str, str]]]
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
WINDOWS_SCHEME: Final[dict[str, str]]
|
||||
|
||||
class install(Command):
|
||||
description: str
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Any, ClassVar
|
||||
from typing import Any, ClassVar, Final
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
PYTHON_SOURCE_EXTENSION: str
|
||||
PYTHON_SOURCE_EXTENSION: Final = ".py"
|
||||
|
||||
class install_lib(Command):
|
||||
description: str
|
||||
|
||||
@@ -3,9 +3,9 @@ from collections.abc import Mapping
|
||||
from distutils.cmd import Command as Command
|
||||
from distutils.dist import Distribution as Distribution
|
||||
from distutils.extension import Extension as Extension
|
||||
from typing import Any, Literal
|
||||
from typing import Any, Final, Literal
|
||||
|
||||
USAGE: str
|
||||
USAGE: Final[str]
|
||||
|
||||
def gen_usage(script_name: StrOrBytesPath) -> str: ...
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
from distutils.unixccompiler import UnixCCompiler
|
||||
from distutils.version import LooseVersion
|
||||
from re import Pattern
|
||||
from typing import Literal
|
||||
from typing import Final, Literal
|
||||
|
||||
def get_msvcr() -> list[str] | None: ...
|
||||
|
||||
class CygwinCCompiler(UnixCCompiler): ...
|
||||
class Mingw32CCompiler(CygwinCCompiler): ...
|
||||
|
||||
CONFIG_H_OK: str
|
||||
CONFIG_H_NOTOK: str
|
||||
CONFIG_H_UNCERTAIN: str
|
||||
CONFIG_H_OK: Final = "ok"
|
||||
CONFIG_H_NOTOK: Final = "not ok"
|
||||
CONFIG_H_UNCERTAIN: Final = "uncertain"
|
||||
|
||||
def check_config_h() -> tuple[Literal["ok", "not ok", "uncertain"], str]: ...
|
||||
|
||||
RE_VERSION: Pattern[bytes]
|
||||
RE_VERSION: Final[Pattern[bytes]]
|
||||
|
||||
def get_versions() -> tuple[LooseVersion | None, ...]: ...
|
||||
def is_cygwingcc() -> bool: ...
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
DEBUG: bool | None
|
||||
from typing import Final
|
||||
|
||||
DEBUG: Final[str | None]
|
||||
|
||||
@@ -88,9 +88,9 @@ class Distribution:
|
||||
display_options: ClassVar[_OptionsList]
|
||||
display_option_names: ClassVar[list[str]]
|
||||
negative_opt: ClassVar[dict[str, str]]
|
||||
verbose: int
|
||||
dry_run: int
|
||||
help: int
|
||||
verbose: Literal[0, 1]
|
||||
dry_run: Literal[0, 1]
|
||||
help: Literal[0, 1]
|
||||
command_packages: list[str] | None
|
||||
script_name: str | None
|
||||
script_args: list[str] | None
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
from collections.abc import Iterable, Mapping
|
||||
from re import Pattern
|
||||
from typing import Any, overload
|
||||
from typing import Any, Final, overload
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
_Option: TypeAlias = tuple[str, str | None, str]
|
||||
_GR: TypeAlias = tuple[list[str], OptionDummy]
|
||||
|
||||
longopt_pat: str
|
||||
longopt_re: Pattern[str]
|
||||
neg_alias_re: Pattern[str]
|
||||
longopt_xlate: dict[int, int]
|
||||
longopt_pat: Final = r"[a-zA-Z](?:[a-zA-Z0-9-]*)"
|
||||
longopt_re: Final[Pattern[str]]
|
||||
neg_alias_re: Final[Pattern[str]]
|
||||
longopt_xlate: Final[dict[int, int]]
|
||||
|
||||
class FancyGetopt:
|
||||
def __init__(self, option_table: list[_Option] | None = None) -> None: ...
|
||||
@@ -25,7 +25,7 @@ def fancy_getopt(
|
||||
options: list[_Option], negative_opt: Mapping[_Option, _Option], object: Any, args: list[str] | None
|
||||
) -> list[str] | _GR: ...
|
||||
|
||||
WS_TRANS: dict[int, str]
|
||||
WS_TRANS: Final[dict[int, str]]
|
||||
|
||||
def wrap_text(text: str, width: int) -> list[str]: ...
|
||||
def translate_longopt(opt: str) -> str: ...
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from typing import Any
|
||||
from typing import Any, Final
|
||||
|
||||
DEBUG: int
|
||||
INFO: int
|
||||
WARN: int
|
||||
ERROR: int
|
||||
FATAL: int
|
||||
DEBUG: Final = 1
|
||||
INFO: Final = 2
|
||||
WARN: Final = 3
|
||||
ERROR: Final = 4
|
||||
FATAL: Final = 5
|
||||
|
||||
class Log:
|
||||
def __init__(self, threshold: int = 3) -> None: ...
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import sys
|
||||
from collections.abc import Mapping
|
||||
from distutils.ccompiler import CCompiler
|
||||
from typing import Literal, overload
|
||||
from typing import Final, Literal, overload
|
||||
from typing_extensions import deprecated
|
||||
|
||||
PREFIX: str
|
||||
EXEC_PREFIX: str
|
||||
BASE_PREFIX: str
|
||||
BASE_EXEC_PREFIX: str
|
||||
project_base: str
|
||||
python_build: bool
|
||||
PREFIX: Final[str]
|
||||
EXEC_PREFIX: Final[str]
|
||||
BASE_PREFIX: Final[str]
|
||||
BASE_EXEC_PREFIX: Final[str]
|
||||
project_base: Final[str]
|
||||
python_build: Final[bool]
|
||||
|
||||
def expand_makefile_vars(s: str, vars: Mapping[str, str]) -> str: ...
|
||||
@overload
|
||||
|
||||
@@ -50,7 +50,8 @@ class Message(Generic[_HeaderT, _HeaderParamT]):
|
||||
def get_payload(self, i: None = None, *, decode: Literal[True]) -> _EncodedPayloadType | Any: ...
|
||||
@overload # not multipart, IDEM but w/o kwarg
|
||||
def get_payload(self, i: None, decode: Literal[True]) -> _EncodedPayloadType | Any: ...
|
||||
# If `charset=None` and payload supports both `encode` AND `decode`, then an invalid payload could be passed, but this is unlikely
|
||||
# If `charset=None` and payload supports both `encode` AND `decode`,
|
||||
# then an invalid payload could be passed, but this is unlikely
|
||||
# Not[_SupportsEncodeToPayload]
|
||||
@overload
|
||||
def set_payload(
|
||||
@@ -146,7 +147,11 @@ class Message(Generic[_HeaderT, _HeaderParamT]):
|
||||
class MIMEPart(Message[_HeaderRegistryT, _HeaderRegistryParamT]):
|
||||
def __init__(self, policy: Policy | None = None) -> None: ...
|
||||
def get_body(self, preferencelist: Sequence[str] = ("related", "html", "plain")) -> MIMEPart[_HeaderRegistryT] | None: ...
|
||||
def iter_attachments(self) -> Iterator[MIMEPart[_HeaderRegistryT]]: ...
|
||||
def attach(self, payload: Self) -> None: ... # type: ignore[override]
|
||||
# The attachments are created via type(self) in the attach method. It's theoretically
|
||||
# possible to sneak other attachment types into a MIMEPart instance, but could cause
|
||||
# cause unforseen consequences.
|
||||
def iter_attachments(self) -> Iterator[Self]: ...
|
||||
def iter_parts(self) -> Iterator[MIMEPart[_HeaderRegistryT]]: ...
|
||||
def get_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> Any: ...
|
||||
def set_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> None: ...
|
||||
|
||||
@@ -86,7 +86,7 @@ class FTP:
|
||||
def makeport(self) -> socket: ...
|
||||
def makepasv(self) -> tuple[str, int]: ...
|
||||
def login(self, user: str = "", passwd: str = "", acct: str = "") -> str: ...
|
||||
# In practice, `rest` rest can actually be anything whose str() is an integer sequence, so to make it simple we allow integers.
|
||||
# In practice, `rest` can actually be anything whose str() is an integer sequence, so to make it simple we allow integers
|
||||
def ntransfercmd(self, cmd: str, rest: int | str | None = None) -> tuple[socket, int | None]: ...
|
||||
def transfercmd(self, cmd: str, rest: int | str | None = None) -> socket: ...
|
||||
def retrbinary(
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from collections.abc import Callable, Iterator
|
||||
from re import Pattern
|
||||
from typing import Any
|
||||
from typing import Any, Final
|
||||
|
||||
ESCAPE: Pattern[str]
|
||||
ESCAPE_ASCII: Pattern[str]
|
||||
HAS_UTF8: Pattern[bytes]
|
||||
ESCAPE_DCT: dict[str, str]
|
||||
INFINITY: float
|
||||
ESCAPE: Final[Pattern[str]]
|
||||
ESCAPE_ASCII: Final[Pattern[str]]
|
||||
HAS_UTF8: Final[Pattern[bytes]]
|
||||
ESCAPE_DCT: Final[dict[str, str]]
|
||||
INFINITY: Final[float]
|
||||
|
||||
def py_encode_basestring(s: str) -> str: ... # undocumented
|
||||
def py_encode_basestring_ascii(s: str) -> str: ... # undocumented
|
||||
|
||||
@@ -61,6 +61,9 @@ __all__ = [
|
||||
if sys.version_info >= (3, 11):
|
||||
__all__ += ["call"]
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
__all__ += ["is_none", "is_not_none"]
|
||||
|
||||
__lt__ = lt
|
||||
__le__ = le
|
||||
__eq__ = eq
|
||||
|
||||
@@ -365,7 +365,9 @@ class stat_result(structseq[float], tuple[int, int, int, int, int, int, int, flo
|
||||
if sys.version_info >= (3, 12) and sys.platform == "win32":
|
||||
@property
|
||||
@deprecated(
|
||||
"Use st_birthtime instead to retrieve the file creation time. In the future, this property will contain the last metadata change time."
|
||||
"""\
|
||||
Use st_birthtime instead to retrieve the file creation time. \
|
||||
In the future, this property will contain the last metadata change time."""
|
||||
)
|
||||
def st_ctime(self) -> float: ...
|
||||
else:
|
||||
|
||||
@@ -67,5 +67,6 @@ class POP3_SSL(POP3):
|
||||
timeout: float = ...,
|
||||
context: ssl.SSLContext | None = None,
|
||||
) -> None: ...
|
||||
# "context" is actually the last argument, but that breaks LSP and it doesn't really matter because all the arguments are ignored
|
||||
# "context" is actually the last argument,
|
||||
# but that breaks LSP and it doesn't really matter because all the arguments are ignored
|
||||
def stls(self, context: Any = None, keyfile: Any = None, certfile: Any = None) -> NoReturn: ...
|
||||
|
||||
@@ -15,9 +15,9 @@ class ExpatError(Exception):
|
||||
offset: int
|
||||
|
||||
error = ExpatError
|
||||
XML_PARAM_ENTITY_PARSING_NEVER: int
|
||||
XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE: int
|
||||
XML_PARAM_ENTITY_PARSING_ALWAYS: int
|
||||
XML_PARAM_ENTITY_PARSING_NEVER: Final = 0
|
||||
XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE: Final = 1
|
||||
XML_PARAM_ENTITY_PARSING_ALWAYS: Final = 2
|
||||
|
||||
_Model: TypeAlias = tuple[int, int, str | None, tuple[Any, ...]]
|
||||
|
||||
|
||||
@@ -1,49 +1,51 @@
|
||||
import sys
|
||||
from typing import Final
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
codes: dict[str, int]
|
||||
messages: dict[int, str]
|
||||
|
||||
XML_ERROR_ABORTED: str
|
||||
XML_ERROR_ASYNC_ENTITY: str
|
||||
XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF: str
|
||||
XML_ERROR_BAD_CHAR_REF: str
|
||||
XML_ERROR_BINARY_ENTITY_REF: str
|
||||
XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING: str
|
||||
XML_ERROR_DUPLICATE_ATTRIBUTE: str
|
||||
XML_ERROR_ENTITY_DECLARED_IN_PE: str
|
||||
XML_ERROR_EXTERNAL_ENTITY_HANDLING: str
|
||||
XML_ERROR_FEATURE_REQUIRES_XML_DTD: str
|
||||
XML_ERROR_FINISHED: str
|
||||
XML_ERROR_INCOMPLETE_PE: str
|
||||
XML_ERROR_INCORRECT_ENCODING: str
|
||||
XML_ERROR_INVALID_TOKEN: str
|
||||
XML_ERROR_JUNK_AFTER_DOC_ELEMENT: str
|
||||
XML_ERROR_MISPLACED_XML_PI: str
|
||||
XML_ERROR_NOT_STANDALONE: str
|
||||
XML_ERROR_NOT_SUSPENDED: str
|
||||
XML_ERROR_NO_ELEMENTS: str
|
||||
XML_ERROR_NO_MEMORY: str
|
||||
XML_ERROR_PARAM_ENTITY_REF: str
|
||||
XML_ERROR_PARTIAL_CHAR: str
|
||||
XML_ERROR_PUBLICID: str
|
||||
XML_ERROR_RECURSIVE_ENTITY_REF: str
|
||||
XML_ERROR_SUSPENDED: str
|
||||
XML_ERROR_SUSPEND_PE: str
|
||||
XML_ERROR_SYNTAX: str
|
||||
XML_ERROR_TAG_MISMATCH: str
|
||||
XML_ERROR_TEXT_DECL: str
|
||||
XML_ERROR_UNBOUND_PREFIX: str
|
||||
XML_ERROR_UNCLOSED_CDATA_SECTION: str
|
||||
XML_ERROR_UNCLOSED_TOKEN: str
|
||||
XML_ERROR_UNDECLARING_PREFIX: str
|
||||
XML_ERROR_UNDEFINED_ENTITY: str
|
||||
XML_ERROR_UNEXPECTED_STATE: str
|
||||
XML_ERROR_UNKNOWN_ENCODING: str
|
||||
XML_ERROR_XML_DECL: str
|
||||
XML_ERROR_ABORTED: Final[LiteralString]
|
||||
XML_ERROR_ASYNC_ENTITY: Final[LiteralString]
|
||||
XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF: Final[LiteralString]
|
||||
XML_ERROR_BAD_CHAR_REF: Final[LiteralString]
|
||||
XML_ERROR_BINARY_ENTITY_REF: Final[LiteralString]
|
||||
XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING: Final[LiteralString]
|
||||
XML_ERROR_DUPLICATE_ATTRIBUTE: Final[LiteralString]
|
||||
XML_ERROR_ENTITY_DECLARED_IN_PE: Final[LiteralString]
|
||||
XML_ERROR_EXTERNAL_ENTITY_HANDLING: Final[LiteralString]
|
||||
XML_ERROR_FEATURE_REQUIRES_XML_DTD: Final[LiteralString]
|
||||
XML_ERROR_FINISHED: Final[LiteralString]
|
||||
XML_ERROR_INCOMPLETE_PE: Final[LiteralString]
|
||||
XML_ERROR_INCORRECT_ENCODING: Final[LiteralString]
|
||||
XML_ERROR_INVALID_TOKEN: Final[LiteralString]
|
||||
XML_ERROR_JUNK_AFTER_DOC_ELEMENT: Final[LiteralString]
|
||||
XML_ERROR_MISPLACED_XML_PI: Final[LiteralString]
|
||||
XML_ERROR_NOT_STANDALONE: Final[LiteralString]
|
||||
XML_ERROR_NOT_SUSPENDED: Final[LiteralString]
|
||||
XML_ERROR_NO_ELEMENTS: Final[LiteralString]
|
||||
XML_ERROR_NO_MEMORY: Final[LiteralString]
|
||||
XML_ERROR_PARAM_ENTITY_REF: Final[LiteralString]
|
||||
XML_ERROR_PARTIAL_CHAR: Final[LiteralString]
|
||||
XML_ERROR_PUBLICID: Final[LiteralString]
|
||||
XML_ERROR_RECURSIVE_ENTITY_REF: Final[LiteralString]
|
||||
XML_ERROR_SUSPENDED: Final[LiteralString]
|
||||
XML_ERROR_SUSPEND_PE: Final[LiteralString]
|
||||
XML_ERROR_SYNTAX: Final[LiteralString]
|
||||
XML_ERROR_TAG_MISMATCH: Final[LiteralString]
|
||||
XML_ERROR_TEXT_DECL: Final[LiteralString]
|
||||
XML_ERROR_UNBOUND_PREFIX: Final[LiteralString]
|
||||
XML_ERROR_UNCLOSED_CDATA_SECTION: Final[LiteralString]
|
||||
XML_ERROR_UNCLOSED_TOKEN: Final[LiteralString]
|
||||
XML_ERROR_UNDECLARING_PREFIX: Final[LiteralString]
|
||||
XML_ERROR_UNDEFINED_ENTITY: Final[LiteralString]
|
||||
XML_ERROR_UNEXPECTED_STATE: Final[LiteralString]
|
||||
XML_ERROR_UNKNOWN_ENCODING: Final[LiteralString]
|
||||
XML_ERROR_XML_DECL: Final[LiteralString]
|
||||
if sys.version_info >= (3, 11):
|
||||
XML_ERROR_RESERVED_PREFIX_XML: str
|
||||
XML_ERROR_RESERVED_PREFIX_XMLNS: str
|
||||
XML_ERROR_RESERVED_NAMESPACE_URI: str
|
||||
XML_ERROR_INVALID_ARGUMENT: str
|
||||
XML_ERROR_NO_BUFFER: str
|
||||
XML_ERROR_AMPLIFICATION_LIMIT_BREACH: str
|
||||
XML_ERROR_RESERVED_PREFIX_XML: Final[LiteralString]
|
||||
XML_ERROR_RESERVED_PREFIX_XMLNS: Final[LiteralString]
|
||||
XML_ERROR_RESERVED_NAMESPACE_URI: Final[LiteralString]
|
||||
XML_ERROR_INVALID_ARGUMENT: Final[LiteralString]
|
||||
XML_ERROR_NO_BUFFER: Final[LiteralString]
|
||||
XML_ERROR_AMPLIFICATION_LIMIT_BREACH: Final[LiteralString]
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
XML_CTYPE_ANY: int
|
||||
XML_CTYPE_CHOICE: int
|
||||
XML_CTYPE_EMPTY: int
|
||||
XML_CTYPE_MIXED: int
|
||||
XML_CTYPE_NAME: int
|
||||
XML_CTYPE_SEQ: int
|
||||
from typing import Final
|
||||
|
||||
XML_CQUANT_NONE: int
|
||||
XML_CQUANT_OPT: int
|
||||
XML_CQUANT_PLUS: int
|
||||
XML_CQUANT_REP: int
|
||||
XML_CTYPE_ANY: Final = 2
|
||||
XML_CTYPE_EMPTY: Final = 1
|
||||
XML_CTYPE_MIXED: Final = 3
|
||||
XML_CTYPE_NAME: Final = 4
|
||||
XML_CTYPE_CHOICE: Final = 5
|
||||
XML_CTYPE_SEQ: Final = 6
|
||||
|
||||
XML_CQUANT_NONE: Final = 0
|
||||
XML_CQUANT_OPT: Final = 1
|
||||
XML_CQUANT_REP: Final = 2
|
||||
XML_CQUANT_PLUS: Final = 3
|
||||
|
||||
@@ -3,8 +3,9 @@ import types
|
||||
from _socket import _Address, _RetAddress
|
||||
from _typeshed import ReadableBuffer
|
||||
from collections.abc import Callable
|
||||
from io import BufferedIOBase
|
||||
from socket import socket as _socket
|
||||
from typing import Any, BinaryIO, ClassVar
|
||||
from typing import Any, ClassVar
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
__all__ = [
|
||||
@@ -158,11 +159,11 @@ class StreamRequestHandler(BaseRequestHandler):
|
||||
timeout: ClassVar[float | None] # undocumented
|
||||
disable_nagle_algorithm: ClassVar[bool] # undocumented
|
||||
connection: Any # undocumented
|
||||
rfile: BinaryIO
|
||||
wfile: BinaryIO
|
||||
rfile: BufferedIOBase
|
||||
wfile: BufferedIOBase
|
||||
|
||||
class DatagramRequestHandler(BaseRequestHandler):
|
||||
packet: _socket # undocumented
|
||||
packet: bytes # undocumented
|
||||
socket: _socket # undocumented
|
||||
rfile: BinaryIO
|
||||
wfile: BinaryIO
|
||||
rfile: BufferedIOBase
|
||||
wfile: BufferedIOBase
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from typing import Final
|
||||
|
||||
# These are not actually bools. See #4669
|
||||
NO: bool
|
||||
YES: bool
|
||||
TRUE: bool
|
||||
FALSE: bool
|
||||
ON: bool
|
||||
OFF: bool
|
||||
NO: Final[bool]
|
||||
YES: Final[bool]
|
||||
TRUE: Final[bool]
|
||||
FALSE: Final[bool]
|
||||
ON: Final[bool]
|
||||
OFF: Final[bool]
|
||||
N: Final = "n"
|
||||
S: Final = "s"
|
||||
W: Final = "w"
|
||||
|
||||
@@ -2,12 +2,12 @@ import sys
|
||||
from _typeshed import Incomplete
|
||||
from collections.abc import Mapping
|
||||
from tkinter import Widget
|
||||
from typing import Any
|
||||
from typing import Any, Final
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
__all__ = ["Dialog"]
|
||||
|
||||
DIALOG_ICON: str
|
||||
DIALOG_ICON: Final = "questhead"
|
||||
|
||||
class Dialog(Widget):
|
||||
widgetName: str
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import sys
|
||||
from tkinter.commondialog import Dialog
|
||||
from typing import ClassVar
|
||||
from typing import ClassVar, Final
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
__all__ = [
|
||||
@@ -14,22 +14,22 @@ if sys.version_info >= (3, 9):
|
||||
"askretrycancel",
|
||||
]
|
||||
|
||||
ERROR: str
|
||||
INFO: str
|
||||
QUESTION: str
|
||||
WARNING: str
|
||||
ABORTRETRYIGNORE: str
|
||||
OK: str
|
||||
OKCANCEL: str
|
||||
RETRYCANCEL: str
|
||||
YESNO: str
|
||||
YESNOCANCEL: str
|
||||
ABORT: str
|
||||
RETRY: str
|
||||
IGNORE: str
|
||||
CANCEL: str
|
||||
YES: str
|
||||
NO: str
|
||||
ERROR: Final = "error"
|
||||
INFO: Final = "info"
|
||||
QUESTION: Final = "question"
|
||||
WARNING: Final = "warning"
|
||||
ABORTRETRYIGNORE: Final = "abortretryignore"
|
||||
OK: Final = "ok"
|
||||
OKCANCEL: Final = "okcancel"
|
||||
RETRYCANCEL: Final = "retrycancel"
|
||||
YESNO: Final = "yesno"
|
||||
YESNOCANCEL: Final = "yesnocancel"
|
||||
ABORT: Final = "abort"
|
||||
RETRY: Final = "retry"
|
||||
IGNORE: Final = "ignore"
|
||||
CANCEL: Final = "cancel"
|
||||
YES: Final = "yes"
|
||||
NO: Final = "no"
|
||||
|
||||
class Message(Dialog):
|
||||
command: ClassVar[str]
|
||||
|
||||
@@ -556,7 +556,9 @@ class Notebook(Widget):
|
||||
sticky: str = ..., # consists of letters 'n', 's', 'w', 'e', no repeats, may be empty
|
||||
padding: _Padding = ...,
|
||||
text: str = ...,
|
||||
image=..., # Sequence of an image name, followed by zero or more (sequences of one or more state names followed by an image name)
|
||||
# `image` is a sequence of an image name, followed by zero or more
|
||||
# (sequences of one or more state names followed by an image name)
|
||||
image=...,
|
||||
compound: tkinter._Compound = ...,
|
||||
underline: int = ...,
|
||||
) -> None: ...
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# ruff: noqa: F811
|
||||
# TODO: The collections import is required, otherwise mypy crashes.
|
||||
# https://github.com/python/mypy/issues/16744
|
||||
import collections # noqa: F401 # pyright: ignore
|
||||
import collections # noqa: F401 # pyright: ignore[reportUnusedImport]
|
||||
import sys
|
||||
import typing_extensions
|
||||
from _collections_abc import dict_items, dict_keys, dict_values
|
||||
@@ -800,18 +800,12 @@ class IO(Iterator[AnyStr]):
|
||||
def writable(self) -> bool: ...
|
||||
@abstractmethod
|
||||
@overload
|
||||
def write(self: IO[str], s: str, /) -> int: ...
|
||||
@abstractmethod
|
||||
@overload
|
||||
def write(self: IO[bytes], s: ReadableBuffer, /) -> int: ...
|
||||
@abstractmethod
|
||||
@overload
|
||||
def write(self, s: AnyStr, /) -> int: ...
|
||||
@abstractmethod
|
||||
@overload
|
||||
def writelines(self: IO[str], lines: Iterable[str], /) -> None: ...
|
||||
@abstractmethod
|
||||
@overload
|
||||
def writelines(self: IO[bytes], lines: Iterable[ReadableBuffer], /) -> None: ...
|
||||
@abstractmethod
|
||||
@overload
|
||||
|
||||
@@ -2,36 +2,52 @@ import sys
|
||||
import unittest.case
|
||||
import unittest.result
|
||||
import unittest.suite
|
||||
from _typeshed import Incomplete
|
||||
from _typeshed import SupportsFlush, SupportsWrite
|
||||
from collections.abc import Callable, Iterable
|
||||
from typing import TextIO
|
||||
from typing_extensions import TypeAlias
|
||||
from typing import Any, Generic, Protocol, TypeVar
|
||||
from typing_extensions import Never, TypeAlias
|
||||
|
||||
_ResultClassType: TypeAlias = Callable[[TextIO, bool, int], unittest.result.TestResult]
|
||||
_ResultClassType: TypeAlias = Callable[[_TextTestStream, bool, int], TextTestResult]
|
||||
|
||||
class TextTestResult(unittest.result.TestResult):
|
||||
class _SupportsWriteAndFlush(SupportsWrite[str], SupportsFlush, Protocol): ...
|
||||
|
||||
# All methods used by unittest.runner.TextTestResult's stream
|
||||
class _TextTestStream(_SupportsWriteAndFlush, Protocol):
|
||||
def writeln(self, arg: str | None = None) -> str: ...
|
||||
|
||||
# _WritelnDecorator should have all the same attrs as its stream param.
|
||||
# But that's not feasible to do Generically
|
||||
# We can expand the attributes if requested
|
||||
class _WritelnDecorator(_TextTestStream):
|
||||
def __init__(self, stream: _TextTestStream) -> None: ...
|
||||
def __getattr__(self, attr: str) -> Any: ... # Any attribute from the stream type passed to __init__
|
||||
# These attributes are prevented by __getattr__
|
||||
stream: Never
|
||||
__getstate__: Never
|
||||
|
||||
_StreamT = TypeVar("_StreamT", bound=_TextTestStream, default=_WritelnDecorator)
|
||||
|
||||
class TextTestResult(unittest.result.TestResult, Generic[_StreamT]):
|
||||
descriptions: bool # undocumented
|
||||
dots: bool # undocumented
|
||||
separator1: str
|
||||
separator2: str
|
||||
showAll: bool # undocumented
|
||||
stream: TextIO # undocumented
|
||||
stream: _StreamT # undocumented
|
||||
if sys.version_info >= (3, 12):
|
||||
durations: unittest.result._DurationsType | None
|
||||
def __init__(
|
||||
self, stream: TextIO, descriptions: bool, verbosity: int, *, durations: unittest.result._DurationsType | None = None
|
||||
self, stream: _StreamT, descriptions: bool, verbosity: int, *, durations: unittest.result._DurationsType | None = None
|
||||
) -> None: ...
|
||||
else:
|
||||
def __init__(self, stream: TextIO, descriptions: bool, verbosity: int) -> None: ...
|
||||
def __init__(self, stream: _StreamT, descriptions: bool, verbosity: int) -> None: ...
|
||||
|
||||
def getDescription(self, test: unittest.case.TestCase) -> str: ...
|
||||
def printErrorList(self, flavour: str, errors: Iterable[tuple[unittest.case.TestCase, str]]) -> None: ...
|
||||
|
||||
class TextTestRunner:
|
||||
resultclass: _ResultClassType
|
||||
# TODO: add `_WritelnDecorator` type
|
||||
# stream: _WritelnDecorator
|
||||
stream: Incomplete
|
||||
stream: _WritelnDecorator
|
||||
descriptions: bool
|
||||
verbosity: int
|
||||
failfast: bool
|
||||
@@ -43,7 +59,7 @@ class TextTestRunner:
|
||||
durations: unittest.result._DurationsType | None
|
||||
def __init__(
|
||||
self,
|
||||
stream: TextIO | None = None,
|
||||
stream: _SupportsWriteAndFlush | None = None,
|
||||
descriptions: bool = True,
|
||||
verbosity: int = 1,
|
||||
failfast: bool = False,
|
||||
@@ -57,7 +73,7 @@ class TextTestRunner:
|
||||
else:
|
||||
def __init__(
|
||||
self,
|
||||
stream: TextIO | None = None,
|
||||
stream: _SupportsWriteAndFlush | None = None,
|
||||
descriptions: bool = True,
|
||||
verbosity: int = 1,
|
||||
failfast: bool = False,
|
||||
@@ -68,5 +84,5 @@ class TextTestRunner:
|
||||
tb_locals: bool = False,
|
||||
) -> None: ...
|
||||
|
||||
def _makeResult(self) -> unittest.result.TestResult: ...
|
||||
def run(self, test: unittest.suite.TestSuite | unittest.case.TestCase) -> unittest.result.TestResult: ...
|
||||
def _makeResult(self) -> TextTestResult: ...
|
||||
def run(self, test: unittest.suite.TestSuite | unittest.case.TestCase) -> TextTestResult: ...
|
||||
|
||||
@@ -304,6 +304,7 @@ else:
|
||||
|
||||
class Path:
|
||||
root: CompleteDirs
|
||||
at: str
|
||||
def __init__(self, root: ZipFile | StrPath | IO[bytes], at: str = "") -> None: ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
|
||||
@@ -31,6 +31,7 @@ if sys.version_info >= (3, 12):
|
||||
|
||||
class Path:
|
||||
root: CompleteDirs
|
||||
at: str
|
||||
def __init__(self, root: ZipFile | StrPath | IO[bytes], at: str = "") -> None: ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
|
||||
@@ -29,6 +29,7 @@ tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing"] }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -164,7 +164,7 @@ fn lint_bad_override(context: &SemanticLintContext, class: &ast::StmtClassDef) {
|
||||
if ty.has_decorator(db, override_ty) {
|
||||
let method_name = ty.name(db);
|
||||
if class_ty
|
||||
.inherited_class_member(db, &method_name)
|
||||
.inherited_class_member(db, method_name)
|
||||
.is_unbound()
|
||||
{
|
||||
// TODO should have a qualname() method to support nested classes
|
||||
|
||||
@@ -297,7 +297,6 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
impl Package {
|
||||
pub fn root(self, db: &dyn Db) -> &SystemPath {
|
||||
self.root_buf(db)
|
||||
|
||||
@@ -21,19 +21,27 @@ fn setup_db(workspace_root: &SystemPath) -> anyhow::Result<RootDatabase> {
|
||||
#[test]
|
||||
#[allow(clippy::print_stdout)]
|
||||
fn corpus_no_panic() -> anyhow::Result<()> {
|
||||
let corpus = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/corpus");
|
||||
let system_corpus =
|
||||
SystemPathBuf::from_path_buf(corpus.clone()).expect("corpus path to be UTF8");
|
||||
let db = setup_db(&system_corpus)?;
|
||||
let root = SystemPathBuf::from_path_buf(tempfile::TempDir::new()?.into_path()).unwrap();
|
||||
let db = setup_db(&root)?;
|
||||
|
||||
for path in fs::read_dir(&corpus).expect("corpus to be a directory") {
|
||||
let path = path.expect("path to not be an error").path();
|
||||
println!("checking {path:?}");
|
||||
let path = SystemPathBuf::from_path_buf(path.clone()).expect("path to be UTF-8");
|
||||
let corpus = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/corpus");
|
||||
|
||||
for path in fs::read_dir(&corpus)? {
|
||||
let source = path?.path();
|
||||
println!("checking {source:?}");
|
||||
let source_fn = source.file_name().unwrap().to_str().unwrap();
|
||||
let py_dest = root.join(source_fn);
|
||||
fs::copy(&source, py_dest.as_std_path())?;
|
||||
// this test is only asserting that we can pull every expression type without a panic
|
||||
// (and some non-expressions that clearly define a single type)
|
||||
let file = system_path_to_file(&db, path).expect("file to exist");
|
||||
let file = system_path_to_file(&db, py_dest).unwrap();
|
||||
pull_types(&db, file);
|
||||
|
||||
// try the file as a stub also
|
||||
println!("re-checking as .pyi");
|
||||
let pyi_dest = root.join(format!("{source_fn}i"));
|
||||
std::fs::copy(source, pyi_dest.as_std_path())?;
|
||||
let file = system_path_to_file(&db, pyi_dest).unwrap();
|
||||
pull_types(&db, file);
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -810,7 +810,11 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
||||
.map(|rule| format!("`{}`", rule.noqa_code()))
|
||||
.collect();
|
||||
rule_names.sort();
|
||||
warn_user_once!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", "));
|
||||
if let [rule] = rule_names.as_slice() {
|
||||
warn_user_once!("The following rule may cause conflicts when used with the formatter: {rule}. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.");
|
||||
} else {
|
||||
warn_user_once!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
// Next, validate settings-specific incompatibilities.
|
||||
|
||||
@@ -151,15 +151,15 @@ impl Printer {
|
||||
let fix_prefix = format!("[{}]", "*".cyan());
|
||||
|
||||
if self.unsafe_fixes.is_hint() {
|
||||
if fixables.applicable > 0 && fixables.unapplicable_unsafe > 0 {
|
||||
let es = if fixables.unapplicable_unsafe == 1 {
|
||||
if fixables.applicable > 0 && fixables.inapplicable_unsafe > 0 {
|
||||
let es = if fixables.inapplicable_unsafe == 1 {
|
||||
""
|
||||
} else {
|
||||
"es"
|
||||
};
|
||||
writeln!(writer,
|
||||
"{fix_prefix} {} fixable with the `--fix` option ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
||||
fixables.applicable, fixables.unapplicable_unsafe
|
||||
fixables.applicable, fixables.inapplicable_unsafe
|
||||
)?;
|
||||
} else if fixables.applicable > 0 {
|
||||
// Only applicable fixes
|
||||
@@ -169,15 +169,15 @@ impl Printer {
|
||||
fixables.applicable,
|
||||
)?;
|
||||
} else {
|
||||
// Only unapplicable fixes
|
||||
let es = if fixables.unapplicable_unsafe == 1 {
|
||||
// Only inapplicable fixes
|
||||
let es = if fixables.inapplicable_unsafe == 1 {
|
||||
""
|
||||
} else {
|
||||
"es"
|
||||
};
|
||||
writeln!(writer,
|
||||
"No fixes available ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
|
||||
fixables.unapplicable_unsafe
|
||||
fixables.inapplicable_unsafe
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
@@ -194,7 +194,7 @@ impl Printer {
|
||||
// Check if there are unapplied fixes
|
||||
let unapplied = {
|
||||
if let Some(fixables) = fixables {
|
||||
fixables.unapplicable_unsafe
|
||||
fixables.inapplicable_unsafe
|
||||
} else {
|
||||
0
|
||||
}
|
||||
@@ -545,33 +545,33 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FixMap) -> Result<()> {
|
||||
#[derive(Debug)]
|
||||
struct FixableStatistics {
|
||||
applicable: u32,
|
||||
unapplicable_unsafe: u32,
|
||||
inapplicable_unsafe: u32,
|
||||
}
|
||||
|
||||
impl FixableStatistics {
|
||||
fn try_from(diagnostics: &Diagnostics, unsafe_fixes: UnsafeFixes) -> Option<Self> {
|
||||
let mut applicable = 0;
|
||||
let mut unapplicable_unsafe = 0;
|
||||
let mut inapplicable_unsafe = 0;
|
||||
|
||||
for message in &diagnostics.messages {
|
||||
if let Some(fix) = message.fix() {
|
||||
if fix.applies(unsafe_fixes.required_applicability()) {
|
||||
applicable += 1;
|
||||
} else {
|
||||
// Do not include unapplicable fixes at other levels that do not provide an opt-in
|
||||
// Do not include inapplicable fixes at other levels that do not provide an opt-in
|
||||
if fix.applicability().is_unsafe() {
|
||||
unapplicable_unsafe += 1;
|
||||
inapplicable_unsafe += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if applicable == 0 && unapplicable_unsafe == 0 {
|
||||
if applicable == 0 && inapplicable_unsafe == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Self {
|
||||
applicable,
|
||||
unapplicable_unsafe,
|
||||
inapplicable_unsafe,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,7 +785,7 @@ if condition:
|
||||
print('Should change quotes')
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rules may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
|
||||
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ struct Case {
|
||||
|
||||
const TOMLLIB_312_URL: &str = "https://raw.githubusercontent.com/python/cpython/8e8a4baf652f6e1cee7acde9d78c4b6154539748/Lib/tomllib";
|
||||
|
||||
// The "unresolved import" is because we don't understand `*` imports yet.
|
||||
// The failed import from 'collections.abc' is due to lack of support for 'import *'.
|
||||
static EXPECTED_DIAGNOSTICS: &[&str] = &[
|
||||
"/src/tomllib/_parser.py:7:29: Module 'collections.abc' has no member 'Iterable'",
|
||||
"Line 69 is too long (89 characters)",
|
||||
@@ -32,10 +32,7 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[
|
||||
"Use double quotes for strings",
|
||||
"Use double quotes for strings",
|
||||
"Use double quotes for strings",
|
||||
"/src/tomllib/_parser.py:330:32: Name 'header' used when not defined.",
|
||||
"/src/tomllib/_parser.py:330:41: Name 'key' used when not defined.",
|
||||
"/src/tomllib/_parser.py:628:75: Name 'e' used when not defined.",
|
||||
"/src/tomllib/_parser.py:686:23: Name 'parse_float' used when not defined.",
|
||||
];
|
||||
|
||||
fn get_test_file(name: &str) -> TestFile {
|
||||
|
||||
@@ -3,3 +3,9 @@ import float
|
||||
from some import other as int
|
||||
from some import input, exec
|
||||
from directory import new as dir
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/13037
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 11):
|
||||
from exceptiongroup import BaseExceptionGroup, ExceptionGroup
|
||||
|
||||
@@ -44,3 +44,11 @@ class PEP695BadDunderNew[T]:
|
||||
|
||||
class PEP695GoodDunderNew[T]:
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
|
||||
|
||||
|
||||
class CustomClassMethod:
|
||||
# Should be recognised as a classmethod decorator
|
||||
# due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators`
|
||||
# in the settings for this test:
|
||||
@foo_classmethod
|
||||
def foo[S](cls: type[S]) -> S: ... # PYI019
|
||||
|
||||
@@ -44,3 +44,11 @@ class PEP695BadDunderNew[T]:
|
||||
|
||||
class PEP695GoodDunderNew[T]:
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
|
||||
|
||||
|
||||
class CustomClassMethod:
|
||||
# Should be recognised as a classmethod decorator
|
||||
# due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators`
|
||||
# in the settings for this test:
|
||||
@foo_classmethod
|
||||
def foo[S](cls: type[S]) -> S: ... # PYI019
|
||||
|
||||
@@ -3,8 +3,8 @@ import typing
|
||||
|
||||
|
||||
class Bad:
|
||||
def __eq__(self, other: Any) -> bool: ... # Y032
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032
|
||||
def __eq__(self, other: Any) -> bool: ... # PYI032
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ... # PYI032
|
||||
|
||||
|
||||
class Good:
|
||||
@@ -22,3 +22,7 @@ class Unannotated:
|
||||
def __eq__(self) -> Any: ...
|
||||
def __ne__(self) -> bool: ...
|
||||
|
||||
|
||||
class BadStringized:
|
||||
def __eq__(self, other: "Any") -> bool: ... # PYI032
|
||||
def __ne__(self, other: "Any") -> bool: ... # PYI032
|
||||
|
||||
@@ -3,8 +3,8 @@ import typing
|
||||
|
||||
|
||||
class Bad:
|
||||
def __eq__(self, other: Any) -> bool: ... # Y032
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032
|
||||
def __eq__(self, other: Any) -> bool: ... # PYI032
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ... # PYI032
|
||||
|
||||
|
||||
class Good:
|
||||
@@ -22,3 +22,6 @@ class Unannotated:
|
||||
def __eq__(self) -> Any: ...
|
||||
def __ne__(self) -> bool: ...
|
||||
|
||||
class BadStringized:
|
||||
def __eq__(self, other: "Any") -> bool: ... # PYI032
|
||||
def __ne__(self, other: "Any") -> bool: ... # PYI032
|
||||
|
||||
@@ -317,3 +317,7 @@ def __ne__(self, other: Any) -> bool:
|
||||
|
||||
def __imul__(self, other: Any) -> list[str]:
|
||||
...
|
||||
|
||||
class UsesStringizedAnnotations:
|
||||
def __iadd__(self, other: "UsesStringizedAnnotations") -> "typing.Self":
|
||||
return self
|
||||
|
||||
@@ -212,3 +212,6 @@ def __str__(self) -> str: ...
|
||||
def __eq__(self, other: Any) -> bool: ...
|
||||
def __ne__(self, other: Any) -> bool: ...
|
||||
def __imul__(self, other: Any) -> list[str]: ...
|
||||
|
||||
class UsesStringizedAnnotations:
|
||||
def __iadd__(self, other: "UsesStringizedAnnotations") -> "typing.Self": ...
|
||||
|
||||
@@ -30,3 +30,11 @@ def foo_no_return_pos_only(arg: int, /, arg2: NoReturn):
|
||||
|
||||
def foo_never(arg: Never):
|
||||
...
|
||||
|
||||
|
||||
def stringized(arg: "NoReturn"):
|
||||
...
|
||||
|
||||
|
||||
def stringized_good(arg: "Never"):
|
||||
...
|
||||
|
||||
@@ -21,3 +21,5 @@ def foo_int_kwargs_no_return(*args: NoReturn, **kwargs: int): ... # Error: PYI0
|
||||
def foo_args_never(*args: Never): ...
|
||||
def foo_kwargs_never(**kwargs: Never): ...
|
||||
def foo_args_kwargs_never(*args: Never, **kwargs: Never): ...
|
||||
def stringized(arg: "NoReturn"): ...
|
||||
def stringized_good(arg: "Never"): ...
|
||||
|
||||
@@ -187,3 +187,50 @@ def foo(s: str) -> str | None:
|
||||
A string.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
# DOC201
|
||||
def bar() -> int | None:
|
||||
"""Bar-y method"""
|
||||
return
|
||||
|
||||
|
||||
from collections.abc import Iterator, Generator
|
||||
|
||||
|
||||
# This is okay -- returning `None` is implied by `Iterator[str]`;
|
||||
# no need to document it
|
||||
def generator_function() -> Iterator[str]:
|
||||
"""Generate some strings"""
|
||||
yield from "abc"
|
||||
return
|
||||
|
||||
|
||||
# This is okay -- returning `None` is stated by `Generator[str, None, None]`;
|
||||
# no need to document it
|
||||
def generator_function_2() -> Generator[str, None, None]:
|
||||
"""Generate some strings"""
|
||||
yield from "abc"
|
||||
return
|
||||
|
||||
|
||||
# DOC201 -- returns None but `Generator[str, None, int | None]`
|
||||
# indicates it could sometimes return `int`
|
||||
def generator_function_3() -> Generator[str, None, int | None]:
|
||||
"""Generate some strings"""
|
||||
yield from "abc"
|
||||
return
|
||||
|
||||
|
||||
# DOC201 -- no type annotation and a non-None return
|
||||
# indicates it could sometimes return `int`
|
||||
def generator_function_4():
|
||||
"""Generate some strings"""
|
||||
yield from "abc"
|
||||
return 42
|
||||
|
||||
|
||||
# DOC201 -- no `yield` expressions, so not a generator function
|
||||
def not_a_generator() -> Iterator[int]:
|
||||
""""No returns documented here, oh no"""
|
||||
return (x for x in range(42))
|
||||
|
||||
@@ -73,3 +73,15 @@ class A(metaclass=abc.abcmeta):
|
||||
dict: The values
|
||||
"""
|
||||
return
|
||||
|
||||
|
||||
# DOC202 -- never explicitly returns anything, just short-circuits
|
||||
def foo(s: str, condition: bool):
|
||||
"""Fooey things.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if not condition:
|
||||
return
|
||||
print(s)
|
||||
|
||||
@@ -87,3 +87,22 @@ def f(num: int):
|
||||
num (int): A number
|
||||
"""
|
||||
yield 1
|
||||
|
||||
|
||||
import collections.abc
|
||||
|
||||
|
||||
# DOC402
|
||||
def foo() -> collections.abc.Generator[int | None, None, None]:
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield
|
||||
|
||||
|
||||
# DOC402
|
||||
def bar() -> collections.abc.Iterator[int | None]:
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield
|
||||
|
||||
@@ -60,3 +60,81 @@ class Bar:
|
||||
A number
|
||||
"""
|
||||
yield 'test'
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
# OK
|
||||
def foo() -> typing.Generator[None, None, None]:
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield None
|
||||
|
||||
|
||||
# OK
|
||||
def foo() -> typing.Generator[None, None, None]:
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield
|
||||
|
||||
|
||||
# DOC402
|
||||
def foo() -> typing.Generator[int | None, None, None]:
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield None
|
||||
yield 1
|
||||
|
||||
|
||||
# DOC402
|
||||
def foo() -> typing.Generator[int, None, None]:
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield None
|
||||
|
||||
|
||||
# OK
|
||||
def foo():
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield None
|
||||
|
||||
|
||||
# OK
|
||||
def foo():
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield
|
||||
|
||||
|
||||
# DOC402
|
||||
def foo():
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield None
|
||||
yield 1
|
||||
|
||||
|
||||
# DOC402
|
||||
def foo():
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield 1
|
||||
yield
|
||||
|
||||
|
||||
# DOC402
|
||||
def bar() -> typing.Iterator[int | None]:
|
||||
"""
|
||||
Do something
|
||||
"""
|
||||
yield
|
||||
|
||||
@@ -48,3 +48,50 @@ class Bar:
|
||||
num (int): A number
|
||||
"""
|
||||
print('test')
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
# OK
|
||||
def foo() -> typing.Generator[None, None, None]:
|
||||
"""
|
||||
Do something
|
||||
|
||||
Yields:
|
||||
When X.
|
||||
"""
|
||||
yield
|
||||
|
||||
|
||||
# OK
|
||||
def foo() -> typing.Generator[None, None, None]:
|
||||
"""
|
||||
Do something
|
||||
|
||||
Yields:
|
||||
When X.
|
||||
"""
|
||||
yield None
|
||||
|
||||
|
||||
# OK
|
||||
def foo():
|
||||
"""
|
||||
Do something
|
||||
|
||||
Yields:
|
||||
When X.
|
||||
"""
|
||||
yield
|
||||
|
||||
|
||||
# OK
|
||||
def foo():
|
||||
"""
|
||||
Do something
|
||||
|
||||
Yields:
|
||||
When X.
|
||||
"""
|
||||
yield None
|
||||
|
||||
@@ -60,3 +60,54 @@ class Bar:
|
||||
A number
|
||||
"""
|
||||
print('test')
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
# OK
|
||||
def foo() -> typing.Generator[None, None, None]:
|
||||
"""
|
||||
Do something
|
||||
|
||||
Yields
|
||||
------
|
||||
When X.
|
||||
"""
|
||||
yield None
|
||||
|
||||
|
||||
# OK
|
||||
def foo() -> typing.Generator[None, None, None]:
|
||||
"""
|
||||
Do something
|
||||
|
||||
Yields
|
||||
------
|
||||
When X.
|
||||
"""
|
||||
yield
|
||||
|
||||
|
||||
# OK
|
||||
def foo():
|
||||
"""
|
||||
Do something
|
||||
|
||||
Yields
|
||||
------
|
||||
When X.
|
||||
"""
|
||||
yield None
|
||||
|
||||
|
||||
# OK
|
||||
def foo():
|
||||
"""
|
||||
Do something
|
||||
|
||||
Yields
|
||||
------
|
||||
When X.
|
||||
"""
|
||||
yield
|
||||
|
||||
@@ -605,3 +605,17 @@ def test_lowercase_sub_section_header_different_kind(returns: int):
|
||||
some value
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# We used to incorrectly infer this as a numpy-style docstring,
|
||||
# which caused us to emit D406 and D407 on it;
|
||||
# see https://github.com/astral-sh/ruff/issues/13139
|
||||
def another_valid_google_style_docstring(a: str) -> str:
|
||||
"""Foo bar.
|
||||
|
||||
Examples:
|
||||
Some explanation here.
|
||||
>>> bla bla bla
|
||||
|
||||
"""
|
||||
return a
|
||||
|
||||
@@ -83,3 +83,23 @@ class B(A):
|
||||
def baz(self):
|
||||
if super().foo():
|
||||
...
|
||||
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/12568
|
||||
from attrs import define, field
|
||||
|
||||
|
||||
@define
|
||||
class Foo:
|
||||
x: int = field()
|
||||
y: int
|
||||
|
||||
@x.validator
|
||||
def validate_x(self, attribute, value):
|
||||
if value <= 0:
|
||||
raise ValueError("x must be a positive integer")
|
||||
|
||||
@y.validator
|
||||
def validate_y(self, attribute, value):
|
||||
if value <= 0:
|
||||
raise ValueError("y must be a positive integer")
|
||||
|
||||
@@ -38,3 +38,16 @@ def value_intentionally_unused():
|
||||
print(FRUITS[fruit_name]) # OK
|
||||
blah = FRUITS[fruit_name] # OK
|
||||
assert FRUITS[fruit_name] == "pear" # OK
|
||||
|
||||
|
||||
def rewrite_client_arrays(value_arrays: dict[str, list[int]]) -> dict[str, list[int]]:
|
||||
"""Function from https://github.com/zulip/zulip/blob/3da91e951cd03cfa0b9c67378224e348353f36a6/analytics/views/stats.py#L617C1-L626C25"""
|
||||
mapped_arrays: dict[str, list[int]] = {}
|
||||
for label, array in value_arrays.items():
|
||||
mapped_label = client_label_map(label)
|
||||
if mapped_label in mapped_arrays:
|
||||
for i in range(len(array)):
|
||||
mapped_arrays[mapped_label][i] += value_arrays[label][i] # PLR1733
|
||||
else:
|
||||
mapped_arrays[mapped_label] = [value_arrays[label][i] for i in range(len(array))] # PLR1733
|
||||
return mapped_arrays
|
||||
|
||||
@@ -76,3 +76,10 @@ def start():
|
||||
# PLR1736
|
||||
for index, list_item in enumerate(some_list):
|
||||
print(some_list[index])
|
||||
|
||||
|
||||
def nested_index_lookup():
|
||||
data = {"a": 1, "b": 2}
|
||||
column_names = ["a", "b"]
|
||||
for index, column_name in enumerate(column_names):
|
||||
_ = data[column_names[index]] # PLR1736
|
||||
|
||||
@@ -119,3 +119,10 @@ def func():
|
||||
def func():
|
||||
with suppress(AttributeError):
|
||||
raise AttributeError("This is an exception") # OK
|
||||
|
||||
|
||||
import builtins
|
||||
|
||||
builtins.TypeError("still an exception even though it's an Attribute")
|
||||
|
||||
PythonFinalizationError("Added in Python 3.13")
|
||||
|
||||
@@ -78,3 +78,10 @@ open("foo", "r")
|
||||
open("foo", "rt")
|
||||
open("f", "r", encoding="UTF-8")
|
||||
open("f", "wt")
|
||||
|
||||
|
||||
import aiofiles
|
||||
|
||||
aiofiles.open("foo", "U")
|
||||
aiofiles.open("foo", "r")
|
||||
aiofiles.open("foo", mode="r")
|
||||
|
||||
@@ -225,3 +225,33 @@ if sys.version_info > (3,0):
|
||||
|
||||
"this is\
|
||||
allowed too"
|
||||
|
||||
if sys.version_info[0] == 3:
|
||||
print("py3")
|
||||
|
||||
if sys.version_info[0] <= 3:
|
||||
print("py3")
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
print("py3")
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
print("py3")
|
||||
|
||||
if sys.version_info[0] > 3:
|
||||
print("py3")
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
print("py3")
|
||||
|
||||
if sys.version_info[0] <= 2:
|
||||
print("py3")
|
||||
|
||||
if sys.version_info[0] < 2:
|
||||
print("py3")
|
||||
|
||||
if sys.version_info[0] >= 2:
|
||||
print("py3")
|
||||
|
||||
if sys.version_info[0] > 2:
|
||||
print("py3")
|
||||
|
||||
@@ -18,6 +18,7 @@ print("", *args)
|
||||
print("", *args, sep="")
|
||||
print("", **kwargs)
|
||||
print(sep="\t")
|
||||
print(sep=print(1))
|
||||
|
||||
# OK.
|
||||
|
||||
|
||||
67
crates/ruff_linter/resources/test/fixtures/ruff/RUF033.py
vendored
Normal file
67
crates/ruff_linter/resources/test/fixtures/ruff/RUF033.py
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
from dataclasses import InitVar, dataclass
|
||||
|
||||
|
||||
# OK
|
||||
@dataclass
|
||||
class Foo:
|
||||
bar: InitVar[int] = 0
|
||||
baz: InitVar[int] = 1
|
||||
|
||||
def __post_init__(self, bar, baz) -> None: ...
|
||||
|
||||
|
||||
# RUF033
|
||||
@dataclass
|
||||
class Foo:
|
||||
bar: InitVar[int] = 0
|
||||
baz: InitVar[int] = 1
|
||||
|
||||
def __post_init__(self, bar = 11, baz = 11) -> None: ...
|
||||
|
||||
|
||||
# RUF033
|
||||
@dataclass
|
||||
class Foo:
|
||||
def __post_init__(self, bar = 11, baz = 11) -> None: ...
|
||||
|
||||
|
||||
# OK
|
||||
@dataclass
|
||||
class Foo:
|
||||
def __something_else__(self, bar = 11, baz = 11) -> None: ...
|
||||
|
||||
|
||||
# OK
|
||||
def __post_init__(foo: bool = True) -> None: ...
|
||||
|
||||
|
||||
# OK
|
||||
class Foo:
|
||||
def __post_init__(self, x="hmm") -> None: ...
|
||||
|
||||
|
||||
# RUF033
|
||||
@dataclass
|
||||
class Foo:
|
||||
def __post_init__(self, bar: int = 11, baz: Something[Whatever | None] = 11) -> None: ...
|
||||
|
||||
|
||||
# RUF033
|
||||
@dataclass
|
||||
class Foo:
|
||||
"""A very helpful docstring.
|
||||
|
||||
Docstrings are very important and totally not a waste of time.
|
||||
"""
|
||||
|
||||
ping = "pong"
|
||||
|
||||
def __post_init__(self, bar: int = 11, baz: int = 12) -> None: ...
|
||||
|
||||
|
||||
# RUF033
|
||||
@dataclass
|
||||
class Foo:
|
||||
bar = "should've used attrs"
|
||||
|
||||
def __post_init__(self, bar: str = "ahhh", baz: str = "hmm") -> None: ...
|
||||
@@ -380,6 +380,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::WhitespaceAfterDecorator) {
|
||||
pycodestyle::rules::whitespace_after_decorator(checker, decorator_list);
|
||||
}
|
||||
if checker.enabled(Rule::PostInitDefault) {
|
||||
ruff::rules::post_init_default(checker, function_def);
|
||||
}
|
||||
}
|
||||
Stmt::Return(_) => {
|
||||
if checker.enabled(Rule::ReturnOutsideFunction) {
|
||||
|
||||
@@ -26,10 +26,12 @@
|
||||
//! represents the lint-rule analysis phase. In the future, these steps may be separated into
|
||||
//! distinct passes over the AST.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, IsolationLevel};
|
||||
use ruff_notebook::{CellOffsets, NotebookIndex};
|
||||
@@ -40,13 +42,13 @@ use ruff_python_ast::str::Quote;
|
||||
use ruff_python_ast::visitor::{walk_except_handler, walk_pattern, Visitor};
|
||||
use ruff_python_ast::{
|
||||
self as ast, AnyParameterRef, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext,
|
||||
FStringElement, Keyword, MatchCase, ModExpression, ModModule, Parameter, Parameters, Pattern,
|
||||
Stmt, Suite, UnaryOp,
|
||||
FStringElement, Keyword, MatchCase, ModModule, Parameter, Parameters, Pattern, Stmt, Suite,
|
||||
UnaryOp,
|
||||
};
|
||||
use ruff_python_ast::{helpers, str, visitor, PySourceType};
|
||||
use ruff_python_codegen::{Generator, Stylist};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
|
||||
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind, ParsedAnnotation};
|
||||
use ruff_python_parser::{Parsed, Tokens};
|
||||
use ruff_python_semantic::all::{DunderAllDefinition, DunderAllFlags};
|
||||
use ruff_python_semantic::analyze::{imports, typing};
|
||||
@@ -55,7 +57,7 @@ use ruff_python_semantic::{
|
||||
ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags,
|
||||
StarImport, SubmoduleImport,
|
||||
};
|
||||
use ruff_python_stdlib::builtins::{IPYTHON_BUILTINS, MAGIC_GLOBALS, PYTHON_BUILTINS};
|
||||
use ruff_python_stdlib::builtins::{python_builtins, MAGIC_GLOBALS};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{Locator, OneIndexed, SourceRow};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
@@ -177,8 +179,10 @@ impl ExpectedDocstringKind {
|
||||
pub(crate) struct Checker<'a> {
|
||||
/// The [`Parsed`] output for the source code.
|
||||
parsed: &'a Parsed<ModModule>,
|
||||
/// An internal cache for parsed string annotations
|
||||
parsed_annotations_cache: ParsedAnnotationsCache<'a>,
|
||||
/// The [`Parsed`] output for the type annotation the checker is currently in.
|
||||
parsed_type_annotation: Option<&'a Parsed<ModExpression>>,
|
||||
parsed_type_annotation: Option<&'a ParsedAnnotation>,
|
||||
/// The [`Path`] to the file under analysis.
|
||||
path: &'a Path,
|
||||
/// The [`Path`] to the package containing the current file.
|
||||
@@ -229,6 +233,7 @@ impl<'a> Checker<'a> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
parsed: &'a Parsed<ModModule>,
|
||||
parsed_annotations_arena: &'a typed_arena::Arena<ParsedAnnotation>,
|
||||
settings: &'a LinterSettings,
|
||||
noqa_line_for: &'a NoqaMapping,
|
||||
noqa: flags::Noqa,
|
||||
@@ -245,6 +250,7 @@ impl<'a> Checker<'a> {
|
||||
Checker {
|
||||
parsed,
|
||||
parsed_type_annotation: None,
|
||||
parsed_annotations_cache: ParsedAnnotationsCache::new(parsed_annotations_arena),
|
||||
settings,
|
||||
noqa_line_for,
|
||||
noqa,
|
||||
@@ -333,8 +339,8 @@ impl<'a> Checker<'a> {
|
||||
/// Returns the [`Tokens`] for the parsed type annotation if the checker is in a typing context
|
||||
/// or the parsed source code.
|
||||
pub(crate) fn tokens(&self) -> &'a Tokens {
|
||||
if let Some(parsed_type_annotation) = self.parsed_type_annotation {
|
||||
parsed_type_annotation.tokens()
|
||||
if let Some(type_annotation) = self.parsed_type_annotation {
|
||||
type_annotation.parsed().tokens()
|
||||
} else {
|
||||
self.parsed.tokens()
|
||||
}
|
||||
@@ -405,6 +411,39 @@ impl<'a> Checker<'a> {
|
||||
.map(|node_id| IsolationLevel::Group(node_id.into()))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Parse a stringified type annotation as an AST expression,
|
||||
/// e.g. `"List[str]"` in `x: "List[str]"`
|
||||
///
|
||||
/// This method is a wrapper around [`ruff_python_parser::typing::parse_type_annotation`]
|
||||
/// that adds caching.
|
||||
pub(crate) fn parse_type_annotation(
|
||||
&self,
|
||||
annotation: &ast::ExprStringLiteral,
|
||||
) -> Option<&'a ParsedAnnotation> {
|
||||
self.parsed_annotations_cache
|
||||
.lookup_or_parse(annotation, self.locator.contents())
|
||||
}
|
||||
|
||||
/// Apply a test to an annotation expression,
|
||||
/// abstracting over the fact that the annotation expression might be "stringized".
|
||||
///
|
||||
/// A stringized annotation is one enclosed in string quotes:
|
||||
/// `foo: "typing.Any"` means the same thing to a type checker as `foo: typing.Any`.
|
||||
pub(crate) fn match_maybe_stringized_annotation(
|
||||
&self,
|
||||
expr: &ast::Expr,
|
||||
match_fn: impl FnOnce(&ast::Expr) -> bool,
|
||||
) -> bool {
|
||||
if let ast::Expr::StringLiteral(string_annotation) = expr {
|
||||
let Some(parsed_annotation) = self.parse_type_annotation(string_annotation) else {
|
||||
return false;
|
||||
};
|
||||
match_fn(parsed_annotation.expression())
|
||||
} else {
|
||||
match_fn(expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for Checker<'a> {
|
||||
@@ -1912,16 +1951,13 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn bind_builtins(&mut self) {
|
||||
for builtin in PYTHON_BUILTINS
|
||||
let standard_builtins = python_builtins(
|
||||
self.settings.target_version.minor(),
|
||||
self.source_type.is_ipynb(),
|
||||
);
|
||||
for builtin in standard_builtins
|
||||
.iter()
|
||||
.chain(MAGIC_GLOBALS.iter())
|
||||
.chain(
|
||||
self.source_type
|
||||
.is_ipynb()
|
||||
.then_some(IPYTHON_BUILTINS)
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.copied()
|
||||
.chain(self.settings.builtins.iter().map(String::as_str))
|
||||
{
|
||||
@@ -2168,18 +2204,13 @@ impl<'a> Checker<'a> {
|
||||
///
|
||||
/// class Bar: pass
|
||||
/// ```
|
||||
fn visit_deferred_string_type_definitions(
|
||||
&mut self,
|
||||
allocator: &'a typed_arena::Arena<Parsed<ModExpression>>,
|
||||
) {
|
||||
fn visit_deferred_string_type_definitions(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.visit.string_type_definitions.is_empty() {
|
||||
let type_definitions = std::mem::take(&mut self.visit.string_type_definitions);
|
||||
for (string_expr, snapshot) in type_definitions {
|
||||
if let Ok((parsed_annotation, kind)) =
|
||||
parse_type_annotation(string_expr, self.locator.contents())
|
||||
{
|
||||
let parsed_annotation = allocator.alloc(parsed_annotation);
|
||||
let annotation_parse_result = self.parse_type_annotation(string_expr);
|
||||
if let Some(parsed_annotation) = annotation_parse_result {
|
||||
self.parsed_type_annotation = Some(parsed_annotation);
|
||||
|
||||
let annotation = string_expr.value.to_str();
|
||||
@@ -2198,7 +2229,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let type_definition_flag = match kind {
|
||||
let type_definition_flag = match parsed_annotation.kind() {
|
||||
AnnotationKind::Simple => SemanticModelFlags::SIMPLE_STRING_TYPE_DEFINITION,
|
||||
AnnotationKind::Complex => {
|
||||
SemanticModelFlags::COMPLEX_STRING_TYPE_DEFINITION
|
||||
@@ -2207,7 +2238,7 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.flags |=
|
||||
SemanticModelFlags::TYPE_DEFINITION | type_definition_flag;
|
||||
self.visit_expr(parsed_annotation.expr());
|
||||
self.visit_expr(parsed_annotation.expression());
|
||||
self.parsed_type_annotation = None;
|
||||
} else {
|
||||
if self.enabled(Rule::ForwardAnnotationSyntaxError) {
|
||||
@@ -2220,6 +2251,35 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're parsing string annotations inside string annotations
|
||||
// (which is the only reason we might enter a second iteration of this loop),
|
||||
// the cache is no longer valid. We must invalidate it to avoid an infinite loop.
|
||||
//
|
||||
// For example, consider the following annotation:
|
||||
// ```python
|
||||
// x: "list['str']"
|
||||
// ```
|
||||
//
|
||||
// The first time we visit the AST, we see `"list['str']"`
|
||||
// and identify it as a stringified annotation.
|
||||
// We store it in `self.visit.string_type_definitions` to be analyzed later.
|
||||
//
|
||||
// After the entire tree has been visited, we look through
|
||||
// `self.visit.string_type_definitions` and find `"list['str']"`.
|
||||
// We parse it, and it becomes `list['str']`.
|
||||
// After parsing it, we call `self.visit_expr()` on the `list['str']` node,
|
||||
// and that `visit_expr` call is going to find `'str'` inside that node and
|
||||
// identify it as a string type definition, appending it to
|
||||
// `self.visit.string_type_definitions`, ensuring that there will be one
|
||||
// more iteration of this loop.
|
||||
//
|
||||
// Unfortunately, the `TextRange` of `'str'`
|
||||
// here will be *relative to the parsed `list['str']` node* rather than
|
||||
// *relative to the original module*, meaning the cache
|
||||
// (which uses `TextSize` as the key) becomes invalid on the second
|
||||
// iteration of this loop.
|
||||
self.parsed_annotations_cache.clear();
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
@@ -2283,14 +2343,14 @@ impl<'a> Checker<'a> {
|
||||
/// After initial traversal of the source tree has been completed,
|
||||
/// recursively visit all AST nodes that were deferred on the first pass.
|
||||
/// This includes lambdas, functions, type parameters, and type annotations.
|
||||
fn visit_deferred(&mut self, allocator: &'a typed_arena::Arena<Parsed<ModExpression>>) {
|
||||
fn visit_deferred(&mut self) {
|
||||
while !self.visit.is_empty() {
|
||||
self.visit_deferred_class_bases();
|
||||
self.visit_deferred_functions();
|
||||
self.visit_deferred_type_param_definitions();
|
||||
self.visit_deferred_lambdas();
|
||||
self.visit_deferred_future_type_definitions();
|
||||
self.visit_deferred_string_type_definitions(allocator);
|
||||
self.visit_deferred_string_type_definitions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2358,6 +2418,42 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
struct ParsedAnnotationsCache<'a> {
|
||||
arena: &'a typed_arena::Arena<ParsedAnnotation>,
|
||||
by_offset: RefCell<FxHashMap<TextSize, Option<&'a ParsedAnnotation>>>,
|
||||
}
|
||||
|
||||
impl<'a> ParsedAnnotationsCache<'a> {
|
||||
fn new(arena: &'a typed_arena::Arena<ParsedAnnotation>) -> Self {
|
||||
Self {
|
||||
arena,
|
||||
by_offset: RefCell::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_or_parse(
|
||||
&self,
|
||||
annotation: &ast::ExprStringLiteral,
|
||||
source: &str,
|
||||
) -> Option<&'a ParsedAnnotation> {
|
||||
*self
|
||||
.by_offset
|
||||
.borrow_mut()
|
||||
.entry(annotation.start())
|
||||
.or_insert_with(|| {
|
||||
if let Ok(annotation) = parse_type_annotation(annotation, source) {
|
||||
Some(self.arena.alloc(annotation))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn clear(&self) {
|
||||
self.by_offset.borrow_mut().clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn check_ast(
|
||||
parsed: &Parsed<ModModule>,
|
||||
@@ -2393,8 +2489,10 @@ pub(crate) fn check_ast(
|
||||
python_ast: parsed.suite(),
|
||||
};
|
||||
|
||||
let allocator = typed_arena::Arena::new();
|
||||
let mut checker = Checker::new(
|
||||
parsed,
|
||||
&allocator,
|
||||
settings,
|
||||
noqa_line_for,
|
||||
noqa,
|
||||
@@ -2417,8 +2515,7 @@ pub(crate) fn check_ast(
|
||||
// Visit any deferred syntax nodes. Take care to visit in order, such that we avoid adding
|
||||
// new deferred nodes after visiting nodes of that kind. For example, visiting a deferred
|
||||
// function can add a deferred lambda, but the opposite is not true.
|
||||
let allocator = typed_arena::Arena::new();
|
||||
checker.visit_deferred(&allocator);
|
||||
checker.visit_deferred();
|
||||
checker.visit_exports();
|
||||
|
||||
// Check docstrings, bindings, and unresolved references.
|
||||
|
||||
@@ -960,6 +960,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "030") => (RuleGroup::Preview, rules::ruff::rules::AssertWithPrintMessage),
|
||||
(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, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::docstrings::google::GOOGLE_SECTIONS;
|
||||
use crate::docstrings::numpy::NUMPY_SECTIONS;
|
||||
use crate::docstrings::sections::SectionKind;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug, is_macro::Is)]
|
||||
pub(crate) enum SectionStyle {
|
||||
Numpy,
|
||||
Google,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite};
|
||||
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite, XmlString};
|
||||
|
||||
use ruff_source_file::SourceLocation;
|
||||
|
||||
@@ -25,7 +25,7 @@ impl Emitter for JunitEmitter {
|
||||
let mut test_suite = TestSuite::new("ruff");
|
||||
test_suite
|
||||
.extra
|
||||
.insert("package".to_string(), "org.ruff".to_string());
|
||||
.insert(XmlString::new("package"), XmlString::new("org.ruff"));
|
||||
let mut case = TestCase::new("No errors found", TestCaseStatus::success());
|
||||
case.set_classname("ruff");
|
||||
test_suite.add_test_case(case);
|
||||
@@ -35,7 +35,7 @@ impl Emitter for JunitEmitter {
|
||||
let mut test_suite = TestSuite::new(filename);
|
||||
test_suite
|
||||
.extra
|
||||
.insert("package".to_string(), "org.ruff".to_string());
|
||||
.insert(XmlString::new("package"), XmlString::new("org.ruff"));
|
||||
|
||||
for message in messages {
|
||||
let MessageWithLocation {
|
||||
@@ -70,10 +70,14 @@ impl Emitter for JunitEmitter {
|
||||
let file_stem = file_path.file_stem().unwrap().to_str().unwrap();
|
||||
let classname = file_path.parent().unwrap().join(file_stem);
|
||||
case.set_classname(classname.to_str().unwrap());
|
||||
case.extra
|
||||
.insert("line".to_string(), location.row.to_string());
|
||||
case.extra
|
||||
.insert("column".to_string(), location.column.to_string());
|
||||
case.extra.insert(
|
||||
XmlString::new("line"),
|
||||
XmlString::new(location.row.to_string()),
|
||||
);
|
||||
case.extra.insert(
|
||||
XmlString::new("column"),
|
||||
XmlString::new(location.column.to_string()),
|
||||
);
|
||||
|
||||
test_suite.add_test_case(case);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ impl SarifResult {
|
||||
Ok(Self {
|
||||
rule: message.rule(),
|
||||
level: "error".to_string(),
|
||||
message: message.name().to_string(),
|
||||
message: message.body().to_string(),
|
||||
uri: url::Url::from_file_path(&path)
|
||||
.map_err(|()| anyhow::anyhow!("Failed to convert path to URL: {}", path.display()))?
|
||||
.to_string(),
|
||||
@@ -141,7 +141,7 @@ impl SarifResult {
|
||||
Ok(Self {
|
||||
rule: message.rule(),
|
||||
level: "error".to_string(),
|
||||
message: message.name().to_string(),
|
||||
message: message.body().to_string(),
|
||||
uri: path.display().to_string(),
|
||||
start_line: start_location.row,
|
||||
start_column: start_location.column,
|
||||
@@ -215,6 +215,10 @@ mod tests {
|
||||
.unwrap();
|
||||
let results = sarif["runs"][0]["results"].as_array().unwrap();
|
||||
assert_eq!(results.len(), 3);
|
||||
assert_eq!(
|
||||
results[0]["message"]["text"].as_str().unwrap(),
|
||||
"`os` imported but unused"
|
||||
);
|
||||
assert!(rules.len() > 3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if self.show_fix_status {
|
||||
if let Some(fix) = self.message.fix() {
|
||||
// Do not display an indicator for unapplicable fixes
|
||||
// Do not display an indicator for inapplicable fixes
|
||||
if fix.applies(self.unsafe_fixes.required_applicability()) {
|
||||
if let Some(rule) = self.message.rule() {
|
||||
write!(f, "{} ", rule.noqa_code().to_string().red().bold())?;
|
||||
|
||||
@@ -4,7 +4,6 @@ use ruff_python_ast::helpers::ReturnStatementVisitor;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Stmt};
|
||||
use ruff_python_parser::typing::parse_type_annotation;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::Definition;
|
||||
use ruff_python_stdlib::typing::simple_magic_return_type;
|
||||
@@ -514,13 +513,10 @@ fn check_dynamically_typed<F>(
|
||||
{
|
||||
if let Expr::StringLiteral(string_expr) = annotation {
|
||||
// Quoted annotations
|
||||
if let Ok((parsed_annotation, _)) =
|
||||
parse_type_annotation(string_expr, checker.locator().contents())
|
||||
{
|
||||
if let Some(parsed_annotation) = checker.parse_type_annotation(string_expr) {
|
||||
if type_hint_resolves_to_any(
|
||||
parsed_annotation.expr(),
|
||||
checker.semantic(),
|
||||
checker.locator(),
|
||||
parsed_annotation.expression(),
|
||||
checker,
|
||||
checker.settings.target_version.minor(),
|
||||
) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
@@ -530,12 +526,7 @@ fn check_dynamically_typed<F>(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if type_hint_resolves_to_any(
|
||||
annotation,
|
||||
checker.semantic(),
|
||||
checker.locator(),
|
||||
checker.settings.target_version.minor(),
|
||||
) {
|
||||
if type_hint_resolves_to_any(annotation, checker, checker.settings.target_version.minor()) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
AnyType { name: func() },
|
||||
annotation.range(),
|
||||
|
||||
@@ -9,13 +9,33 @@ use crate::rules::flake8_async::helpers::AsyncModule;
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `async` functions with a `timeout` argument.
|
||||
/// Checks for `async` function definitions with `timeout` parameters.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Rather than implementing asynchronous timeout behavior manually, prefer
|
||||
/// built-in timeout functionality, such as `asyncio.timeout`, `trio.fail_after`,
|
||||
/// or `anyio.move_on_after`, among others.
|
||||
///
|
||||
/// This rule is highly opinionated to enforce a design pattern
|
||||
/// called ["structured concurrency"] that allows for
|
||||
/// `async` functions to be oblivious to timeouts,
|
||||
/// instead letting callers to handle the logic with a context manager.
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// This rule attempts to detect which async framework your code is using
|
||||
/// by analysing the imports in the file it's checking. If it sees an
|
||||
/// `anyio` import in your code, it will assume `anyio` is your framework
|
||||
/// of choice; if it sees a `trio` import, it will assume `trio`; if it
|
||||
/// sees neither, it will assume `asyncio`. `asyncio.timeout` was added
|
||||
/// in Python 3.11, so if `asyncio` is detected as the framework being used,
|
||||
/// this rule will be ignored when your configured [`target-version`] is set
|
||||
/// to less than Python 3.11.
|
||||
///
|
||||
/// For functions that wrap `asyncio.timeout`, `trio.fail_after` or
|
||||
/// `anyio.move_on_after`, false positives from this rule can be avoided
|
||||
/// by using a different parameter name.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
@@ -41,6 +61,8 @@ use crate::settings::types::PythonVersion;
|
||||
/// - [`asyncio` timeouts](https://docs.python.org/3/library/asyncio-task.html#timeouts)
|
||||
/// - [`anyio` timeouts](https://anyio.readthedocs.io/en/stable/cancellation.html)
|
||||
/// - [`trio` timeouts](https://trio.readthedocs.io/en/stable/reference-core.html#cancellation-and-timeouts)
|
||||
///
|
||||
/// ["structured concurrency"]: https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/#timeouts-and-cancellation
|
||||
#[violation]
|
||||
pub struct AsyncFunctionWithTimeout {
|
||||
module: AsyncModule,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use crate::settings::types::PythonVersion;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_stdlib::builtins::{is_ipython_builtin, is_python_builtin};
|
||||
use ruff_python_stdlib::builtins::is_python_builtin;
|
||||
|
||||
pub(super) fn shadows_builtin(
|
||||
name: &str,
|
||||
ignorelist: &[String],
|
||||
source_type: PySourceType,
|
||||
ignorelist: &[String],
|
||||
python_version: PythonVersion,
|
||||
) -> bool {
|
||||
if is_python_builtin(name) || source_type.is_ipynb() && is_ipython_builtin(name) {
|
||||
if is_python_builtin(name, python_version.minor(), source_type.is_ipynb()) {
|
||||
ignorelist.iter().all(|ignore| ignore != name)
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -12,6 +12,7 @@ mod tests {
|
||||
|
||||
use crate::assert_messages;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
|
||||
@@ -112,4 +113,18 @@ mod tests {
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::BuiltinImportShadowing, Path::new("A004.py"))]
|
||||
fn rules_py312(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}_py38", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_builtins").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
target_version: PythonVersion::Py38,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user