From 0ab58152c6799910d3be3a2582b330d18fb86409 Mon Sep 17 00:00:00 2001 From: "Edward K. Ream" Date: Sat, 25 Feb 2023 05:46:30 -0600 Subject: [PATCH 1/7] Add minimal mypy annotations to eliminate all complaints. Add two necessary base-class methods --- rope/base/ast.py | 2 +- rope/base/fscommands.py | 11 ++++---- rope/base/oi/doa.py | 4 +-- rope/base/oi/runmod.py | 2 +- rope/base/oi/type_hinting/evaluate.py | 26 +++++++++---------- .../type_hinting/providers/numpydocstrings.py | 4 +-- rope/base/oi/type_hinting/utils.py | 9 ++++--- rope/base/prefs.py | 2 ++ rope/base/project.py | 10 ++++--- rope/base/pyobjects.py | 6 +++++ rope/contrib/autoimport/sqlite.py | 2 +- rope/refactor/extract.py | 3 ++- rope/refactor/importutils/importinfo.py | 5 +++- rope/refactor/move.py | 18 ++++++++----- 14 files changed, 65 insertions(+), 39 deletions(-) diff --git a/rope/base/ast.py b/rope/base/ast.py index 3aff82699..49362fbaf 100644 --- a/rope/base/ast.py +++ b/rope/base/ast.py @@ -5,7 +5,7 @@ from rope.base import fscommands try: - from ast import _const_node_type_names + from ast import _const_node_type_names # type:ignore except ImportError: # backported from stdlib `ast` assert sys.version_info < (3, 8) diff --git a/rope/base/fscommands.py b/rope/base/fscommands.py index b7960594a..84c0ab259 100644 --- a/rope/base/fscommands.py +++ b/rope/base/fscommands.py @@ -11,6 +11,7 @@ import shutil import subprocess import typing +from typing import Any FileContent = typing.NewType("FileContent", bytes) @@ -64,7 +65,7 @@ def read(self, path): class SubversionCommands: def __init__(self, *args): self.normal_actions = FileSystemCommands() - import pysvn + import pysvn # type:ignore self.client = pysvn.Client() @@ -113,10 +114,10 @@ def __init__(self, root): self.repo = self.hg.hg.repository(self.ui, root) - def _import_mercurial(self): - import mercurial.commands - import mercurial.hg - import mercurial.ui + def _import_mercurial(self) -> Any: + import mercurial.commands # type:ignore + import mercurial.hg # type:ignore + import mercurial.ui # type:ignore return mercurial diff --git a/rope/base/oi/doa.py b/rope/base/oi/doa.py index 57b68ce5c..82958ce3d 100644 --- a/rope/base/oi/doa.py +++ b/rope/base/oi/doa.py @@ -4,9 +4,9 @@ import hmac try: - import cPickle as pickle + import cPickle as pickle # type:ignore except ImportError: - import pickle + import pickle # type:ignore import marshal import os import socket diff --git a/rope/base/oi/runmod.py b/rope/base/oi/runmod.py index 4679ebb45..3bb7c1394 100644 --- a/rope/base/oi/runmod.py +++ b/rope/base/oi/runmod.py @@ -4,7 +4,7 @@ def __rope_start_everything(): import sys try: - import cPickle as pickle + import cPickle as pickle # type:ignore except ImportError: import pickle import base64 diff --git a/rope/base/oi/type_hinting/evaluate.py b/rope/base/oi/type_hinting/evaluate.py index e4910e7b7..acf04bdc4 100644 --- a/rope/base/oi/type_hinting/evaluate.py +++ b/rope/base/oi/type_hinting/evaluate.py @@ -69,7 +69,7 @@ class S(SymbolBase): s.lbp = max(bp, s.lbp) return s - @multi + @multi # type:ignore def infix(self, name, bp): symbol = self.symbol(name, bp) @@ -79,7 +79,7 @@ def led(self, left, parser): self.second = parser.expression(bp) return self - @multi + @multi # type:ignore def infix_r(self, name, bp): symbol = self.symbol(name, bp) @@ -101,8 +101,8 @@ def led(self, left, parser): self.third = parser.expression(symbol2.lbp + 0.1) return self - @multi - def prefix(self, name, bp): + @multi # type:ignore + def prefix(self, name, bp): # type:ignore symbol = self.symbol(name, bp) @method(symbol) @@ -110,7 +110,7 @@ def nud(self, parser): self.first = parser.expression(bp) return self - @multi + @multi # type:ignore def postfix(self, name, bp): symbol = self.symbol(name, bp) @@ -237,18 +237,18 @@ def bind(fn): symbol("(end)") -@method(symbol("(name)")) +@method(symbol("(name)")) # type:ignore def nud(self, parser): return self -@method(symbol("(name)")) +@method(symbol("(name)")) # type:ignore def evaluate(self, pyobject): return utils.resolve_type(self.value, pyobject) # Parametrized objects -@method(symbol("[")) +@method(symbol("[")) # type:ignore def led(self, left, parser): self.first = left self.second = [] @@ -264,7 +264,7 @@ def led(self, left, parser): return self -@method(symbol("[")) +@method(symbol("[")) # type:ignore def evaluate(self, pyobject): return utils.parametrize_type( self.first.evaluate(pyobject), *[i.evaluate(pyobject) for i in self.second] @@ -272,7 +272,7 @@ def evaluate(self, pyobject): # Anonymous Function Calls -@method(symbol("(")) +@method(symbol("(")) # type:ignore def nud(self, parser): self.second = [] if parser.token.name != ")": @@ -288,7 +288,7 @@ def nud(self, parser): # Function Calls -@method(symbol("(")) +@method(symbol("(")) # type:ignore def led(self, left, parser): self.first = left self.second = [] @@ -304,13 +304,13 @@ def led(self, left, parser): return self -@method(symbol("(")) +@method(symbol("(")) # type:ignore def evaluate(self, pyobject): # TODO: Implement me raise NotImplementedError -@method(symbol("or")) +@method(symbol("or")) # type:ignore @method(symbol("|")) def evaluate(self, pyobject): # TODO: Implement me diff --git a/rope/base/oi/type_hinting/providers/numpydocstrings.py b/rope/base/oi/type_hinting/providers/numpydocstrings.py index 14c6cc442..35788f797 100644 --- a/rope/base/oi/type_hinting/providers/numpydocstrings.py +++ b/rope/base/oi/type_hinting/providers/numpydocstrings.py @@ -9,7 +9,7 @@ from rope.base.oi.type_hinting.providers import docstrings try: - from numpydoc.docscrape import NumpyDocString + from numpydoc.docscrape import NumpyDocString # type:ignore except ImportError: NumpyDocString = None @@ -40,4 +40,4 @@ def __call__(self, docstring, param_name): if not NumpyDocString: - NumPyDocstringParamParser = _DummyParamParser + NumPyDocstringParamParser = _DummyParamParser # type:ignore diff --git a/rope/base/oi/type_hinting/utils.py b/rope/base/oi/type_hinting/utils.py index 73e6d0dea..23c52695e 100644 --- a/rope/base/oi/type_hinting/utils.py +++ b/rope/base/oi/type_hinting/utils.py @@ -1,11 +1,15 @@ +from __future__ import annotations import logging -from typing import Optional, Union +from typing import Optional, Union, TYPE_CHECKING import rope.base.utils as base_utils from rope.base import evaluate from rope.base.exceptions import AttributeNotFoundError from rope.base.pyobjects import PyClass, PyDefinedObject, PyFunction, PyObject +if TYPE_CHECKING: + PyObj = Union[PyDefinedObject, PyObject] + def get_super_func(pyfunc): if not isinstance(pyfunc.parent, PyClass): @@ -72,8 +76,7 @@ def get_mro(pyclass): return class_list -def resolve_type(type_name, pyobject): - # type: (str, Union[PyDefinedObject, PyObject]) -> Optional[PyDefinedObject, PyObject] +def resolve_type(type_name: str, pyobject: PyObj) -> Optional[PyObj]: """ Find proper type object from its name. """ diff --git a/rope/base/prefs.py b/rope/base/prefs.py index 25ee93b47..9d4fd4a43 100644 --- a/rope/base/prefs.py +++ b/rope/base/prefs.py @@ -1,3 +1,5 @@ +# mypy reports many problems. +# type: ignore """Rope preferences.""" from dataclasses import asdict, dataclass from textwrap import dedent diff --git a/rope/base/project.py b/rope/base/project.py index 81e78d2d1..067a74037 100644 --- a/rope/base/project.py +++ b/rope/base/project.py @@ -1,3 +1,4 @@ +from __future__ import annotations import contextlib import json import os @@ -10,13 +11,16 @@ import rope.base.resourceobserver as resourceobserver from rope.base import exceptions, history, pycore, taskhandle, utils from rope.base.exceptions import ModuleNotFoundError -from rope.base.prefs import Prefs, get_config + +# At present rope.base.prefs starts with `# type:ignore`. +# As a result, mypy knows nothing about Prefs and get_config. +from rope.base.prefs import Prefs, get_config # type:ignore from rope.base.resources import File, Folder, _ResourceMatcher try: - import cPickle as pickle + import cPickle as pickle # type:ignore except ImportError: - import pickle + import pickle # type:ignore class _Project: diff --git a/rope/base/pyobjects.py b/rope/base/pyobjects.py index 4c9fadf27..bb1226f92 100644 --- a/rope/base/pyobjects.py +++ b/rope/base/pyobjects.py @@ -19,6 +19,9 @@ def get_attribute(self, name): raise exceptions.AttributeNotFoundError("Attribute %s not found" % name) return self.get_attributes()[name] + def get_module(self): + return None + def get_type(self): return self.type @@ -225,6 +228,9 @@ def get_module(self): current_object = current_object.parent return current_object + def get_name(self): + return None + def get_doc(self) -> Optional[str]: if len(self.get_ast().body) > 0: expr = self.get_ast().body[0] diff --git a/rope/contrib/autoimport/sqlite.py b/rope/contrib/autoimport/sqlite.py index 2f9744f15..3b4e1a0c8 100644 --- a/rope/contrib/autoimport/sqlite.py +++ b/rope/contrib/autoimport/sqlite.py @@ -435,7 +435,7 @@ def filter_folders(folder: Path) -> bool: folders = self.project.get_python_path_folders() folder_paths = map(lambda folder: Path(folder.real_path), folders) - folder_paths = filter(filter_folders, folder_paths) + folder_paths = filter(filter_folders, folder_paths) # type:ignore return list(OrderedDict.fromkeys(folder_paths)) def _get_available_packages(self) -> List[Package]: diff --git a/rope/refactor/extract.py b/rope/refactor/extract.py index 0136b22cf..1f3e73f2a 100644 --- a/rope/refactor/extract.py +++ b/rope/refactor/extract.py @@ -1,4 +1,5 @@ import re +from typing import Dict from contextlib import contextmanager from itertools import chain @@ -34,7 +35,7 @@ # There are a few more helper functions and classes used by above # classes. class _ExtractRefactoring: - kind_prefixes = {} + kind_prefixes: Dict[str, str] = {} def __init__(self, project, resource, start_offset, end_offset, variable=False): self.project = project diff --git a/rope/refactor/importutils/importinfo.py b/rope/refactor/importutils/importinfo.py index 6c550402e..283513c4a 100644 --- a/rope/refactor/importutils/importinfo.py +++ b/rope/refactor/importutils/importinfo.py @@ -1,3 +1,6 @@ +from typing import List, Tuple + + class ImportStatement: """Represent an import in a module @@ -187,7 +190,7 @@ def is_star_import(self): class EmptyImport(ImportInfo): - names_and_aliases = [] + names_and_aliases: List[Tuple[str, str]] = [] def is_empty(self): return True diff --git a/rope/refactor/move.py b/rope/refactor/move.py index 2081dee02..5916f00af 100644 --- a/rope/refactor/move.py +++ b/rope/refactor/move.py @@ -7,7 +7,7 @@ from __future__ import annotations import typing -from typing import Union +from typing import Optional, List, Union from rope.base import ( codeanalyze, @@ -310,8 +310,8 @@ def _is_variable(self, pyname): def get_changes( self, - dest: Union[None, str, resources.Resource], - resources=None, + dest: Optional[Union[str, resources.Resource]], + resources: Optional[List[resources.File]] = None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Return the changes needed for this refactoring @@ -324,15 +324,21 @@ def get_changes( will be applied to all python files. """ + # To do (in another PR): Create a new var: dest_resource: resource.Reource + # Doing so should remove the type:ignore below. if isinstance(dest, str): dest = self.project.find_module(dest) if resources is None: resources = self.project.get_python_files() + # The previous guards protect against this mypy complaint: + # "Resource" has no attribute "has_child" if dest is None or not dest.exists(): raise exceptions.RefactoringError("Move destination does not exist.") - if dest.is_folder() and dest.has_child("__init__.py"): - dest = dest.get_child("__init__.py") - if dest.is_folder(): + if dest.is_folder() and dest.has_child("__init__.py"): # type:ignore + dest = dest.get_child("__init__.py") # type:ignore + # The previous guards protect against this mypy complaint: + # Item "None" of "Union[str, Resource, None]" has no attribute "is_folder" + if dest.is_folder(): # type:ignore raise exceptions.RefactoringError( "Move destination for non-modules should not be folders." ) From df8224adaf4d9d98675f62ee8e11719ce304e390 Mon Sep 17 00:00:00 2001 From: "Edward K. Ream" Date: Sat, 25 Feb 2023 05:55:43 -0600 Subject: [PATCH 2/7] Explain the suppression in rope.base.ast --- rope/base/ast.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rope/base/ast.py b/rope/base/ast.py index 49362fbaf..22f667395 100644 --- a/rope/base/ast.py +++ b/rope/base/ast.py @@ -5,6 +5,7 @@ from rope.base import fscommands try: + # mypy right complains: Module "ast" has no attribute "_const_node_type_names" from ast import _const_node_type_names # type:ignore except ImportError: # backported from stdlib `ast` From bcabc86bf6d3c298d7d3a956d869419ac1789567 Mon Sep 17 00:00:00 2001 From: "Edward K. Ream" Date: Sat, 25 Feb 2023 06:05:50 -0600 Subject: [PATCH 3/7] Remove unnecessary 'Any' annotation to reduce the diffs --- rope/base/fscommands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rope/base/fscommands.py b/rope/base/fscommands.py index 84c0ab259..00ba71c8a 100644 --- a/rope/base/fscommands.py +++ b/rope/base/fscommands.py @@ -11,7 +11,6 @@ import shutil import subprocess import typing -from typing import Any FileContent = typing.NewType("FileContent", bytes) @@ -114,7 +113,7 @@ def __init__(self, root): self.repo = self.hg.hg.repository(self.ui, root) - def _import_mercurial(self) -> Any: + def _import_mercurial(self): import mercurial.commands # type:ignore import mercurial.hg # type:ignore import mercurial.ui # type:ignore From 845786b665561a90247c287ea0cf138d0cdb9ef7 Mon Sep 17 00:00:00 2001 From: "Edward K. Ream" Date: Sat, 25 Feb 2023 07:09:53 -0600 Subject: [PATCH 4/7] Improve a comment --- rope/base/ast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rope/base/ast.py b/rope/base/ast.py index 22f667395..eeed6fe84 100644 --- a/rope/base/ast.py +++ b/rope/base/ast.py @@ -5,7 +5,7 @@ from rope.base import fscommands try: - # mypy right complains: Module "ast" has no attribute "_const_node_type_names" + # Suppress the mypy complaint: Module "ast" has no attribute "_const_node_type_names" from ast import _const_node_type_names # type:ignore except ImportError: # backported from stdlib `ast` From 7af5370bdde05b77150c26b7d0e5ddd2d109a779 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Thu, 2 Mar 2023 16:12:01 +1100 Subject: [PATCH 5/7] Inline PyObj Seems like an unnecessary mental indirection to alias this when it's just used twice in the same type definition. Maybe if there's more of this pair, then it might be worth it to define this alias. --- rope/base/oi/type_hinting/utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rope/base/oi/type_hinting/utils.py b/rope/base/oi/type_hinting/utils.py index 23c52695e..c998151a6 100644 --- a/rope/base/oi/type_hinting/utils.py +++ b/rope/base/oi/type_hinting/utils.py @@ -1,15 +1,13 @@ from __future__ import annotations + import logging -from typing import Optional, Union, TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Union import rope.base.utils as base_utils from rope.base import evaluate from rope.base.exceptions import AttributeNotFoundError from rope.base.pyobjects import PyClass, PyDefinedObject, PyFunction, PyObject -if TYPE_CHECKING: - PyObj = Union[PyDefinedObject, PyObject] - def get_super_func(pyfunc): if not isinstance(pyfunc.parent, PyClass): @@ -76,7 +74,10 @@ def get_mro(pyclass): return class_list -def resolve_type(type_name: str, pyobject: PyObj) -> Optional[PyObj]: +def resolve_type( + type_name: str, + pyobject: Union[PyDefinedObject, PyObject], +) -> Optional[Union[PyDefinedObject, PyObject]]: """ Find proper type object from its name. """ From bcc5cf889d6c6a27830fb3cb0a1886bc1dfc9151 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Thu, 2 Mar 2023 16:13:05 +1100 Subject: [PATCH 6/7] Add a comment on resolve_type() --- rope/base/oi/type_hinting/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rope/base/oi/type_hinting/utils.py b/rope/base/oi/type_hinting/utils.py index c998151a6..b0a7aff97 100644 --- a/rope/base/oi/type_hinting/utils.py +++ b/rope/base/oi/type_hinting/utils.py @@ -86,6 +86,9 @@ def resolve_type( logging.debug("Looking for %s", type_name) if "." not in type_name: try: + # XXX: this looks incorrect? It doesn't seem like it would work + # correctly if you have a type/class not defined in the + # module/global scope ret_type = ( pyobject.get_module().get_scope().get_name(type_name).get_object() ) From 0d13f873675216d374d8545460dca0553b57e4a8 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Thu, 2 Mar 2023 16:15:59 +1100 Subject: [PATCH 7/7] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 180586725..07152af9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - #650 Install pre-commit hooks on rope repository (@lieryan) - #655 Remove unused __init__() methods (@edreamleo, @lieryan) +- #674 Fix/supress all mypy complaints # Release 1.7.0