Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3325,7 +3325,11 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
# This should be safe as generally semantic analyzer is idempotent.
with self.allow_unbound_tvars_set():
s.rvalue.accept(self)

if self.is_class_scope() and not self.is_stub_file and not self.is_typeshed_stub_file:
for lvalue in s.lvalues:
if isinstance(lvalue, NameExpr) and lvalue.name == "__qualname__":
if not isinstance(s.rvalue, StrExpr):
self.fail('"__qualname__" must be str', s)
# The r.h.s. is now ready to be classified, first check if it is a special form:
special_form = False
# * type alias
Expand Down
6 changes: 6 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -9412,3 +9412,9 @@ from typ import NT
def f() -> NT:
return NT(x='')
[builtins fixtures/tuple.pyi]

[case test_qualname_must_be_str]
class X:
__qualname__ = 5
[out]
main:2: error: "__qualname__" must be str
Loading