diff --git a/CHANGELOG.md b/CHANGELOG.md
index c00e2c0a..cb92b7cf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ Features:
* introduce Y033 (always use annotations in stubs, rather than type comments).
* introduce Y034 (detect common errors where return types are hardcoded, but they
should use `TypeVar`s instead).
+* introduce Y035 (`__all__` in a stub has the same semantics as at runtime).
## 22.1.0
diff --git a/README.md b/README.md
index a1013383..02df231e 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,7 @@ currently emitted:
| Y032 | The second argument of an `__eq__` or `__ne__` method should usually be annotated with `object` rather than `Any`.
| Y033 | Do not use type comments (e.g. `x = ... # type: int`) in stubs, even if the stub supports Python 2. Always use annotations instead (e.g. `x: int`).
| Y034 | Y034 detects common errors where certain methods are annotated as having a fixed return type, despite returning `self` at runtime. Such methods should be annotated with `_typeshed.Self`.
This check looks for `__new__`, `__enter__` and `__aenter__` methods that return the class's name unparameterised. It also looks for `__iter__` methods that return `Iterator`, even if the class inherits directly from `Iterator`, and for `__aiter__` methods that return `AsyncIterator`, even if the class inherits directly from `AsyncIterator`. The check excludes methods decorated with `@overload` or `@abstractmethod`.
+| Y035 | `__all__` in a stub file should always have a value, as `__all__` in a `.pyi` file has identical semantics to `__all__` in a `.py` file. E.g. write `__all__ = ["foo", "bar"]` instead of `__all__: list[str]`.
Many error codes enforce modern conventions, and some cannot yet be used in
all cases:
diff --git a/pyi.py b/pyi.py
index df1ab41d..eec09464 100644
--- a/pyi.py
+++ b/pyi.py
@@ -660,6 +660,12 @@ def visit_Expr(self, node: ast.Expr) -> None:
self.generic_visit(node)
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
+ if _is_name(node.target, "__all__") and not self.in_class.active:
+ with self.string_literals_allowed.enabled():
+ self.generic_visit(node)
+ if node.value is None:
+ self.error(node, Y035)
+ return
self.generic_visit(node)
if _is_TypeAlias(node.annotation):
return
@@ -1258,3 +1264,4 @@ def parse_options(cls, optmanager, options, extra_args) -> None:
)
Y033 = 'Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int")'
Y034 = 'Y034 {methods} usually return "self" at runtime. Consider using "_typeshed.Self" in "{method_name}", e.g. "{suggested_syntax}"'
+Y035 = 'Y035 "__all__" in a stub file must have a value, as it has the same semantics as "__all__" at runtime.'
diff --git a/tests/__all__.pyi b/tests/__all__.pyi
new file mode 100644
index 00000000..a20a0162
--- /dev/null
+++ b/tests/__all__.pyi
@@ -0,0 +1,7 @@
+__all__: list[str] # Y035 "__all__" in a stub file must have a value, as it has the same semantics as "__all__" at runtime.
+__all__: list[str] = ["foo", "bar", "baz"]
+__all__ = ["foo", "bar", "baz"]
+
+foo: int = ...
+bar: str = ...
+baz: list[set[bytes]] = ...