Skip to content

Commit 263cc42

Browse files
committed
Change the error message
1 parent a910afe commit 263cc42

File tree

7 files changed

+126
-30
lines changed

7 files changed

+126
-30
lines changed

mypy/checkexpr.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from mypy.maptype import map_instance_to_supertype
2222
from mypy.meet import is_overlapping_types, narrow_declared_type
2323
from mypy.message_registry import ErrorMessage
24-
from mypy.messages import MessageBuilder
24+
from mypy.messages import MessageBuilder, format_string_list
2525
from mypy.nodes import (
2626
ARG_NAMED,
2727
ARG_POS,
@@ -1355,24 +1355,21 @@ def check_callable_call(
13551355
# An Enum() call that failed SemanticAnalyzerPass2.check_enum_call().
13561356
return callee.ret_type, callee
13571357

1358+
type = callee.type_object() if callee.is_type_obj() else None
13581359
if (
1359-
callee.is_type_obj()
1360-
and callee.type_object().is_protocol
1360+
type is not None
1361+
and type.is_protocol
13611362
# Exception for Type[...]
13621363
and not callee.from_type_type
13631364
):
1364-
self.chk.fail(
1365-
message_registry.CANNOT_INSTANTIATE_PROTOCOL.format(callee.type_object().name),
1366-
context,
1367-
)
1365+
self.chk.fail(message_registry.CANNOT_INSTANTIATE_PROTOCOL.format(type.name), context)
13681366
elif (
1369-
callee.is_type_obj()
1370-
and callee.type_object().is_abstract
1367+
type is not None
1368+
and type.is_abstract
13711369
# Exception for Type[...]
13721370
and not callee.from_type_type
13731371
and not callee.type_object().fallback_to_any
13741372
):
1375-
type = callee.type_object()
13761373
# Determine whether the implicitly abstract attributes are functions with
13771374
# None-compatible return types.
13781375
abstract_attributes: dict[str, bool] = {}
@@ -1381,8 +1378,19 @@ def check_callable_call(
13811378
abstract_attributes[attr_name] = self.can_return_none(type, attr_name)
13821379
else:
13831380
abstract_attributes[attr_name] = False
1384-
self.msg.cannot_instantiate_abstract_class(
1385-
callee.type_object().name, abstract_attributes, context
1381+
self.msg.cannot_instantiate_abstract_class(type.name, abstract_attributes, context)
1382+
elif (
1383+
type is not None
1384+
and type.uninitialized_vars
1385+
# Exception for Type[...]
1386+
and not callee.from_type_type
1387+
and not callee.type_object().fallback_to_any
1388+
):
1389+
self.chk.fail(
1390+
message_registry.CLASS_HAS_UNINITIALIZED_VARS.format(
1391+
type.name, format_string_list([f'"{v}"' for v in type.uninitialized_vars])
1392+
),
1393+
context,
13861394
)
13871395

13881396
formal_to_actual = map_actuals_to_formals(

mypy/message_registry.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
152152
)
153153
NOT_CALLABLE: Final = "{} not callable"
154154
TYPE_MUST_BE_USED: Final = "Value of type {} must be used"
155+
CLASS_HAS_UNINITIALIZED_VARS: Final = 'Class "{}" has annotated but unset attributes: {}'
155156

156157
# Generic
157158
GENERIC_INSTANCE_VAR_CLASS_ACCESS: Final = (

mypy/nodes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2654,6 +2654,7 @@ class is generic then it will be a type constructor of higher kind.
26542654
"is_protocol",
26552655
"runtime_protocol",
26562656
"abstract_attributes",
2657+
"uninitialized_vars",
26572658
"deletable_attributes",
26582659
"slots",
26592660
"assuming",
@@ -2703,6 +2704,8 @@ class is generic then it will be a type constructor of higher kind.
27032704
# List of names of abstract attributes together with their abstract status.
27042705
# The abstract status must be one of `NOT_ABSTRACT`, `IS_ABSTRACT`, `IMPLICITLY_ABSTRACT`.
27052706
abstract_attributes: list[tuple[str, int]]
2707+
# List of names of variables (instance vars and class vars) that are not initialized.
2708+
uninitialized_vars: list[str]
27062709
deletable_attributes: list[str] # Used by mypyc only
27072710
# Does this type have concrete `__slots__` defined?
27082711
# If class does not have `__slots__` defined then it is `None`,
@@ -2846,6 +2849,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None
28462849
self.metaclass_type = None
28472850
self.is_abstract = False
28482851
self.abstract_attributes = []
2852+
self.uninitialized_vars = []
28492853
self.deletable_attributes = []
28502854
self.slots = None
28512855
self.assuming = []
@@ -3066,6 +3070,7 @@ def serialize(self) -> JsonDict:
30663070
"names": self.names.serialize(self.fullname),
30673071
"defn": self.defn.serialize(),
30683072
"abstract_attributes": self.abstract_attributes,
3073+
"uninitialized_vars": self.uninitialized_vars,
30693074
"type_vars": self.type_vars,
30703075
"has_param_spec_type": self.has_param_spec_type,
30713076
"bases": [b.serialize() for b in self.bases],
@@ -3097,6 +3102,7 @@ def deserialize(cls, data: JsonDict) -> TypeInfo:
30973102
ti._fullname = data["fullname"]
30983103
# TODO: Is there a reason to reconstruct ti.subtypes?
30993104
ti.abstract_attributes = [(attr[0], attr[1]) for attr in data["abstract_attributes"]]
3105+
ti.uninitialized_vars = data["uninitialized_vars"]
31003106
ti.type_vars = data["type_vars"]
31013107
ti.has_param_spec_type = data["has_param_spec_type"]
31023108
ti.bases = [mypy.types.Instance.deserialize(b) for b in data["bases"]]

mypy/semanal_classprop.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
}
4040

4141

42-
def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: Errors) -> None:
42+
def calculate_class_abstract_status(
43+
typ: TypeInfo, is_stub_file: bool, errors: Errors, options: Options
44+
) -> None:
4345
"""Calculate abstract status of a class.
4446
4547
Set is_abstract of the type to True if the type has an unimplemented
@@ -52,6 +54,7 @@ def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: E
5254
# List of abstract attributes together with their abstract status
5355
abstract: list[tuple[str, int]] = []
5456
abstract_in_this_class: list[str] = []
57+
uninitialized_vars: set[str] = set()
5558
if typ.is_newtype:
5659
# Special case: NewTypes are considered as always non-abstract, so they can be used as:
5760
# Config = NewType('Config', Mapping[str, str])
@@ -85,15 +88,29 @@ def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: E
8588
abstract_in_this_class.append(name)
8689
elif isinstance(node, Var):
8790
if node.is_abstract_var and name not in concrete:
88-
typ.is_abstract = True
89-
abstract.append((name, IS_ABSTRACT))
90-
if base is typ:
91-
abstract_in_this_class.append(name)
91+
# If this abstract variable comes from a protocol, then `typ`
92+
# is abstract.
93+
if base.is_protocol:
94+
typ.is_abstract = True
95+
abstract.append((name, IS_ABSTRACT))
96+
if base is typ:
97+
abstract_in_this_class.append(name)
98+
elif options.warn_uninitialized_attributes:
99+
uninitialized_vars.add(name)
100+
elif (
101+
options.warn_uninitialized_attributes
102+
and not node.is_abstract_var
103+
and name in uninitialized_vars
104+
):
105+
# A variable can also be initialized in a parent class.
106+
uninitialized_vars.remove(name)
92107
concrete.add(name)
108+
typ.abstract_attributes = sorted(abstract)
109+
if uninitialized_vars:
110+
typ.uninitialized_vars = sorted(list(uninitialized_vars))
93111
# In stubs, abstract classes need to be explicitly marked because it is too
94112
# easy to accidentally leave a concrete class abstract by forgetting to
95113
# implement some methods.
96-
typ.abstract_attributes = sorted(abstract)
97114
if is_stub_file:
98115
if typ.declared_metaclass and typ.declared_metaclass.type.has_base("abc.ABCMeta"):
99116
return

mypy/semanal_main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,9 @@ def calculate_class_properties(graph: Graph, scc: list[str], errors: Errors) ->
472472
for _, node, _ in tree.local_definitions():
473473
if isinstance(node.node, TypeInfo):
474474
with state.manager.semantic_analyzer.file_context(tree, state.options, node.node):
475-
calculate_class_abstract_status(node.node, tree.is_stub, errors)
475+
calculate_class_abstract_status(
476+
node.node, tree.is_stub, errors, graph[module].options
477+
)
476478
check_protocol_status(node.node, errors)
477479
calculate_class_vars(node.node)
478480
add_type_promotion(

test-data/unit/check-protocols.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3806,3 +3806,21 @@ a = A()
38063806
reveal_type(a.s) # N: Revealed type is "builtins.str"
38073807
reveal_type(a.l) # N: Revealed type is "builtins.list[builtins.str]"
38083808
reveal_type(a.l2) # N: Revealed type is "builtins.list[builtins.str]"
3809+
3810+
[case testClassAssignment]
3811+
# flags: --warn-uninitialized-attributes
3812+
from typing import ClassVar, Protocol
3813+
3814+
class P(Protocol):
3815+
c: ClassVar[int]
3816+
c = 2
3817+
class A(P): ...
3818+
A()
3819+
class B: ...
3820+
b: P = B() # E: Incompatible types in assignment (expression has type "B", variable has type "P")
3821+
class P2(Protocol):
3822+
c: ClassVar[int] = 2
3823+
class A2(P2): ...
3824+
A2()
3825+
class B2: ...
3826+
b2: P2 = B2() # E: Incompatible types in assignment (expression has type "B2", variable has type "P2")

test-data/unit/check-warnings.test

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,13 @@ class A:
239239
# flags: --warn-uninitialized-attributes
240240
class A:
241241
x: int
242-
a = A() # E: Cannot instantiate abstract class "A" with abstract attribute "x"
242+
a = A() # E: Class "A" has annotated but unset attributes: "x"
243243
class B(A):
244244
def __init__(self) -> None:
245245
self.x = 10
246246
B() # OK
247247
class C(A): ...
248-
C() # E: Cannot instantiate abstract class "C" with abstract attribute "x"
248+
C() # E: Class "C" has annotated but unset attributes: "x"
249249
class D(A):
250250
def f(self) -> None:
251251
self.x = "foo" # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
@@ -273,9 +273,9 @@ from abc import ABC
273273

274274
class A(ABC):
275275
x: str
276-
A() # E: Cannot instantiate abstract class "A" with abstract attribute "x"
276+
A() # E: Class "A" has annotated but unset attributes: "x"
277277
class B(A): ...
278-
B() # E: Cannot instantiate abstract class "B" with abstract attribute "x"
278+
B() # E: Class "B" has annotated but unset attributes: "x"
279279
class C(A):
280280
def f(self) -> None:
281281
self.x = "foo"
@@ -287,11 +287,11 @@ class A:
287287
x: int
288288
y: str = "foo"
289289
z: bool
290-
A() # E: Cannot instantiate abstract class "A" with abstract attributes "x" and "z"
290+
A() # E: Class "A" has annotated but unset attributes: "x" and "z"
291291
class B(A):
292292
def __init__(self) -> None:
293293
self.x = 3
294-
B() # E: Cannot instantiate abstract class "B" with abstract attribute "z"
294+
B() # E: Class "B" has annotated but unset attributes: "z"
295295
class C(B):
296296
def __init__(self) -> None:
297297
self.z = True
@@ -310,7 +310,7 @@ class A:
310310
@classmethod
311311
def f(cls) -> None:
312312
cls.z = True
313-
A(3, "foo") # E: Cannot instantiate abstract class "A" with abstract attribute "z"
313+
A(3, "foo") # E: Class "A" has annotated but unset attributes: "z"
314314
reveal_type(A.z) # N: Revealed type is "builtins.bool"
315315
@dataclass
316316
class A2(A):
@@ -323,7 +323,7 @@ class B:
323323

324324
@dataclass
325325
class C(B): ...
326-
C() # E: Cannot instantiate abstract class "C" with abstract attributes "x" and "y"
326+
C() # E: Class "C" has annotated but unset attributes: "x" and "y"
327327

328328
@dataclass
329329
class D(B):
@@ -341,15 +341,15 @@ from typing import ClassVar
341341
class A:
342342
x: int
343343
z: ClassVar[bool]
344-
A(3) # E: Cannot instantiate abstract class "A" with abstract attribute "z"
344+
A(3) # E: Class "A" has annotated but unset attributes: "z"
345345

346346
class B:
347347
x: int
348348
y: str
349349

350350
@attr.define
351351
class C(B): ...
352-
C() # E: Cannot instantiate abstract class "C" with abstract attributes "x" and "y"
352+
C() # E: Class "C" has annotated but unset attributes: "x" and "y"
353353

354354
@attr.define
355355
class D(B):
@@ -400,14 +400,15 @@ if TYPE_CHECKING:
400400
class C(B): ...
401401
else:
402402
class C: ...
403-
C() # E: Cannot instantiate abstract class "C" with abstract attribute "x"
403+
C() # E: Class "C" has annotated but unset attributes: "x"
404404
[file stub.pyi]
405405
class A:
406406
x: int
407407

408408
[case testUninitializedAttributeClassVar]
409409
# flags: --warn-uninitialized-attributes
410410
from typing import ClassVar
411+
from typing_extensions import Protocol
411412
class A:
412413
CONST: int
413414
CONST = 4
@@ -417,3 +418,46 @@ class B:
417418
CONST: ClassVar[int]
418419
CONST = 4
419420
B()
421+
422+
class C(Protocol):
423+
CONST: ClassVar[int]
424+
CONST = 4
425+
class D(C): ...
426+
D()
427+
[builtins fixtures/tuple.pyi]
428+
429+
[case testUninitializedAttributeSuperClass]
430+
# flags: --warn-uninitialized-attributes
431+
class A:
432+
def __init__(self, x: int):
433+
self.x = x
434+
435+
class B(A):
436+
x: int
437+
def __init__(self, x: int):
438+
super().__init__(x)
439+
440+
b = B(0)
441+
reveal_type(b.x) # N: Revealed type is "builtins.int"
442+
443+
class C:
444+
x: int
445+
class D(C):
446+
x: int
447+
D() # E: Class "D" has annotated but unset attributes: "x"
448+
449+
class E:
450+
a: int
451+
def __init__(self, a: int) -> None:
452+
self.a = a
453+
454+
class F(E):
455+
a: int
456+
b: int
457+
def __init__(self, a: int, b: int) -> None:
458+
super().__init__(a)
459+
# or `self.a = a`
460+
self.b = b
461+
462+
E(0) # ok
463+
F(1, 1) # ok

0 commit comments

Comments
 (0)