Compare commits

...

11 Commits

Author SHA1 Message Date
Carl Meyer
872ad950aa [red-knot] remove fixpoint handling from inheritance_cycle query 2025-04-24 19:00:44 -07:00
Carl Meyer
59dedb5d3c [red-knot] remove fixpoint handling from try_mro query 2025-04-24 18:39:43 -07:00
Carl Meyer
46a1fd3b3e [red-knot] fix detecting a metaclass on a not-explicitly-specialized generic base 2025-04-24 18:37:48 -07:00
Carl Meyer
327b913d68 [red-knot] fix inheritance-cycle detection for generic classes 2025-04-24 17:51:41 -07:00
Carl Meyer
afc18ff1a1 [red-knot] change TypeVarInstance to be interned, not tracked (#17616)
## Summary

Tracked structs have some issues with fixpoint iteration in Salsa, and
there's not actually any need for this to be tracked, it should be
interned like most of our type structs.

The removed comment was probably never correct (in that we could have
disambiguated sufficiently), and is definitely not relevant now that
`TypeVarInstance` also holds its `Definition`.

## Test Plan

Existing tests.
2025-04-24 14:52:25 -07:00
Dhruv Manilawala
f1a539dac6 [red-knot] Special case @final, @override (#17608)
## Summary

This PR adds special-casing for `@final` and `@override` decorator for a
similar reason as https://github.com/astral-sh/ruff/pull/17591 to
support the invalid overload check.

Both `final` and `override` are identity functions which can be removed
once `TypeVar` support is added.
2025-04-25 03:15:23 +05:30
Carl Meyer
ef0343189c [red-knot] add TODO comment in specialization code (#17615)
## Summary

As promised, this just adds a TODO comment to document something we
discussed today that should probably be improved at some point, but
isn't a priority right now (since it's an issue that in practice would
only affect generic classes with both `__init__` and `__new__` methods,
where some typevar is bound to `Unknown` in one and to some other type
in another.)
2025-04-24 14:41:19 -07:00
Vasco Schiavo
4eecc40110 [semantic-syntax-errors] test for LoadBeforeGlobalDeclaration - ruff linter (#17592)
Hey @ntBre 

just one easy case to see if I understood the issue #17526 

Let me know if is this what you had in mind.
2025-04-24 16:14:33 -04:00
Abhijeet Prasad Bodas
cf59cee928 [syntax-errors] nonlocal declaration at module level (#17559)
## Summary

Part of #17412

Add a new compile-time syntax error for detecting `nonlocal`
declarations at a module level.

## Test Plan

- Added new inline tests for the syntax error
- Updated existing tests for `nonlocal` statement parsing to be inside a
function scope

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-04-24 16:11:46 -04:00
Wei Lee
538393d1f3 [airflow] Apply auto fix to cases where name has been changed in Airflow 3 (AIR311) (#17571)
## Summary

Apply auto fix to cases where the name has been changed in Airflow 3
(`AIR311`)

## Test Plan

The test features has been updated
2025-04-24 15:48:54 -04:00
Brent Westbrook
92ecfc908b [syntax-errors] Make async-comprehension-in-sync-comprehension more specific (#17460)
## Summary

While adding semantic error support to red-knot, I noticed duplicate
diagnostics for code like this:

```py
# error: [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.9 (syntax was added in 3.11)"
# error: [invalid-syntax] "`asynchronous comprehension` outside of an asynchronous function"
 [reveal_type(x) async for x in AsyncIterable()]
```

Beyond the duplication, the first error message doesn't make much sense
because this syntax is _not_ allowed on Python 3.11 either.

To fix this, this PR renames the
`async-comprehension-outside-async-function` semantic syntax error to
`async-comprehension-in-sync-comprehension` and fixes the rule to avoid
applying outside of sync comprehensions at all.

## Test Plan

New linter test demonstrating the false positive. The mdtests from my red-knot 
PR also reflect this change.
2025-04-24 15:45:54 -04:00
37 changed files with 668 additions and 243 deletions

View File

@@ -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)}
```

View File

@@ -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

View File

@@ -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

View File

@@ -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)]
|

View File

@@ -1,3 +1,4 @@
Expression # cycle panic (signature_)
Tanjun # cycle panic (signature_)
aiohttp # missing expression ID
alerta # missing expression ID

View File

@@ -1,5 +1,4 @@
AutoSplit
Expression
PyGithub
PyWinCtl
SinbadCogs

View File

@@ -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

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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()

View File

@@ -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;
}
_ => {}
}
}

View 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()

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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

View File

@@ -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)]
| ^^^^^^^^^^^^^^^^^^^^^

View File

@@ -0,0 +1,3 @@
---
source: crates/ruff_linter/src/linter.rs
---

View File

@@ -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

View File

@@ -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
|

View File

@@ -0,0 +1,2 @@
nonlocal x
nonlocal x, y

View File

@@ -1 +1,2 @@
nonlocal
def _():
nonlocal

View File

@@ -1 +1,2 @@
nonlocal x + 1
def _():
nonlocal x + 1

View File

@@ -1,3 +1,4 @@
nonlocal ,
nonlocal x,
nonlocal x, y,
def _():
nonlocal ,
nonlocal x,
nonlocal x, y,

View File

@@ -0,0 +1,2 @@
def _():
nonlocal x

View File

@@ -1,2 +1,3 @@
nonlocal x
nonlocal x, y, z
def _():
nonlocal x
nonlocal x, y, z

View File

@@ -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,

View File

@@ -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)]

View File

@@ -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 {

View File

@@ -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)
|

View File

@@ -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
|

View File

@@ -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
|

View File

@@ -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
|

View File

@@ -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
|

View File

@@ -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,
},
],
},
),
],
},
),
],
},
)
```

View File

@@ -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,
},
],
},
),
],
},
),