Compare commits
11 Commits
0.11.7
...
cjm/noinhe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
872ad950aa | ||
|
|
59dedb5d3c | ||
|
|
46a1fd3b3e | ||
|
|
327b913d68 | ||
|
|
afc18ff1a1 | ||
|
|
f1a539dac6 | ||
|
|
ef0343189c | ||
|
|
4eecc40110 | ||
|
|
cf59cee928 | ||
|
|
538393d1f3 | ||
|
|
92ecfc908b |
@@ -19,7 +19,7 @@ async def elements(n):
|
||||
yield n
|
||||
|
||||
async def f():
|
||||
# error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)"
|
||||
# error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)"
|
||||
return {n: [x async for x in elements(n)] for n in range(3)}
|
||||
```
|
||||
|
||||
|
||||
@@ -275,14 +275,16 @@ c: C[int] = C[int]()
|
||||
reveal_type(c.method("string")) # revealed: Literal["string"]
|
||||
```
|
||||
|
||||
## Cyclic class definition
|
||||
## Cyclic class definitions
|
||||
|
||||
### F-bounded quantification
|
||||
|
||||
A class can use itself as the type parameter of one of its superclasses. (This is also known as the
|
||||
[curiously recurring template pattern][crtp] or [F-bounded quantification][f-bound].)
|
||||
|
||||
Here, `Sub` is not a generic class, since it fills its superclass's type parameter (with itself).
|
||||
#### In a stub file
|
||||
|
||||
`stub.pyi`:
|
||||
Here, `Sub` is not a generic class, since it fills its superclass's type parameter (with itself).
|
||||
|
||||
```pyi
|
||||
class Base[T]: ...
|
||||
@@ -291,9 +293,9 @@ class Sub(Base[Sub]): ...
|
||||
reveal_type(Sub) # revealed: Literal[Sub]
|
||||
```
|
||||
|
||||
A similar case can work in a non-stub file, if forward references are stringified:
|
||||
#### With string forward references
|
||||
|
||||
`string_annotation.py`:
|
||||
A similar case can work in a non-stub file, if forward references are stringified:
|
||||
|
||||
```py
|
||||
class Base[T]: ...
|
||||
@@ -302,9 +304,9 @@ class Sub(Base["Sub"]): ...
|
||||
reveal_type(Sub) # revealed: Literal[Sub]
|
||||
```
|
||||
|
||||
In a non-stub file, without stringified forward references, this raises a `NameError`:
|
||||
#### Without string forward references
|
||||
|
||||
`bare_annotation.py`:
|
||||
In a non-stub file, without stringified forward references, this raises a `NameError`:
|
||||
|
||||
```py
|
||||
class Base[T]: ...
|
||||
@@ -313,11 +315,23 @@ class Base[T]: ...
|
||||
class Sub(Base[Sub]): ...
|
||||
```
|
||||
|
||||
## Another cyclic case
|
||||
### Cyclic inheritance as a generic parameter
|
||||
|
||||
```pyi
|
||||
class Derived[T](list[Derived[T]]): ...
|
||||
```
|
||||
|
||||
### Direct cyclic inheritance
|
||||
|
||||
Inheritance that would result in a cyclic MRO is detected as an error.
|
||||
|
||||
```py
|
||||
# error: [cyclic-class-definition]
|
||||
class C[T](C): ...
|
||||
|
||||
# error: [cyclic-class-definition]
|
||||
class D[T](D[int]): ...
|
||||
```
|
||||
|
||||
[crtp]: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
|
||||
[f-bound]: https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification
|
||||
|
||||
@@ -53,6 +53,25 @@ class B(A): ...
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Linear inheritance with PEP 695 generic class
|
||||
|
||||
The same is true if the base with the metaclass is a generic class.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A[T](metaclass=M): ...
|
||||
class B(A): ...
|
||||
class C(A[int]): ...
|
||||
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
reveal_type(C.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Conflict (1)
|
||||
|
||||
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its
|
||||
|
||||
@@ -16,7 +16,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/semant
|
||||
2 | yield n
|
||||
3 |
|
||||
4 | async def f():
|
||||
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)"
|
||||
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)"
|
||||
6 | return {n: [x async for x in elements(n)] for n in range(3)}
|
||||
7 | async def test():
|
||||
8 | return [[x async for x in elements(n)] async for n in range(3)]
|
||||
@@ -36,9 +36,9 @@ error: invalid-syntax
|
||||
--> /src/mdtest_snippet.py:6:19
|
||||
|
|
||||
4 | async def f():
|
||||
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax...
|
||||
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (synt...
|
||||
6 | return {n: [x async for x in elements(n)] for n in range(3)}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
7 | async def test():
|
||||
8 | return [[x async for x in elements(n)] async for n in range(3)]
|
||||
|
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
Expression # cycle panic (signature_)
|
||||
Tanjun # cycle panic (signature_)
|
||||
aiohttp # missing expression ID
|
||||
alerta # missing expression ID
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
AutoSplit
|
||||
Expression
|
||||
PyGithub
|
||||
PyWinCtl
|
||||
SinbadCogs
|
||||
|
||||
@@ -688,20 +688,21 @@ impl<'db> Type<'db> {
|
||||
matches!(self, Type::ClassLiteral(..))
|
||||
}
|
||||
|
||||
pub const fn into_class_type(self) -> Option<ClassType<'db>> {
|
||||
/// Turn a class literal (`Type::ClassLiteral` or `Type::GenericAlias`) into a `ClassType`.
|
||||
/// Since a `ClassType` must be specialized, apply the default specialization to any
|
||||
/// unspecialized generic class literal.
|
||||
pub fn to_class_type(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
|
||||
match self {
|
||||
Type::ClassLiteral(ClassLiteralType::NonGeneric(non_generic)) => {
|
||||
Some(ClassType::NonGeneric(non_generic))
|
||||
}
|
||||
Type::ClassLiteral(class_literal) => Some(class_literal.default_specialization(db)),
|
||||
Type::GenericAlias(alias) => Some(ClassType::Generic(alias)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn expect_class_type(self) -> ClassType<'db> {
|
||||
self.into_class_type()
|
||||
.expect("Expected a Type::GenericAlias or non-generic Type::ClassLiteral variant")
|
||||
pub fn expect_class_type(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
self.to_class_type(db)
|
||||
.expect("Expected a Type::GenericAlias or Type::ClassLiteral variant")
|
||||
}
|
||||
|
||||
pub const fn is_class_type(&self) -> bool {
|
||||
@@ -5230,11 +5231,7 @@ impl<'db> InvalidTypeExpression<'db> {
|
||||
/// typevar represents as an annotation: that is, an unknown set of objects, constrained by the
|
||||
/// upper-bound/constraints on this type var, defaulting to the default type of this type var when
|
||||
/// not otherwise bound to a type.
|
||||
///
|
||||
/// This must be a tracked struct, not an interned one, because typevar equivalence is by identity,
|
||||
/// not by value. Two typevars that have the same name, bound/constraints, and default, are still
|
||||
/// different typevars: if used in the same scope, they may be bound to different types.
|
||||
#[salsa::tracked(debug)]
|
||||
#[salsa::interned(debug)]
|
||||
pub struct TypeVarInstance<'db> {
|
||||
/// The name of this TypeVar (e.g. `T`)
|
||||
#[return_ref]
|
||||
@@ -6027,6 +6024,10 @@ bitflags! {
|
||||
const OVERLOAD = 1 << 2;
|
||||
/// `@abc.abstractmethod`
|
||||
const ABSTRACT_METHOD = 1 << 3;
|
||||
/// `@typing.final`
|
||||
const FINAL = 1 << 4;
|
||||
/// `@typing.override`
|
||||
const OVERRIDE = 1 << 6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6400,6 +6401,8 @@ pub enum KnownFunction {
|
||||
Cast,
|
||||
/// `typing(_extensions).overload`
|
||||
Overload,
|
||||
/// `typing(_extensions).override`
|
||||
Override,
|
||||
/// `typing(_extensions).is_protocol`
|
||||
IsProtocol,
|
||||
/// `typing(_extensions).get_protocol_members`
|
||||
@@ -6467,6 +6470,7 @@ impl KnownFunction {
|
||||
| Self::AssertNever
|
||||
| Self::Cast
|
||||
| Self::Overload
|
||||
| Self::Override
|
||||
| Self::RevealType
|
||||
| Self::Final
|
||||
| Self::IsProtocol
|
||||
@@ -7844,6 +7848,7 @@ pub(crate) mod tests {
|
||||
KnownFunction::Cast
|
||||
| KnownFunction::Final
|
||||
| KnownFunction::Overload
|
||||
| KnownFunction::Override
|
||||
| KnownFunction::RevealType
|
||||
| KnownFunction::AssertType
|
||||
| KnownFunction::AssertNever
|
||||
|
||||
@@ -584,6 +584,14 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::Override) => {
|
||||
// TODO: This can be removed once we understand legacy generics because the
|
||||
// typeshed definition for `typing.overload` is an identity function.
|
||||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(*ty);
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::AbstractMethod) => {
|
||||
// TODO: This can be removed once we understand legacy generics because the
|
||||
// typeshed definition for `abc.abstractmethod` is an identity function.
|
||||
@@ -592,6 +600,14 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::Final) => {
|
||||
// TODO: This can be removed once we understand legacy generics because the
|
||||
// typeshed definition for `abc.abstractmethod` is an identity function.
|
||||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(*ty);
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::GetattrStatic) => {
|
||||
let [Some(instance_ty), Some(attr_name), default] =
|
||||
overload.parameter_types()
|
||||
|
||||
@@ -62,45 +62,6 @@ fn explicit_bases_cycle_initial<'db>(
|
||||
Box::default()
|
||||
}
|
||||
|
||||
fn try_mro_cycle_recover<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_value: &Result<Mro<'db>, MroError<'db>>,
|
||||
_count: u32,
|
||||
_self: ClassLiteralType<'db>,
|
||||
_specialization: Option<Specialization<'db>>,
|
||||
) -> salsa::CycleRecoveryAction<Result<Mro<'db>, MroError<'db>>> {
|
||||
salsa::CycleRecoveryAction::Iterate
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn try_mro_cycle_initial<'db>(
|
||||
db: &'db dyn Db,
|
||||
self_: ClassLiteralType<'db>,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
) -> Result<Mro<'db>, MroError<'db>> {
|
||||
Ok(Mro::from_error(
|
||||
db,
|
||||
self_.apply_optional_specialization(db, specialization),
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::ref_option, clippy::trivially_copy_pass_by_ref)]
|
||||
fn inheritance_cycle_recover<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_value: &Option<InheritanceCycle>,
|
||||
_count: u32,
|
||||
_self: ClassLiteralType<'db>,
|
||||
) -> salsa::CycleRecoveryAction<Option<InheritanceCycle>> {
|
||||
salsa::CycleRecoveryAction::Iterate
|
||||
}
|
||||
|
||||
fn inheritance_cycle_initial<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_self: ClassLiteralType<'db>,
|
||||
) -> Option<InheritanceCycle> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Representation of a class definition statement in the AST. This does not in itself represent a
|
||||
/// type, but is used as the inner data for several structs that *do* represent types.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
|
||||
@@ -174,6 +135,10 @@ impl<'db> GenericAlias<'db> {
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||
self.origin(db).class(db).definition(db)
|
||||
}
|
||||
|
||||
pub(crate) fn class_literal(self, db: &'db dyn Db) -> ClassLiteralType<'db> {
|
||||
ClassLiteralType::Generic(self.origin(db))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<GenericAlias<'db>> for Type<'db> {
|
||||
@@ -573,12 +538,13 @@ impl<'db> ClassLiteralType<'db> {
|
||||
self.explicit_bases_query(db)
|
||||
}
|
||||
|
||||
/// Iterate over this class's explicit bases, filtering out any bases that are not class objects.
|
||||
/// Iterate over this class's explicit bases, filtering out any bases that are not class
|
||||
/// objects, and applying default specialization to any unspecialized generic class literals.
|
||||
fn fully_static_explicit_bases(self, db: &'db dyn Db) -> impl Iterator<Item = ClassType<'db>> {
|
||||
self.explicit_bases(db)
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(Type::into_class_type)
|
||||
.filter_map(|ty| ty.to_class_type(db))
|
||||
}
|
||||
|
||||
#[salsa::tracked(return_ref, cycle_fn=explicit_bases_cycle_recover, cycle_initial=explicit_bases_cycle_initial)]
|
||||
@@ -656,7 +622,7 @@ impl<'db> ClassLiteralType<'db> {
|
||||
/// attribute on a class at runtime.
|
||||
///
|
||||
/// [method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
|
||||
#[salsa::tracked(return_ref, cycle_fn=try_mro_cycle_recover, cycle_initial=try_mro_cycle_initial)]
|
||||
#[salsa::tracked(return_ref)]
|
||||
pub(super) fn try_mro(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
@@ -763,7 +729,7 @@ impl<'db> ClassLiteralType<'db> {
|
||||
(KnownClass::Type.to_class_literal(db), self)
|
||||
};
|
||||
|
||||
let mut candidate = if let Some(metaclass_ty) = metaclass.into_class_type() {
|
||||
let mut candidate = if let Some(metaclass_ty) = metaclass.to_class_type(db) {
|
||||
MetaclassCandidate {
|
||||
metaclass: metaclass_ty,
|
||||
explicit_metaclass_of: class_metaclass_was_from,
|
||||
@@ -805,7 +771,7 @@ impl<'db> ClassLiteralType<'db> {
|
||||
// - https://github.com/python/cpython/blob/83ba8c2bba834c0b92de669cac16fcda17485e0e/Objects/typeobject.c#L3629-L3663
|
||||
for base_class in base_classes {
|
||||
let metaclass = base_class.metaclass(db);
|
||||
let Some(metaclass) = metaclass.into_class_type() else {
|
||||
let Some(metaclass) = metaclass.to_class_type(db) else {
|
||||
continue;
|
||||
};
|
||||
if metaclass.is_subclass_of(db, candidate.metaclass) {
|
||||
@@ -1678,7 +1644,7 @@ impl<'db> ClassLiteralType<'db> {
|
||||
///
|
||||
/// A class definition like this will fail at runtime,
|
||||
/// but we must be resilient to it or we could panic.
|
||||
#[salsa::tracked(cycle_fn=inheritance_cycle_recover, cycle_initial=inheritance_cycle_initial)]
|
||||
#[salsa::tracked]
|
||||
pub(super) fn inheritance_cycle(self, db: &'db dyn Db) -> Option<InheritanceCycle> {
|
||||
/// Return `true` if the class is cyclically defined.
|
||||
///
|
||||
@@ -1690,8 +1656,12 @@ impl<'db> ClassLiteralType<'db> {
|
||||
visited_classes: &mut IndexSet<ClassLiteralType<'db>>,
|
||||
) -> bool {
|
||||
let mut result = false;
|
||||
for explicit_base_class in class.fully_static_explicit_bases(db) {
|
||||
let (explicit_base_class_literal, _) = explicit_base_class.class_literal(db);
|
||||
for explicit_base in class.explicit_bases(db) {
|
||||
let explicit_base_class_literal = match explicit_base {
|
||||
Type::ClassLiteral(class_literal) => *class_literal,
|
||||
Type::GenericAlias(generic_alias) => generic_alias.class_literal(db),
|
||||
_ => continue,
|
||||
};
|
||||
if !classes_on_stack.insert(explicit_base_class_literal) {
|
||||
return true;
|
||||
}
|
||||
@@ -1705,7 +1675,6 @@ impl<'db> ClassLiteralType<'db> {
|
||||
visited_classes,
|
||||
);
|
||||
}
|
||||
|
||||
classes_on_stack.pop();
|
||||
}
|
||||
result
|
||||
@@ -2157,7 +2126,7 @@ impl<'db> KnownClass {
|
||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||
pub(crate) fn to_instance(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.to_class_literal(db)
|
||||
.into_class_type()
|
||||
.to_class_type(db)
|
||||
.map(Type::instance)
|
||||
.unwrap_or_else(Type::unknown)
|
||||
}
|
||||
@@ -2224,7 +2193,7 @@ impl<'db> KnownClass {
|
||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||
pub(crate) fn to_subclass_of(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.to_class_literal(db)
|
||||
.into_class_type()
|
||||
.to_class_type(db)
|
||||
.map(|class| SubclassOfType::from(db, class))
|
||||
.unwrap_or_else(SubclassOfType::subclass_of_unknown)
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ impl<'db> ClassBase<'db> {
|
||||
pub(super) fn object(db: &'db dyn Db) -> Self {
|
||||
KnownClass::Object
|
||||
.to_class_literal(db)
|
||||
.into_class_type()
|
||||
.to_class_type(db)
|
||||
.map_or(Self::unknown(), Self::Class)
|
||||
}
|
||||
|
||||
|
||||
@@ -154,6 +154,10 @@ impl<'db> Specialization<'db> {
|
||||
pub(crate) fn combine(self, db: &'db dyn Db, other: Self) -> Self {
|
||||
let generic_context = self.generic_context(db);
|
||||
assert!(other.generic_context(db) == generic_context);
|
||||
// TODO special-casing Unknown to mean "no mapping" is not right here, and can give
|
||||
// confusing/wrong results in cases where there was a mapping found for a typevar, and it
|
||||
// was of type Unknown. We should probably add a bitset or similar to Specialization that
|
||||
// explicitly tells us which typevars are mapped.
|
||||
let types: Box<[_]> = self
|
||||
.types(db)
|
||||
.into_iter()
|
||||
|
||||
@@ -1500,6 +1500,14 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
function_decorators |= FunctionDecorators::ABSTRACT_METHOD;
|
||||
continue;
|
||||
}
|
||||
Some(KnownFunction::Final) => {
|
||||
function_decorators |= FunctionDecorators::FINAL;
|
||||
continue;
|
||||
}
|
||||
Some(KnownFunction::Override) => {
|
||||
function_decorators |= FunctionDecorators::OVERRIDE;
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
23
crates/ruff_linter/resources/test/fixtures/syntax_errors/load_before_global_declaration.py
vendored
Normal file
23
crates/ruff_linter/resources/test/fixtures/syntax_errors/load_before_global_declaration.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
x = 10
|
||||
def test_1():
|
||||
global x # ok
|
||||
x += 1
|
||||
|
||||
|
||||
x = 10
|
||||
def test_2():
|
||||
x += 1 # error
|
||||
global x
|
||||
|
||||
def test_3():
|
||||
print(x) # error
|
||||
global x
|
||||
x = 5
|
||||
|
||||
def test_4():
|
||||
global x
|
||||
print(x)
|
||||
x = 1
|
||||
|
||||
x = 0
|
||||
test_4()
|
||||
@@ -615,8 +615,9 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
| SemanticSyntaxErrorKind::DuplicateMatchKey(_)
|
||||
| SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_)
|
||||
| SemanticSyntaxErrorKind::InvalidStarExpression
|
||||
| SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(_)
|
||||
| SemanticSyntaxErrorKind::DuplicateParameter(_) => {
|
||||
| SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_)
|
||||
| SemanticSyntaxErrorKind::DuplicateParameter(_)
|
||||
| SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
|
||||
if self.settings.preview.is_enabled() {
|
||||
self.semantic_errors.borrow_mut().push(error);
|
||||
}
|
||||
|
||||
@@ -1022,6 +1022,7 @@ mod tests {
|
||||
",
|
||||
PythonVersion::PY310
|
||||
)]
|
||||
#[test_case("false_positive", "[x async for x in y]", PythonVersion::PY310)]
|
||||
fn test_async_comprehension_in_sync_comprehension(
|
||||
name: &str,
|
||||
contents: &str,
|
||||
@@ -1063,6 +1064,10 @@ mod tests {
|
||||
|
||||
#[test_case(Rule::YieldOutsideFunction, Path::new("yield_scope.py"))]
|
||||
#[test_case(Rule::ReturnOutsideFunction, Path::new("return_outside_function.py"))]
|
||||
#[test_case(
|
||||
Rule::LoadBeforeGlobalDeclaration,
|
||||
Path::new("load_before_global_declaration.py")
|
||||
)]
|
||||
fn test_syntax_errors(rule: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = path.to_string_lossy().to_string();
|
||||
let path = Path::new("resources/test/fixtures/syntax_errors").join(path);
|
||||
|
||||
@@ -194,9 +194,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
|
||||
let replacement = match qualified_name.segments() {
|
||||
// airflow.datasets.metadata
|
||||
["airflow", "datasets", "metadata", "Metadata"] => {
|
||||
Replacement::Name("airflow.sdk.Metadata")
|
||||
}
|
||||
["airflow", "datasets", "metadata", "Metadata"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "Metadata",
|
||||
},
|
||||
// airflow.datasets
|
||||
["airflow", "Dataset"] | ["airflow", "datasets", "Dataset"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
@@ -204,10 +205,22 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
},
|
||||
["airflow", "datasets", rest] => match *rest {
|
||||
"DatasetAliasEvent" => Replacement::None,
|
||||
"DatasetAlias" => Replacement::Name("airflow.sdk.AssetAlias"),
|
||||
"DatasetAll" => Replacement::Name("airflow.sdk.AssetAll"),
|
||||
"DatasetAny" => Replacement::Name("airflow.sdk.AssetAny"),
|
||||
"expand_alias_to_datasets" => Replacement::Name("airflow.sdk.expand_alias_to_assets"),
|
||||
"DatasetAlias" => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "AssetAlias",
|
||||
},
|
||||
"DatasetAll" => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "AssetAll",
|
||||
},
|
||||
"DatasetAny" => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "AssetAny",
|
||||
},
|
||||
"expand_alias_to_datasets" => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "expand_alias_to_assets",
|
||||
},
|
||||
_ => return,
|
||||
},
|
||||
|
||||
@@ -235,9 +248,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.sdk",
|
||||
name: (*rest).to_string(),
|
||||
},
|
||||
"BaseOperatorLink" => {
|
||||
Replacement::Name("airflow.sdk.definitions.baseoperatorlink.BaseOperatorLink")
|
||||
}
|
||||
"BaseOperatorLink" => Replacement::AutoImport {
|
||||
module: "airflow.sdk.definitions.baseoperatorlink",
|
||||
name: "BaseOperatorLink",
|
||||
},
|
||||
_ => return,
|
||||
},
|
||||
// airflow.model..DAG
|
||||
@@ -246,12 +260,16 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
name: "DAG".to_string(),
|
||||
},
|
||||
// airflow.timetables
|
||||
["airflow", "timetables", "datasets", "DatasetOrTimeSchedule"] => {
|
||||
Replacement::Name("airflow.timetables.assets.AssetOrTimeSchedule")
|
||||
}
|
||||
["airflow", "timetables", "datasets", "DatasetOrTimeSchedule"] => Replacement::AutoImport {
|
||||
module: "airflow.timetables.assets",
|
||||
name: "AssetOrTimeSchedule",
|
||||
},
|
||||
// airflow.utils
|
||||
["airflow", "utils", "dag_parsing_context", "get_parsing_context"] => {
|
||||
Replacement::Name("airflow.sdk.get_parsing_context")
|
||||
Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "get_parsing_context",
|
||||
}
|
||||
}
|
||||
|
||||
_ => return,
|
||||
|
||||
@@ -50,7 +50,7 @@ AIR311_names.py:26:1: AIR311 [*] `airflow.datasets.Dataset` is removed in Airflo
|
||||
28 29 | DatasetAll()
|
||||
29 30 | DatasetAny()
|
||||
|
||||
AIR311_names.py:27:1: AIR311 `airflow.datasets.DatasetAlias` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:27:1: AIR311 [*] `airflow.datasets.DatasetAlias` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
25 | # airflow.datasets
|
||||
26 | Dataset()
|
||||
@@ -61,7 +61,24 @@ AIR311_names.py:27:1: AIR311 `airflow.datasets.DatasetAlias` is removed in Airfl
|
||||
|
|
||||
= help: Use `airflow.sdk.AssetAlias` instead
|
||||
|
||||
AIR311_names.py:28:1: AIR311 `airflow.datasets.DatasetAll` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.sdk import AssetAlias
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
24 25 |
|
||||
25 26 | # airflow.datasets
|
||||
26 27 | Dataset()
|
||||
27 |-DatasetAlias()
|
||||
28 |+AssetAlias()
|
||||
28 29 | DatasetAll()
|
||||
29 30 | DatasetAny()
|
||||
30 31 | Metadata()
|
||||
|
||||
AIR311_names.py:28:1: AIR311 [*] `airflow.datasets.DatasetAll` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
26 | Dataset()
|
||||
27 | DatasetAlias()
|
||||
@@ -72,7 +89,25 @@ AIR311_names.py:28:1: AIR311 `airflow.datasets.DatasetAll` is removed in Airflow
|
||||
|
|
||||
= help: Use `airflow.sdk.AssetAll` instead
|
||||
|
||||
AIR311_names.py:29:1: AIR311 `airflow.datasets.DatasetAny` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.sdk import AssetAll
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
--------------------------------------------------------------------------------
|
||||
25 26 | # airflow.datasets
|
||||
26 27 | Dataset()
|
||||
27 28 | DatasetAlias()
|
||||
28 |-DatasetAll()
|
||||
29 |+AssetAll()
|
||||
29 30 | DatasetAny()
|
||||
30 31 | Metadata()
|
||||
31 32 | expand_alias_to_datasets()
|
||||
|
||||
AIR311_names.py:29:1: AIR311 [*] `airflow.datasets.DatasetAny` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
27 | DatasetAlias()
|
||||
28 | DatasetAll()
|
||||
@@ -83,6 +118,24 @@ AIR311_names.py:29:1: AIR311 `airflow.datasets.DatasetAny` is removed in Airflow
|
||||
|
|
||||
= help: Use `airflow.sdk.AssetAny` instead
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.sdk import AssetAny
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
--------------------------------------------------------------------------------
|
||||
26 27 | Dataset()
|
||||
27 28 | DatasetAlias()
|
||||
28 29 | DatasetAll()
|
||||
29 |-DatasetAny()
|
||||
30 |+AssetAny()
|
||||
30 31 | Metadata()
|
||||
31 32 | expand_alias_to_datasets()
|
||||
32 33 |
|
||||
|
||||
AIR311_names.py:30:1: AIR311 `airflow.datasets.metadata.Metadata` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
28 | DatasetAll()
|
||||
@@ -93,7 +146,7 @@ AIR311_names.py:30:1: AIR311 `airflow.datasets.metadata.Metadata` is removed in
|
||||
|
|
||||
= help: Use `airflow.sdk.Metadata` instead
|
||||
|
||||
AIR311_names.py:31:1: AIR311 `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:31:1: AIR311 [*] `airflow.datasets.expand_alias_to_datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
29 | DatasetAny()
|
||||
30 | Metadata()
|
||||
@@ -104,6 +157,24 @@ AIR311_names.py:31:1: AIR311 `airflow.datasets.expand_alias_to_datasets` is remo
|
||||
|
|
||||
= help: Use `airflow.sdk.expand_alias_to_assets` instead
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.sdk import expand_alias_to_assets
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
--------------------------------------------------------------------------------
|
||||
28 29 | DatasetAll()
|
||||
29 30 | DatasetAny()
|
||||
30 31 | Metadata()
|
||||
31 |-expand_alias_to_datasets()
|
||||
32 |+expand_alias_to_assets()
|
||||
32 33 |
|
||||
33 34 | # airflow.decorators
|
||||
34 35 | dag()
|
||||
|
||||
AIR311_names.py:34:1: AIR311 `airflow.decorators.dag` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
33 | # airflow.decorators
|
||||
@@ -228,7 +299,7 @@ AIR311_names.py:56:1: AIR311 `airflow.models.dag.DAG` is removed in Airflow 3.0;
|
||||
|
|
||||
= help: Use `airflow.sdk.DAG` instead
|
||||
|
||||
AIR311_names.py:58:1: AIR311 `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311_names.py:58:1: AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
56 | DAGFromDag()
|
||||
57 | # airflow.timetables.datasets
|
||||
@@ -239,6 +310,24 @@ AIR311_names.py:58:1: AIR311 `airflow.timetables.datasets.DatasetOrTimeSchedule`
|
||||
|
|
||||
= help: Use `airflow.timetables.assets.AssetOrTimeSchedule` instead
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 | from airflow.models.dag import DAG as DAGFromDag
|
||||
19 19 | from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
20 20 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
21 |+from airflow.timetables.assets import AssetOrTimeSchedule
|
||||
21 22 |
|
||||
22 23 | # airflow
|
||||
23 24 | DatasetFromRoot()
|
||||
--------------------------------------------------------------------------------
|
||||
55 56 | # airflow.models.dag
|
||||
56 57 | DAGFromDag()
|
||||
57 58 | # airflow.timetables.datasets
|
||||
58 |-DatasetOrTimeSchedule()
|
||||
59 |+AssetOrTimeSchedule()
|
||||
59 60 |
|
||||
60 61 | # airflow.utils.dag_parsing_context
|
||||
61 62 | get_parsing_context()
|
||||
|
||||
AIR311_names.py:61:1: AIR311 `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
|
||||
60 | # airflow.utils.dag_parsing_context
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:1:27: SyntaxError: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
||||
<filename>:1:27: SyntaxError: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
|
|
||||
1 | async def f(): return [[x async for x in foo(n)] for n in range(3)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
resources/test/fixtures/syntax_errors/async_comprehension.ipynb:3:5: SyntaxError: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
||||
resources/test/fixtures/syntax_errors/async_comprehension.ipynb:3:5: SyntaxError: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
|
|
||||
1 | async def elements(n): yield n
|
||||
2 | [x async for x in elements(5)] # okay, async at top level
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
resources/test/fixtures/syntax_errors/load_before_global_declaration.py:9:5: PLE0118 Name `x` is used prior to global declaration on line 10
|
||||
|
|
||||
7 | x = 10
|
||||
8 | def test_2():
|
||||
9 | x += 1 # error
|
||||
| ^ PLE0118
|
||||
10 | global x
|
||||
|
|
||||
|
||||
resources/test/fixtures/syntax_errors/load_before_global_declaration.py:13:11: PLE0118 Name `x` is used prior to global declaration on line 14
|
||||
|
|
||||
12 | def test_3():
|
||||
13 | print(x) # error
|
||||
| ^ PLE0118
|
||||
14 | global x
|
||||
15 | x = 5
|
||||
|
|
||||
@@ -0,0 +1,2 @@
|
||||
nonlocal x
|
||||
nonlocal x, y
|
||||
@@ -1 +1,2 @@
|
||||
nonlocal
|
||||
def _():
|
||||
nonlocal
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
nonlocal x + 1
|
||||
def _():
|
||||
nonlocal x + 1
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
nonlocal ,
|
||||
nonlocal x,
|
||||
nonlocal x, y,
|
||||
def _():
|
||||
nonlocal ,
|
||||
nonlocal x,
|
||||
nonlocal x, y,
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
def _():
|
||||
nonlocal x
|
||||
@@ -1,2 +1,3 @@
|
||||
nonlocal x
|
||||
nonlocal x, y, z
|
||||
def _():
|
||||
nonlocal x
|
||||
nonlocal x, y, z
|
||||
|
||||
@@ -897,12 +897,14 @@ impl<'src> Parser<'src> {
|
||||
self.bump(TokenKind::Nonlocal);
|
||||
|
||||
// test_err nonlocal_stmt_trailing_comma
|
||||
// nonlocal ,
|
||||
// nonlocal x,
|
||||
// nonlocal x, y,
|
||||
// def _():
|
||||
// nonlocal ,
|
||||
// nonlocal x,
|
||||
// nonlocal x, y,
|
||||
|
||||
// test_err nonlocal_stmt_expression
|
||||
// nonlocal x + 1
|
||||
// def _():
|
||||
// nonlocal x + 1
|
||||
let names = self.parse_comma_separated_list_into_vec(
|
||||
RecoveryContextKind::Identifiers,
|
||||
Parser::parse_identifier,
|
||||
@@ -910,7 +912,8 @@ impl<'src> Parser<'src> {
|
||||
|
||||
if names.is_empty() {
|
||||
// test_err nonlocal_stmt_empty
|
||||
// nonlocal
|
||||
// def _():
|
||||
// nonlocal
|
||||
self.add_error(
|
||||
ParseErrorType::EmptyNonlocalNames,
|
||||
self.current_token_range(),
|
||||
@@ -918,8 +921,9 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
|
||||
// test_ok nonlocal_stmt
|
||||
// nonlocal x
|
||||
// nonlocal x, y, z
|
||||
// def _():
|
||||
// nonlocal x
|
||||
// nonlocal x, y, z
|
||||
ast::StmtNonlocal {
|
||||
range: self.node_range(start),
|
||||
names,
|
||||
|
||||
@@ -142,6 +142,22 @@ impl SemanticSyntaxChecker {
|
||||
AwaitOutsideAsyncFunctionKind::AsyncWith,
|
||||
);
|
||||
}
|
||||
Stmt::Nonlocal(ast::StmtNonlocal { range, .. }) => {
|
||||
// test_ok nonlocal_declaration_at_module_level
|
||||
// def _():
|
||||
// nonlocal x
|
||||
|
||||
// test_err nonlocal_declaration_at_module_level
|
||||
// nonlocal x
|
||||
// nonlocal x, y
|
||||
if ctx.in_module_scope() {
|
||||
Self::add_error(
|
||||
ctx,
|
||||
SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel,
|
||||
*range,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -573,7 +589,7 @@ impl SemanticSyntaxChecker {
|
||||
elt, generators, ..
|
||||
}) => {
|
||||
Self::check_generator_expr(elt, generators, ctx);
|
||||
Self::async_comprehension_outside_async_function(ctx, generators);
|
||||
Self::async_comprehension_in_sync_comprehension(ctx, generators);
|
||||
for generator in generators.iter().filter(|g| g.is_async) {
|
||||
Self::await_outside_async_function(
|
||||
ctx,
|
||||
@@ -590,7 +606,7 @@ impl SemanticSyntaxChecker {
|
||||
}) => {
|
||||
Self::check_generator_expr(key, generators, ctx);
|
||||
Self::check_generator_expr(value, generators, ctx);
|
||||
Self::async_comprehension_outside_async_function(ctx, generators);
|
||||
Self::async_comprehension_in_sync_comprehension(ctx, generators);
|
||||
for generator in generators.iter().filter(|g| g.is_async) {
|
||||
Self::await_outside_async_function(
|
||||
ctx,
|
||||
@@ -801,7 +817,7 @@ impl SemanticSyntaxChecker {
|
||||
}
|
||||
}
|
||||
|
||||
fn async_comprehension_outside_async_function<Ctx: SemanticSyntaxContext>(
|
||||
fn async_comprehension_in_sync_comprehension<Ctx: SemanticSyntaxContext>(
|
||||
ctx: &Ctx,
|
||||
generators: &[ast::Comprehension],
|
||||
) {
|
||||
@@ -813,7 +829,7 @@ impl SemanticSyntaxChecker {
|
||||
if ctx.in_notebook() && ctx.in_module_scope() {
|
||||
return;
|
||||
}
|
||||
if ctx.in_async_context() && !ctx.in_sync_comprehension() {
|
||||
if !ctx.in_sync_comprehension() {
|
||||
return;
|
||||
}
|
||||
for generator in generators.iter().filter(|gen| gen.is_async) {
|
||||
@@ -845,7 +861,7 @@ impl SemanticSyntaxChecker {
|
||||
// async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
||||
Self::add_error(
|
||||
ctx,
|
||||
SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(python_version),
|
||||
SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(python_version),
|
||||
generator.range,
|
||||
);
|
||||
}
|
||||
@@ -914,11 +930,11 @@ impl Display for SemanticSyntaxError {
|
||||
SemanticSyntaxErrorKind::InvalidStarExpression => {
|
||||
f.write_str("can't use starred expression here")
|
||||
}
|
||||
SemanticSyntaxErrorKind::AsyncComprehensionOutsideAsyncFunction(python_version) => {
|
||||
SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(python_version) => {
|
||||
write!(
|
||||
f,
|
||||
"cannot use an asynchronous comprehension outside of an asynchronous \
|
||||
function on Python {python_version} (syntax was added in 3.11)",
|
||||
"cannot use an asynchronous comprehension inside of a synchronous comprehension \
|
||||
on Python {python_version} (syntax was added in 3.11)",
|
||||
)
|
||||
}
|
||||
SemanticSyntaxErrorKind::YieldOutsideFunction(kind) => {
|
||||
@@ -933,6 +949,9 @@ impl Display for SemanticSyntaxError {
|
||||
SemanticSyntaxErrorKind::DuplicateParameter(name) => {
|
||||
write!(f, r#"Duplicate parameter "{name}""#)
|
||||
}
|
||||
SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
|
||||
write!(f, "nonlocal declaration not allowed at module level")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1187,7 +1206,7 @@ pub enum SemanticSyntaxErrorKind {
|
||||
/// This was discussed in [BPO 33346] and fixed in Python 3.11.
|
||||
///
|
||||
/// [BPO 33346]: https://github.com/python/cpython/issues/77527
|
||||
AsyncComprehensionOutsideAsyncFunction(PythonVersion),
|
||||
AsyncComprehensionInSyncComprehension(PythonVersion),
|
||||
|
||||
/// Represents the use of `yield`, `yield from`, or `await` outside of a function scope.
|
||||
///
|
||||
@@ -1254,6 +1273,9 @@ pub enum SemanticSyntaxErrorKind {
|
||||
/// lambda x, x: ...
|
||||
/// ```
|
||||
DuplicateParameter(String),
|
||||
|
||||
/// Represents a nonlocal declaration at module level
|
||||
NonlocalDeclarationAtModuleLevel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
||||
@@ -538,7 +538,7 @@ impl SemanticSyntaxContext for SemanticSyntaxCheckerVisitor<'_> {
|
||||
}
|
||||
|
||||
fn in_module_scope(&self) -> bool {
|
||||
true
|
||||
self.scopes.len() == 1
|
||||
}
|
||||
|
||||
fn in_function_scope(&self) -> bool {
|
||||
|
||||
@@ -780,7 +780,7 @@ Module(
|
||||
|
|
||||
1 | # parse_options: {"target-version": "3.10"}
|
||||
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
||||
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
||||
|
|
||||
@@ -790,7 +790,7 @@ Module(
|
||||
1 | # parse_options: {"target-version": "3.10"}
|
||||
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
|
||||
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
||||
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
||||
|
|
||||
@@ -800,7 +800,7 @@ Module(
|
||||
2 | async def f(): return [[x async for x in foo(n)] for n in range(3)] # list
|
||||
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
||||
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
||||
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
||||
|
|
||||
@@ -810,7 +810,7 @@ Module(
|
||||
3 | async def g(): return [{x: 1 async for x in foo(n)} for n in range(3)] # dict
|
||||
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
||||
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
||||
|
|
||||
|
||||
@@ -819,5 +819,5 @@ Module(
|
||||
4 | async def h(): return [{x async for x in foo(n)} for n in range(3)] # set
|
||||
5 | async def i(): return [([y async for y in range(1)], [z for z in range(2)]) for x in range(5)]
|
||||
6 | async def j(): return [([y for y in range(1)], [z async for z in range(2)]) for x in range(5)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
|
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/nonlocal_declaration_at_module_level.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..25,
|
||||
body: [
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 0..10,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 9..10,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 11..24,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 20..21,
|
||||
},
|
||||
Identifier {
|
||||
id: Name("y"),
|
||||
range: 23..24,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
## Semantic Syntax Errors
|
||||
|
||||
|
|
||||
1 | nonlocal x
|
||||
| ^^^^^^^^^^ Syntax Error: nonlocal declaration not allowed at module level
|
||||
2 | nonlocal x, y
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | nonlocal x
|
||||
2 | nonlocal x, y
|
||||
| ^^^^^^^^^^^^^ Syntax Error: nonlocal declaration not allowed at module level
|
||||
|
|
||||
@@ -1,19 +1,41 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/nonlocal_stmt_empty.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..9,
|
||||
range: 0..22,
|
||||
body: [
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 0..8,
|
||||
names: [],
|
||||
FunctionDef(
|
||||
StmtFunctionDef {
|
||||
range: 0..21,
|
||||
is_async: false,
|
||||
decorator_list: [],
|
||||
name: Identifier {
|
||||
id: Name("_"),
|
||||
range: 4..5,
|
||||
},
|
||||
type_params: None,
|
||||
parameters: Parameters {
|
||||
range: 5..7,
|
||||
posonlyargs: [],
|
||||
args: [],
|
||||
vararg: None,
|
||||
kwonlyargs: [],
|
||||
kwarg: None,
|
||||
},
|
||||
returns: None,
|
||||
body: [
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 13..21,
|
||||
names: [],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -23,6 +45,7 @@ Module(
|
||||
## Errors
|
||||
|
||||
|
|
||||
1 | nonlocal
|
||||
| ^ Syntax Error: Nonlocal statement must have at least one name
|
||||
1 | def _():
|
||||
2 | nonlocal
|
||||
| ^ Syntax Error: Nonlocal statement must have at least one name
|
||||
|
|
||||
|
||||
@@ -1,45 +1,67 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/nonlocal_stmt_expression.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..15,
|
||||
range: 0..28,
|
||||
body: [
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 0..10,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 9..10,
|
||||
},
|
||||
FunctionDef(
|
||||
StmtFunctionDef {
|
||||
range: 0..27,
|
||||
is_async: false,
|
||||
decorator_list: [],
|
||||
name: Identifier {
|
||||
id: Name("_"),
|
||||
range: 4..5,
|
||||
},
|
||||
type_params: None,
|
||||
parameters: Parameters {
|
||||
range: 5..7,
|
||||
posonlyargs: [],
|
||||
args: [],
|
||||
vararg: None,
|
||||
kwonlyargs: [],
|
||||
kwarg: None,
|
||||
},
|
||||
returns: None,
|
||||
body: [
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 13..23,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 22..23,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 24..27,
|
||||
value: UnaryOp(
|
||||
ExprUnaryOp {
|
||||
range: 24..27,
|
||||
op: UAdd,
|
||||
operand: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 26..27,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
range: 11..14,
|
||||
value: UnaryOp(
|
||||
ExprUnaryOp {
|
||||
range: 11..14,
|
||||
op: UAdd,
|
||||
operand: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
range: 13..14,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
@@ -47,6 +69,7 @@ Module(
|
||||
## Errors
|
||||
|
||||
|
|
||||
1 | nonlocal x + 1
|
||||
| ^ Syntax Error: Simple statements must be separated by newlines or semicolons
|
||||
1 | def _():
|
||||
2 | nonlocal x + 1
|
||||
| ^ Syntax Error: Simple statements must be separated by newlines or semicolons
|
||||
|
|
||||
|
||||
@@ -1,44 +1,66 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/nonlocal_stmt_trailing_comma.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..38,
|
||||
range: 0..59,
|
||||
body: [
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 0..10,
|
||||
names: [],
|
||||
},
|
||||
),
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 11..22,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 20..21,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 23..37,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 32..33,
|
||||
},
|
||||
Identifier {
|
||||
id: Name("y"),
|
||||
range: 35..36,
|
||||
},
|
||||
FunctionDef(
|
||||
StmtFunctionDef {
|
||||
range: 0..58,
|
||||
is_async: false,
|
||||
decorator_list: [],
|
||||
name: Identifier {
|
||||
id: Name("_"),
|
||||
range: 4..5,
|
||||
},
|
||||
type_params: None,
|
||||
parameters: Parameters {
|
||||
range: 5..7,
|
||||
posonlyargs: [],
|
||||
args: [],
|
||||
vararg: None,
|
||||
kwonlyargs: [],
|
||||
kwarg: None,
|
||||
},
|
||||
returns: None,
|
||||
body: [
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 13..23,
|
||||
names: [],
|
||||
},
|
||||
),
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 28..39,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 37..38,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 44..58,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 53..54,
|
||||
},
|
||||
Identifier {
|
||||
id: Name("y"),
|
||||
range: 56..57,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
@@ -49,32 +71,35 @@ Module(
|
||||
## Errors
|
||||
|
||||
|
|
||||
1 | nonlocal ,
|
||||
| ^ Syntax Error: Expected an identifier
|
||||
2 | nonlocal x,
|
||||
3 | nonlocal x, y,
|
||||
1 | def _():
|
||||
2 | nonlocal ,
|
||||
| ^ Syntax Error: Expected an identifier
|
||||
3 | nonlocal x,
|
||||
4 | nonlocal x, y,
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | nonlocal ,
|
||||
| ^ Syntax Error: Nonlocal statement must have at least one name
|
||||
2 | nonlocal x,
|
||||
3 | nonlocal x, y,
|
||||
1 | def _():
|
||||
2 | nonlocal ,
|
||||
| ^ Syntax Error: Nonlocal statement must have at least one name
|
||||
3 | nonlocal x,
|
||||
4 | nonlocal x, y,
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | nonlocal ,
|
||||
2 | nonlocal x,
|
||||
| ^ Syntax Error: Trailing comma not allowed
|
||||
3 | nonlocal x, y,
|
||||
1 | def _():
|
||||
2 | nonlocal ,
|
||||
3 | nonlocal x,
|
||||
| ^ Syntax Error: Trailing comma not allowed
|
||||
4 | nonlocal x, y,
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | nonlocal ,
|
||||
2 | nonlocal x,
|
||||
3 | nonlocal x, y,
|
||||
| ^ Syntax Error: Trailing comma not allowed
|
||||
2 | nonlocal ,
|
||||
3 | nonlocal x,
|
||||
4 | nonlocal x, y,
|
||||
| ^ Syntax Error: Trailing comma not allowed
|
||||
|
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/ok/nonlocal_declaration_at_module_level.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..24,
|
||||
body: [
|
||||
FunctionDef(
|
||||
StmtFunctionDef {
|
||||
range: 0..23,
|
||||
is_async: false,
|
||||
decorator_list: [],
|
||||
name: Identifier {
|
||||
id: Name("_"),
|
||||
range: 4..5,
|
||||
},
|
||||
type_params: None,
|
||||
parameters: Parameters {
|
||||
range: 5..7,
|
||||
posonlyargs: [],
|
||||
args: [],
|
||||
vararg: None,
|
||||
kwonlyargs: [],
|
||||
kwarg: None,
|
||||
},
|
||||
returns: None,
|
||||
body: [
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 13..23,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 22..23,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
@@ -1,42 +1,64 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/ok/nonlocal_stmt.py
|
||||
snapshot_kind: text
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..28,
|
||||
range: 0..45,
|
||||
body: [
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 0..10,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 9..10,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 11..27,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 20..21,
|
||||
},
|
||||
Identifier {
|
||||
id: Name("y"),
|
||||
range: 23..24,
|
||||
},
|
||||
Identifier {
|
||||
id: Name("z"),
|
||||
range: 26..27,
|
||||
},
|
||||
FunctionDef(
|
||||
StmtFunctionDef {
|
||||
range: 0..44,
|
||||
is_async: false,
|
||||
decorator_list: [],
|
||||
name: Identifier {
|
||||
id: Name("_"),
|
||||
range: 4..5,
|
||||
},
|
||||
type_params: None,
|
||||
parameters: Parameters {
|
||||
range: 5..7,
|
||||
posonlyargs: [],
|
||||
args: [],
|
||||
vararg: None,
|
||||
kwonlyargs: [],
|
||||
kwarg: None,
|
||||
},
|
||||
returns: None,
|
||||
body: [
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 13..23,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 22..23,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
Nonlocal(
|
||||
StmtNonlocal {
|
||||
range: 28..44,
|
||||
names: [
|
||||
Identifier {
|
||||
id: Name("x"),
|
||||
range: 37..38,
|
||||
},
|
||||
Identifier {
|
||||
id: Name("y"),
|
||||
range: 40..41,
|
||||
},
|
||||
Identifier {
|
||||
id: Name("z"),
|
||||
range: 43..44,
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user