From 95cd2f1b5d65c9e72e929df41757fdc27a0ee914 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 17 Apr 2022 11:46:06 +0100 Subject: [PATCH 1/5] stubtest: emit error if a stub defines a public type alias that's missing at runtime --- mypy/stubtest.py | 34 ++++++++++++++++++++++++++-------- mypy/test/teststubtest.py | 12 ++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 582c467ee2b0..a8af4c02a7c3 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -55,11 +55,15 @@ def __init__( self, object_path: List[str], message: str, - stub_object: MaybeMissing[nodes.Node], + stub_object: MaybeMissing[Union[nodes.Node, mypy.types.ProperType]], runtime_object: MaybeMissing[Any], *, stub_desc: Optional[str] = None, - runtime_desc: Optional[str] = None + runtime_desc: Optional[str] = None, + # Extra field that's usually None, + # to help us get correct line numbers + # when reporting errors for type aliases. + typealias_node: Optional[nodes.TypeAlias] = None ) -> None: """Represents an error found by stubtest. @@ -76,8 +80,22 @@ def __init__( self.message = message self.stub_object = stub_object self.runtime_object = runtime_object - self.stub_desc = stub_desc or str(getattr(stub_object, "type", stub_object)) + if not stub_desc: + obj = getattr(stub_object, "type", stub_object) + if isinstance(obj, nodes.TypeInfo): + obj = obj.name # we get very verbose, not particularly helpful, messages otherwise + stub_desc = str(obj) + if typealias_node is not None: + stub_desc = 'Type alias for: ' + stub_desc + self.stub_desc = stub_desc self.runtime_desc = runtime_desc or str(runtime_object) + self.typealias_node = typealias_node + if isinstance(stub_object, Missing): + self.stub_line: Optional[int] = None + elif typealias_node is None: + self.stub_line = stub_object.line + else: + self.stub_line = typealias_node.line def is_missing_stub(self) -> bool: """Whether or not the error is for something missing from the stub.""" @@ -97,10 +115,8 @@ def get_description(self, concise: bool = False) -> str: if concise: return _style(self.object_desc, bold=True) + " " + self.message - stub_line = None + stub_line = self.stub_line stub_file: None = None - if not isinstance(self.stub_object, Missing): - stub_line = self.stub_object.line # TODO: Find a way of getting the stub file stub_loc_str = "" @@ -956,10 +972,12 @@ def verify_decorator( def verify_typealias( stub: nodes.TypeAlias, runtime: MaybeMissing[Any], object_path: List[str] ) -> Iterator[Error]: + stub_target = mypy.types.get_proper_type(stub.target) if isinstance(runtime, Missing): - # ignore type aliases that don't have a runtime counterpart + yield Error( + object_path, "is not present at runtime", stub_target, runtime, typealias_node=stub + ) return - stub_target = mypy.types.get_proper_type(stub.target) if isinstance(stub_target, mypy.types.Instance): yield from verify(stub_target.type, runtime, object_path) return diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 1cdcd34c666c..869d2e110a66 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -712,6 +712,18 @@ class Y: ... runtime="A = (int, str)", error="A", ) + # Error if an alias isn't present at runtime... + yield Case( + stub="B = str", + runtime="", + error="B" + ) + # ... but only if the alias isn't private + yield Case( + stub="_C = int", + runtime="", + error=None + ) @collect_cases def test_enum(self) -> Iterator[Case]: From 42b964833063209a0380d4157eb661bba8e3db07 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 17 Apr 2022 11:56:20 +0100 Subject: [PATCH 2/5] Use `fullname` instead of `name` --- mypy/stubtest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index a8af4c02a7c3..71cd3f3cd68a 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -83,7 +83,8 @@ def __init__( if not stub_desc: obj = getattr(stub_object, "type", stub_object) if isinstance(obj, nodes.TypeInfo): - obj = obj.name # we get very verbose, not particularly helpful, messages otherwise + # we get very verbose, not particularly helpful, messages otherwise + obj = obj.fullname stub_desc = str(obj) if typealias_node is not None: stub_desc = 'Type alias for: ' + stub_desc From 5f12a98afa7a652edb198543712051d7a2347b47 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 17 Apr 2022 21:40:30 -0700 Subject: [PATCH 3/5] undo some reporting changes --- mypy/stubtest.py | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 71cd3f3cd68a..2aeb28e51020 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -55,15 +55,11 @@ def __init__( self, object_path: List[str], message: str, - stub_object: MaybeMissing[Union[nodes.Node, mypy.types.ProperType]], + stub_object: MaybeMissing[nodes.Node], runtime_object: MaybeMissing[Any], *, stub_desc: Optional[str] = None, - runtime_desc: Optional[str] = None, - # Extra field that's usually None, - # to help us get correct line numbers - # when reporting errors for type aliases. - typealias_node: Optional[nodes.TypeAlias] = None + runtime_desc: Optional[str] = None ) -> None: """Represents an error found by stubtest. @@ -80,23 +76,8 @@ def __init__( self.message = message self.stub_object = stub_object self.runtime_object = runtime_object - if not stub_desc: - obj = getattr(stub_object, "type", stub_object) - if isinstance(obj, nodes.TypeInfo): - # we get very verbose, not particularly helpful, messages otherwise - obj = obj.fullname - stub_desc = str(obj) - if typealias_node is not None: - stub_desc = 'Type alias for: ' + stub_desc - self.stub_desc = stub_desc + self.stub_desc = stub_desc or str(getattr(stub_object, "type", stub_object)) self.runtime_desc = runtime_desc or str(runtime_object) - self.typealias_node = typealias_node - if isinstance(stub_object, Missing): - self.stub_line: Optional[int] = None - elif typealias_node is None: - self.stub_line = stub_object.line - else: - self.stub_line = typealias_node.line def is_missing_stub(self) -> bool: """Whether or not the error is for something missing from the stub.""" @@ -116,8 +97,10 @@ def get_description(self, concise: bool = False) -> str: if concise: return _style(self.object_desc, bold=True) + " " + self.message - stub_line = self.stub_line + stub_line = None stub_file: None = None + if not isinstance(self.stub_object, Missing): + stub_line = self.stub_object.line # TODO: Find a way of getting the stub file stub_loc_str = "" From b4ee9d1bdf34415a30b261fa1469817ec795e40c Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 17 Apr 2022 21:48:10 -0700 Subject: [PATCH 4/5] new reporting --- mypy/stubtest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 2aeb28e51020..9c7886583a90 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -959,7 +959,8 @@ def verify_typealias( stub_target = mypy.types.get_proper_type(stub.target) if isinstance(runtime, Missing): yield Error( - object_path, "is not present at runtime", stub_target, runtime, typealias_node=stub + object_path, "is not present at runtime", stub, runtime, + stub_desc=f"Type alias for {stub_target}" ) return if isinstance(stub_target, mypy.types.Instance): From 378d3d2b8ed8bca3aded24b5722e3171395b6a96 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 18 Apr 2022 11:51:27 -0700 Subject: [PATCH 5/5] Update mypy/stubtest.py --- mypy/stubtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 9c7886583a90..54dfa6196f9c 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -960,7 +960,7 @@ def verify_typealias( if isinstance(runtime, Missing): yield Error( object_path, "is not present at runtime", stub, runtime, - stub_desc=f"Type alias for {stub_target}" + stub_desc=f"Type alias for: {stub_target}" ) return if isinstance(stub_target, mypy.types.Instance):