Skip to content
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