From 8059a6d051b57e36094bb7a0fb454a0857568fb8 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Wed, 17 Mar 2021 00:45:32 -0400 Subject: [PATCH 01/39] Add gitignore entries for pyenv --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c1113eab637a..8e967284ecbf 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,8 @@ htmlcov bin/ lib/ include/ +.python-version +pyvenv.cfg .tox pip-wheel-metadata From b86c6c0d99e06b259e7e894c346e8896f9c5a753 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Wed, 17 Mar 2021 01:15:02 -0400 Subject: [PATCH 02/39] Add support for pulling configuration from pyproject.toml files --- docs/source/config_file.rst | 33 +- mypy/config_parser.py | 64 ++- mypy/defaults.py | 4 +- test-data/unit/check-custom-plugin.test | 733 ++++++++++++++++++++++++ test-data/unit/check-flags.test | 272 +++++++++ test-data/unit/check-incremental.test | 282 +++++++++ test-data/unit/check-modules-case.test | 55 ++ test-data/unit/check-modules.test | 90 +++ test-data/unit/check-newsemanal.test | 28 + test-data/unit/check-optional.test | 25 + test-data/unit/cmdline.test | 677 ++++++++++++++++++++++ test-data/unit/daemon.test | 61 ++ test-data/unit/diff.test | 39 ++ test-data/unit/pep561.test | 28 + test-data/unit/reports.test | 9 + 15 files changed, 2383 insertions(+), 17 deletions(-) diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 1c3607033afc..310078adabdc 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -4,8 +4,8 @@ The mypy configuration file =========================== Mypy supports reading configuration settings from a file. By default -it uses the file ``mypy.ini`` with a fallback to ``.mypy.ini``, then ``setup.cfg`` in -the current directory, then ``$XDG_CONFIG_HOME/mypy/config``, then +it uses the file ``mypy.ini`` with a fallback to ``.mypy.ini``, then ``pyproject.toml``, +then ``setup.cfg`` in the current directory, then ``$XDG_CONFIG_HOME/mypy/config``, then ``~/.config/mypy/config``, and finally ``.mypy.ini`` in the user home directory if none of them are found; the :option:`--config-file ` command-line flag can be used to read a different file instead (see :ref:`config-file-flag`). @@ -885,5 +885,34 @@ These options may only be set in the global section (``[mypy]``). Controls how much debug output will be generated. Higher numbers are more verbose. + +Using a pyproject.toml file +*************************** + +Instead of using a ``mypy.ini`` file, a ``pyproject.toml`` file (as specified by +`PEP 518`_) may be used instead. A few notes on doing so: + +* All ``[mypy]`` sections should have ``tool.`` prepended to their name: + + * ``[mypy]`` would become ``[tool.mypy]`` + + * For example, ``[mypy-packagename]`` would become ``[tool.mypy-packagename]`` + +* Any section with special characters (such as ``.`` or ``*``) will need to be wrapped in single quotes: + + * For example, ``[tool.mypy-package.*]`` would become ``[tool.'mypy-package.*']`` + +* The following care should be given to values in the ``pyproject.toml`` files as compared to ``ini`` files: + + * Strings must be wrapped in double quotes + + * Boolean values should be all lower case + +Please see the `TOML Documentation`_ for more details and information on +what is allowed in a ``toml`` file. See `PEP 518`_ for more information on the layout +and structure of the ``pyproject.toml`` file. + .. _lxml: https://pypi.org/project/lxml/ .. _SQLite: https://www.sqlite.org/ +.. _PEP 518: https://www.python.org/dev/peps/pep-0518/ +.. _TOML Documentation: https://toml.io/ diff --git a/mypy/config_parser.py b/mypy/config_parser.py index dd79869030e5..42a36a6ae367 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -6,14 +6,17 @@ import re import sys -from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, TextIO +import toml +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, TextIO, Tuple, Union from typing_extensions import Final from mypy import defaults from mypy.options import Options, PER_MODULE_OPTIONS -def parse_version(v: str) -> Tuple[int, int]: +def parse_version(v: Union[str, float]) -> Tuple[int, int]: + if isinstance(v, float): + v = str(v) m = re.match(r'\A(\d)\.(\d+)\Z', v) if not m: raise argparse.ArgumentTypeError( @@ -34,6 +37,13 @@ def parse_version(v: str) -> Tuple[int, int]: return major, minor +def try_split(v: Union[str, Sequence[str]]) -> List[str]: + if isinstance(v, str): + return [p.strip() for p in v.split(',')] + + return [p.strip() for p in v] + + def expand_path(path: str) -> str: """Expand the user home directory and any environment variables contained within the provided path. @@ -42,7 +52,7 @@ def expand_path(path: str) -> str: return os.path.expandvars(os.path.expanduser(path)) -def split_and_match_files(paths: str) -> List[str]: +def split_and_match_files(paths: Union[str, Sequence[str]]) -> List[str]: """Take a string representing a list of files/directories (with support for globbing through the glob library). @@ -52,7 +62,10 @@ def split_and_match_files(paths: str) -> List[str]: """ expanded_paths = [] - for path in paths.split(','): + if isinstance(paths, str): + paths = paths.split(',') + + for path in paths: path = expand_path(path.strip()) globbed_files = fileglob.glob(path, recursive=True) if globbed_files: @@ -91,12 +104,12 @@ def check_follow_imports(choice: str) -> str: 'almost_silent': bool, 'follow_imports': check_follow_imports, 'no_site_packages': bool, - 'plugins': lambda s: [p.strip() for p in s.split(',')], - 'always_true': lambda s: [p.strip() for p in s.split(',')], - 'always_false': lambda s: [p.strip() for p in s.split(',')], - 'disable_error_code': lambda s: [p.strip() for p in s.split(',')], - 'enable_error_code': lambda s: [p.strip() for p in s.split(',')], - 'package_root': lambda s: [p.strip() for p in s.split(',')], + 'plugins': try_split, + 'always_true': try_split, + 'always_false': try_split, + 'disable_error_code': try_split, + 'enable_error_code': try_split, + 'package_root': try_split, 'cache_dir': expand_path, 'python_executable': expand_path, 'strict': bool, @@ -121,14 +134,22 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], else: config_files = tuple(map(os.path.expanduser, defaults.CONFIG_FILES)) - parser = configparser.RawConfigParser() + config_parser = configparser.RawConfigParser() for config_file in config_files: if not os.path.exists(config_file): continue try: - parser.read(config_file) - except configparser.Error as err: + if config_file.endswith('.toml'): + toml_data = toml.load(config_file) + # Filter down to just mypy relevant toml keys + parser = {key: value + for key, value in toml_data.get('tool', {}).items() + if key.split('-')[0] == 'mypy'} + else: + config_parser.read(config_file) + parser = {key: value for key, value in config_parser.items()} + except (toml.TomlDecodeError, configparser.Error) as err: print("%s: %s" % (config_file, err), file=stderr) else: if config_file in defaults.SHARED_CONFIG_FILES and 'mypy' not in parser: @@ -240,7 +261,10 @@ def parse_section(prefix: str, template: Options, v = None # type: Any try: if ct is bool: - v = section.getboolean(key) # type: ignore[attr-defined] # Until better stub + if isinstance(section, dict): + v = convert_to_boolean(section.get(key)) + else: + v = section.getboolean(key) # type: ignore[attr-defined] # Until better stub if invert: v = not v elif callable(ct): @@ -281,6 +305,18 @@ def parse_section(prefix: str, template: Options, return results, report_dirs +def convert_to_boolean(value: Optional[Any]) -> bool: + """Return a boolean value translating from other types if necessary. + """ + if isinstance(value, bool): + return value + if not isinstance(value, str): + value = str(value) + if value.lower() not in configparser.RawConfigParser.BOOLEAN_STATES: + raise ValueError('Not a boolean: %s' % value) + return configparser.RawConfigParser.BOOLEAN_STATES[value.lower()] + + def split_directive(s: str) -> Tuple[List[str], List[str]]: """Split s on commas, except during quoted sections. diff --git a/mypy/defaults.py b/mypy/defaults.py index 9f1c10c02930..7e19bfe4a56f 100644 --- a/mypy/defaults.py +++ b/mypy/defaults.py @@ -7,12 +7,14 @@ PYTHON3_VERSION_MIN = (3, 4) # type: Final CACHE_DIR = '.mypy_cache' # type: Final CONFIG_FILE = ['mypy.ini', '.mypy.ini'] # type: Final +PYPROJECT_CONFIG_FILES = ['pyproject.toml', ] # type: Final SHARED_CONFIG_FILES = ['setup.cfg', ] # type: Final USER_CONFIG_FILES = ['~/.config/mypy/config', '~/.mypy.ini', ] # type: Final if os.environ.get('XDG_CONFIG_HOME'): USER_CONFIG_FILES.insert(0, os.path.join(os.environ['XDG_CONFIG_HOME'], 'mypy/config')) -CONFIG_FILES = CONFIG_FILE + SHARED_CONFIG_FILES + USER_CONFIG_FILES # type: Final +CONFIG_FILES = (CONFIG_FILE + PYPROJECT_CONFIG_FILES + SHARED_CONFIG_FILES + + USER_CONFIG_FILES) # type: Final # This must include all reporters defined in mypy.report. This is defined here # to make reporter names available without importing mypy.report -- this speeds diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index 645ab50db071..952dc17e3d26 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -11,6 +11,14 @@ reveal_type(f()) # N: Revealed type is 'builtins.int' \[mypy] plugins=/test-data/unit/plugins/fnplugin.py +[case testFunctionPluginFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f() -> str: ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/fnplugin.py" + [case testFunctionPlugin] # flags: --config-file tmp/mypy.ini def f() -> str: ... @@ -19,6 +27,14 @@ reveal_type(f()) # N: Revealed type is 'builtins.int' \[mypy] plugins=fnplugin +[case testFunctionPluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f() -> str: ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +[file pyproject.toml] +\[tool.mypy] +plugins = "fnplugin" + [case testFunctionPluginFullnameIsNotNone] # flags: --config-file tmp/mypy.ini from typing import Callable, TypeVar @@ -30,6 +46,17 @@ g(f)() \[mypy] plugins=/test-data/unit/plugins/fnplugin.py +[case testFunctionPluginFullnameIsNotNonePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Callable, TypeVar +f: Callable[[], None] +T = TypeVar('T') +def g(x: T) -> T: return x # This strips out the name of a callable +g(f)() +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/fnplugin.py" + [case testTwoPlugins] # flags: --config-file tmp/mypy.ini def f(): ... @@ -43,6 +70,21 @@ reveal_type(h()) # N: Revealed type is 'Any' plugins=/test-data/unit/plugins/fnplugin.py, /test-data/unit/plugins/plugin2.py +[case testTwoPluginsPyprojetTOML] +# flags: --config-file tmp/pyproject.toml +def f(): ... +def g(): ... +def h(): ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +reveal_type(g()) # N: Revealed type is 'builtins.str' +reveal_type(h()) # N: Revealed type is 'Any' +[file pyproject.toml] +\[tool.mypy] +plugins=[ + "/test-data/unit/plugins/fnplugin.py", + "/test-data/unit/plugins/plugin2.py" +] + [case testTwoPluginsMixedType] # flags: --config-file tmp/mypy.ini def f(): ... @@ -55,6 +97,21 @@ reveal_type(h()) # N: Revealed type is 'Any' \[mypy] plugins=/test-data/unit/plugins/fnplugin.py, plugin2 +[case testTwoPluginsMixedTypePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f(): ... +def g(): ... +def h(): ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +reveal_type(g()) # N: Revealed type is 'builtins.str' +reveal_type(h()) # N: Revealed type is 'Any' +[file pyproject.toml] +\[tool.mypy] +plugins = [ + "/test-data/unit/plugins/fnplugin.py", + "plugin2" +] + [case testMissingPluginFile] # flags: --config-file tmp/mypy.ini [file mypy.ini] @@ -64,6 +121,15 @@ plugins=missing.py tmp/mypy.ini:2: error: Can't find plugin 'tmp/missing.py' --' (work around syntax highlighting) +[case testMissingPluginFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +\[tool.mypy] +plugins = "missing.py" +[out] +tmp/pyproject.toml:1: error: Can't find plugin 'tmp/missing.py' +--' (work around syntax highlighting) + [case testMissingPlugin] # flags: --config-file tmp/mypy.ini [file mypy.ini] @@ -72,6 +138,14 @@ plugins=missing [out] tmp/mypy.ini:2: error: Error importing plugin 'missing': No module named 'missing' +[case testMissingPluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +\[tool.mypy] +plugins = "missing" +[out] +tmp/pyproject.toml:1: error: Error importing plugin 'missing': No module named 'missing' + [case testMultipleSectionsDefinePlugin] # flags: --config-file tmp/mypy.ini [file mypy.ini] @@ -85,6 +159,19 @@ plugins=another_plugin tmp/mypy.ini:4: error: Can't find plugin 'tmp/missing.py' --' (work around syntax highlighting) +[case testMultipleSectionsDefinePluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +\[acme] +plugins = "acmeplugin" +\[tool.mypy] +plugins = "missing.py" +\[another] +plugins = "another_plugin" +[out] +tmp/pyproject.toml:1: error: Can't find plugin 'tmp/missing.py' +--' (work around syntax highlighting) + [case testInvalidPluginExtension] # flags: --config-file tmp/mypy.ini [file mypy.ini] @@ -94,6 +181,15 @@ plugins=dir/badext.pyi [out] tmp/mypy.ini:2: error: Plugin 'badext.pyi' does not have a .py extension +[case testInvalidPluginExtensionPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +\[tool.mypy] +plugins = "dir/badext.pyi" +[file dir/badext.pyi] +[out] +tmp/pyproject.toml:1: error: Plugin 'badext.pyi' does not have a .py extension + [case testMissingPluginEntryPoint] # flags: --config-file tmp/mypy.ini [file mypy.ini] @@ -102,6 +198,14 @@ tmp/mypy.ini:2: error: Plugin 'badext.pyi' does not have a .py extension [out] tmp/mypy.ini:2: error: Plugin '/test-data/unit/plugins/noentry.py' does not define entry point function "plugin" +[case testMissingPluginEntryPointPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +\[tool.mypy] + plugins = "/test-data/unit/plugins/noentry.py" +[out] +tmp/pyproject.toml:1: error: Plugin '/test-data/unit/plugins/noentry.py' does not define entry point function "plugin" + [case testCustomPluginEntryPointFile] # flags: --config-file tmp/mypy.ini def f() -> str: ... @@ -110,6 +214,14 @@ reveal_type(f()) # N: Revealed type is 'builtins.int' \[mypy] plugins=/test-data/unit/plugins/customentry.py:register +[case testCustomPluginEntryPointFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f() -> str: ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/customentry.py:register" + [case testCustomPluginEntryPoint] # flags: --config-file tmp/mypy.ini def f() -> str: ... @@ -118,6 +230,14 @@ reveal_type(f()) # N: Revealed type is 'builtins.int' \[mypy] plugins=customentry:register +[case testCustomPluginEntryPointPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f() -> str: ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +[file pyproject.toml] +\[tool.mypy] +plugins = "customentry:register" + [case testInvalidPluginEntryPointReturnValue] # flags: --config-file tmp/mypy.ini def f(): pass @@ -139,6 +259,27 @@ plugins=/test-data/unit/plugins/badreturn2.py [out] tmp/mypy.ini:2: error: Return value of "plugin" must be a subclass of "mypy.plugin.Plugin" (in /test-data/unit/plugins/badreturn2.py) +[case testInvalidPluginEntryPointReturnValuePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f(): pass +f() +[file pyproject.toml] +\[tool.mypy] + +plugins = "/test-data/unit/plugins/badreturn.py" +[out] +tmp/pyproject.toml:1: error: Type object expected as the return value of "plugin"; got None (in /test-data/unit/plugins/badreturn.py) + +[case testInvalidPluginEntryPointReturnValuePyprojectTOML2] +# flags: --config-file tmp/pyproject.toml +def f(): pass +f() +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/badreturn2.py" +[out] +tmp/pyproject.toml:1: error: Return value of "plugin" must be a subclass of "mypy.plugin.Plugin" (in /test-data/unit/plugins/badreturn2.py) + [case testAttributeTypeHookPlugin] # flags: --config-file tmp/mypy.ini from typing import Callable @@ -160,6 +301,27 @@ class DerivedSignal(Signal[T]): ... \[mypy] plugins=/test-data/unit/plugins/attrhook.py +[case testAttributeTypeHookPluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Callable +from m import Signal, DerivedSignal +s: Signal[Callable[[int], None]] = Signal() +s(1) +s('') # E: Argument 1 has incompatible type "str"; expected "int" + +ds: DerivedSignal[Callable[[int], None]] = DerivedSignal() +ds('') # E: Argument 1 has incompatible type "str"; expected "int" +[file m.py] +from typing import TypeVar, Generic, Callable +T = TypeVar('T', bound=Callable[..., None]) +class Signal(Generic[T]): + __call__: Callable[..., None] # This type is replaced by the plugin + +class DerivedSignal(Signal[T]): ... +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/attrhook.py" + [case testAttributeHookPluginForDynamicClass] # flags: --config-file tmp/mypy.ini from m import Magic, DerivedMagic @@ -185,6 +347,31 @@ class DerivedMagic(Magic): ... \[mypy] plugins=/test-data/unit/plugins/attrhook2.py +[case testAttributeHookPluginForDynamicClassPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from m import Magic, DerivedMagic + +magic = Magic() +reveal_type(magic.magic_field) # N: Revealed type is 'builtins.str' +reveal_type(magic.non_magic_method()) # N: Revealed type is 'builtins.int' +reveal_type(magic.non_magic_field) # N: Revealed type is 'builtins.int' +magic.nonexistent_field # E: Field does not exist +reveal_type(magic.fallback_example) # N: Revealed type is 'Any' +reveal_type(DerivedMagic().magic_field) # N: Revealed type is 'builtins.str' +[file m.py] +from typing import Any +class Magic: + # Triggers plugin infrastructure: + def __getattr__(self, x: Any) -> Any: ... + def non_magic_method(self) -> int: ... + non_magic_field: int + +class DerivedMagic(Magic): ... + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/attrhook2.py" + [case testTypeAnalyzeHookPlugin] # flags: --config-file tmp/mypy.ini from typing import Callable @@ -204,6 +391,25 @@ class Signal(Generic[T]): plugins=/test-data/unit/plugins/type_anal_hook.py [builtins fixtures/dict.pyi] +[case testTypeAnalyzeHookPluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Callable +from mypy_extensions import DefaultArg +from m import Signal +s: Signal[[int, DefaultArg(str, 'x')]] = Signal() +reveal_type(s) # N: Revealed type is 'm.Signal[def (builtins.int, x: builtins.str =)]' +s.x # E: "Signal[Callable[[int, str], None]]" has no attribute "x" +ss: Signal[int, str] # E: Invalid "Signal" type (expected "Signal[[t, ...]]") +[file m.py] +from typing import TypeVar, Generic, Callable +T = TypeVar('T', bound=Callable[..., None]) +class Signal(Generic[T]): + __call__: Callable[..., None] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/type_anal_hook.py" +[builtins fixtures/dict.pyi] + [case testFunctionPluginHookForClass] # flags: --config-file tmp/mypy.ini import mod @@ -242,6 +448,44 @@ plugins=/test-data/unit/plugins/class_callable.py [builtins fixtures/bool.pyi] [out] +[case testFunctionPluginHookForClassPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import mod +from mod import AttrInt + +Alias = AttrInt +AnotherAlias = mod.Attr + +class C: + x = Alias() + y = mod.AttrInt(required=True) + z = AnotherAlias(int, required=False) + +c = C() +reveal_type(c.x) # N: Revealed type is 'Union[builtins.int, None]' +reveal_type(c.y) # N: Revealed type is 'builtins.int*' +reveal_type(c.z) # N: Revealed type is 'Union[builtins.int*, None]' + +[file mod.py] +from typing import Generic, TypeVar, Type +T = TypeVar('T') + +class Attr(Generic[T]): + def __init__(self, tp: Type[T], required: bool = False) -> None: + pass + def __get__(self, instance: object, owner: type) -> T: + pass + +class AttrInt(Attr[int]): + def __init__(self, required: bool = False) -> None: + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/class_callable.py" +[builtins fixtures/bool.pyi] +[out] + [case testFunctionPluginHookForReturnedCallable] # flags: --config-file tmp/mypy.ini from m import decorator1, decorator2 @@ -259,6 +503,23 @@ def decorator2() -> Callable[..., Callable[..., int]]: pass \[mypy] plugins=/test-data/unit/plugins/named_callable.py +[case testFunctionPluginHookForReturnedCallablePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from m import decorator1, decorator2 +@decorator1() +def f() -> None: pass +@decorator2() +def g() -> None: pass +reveal_type(f) # N: Revealed type is 'def (*Any, **Any) -> builtins.str' +reveal_type(g) # N: Revealed type is 'def (*Any, **Any) -> builtins.int' +[file m.py] +from typing import Callable +def decorator1() -> Callable[..., Callable[..., int]]: pass +def decorator2() -> Callable[..., Callable[..., int]]: pass +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/named_callable.py" + [case testFunctionMethodContextsHasArgNames] # flags: --config-file tmp/mypy.ini from mod import Class, func @@ -288,6 +549,35 @@ def func(classname: str, arg1: Any, arg2: Any) -> Any: plugins=/test-data/unit/plugins/arg_names.py [builtins fixtures/classmethod.pyi] +[case testFunctionMethodContextsHasArgNamesPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import Class, func + +reveal_type(Class().method(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(Class.myclassmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(Class.mystaticmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(Class.method(self=Class(), arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(func(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' + +[file mod.py] +from typing import Any +class Class: + def method(self, classname: str, arg1: Any, arg2: Any) -> Any: + pass + @classmethod + def myclassmethod(cls, classname: str, arg1: Any, arg2: Any): + pass + @staticmethod + def mystaticmethod(classname: str, arg1: Any, arg2: Any): + pass +def func(classname: str, arg1: Any, arg2: Any) -> Any: + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" +[builtins fixtures/classmethod.pyi] + [case testFunctionMethodContextsHasArgNamesPositionals] # flags: --config-file tmp/mypy.ini from mod import Class, func @@ -317,6 +607,35 @@ def func(classname: str, arg1: Any, arg2: Any) -> Any: plugins=/test-data/unit/plugins/arg_names.py [builtins fixtures/classmethod.pyi] +[case testFunctionMethodContextsHasArgNamesPositionalsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import Class, func + +reveal_type(Class().method('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' +reveal_type(Class.myclassmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' +reveal_type(Class.mystaticmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' +reveal_type(Class.method(Class(), 'builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' +reveal_type(func('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' + +[file mod.py] +from typing import Any +class Class: + def method(self, classname: str, arg1: Any, arg2: Any) -> Any: + pass + @classmethod + def myclassmethod(cls, classname: str, arg1: Any, arg2: Any): + pass + @staticmethod + def mystaticmethod(classname: str, arg1: Any, arg2: Any): + pass +def func(classname: str, arg1: Any, arg2: Any) -> Any: + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" +[builtins fixtures/classmethod.pyi] + [case testFunctionMethodContextsHasArgNamesInitMethod] # flags: --config-file tmp/mypy.ini from mod import ClassInit, Outer @@ -338,6 +657,27 @@ class Outer: \[mypy] plugins=/test-data/unit/plugins/arg_names.py +[case testFunctionMethodContextsHasArgNamesInitMethodPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import ClassInit, Outer + +reveal_type(ClassInit('builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(ClassInit(classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(Outer.NestedClassInit(classname='builtins.str')) # N: Revealed type is 'builtins.str' +[file mod.py] +from typing import Any +class ClassInit: + def __init__(self, classname: str): + pass +class Outer: + class NestedClassInit: + def __init__(self, classname: str): + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" + [case testFunctionMethodContextsHasArgNamesUnfilledArguments] # flags: --config-file tmp/mypy.ini from mod import ClassUnfilled, func_unfilled @@ -361,6 +701,29 @@ def func_unfilled(classname: str, arg1: Any = None, arg2: Any = None) -> Any: \[mypy] plugins=/test-data/unit/plugins/arg_names.py +[case testFunctionMethodContextsHasArgNamesUnfilledArgumentsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import ClassUnfilled, func_unfilled + +reveal_type(ClassUnfilled().method(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassUnfilled().method(arg2=1, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(ClassUnfilled().method('builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(func_unfilled(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(func_unfilled(arg2=1, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(func_unfilled('builtins.str')) # N: Revealed type is 'builtins.str' + +[file mod.py] +from typing import Any +class ClassUnfilled: + def method(self, classname: str, arg1: Any = None, arg2: Any = None) -> Any: + pass +def func_unfilled(classname: str, arg1: Any = None, arg2: Any = None) -> Any: + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" + [case testFunctionMethodContextsHasArgNamesStarExpressions] # flags: --config-file tmp/mypy.ini from mod import ClassStarExpr, func_star_expr @@ -386,6 +749,31 @@ def func_star_expr(classname: str, *args, **kwargs) -> Any: plugins=/test-data/unit/plugins/arg_names.py [builtins fixtures/dict.pyi] +[case testFunctionMethodContextsHasArgNamesStarExpressionsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import ClassStarExpr, func_star_expr + +reveal_type(ClassStarExpr().method(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassStarExpr().method('builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassStarExpr().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassStarExpr().method('builtins.str', 2, 3, 4, arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' +reveal_type(func_star_expr(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(func_star_expr('builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(func_star_expr('builtins.str', 2, 3, 4, arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' + +[file mod.py] +from typing import Any +class ClassStarExpr: + def method(self, classname: str, *args, **kwargs) -> Any: + pass +def func_star_expr(classname: str, *args, **kwargs) -> Any: + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" +[builtins fixtures/dict.pyi] + [case testFunctionMethodContextArgNamesForInheritedMethods] # flags: --config-file tmp/mypy.ini from mod import ClassChild @@ -410,6 +798,30 @@ class ClassChild(Base): plugins=/test-data/unit/plugins/arg_names.py [builtins fixtures/classmethod.pyi] +[case testFunctionMethodContextArgNamesForInheritedMethodsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import ClassChild + +reveal_type(ClassChild().method(classname='builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassChild().method(arg1=1, classname='builtins.str', arg2=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassChild().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassChild.myclassmethod('builtins.str')) # N: Revealed type is 'builtins.str' +[file mod.py] +from typing import Any +class Base: + def method(self, classname: str, arg1: Any, arg2: Any) -> Any: + pass + @classmethod + def myclassmethod(cls, classname: str) -> Any: + pass +class ClassChild(Base): + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" +[builtins fixtures/classmethod.pyi] + [case testMethodSignatureHook] # flags: --config-file tmp/mypy.ini from typing import Iterator @@ -439,6 +851,35 @@ for x in foo: plugins=/test-data/unit/plugins/method_sig_hook.py [builtins fixtures/tuple.pyi] +[case testMethodSignatureHookPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Iterator + +class Foo: + # Test that method signature hooks are applied in various cases: explicit method calls, and + # implicit dunder method calls through language syntax. + # The plugin's method signature hook should turn all str occurrences into int. + def __init__(self) -> None: ... + def __getitem__(self, index: str) -> str: ... + def __setitem__(self, index: str, value: str) -> None: ... + def __iter__(self) -> Iterator[str]: ... + def __next__(self) -> str: ... + def __call__(self, *args: str) -> str: ... + def m(self, arg: str) -> str: ... + +foo = Foo() +reveal_type(foo.m(2)) # N: Revealed type is 'builtins.int' +reveal_type(foo[3]) # N: Revealed type is 'builtins.int' +reveal_type(foo(4, 5, 6)) # N: Revealed type is 'builtins.int' +foo[4] = 5 +for x in foo: + reveal_type(x) # N: Revealed type is 'builtins.int*' + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/method_sig_hook.py" +[builtins fixtures/tuple.pyi] + [case testMethodSignatureHookNamesFullyQualified] # flags: --config-file tmp/mypy.ini from mypy_extensions import TypedDict @@ -464,6 +905,31 @@ reveal_type(FullyQualifiedTestNamedTuple('')._asdict()) # N: Revealed type is 'b plugins=/test-data/unit/plugins/fully_qualified_test_hook.py [builtins fixtures/classmethod.pyi] +[case testMethodSignatureHookNamesFullyQualifiedPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mypy_extensions import TypedDict +from typing import NamedTuple + +class FullyQualifiedTestClass: + @classmethod + def class_method(self) -> str: ... + def instance_method(self) -> str: ... + +class FullyQualifiedTestTypedDict(TypedDict): + foo: str + +FullyQualifiedTestNamedTuple = NamedTuple('FullyQualifiedTestNamedTuple', [('foo', str)]) + +# Check the return types to ensure that the method signature hook is called in each case +reveal_type(FullyQualifiedTestClass.class_method()) # N: Revealed type is 'builtins.int' +reveal_type(FullyQualifiedTestClass().instance_method()) # N: Revealed type is 'builtins.int' +reveal_type(FullyQualifiedTestNamedTuple('')._asdict()) # N: Revealed type is 'builtins.int' + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/fully_qualified_test_hook.py" +[builtins fixtures/classmethod.pyi] + [case testDynamicClassPlugin] # flags: --config-file tmp/mypy.ini from mod import declarative_base, Column, Instr @@ -490,6 +956,32 @@ class Instr(Generic[T]): ... \[mypy] plugins=/test-data/unit/plugins/dyn_class.py +[case testDynamicClassPluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import declarative_base, Column, Instr + +Base = declarative_base() + +class Model(Base): + x: Column[int] +class Other: + x: Column[int] + +reveal_type(Model().x) # N: Revealed type is 'mod.Instr[builtins.int]' +reveal_type(Other().x) # N: Revealed type is 'mod.Column[builtins.int]' +[file mod.py] +from typing import Generic, TypeVar +def declarative_base(): ... + +T = TypeVar('T') + +class Column(Generic[T]): ... +class Instr(Generic[T]): ... + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/dyn_class.py" + [case testDynamicClassPluginNegatives] # flags: --config-file tmp/mypy.ini from mod import declarative_base, Column, Instr, non_declarative_base @@ -520,6 +1012,36 @@ class Instr(Generic[T]): ... \[mypy] plugins=/test-data/unit/plugins/dyn_class.py +[case testDynamicClassPluginNegativesPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import declarative_base, Column, Instr, non_declarative_base + +Bad1 = non_declarative_base() +Bad2 = Bad3 = declarative_base() + +class C1(Bad1): ... # E: Variable "__main__.Bad1" is not valid as a type \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ + # E: Invalid base class "Bad1" +class C2(Bad2): ... # E: Variable "__main__.Bad2" is not valid as a type \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ + # E: Invalid base class "Bad2" +class C3(Bad3): ... # E: Variable "__main__.Bad3" is not valid as a type \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ + # E: Invalid base class "Bad3" +[file mod.py] +from typing import Generic, TypeVar +def declarative_base(): ... +def non_declarative_base(): ... + +T = TypeVar('T') + +class Column(Generic[T]): ... +class Instr(Generic[T]): ... + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/dyn_class.py" + [case testDynamicClassHookFromClassMethod] # flags: --config-file tmp/mypy.ini @@ -549,6 +1071,35 @@ class Manager: \[mypy] plugins=/test-data/unit/plugins/dyn_class_from_method.py +[case testDynamicClassHookFromClassMethodPyprojectTOML] +# flags: --config-file tmp/pyproject.toml + +from mod import QuerySet, Manager + +MyManager = Manager.from_queryset(QuerySet) + +reveal_type(MyManager()) # N: Revealed type is '__main__.MyManager' +reveal_type(MyManager().attr) # N: Revealed type is 'builtins.str' + +def func(manager: MyManager) -> None: + reveal_type(manager) # N: Revealed type is '__main__.MyManager' + reveal_type(manager.attr) # N: Revealed type is 'builtins.str' + +func(MyManager()) + +[file mod.py] +from typing import Generic, TypeVar, Type +class QuerySet: + attr: str +class Manager: + @classmethod + def from_queryset(cls, queryset_cls: Type[QuerySet]): ... + +[builtins fixtures/classmethod.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/dyn_class_from_method.py" + [case testBaseClassPluginHookWorksIncremental] # flags: --config-file tmp/mypy.ini import a @@ -580,6 +1131,37 @@ plugins=/test-data/unit/plugins/common_api_incremental.py tmp/a.py:3: note: Revealed type is 'builtins.str' tmp/a.py:4: error: "Type[Base]" has no attribute "__magic__" +[case testBaseClassPluginHookWorksIncrementalPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a + +[file a.py] +from base import Base +class C(Base): ... + +[file a.py.2] +from base import Base +class C(Base): ... +reveal_type(C().__magic__) +Base.__magic__ + +[file base.py] +from lib import declarative_base +Base = declarative_base() + +[file lib.py] +from typing import Any +def declarative_base() -> Any: ... + +[file pyproject.toml] +\[tool.mypy] +python_version = 3.6 +plugins = "/test-data/unit/plugins/common_api_incremental.py" +[out] +[out2] +tmp/a.py:3: note: Revealed type is 'builtins.str' +tmp/a.py:4: error: "Type[Base]" has no attribute "__magic__" + [case testArgKindsMethod] # flags: --config-file tmp/mypy.ini class Class: @@ -592,6 +1174,18 @@ Class().method(1, *[2], **{'a': 1}) # E: [[0, 2], [4]] \[mypy] plugins=/test-data/unit/plugins/arg_kinds.py +[case testArgKindsMethodPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +class Class: + def method(self, *args, **kwargs): + pass + +Class().method(1, *[2], **{'a': 1}) # E: [[0, 2], [4]] +[builtins fixtures/dict.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_kinds.py" + [case testArgKindsFunction] # flags: --config-file tmp/mypy.ini def func(*args, **kwargs): @@ -603,6 +1197,17 @@ func(1, 2, [3, 4], *[5, 6, 7], **{'a': 1}) # E: [[0, 0, 0, 2], [4]] \[mypy] plugins=/test-data/unit/plugins/arg_kinds.py +[case testArgKindsFunctionPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def func(*args, **kwargs): + pass + +func(1, 2, [3, 4], *[5, 6, 7], **{'a': 1}) # E: [[0, 0, 0, 2], [4]] +[builtins fixtures/dict.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_kinds.py" + [case testHookCallableInstance] # flags: --config-file tmp/mypy.ini from typing import Generic, TypeVar @@ -618,6 +1223,21 @@ reveal_type(instance(2)) # N: Revealed type is 'builtins.float*' \[mypy] plugins=/test-data/unit/plugins/callable_instance.py +[case testHookCallableInstancePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Generic, TypeVar +T = TypeVar("T") +class Class(Generic[T]): + def __init__(self, one: T): ... + def __call__(self, two: T) -> int: ... +reveal_type(Class("hi")("there")) # N: Revealed type is 'builtins.str*' +instance = Class(3.14) +reveal_type(instance(2)) # N: Revealed type is 'builtins.float*' + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/callable_instance.py" + [case testGetMethodHooksOnUnions] # flags: --config-file tmp/mypy.ini --no-strict-optional from typing import Union @@ -640,6 +1260,28 @@ else: \[mypy] plugins=/test-data/unit/plugins/union_method.py +[case testGetMethodHooksOnUnionsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml --no-strict-optional +from typing import Union + +class Foo: + def meth(self, x: str) -> str: ... +class Bar: + def meth(self, x: int) -> float: ... +class Other: + meth: int + +x: Union[Foo, Bar, Other] +if isinstance(x.meth, int): + reveal_type(x.meth) # N: Revealed type is 'builtins.int' +else: + reveal_type(x.meth(int())) # N: Revealed type is 'builtins.int' + +[builtins fixtures/isinstancelist.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/union_method.py" + [case testGetMethodHooksOnUnionsStrictOptional] # flags: --config-file tmp/mypy.ini --strict-optional from typing import Union @@ -662,6 +1304,28 @@ else: \[mypy] plugins=/test-data/unit/plugins/union_method.py +[case testGetMethodHooksOnUnionsStrictOptionalPyprojectTOML] +# flags: --config-file tmp/pyproject.toml --strict-optional +from typing import Union + +class Foo: + def meth(self, x: str) -> str: ... +class Bar: + def meth(self, x: int) -> float: ... +class Other: + meth: int + +x: Union[Foo, Bar, Other] +if isinstance(x.meth, int): + reveal_type(x.meth) # N: Revealed type is 'builtins.int' +else: + reveal_type(x.meth(int())) # N: Revealed type is 'builtins.int' + +[builtins fixtures/isinstancelist.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/union_method.py" + [case testGetMethodHooksOnUnionsSpecial] # flags: --config-file tmp/mypy.ini from typing import Union @@ -679,6 +1343,23 @@ reveal_type(x[int()]) # N: Revealed type is 'builtins.int' \[mypy] plugins=/test-data/unit/plugins/union_method.py +[case testGetMethodHooksOnUnionsSpecialPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Union + +class Foo: + def __getitem__(self, x: str) -> str: ... +class Bar: + def __getitem__(self, x: int) -> float: ... + +x: Union[Foo, Bar] +reveal_type(x[int()]) # N: Revealed type is 'builtins.int' + +[builtins fixtures/isinstancelist.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/union_method.py" + [case testPluginDependencies] # flags: --config-file tmp/mypy.ini @@ -692,6 +1373,19 @@ plugins=/test-data/unit/plugins/union_method.py \[mypy] plugins=/test-data/unit/plugins/depshook.py +[case testPluginDependenciesPyprojectTOML] +# flags: --config-file tmp/pyproject.toml + +# The top level file here doesn't do anything, but the plugin should add +# a dependency on err that will cause err to be processed and an error reported. + +[file err.py] +1 + 'lol' # E: Unsupported operand types for + ("int" and "str") + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/depshook.py" + [case testCustomizeMroTrivial] # flags: --config-file tmp/mypy.ini class A: pass @@ -699,6 +1393,13 @@ class A: pass \[mypy] plugins=/test-data/unit/plugins/customize_mro.py +[case testCustomizeMroTrivialPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +class A: pass +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/customize_mro.py" + [case testDescriptorMethods] # flags: --config-file tmp/mypy.ini @@ -722,6 +1423,29 @@ Cls().attr = "foo" # E: Incompatible types in assignment (expression has type " \[mypy] plugins=/test-data/unit/plugins/descriptor.py +[case testDescriptorMethodsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml + +class Desc: + def __get__(self, obj, cls): + pass + + def __set__(self, obj, val): + pass + +class Cls: + attr = Desc() + +reveal_type(Cls().attr) # N: Revealed type is 'builtins.int' +reveal_type(Cls.attr) # N: Revealed type is 'builtins.str' + +Cls().attr = 3 +Cls().attr = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "int") + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/descriptor.py" + [case testFunctionSigPluginFile] # flags: --config-file tmp/mypy.ini @@ -730,3 +1454,12 @@ reveal_type(dynamic_signature(1)) # N: Revealed type is 'builtins.int' [file mypy.ini] \[mypy] plugins=/test-data/unit/plugins/function_sig_hook.py + +[case testFunctionSigPluginFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml + +def dynamic_signature(arg1: str) -> str: ... +reveal_type(dynamic_signature(1)) # N: Revealed type is 'builtins.int' +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/function_sig_hook.py" diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 6a94ded4c489..6dc67d316372 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -477,6 +477,21 @@ disallow_incomplete_defs = False \[mypy-incomplete] disallow_incomplete_defs = True +[case testPerFileIncompleteDefsBasicPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, incomplete + +[file standard.py] +def incomplete(x) -> int: + return 0 +[file incomplete.py] +def incomplete(x) -> int: # E: Function is missing a type annotation for one or more arguments + return 0 +[file pyproject.toml] +\[tool.mypy] +disallow_incomplete_defs = false +\[tool.mypy-incomplete] +disallow_incomplete_defs = true [case testPerFileStrictOptionalBasic] # flags: --config-file tmp/mypy.ini @@ -497,6 +512,24 @@ strict_optional = False \[mypy-optional] strict_optional = True +[case testPerFileStrictOptionalBasicPyprojectToml] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +x = 0 +if int(): + x = None +[file optional.py] +x = 0 +if int(): + x = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.mypy-optional] +strict_optional = true [case testPerFileStrictOptionalBasicImportStandard] # flags: --config-file tmp/mypy.ini @@ -524,6 +557,32 @@ strict_optional = False \[mypy-optional] strict_optional = True +[case testPerFileStrictOptionalBasicImportStandardPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +from typing import Optional +def f(x: int) -> None: pass +an_int = 0 # type: int +optional_int = None # type: Optional[int] +f(an_int) # ints can be used as ints +f(optional_int) # optional ints can be used as ints in this file + +[file optional.py] +import standard +def f(x: int) -> None: pass +standard.an_int = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") +standard.optional_int = None # OK -- explicitly declared as optional +f(standard.an_int) # ints can be used as ints +f(standard.optional_int) # E: Argument 1 to "f" has incompatible type "None"; expected "int" + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.mypy-optional] +strict_optional = true + [case testPerFileStrictOptionalBasicImportOptional] # flags: --config-file tmp/mypy.ini @@ -547,6 +606,28 @@ strict_optional = False \[mypy-optional] strict_optional = True +[case testPerFileStrictOptionalBasicImportOptionalPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +import optional +def f(x: int) -> None: pass +f(optional.x) # OK -- in non-strict Optional context +f(optional.y) # OK -- in non-strict Optional context + +[file optional.py] +from typing import Optional +def f(x: int) -> None: pass +x = 0 # type: Optional[int] +y = None # type: None + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.mypy-optional] +strict_optional = true + [case testPerFileStrictOptionalListItemImportOptional] # flags: --config-file tmp/mypy.ini import standard, optional @@ -571,6 +652,30 @@ strict_optional = False strict_optional = True [builtins fixtures/list.pyi] +[case testPerFileStrictOptionalListItemImportOptionalPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +import optional +from typing import List +def f(x: List[int]) -> None: pass +f(optional.x) # OK -- in non-strict Optional context +f(optional.y) # OK -- in non-strict Optional context + +[file optional.py] +from typing import Optional, List +def f(x: List[int]) -> None: pass +x = [] # type: List[Optional[int]] +y = [] # type: List[int] + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.mypy-optional] +strict_optional = true +[builtins fixtures/list.pyi] + [case testPerFileStrictOptionalComplicatedList] from typing import Union, Optional, List @@ -596,6 +701,24 @@ strict_optional = False \[mypy-optional] strict_optional = True +[case testPerFileStrictOptionalNoneArgumentsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +def f(x: int = None) -> None: pass + +[file optional.py] +import standard +def f(x: int = None) -> None: pass +standard.f(None) + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.mypy-optional] +strict_optional = true + [case testDisallowImplicitTypesIgnoreMissingTypes] # flags: --ignore-missing-imports --disallow-any-unimported from missing import MyType @@ -1078,6 +1201,26 @@ always_true = YOLO1, YOLO always_false = BLAH, BLAH1 [builtins fixtures/bool.pyi] +[case testAlwaysTrueAlwaysFalseConfigFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from somewhere import YOLO, BLAH +if not YOLO: + 1+() +if BLAH: + 1+() +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = true +always_true = [ + "YOLO1", + "YOLO" +] +always_false = [ + "BLAH", + "BLAH1" +] +[builtins fixtures/bool.pyi] + [case testDisableErrorCodeConfigFile] # flags: --config-file tmp/mypy.ini --disallow-untyped-defs import foo @@ -1087,6 +1230,18 @@ def bar(): \[mypy] disable_error_code = import, no-untyped-def +[case testDisableErrorCodeConfigFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml --disallow-untyped-defs +import foo +def bar(): + pass +[file pyproject.toml] +\[tool.mypy] +disable_error_code = [ + "import", + "no-untyped-def" +] + [case testCheckDisallowAnyGenericsNamedTuple] # flags: --disallow-any-generics from typing import NamedTuple @@ -1190,6 +1345,22 @@ def f(c: A) -> None: # E: Missing type parameters for generic type "A" strict = True [out] +[case testStrictInConfigAnyGenericPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import TypeVar, Generic + +T = TypeVar('T') + +class A(Generic[T]): + pass + +def f(c: A) -> None: # E: Missing type parameters for generic type "A" + pass +[file pyproject.toml] +\[tool.mypy] +strict = true +[out] + [case testStrictFalseInConfigAnyGeneric] # flags: --config-file tmp/mypy.ini from typing import TypeVar, Generic @@ -1206,6 +1377,22 @@ def f(c: A) -> None: strict = False [out] +[case testStrictFalseInConfigAnyGenericPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import TypeVar, Generic + +T = TypeVar('T') + +class A(Generic[T]): + pass + +def f(c: A) -> None: + pass +[file pyproject.toml] +\[tool.mypy] +strict = false +[out] + [case testStrictAndStrictEquality] # flags: --strict x = 0 @@ -1227,6 +1414,19 @@ strict_equality = True strict_equality = False [builtins fixtures/bool.pyi] +[case testStrictEqualityPerFile]PyprojectTOML +# flags: --config-file tmp/pyproject.toml +import b +42 == 'no' # E: Non-overlapping equality check (left operand type: "Literal[42]", right operand type: "Literal['no']") +[file b.py] +42 == 'no' +[file pyproject.toml] +\[toolmypy] +strict_equality = true +\[tool.mypy-b] +strict_equality = false +[builtins fixtures/bool.pyi] + [case testNoImplicitReexport] # flags: --no-implicit-reexport from other_module_2 import a @@ -1305,6 +1505,24 @@ implicit_reexport = False [out] main:2: error: Module 'other_module_2' has no attribute 'a' +[case testNoImplicitReexportPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from other_module_2 import a + +[file other_module_1.py] +a = 5 + +[file other_module_2.py] +from other_module_1 import a + +[file pyproject.toml] +\[tool.mypy] +implicit_reexport = true +\[tool.mypy-other_module_2] +implicit_reexport = false +[out] +main:2: error: Module 'other_module_2' has no attribute 'a' + [case testImplicitAnyOKForNoArgs] # flags: --disallow-any-generics --show-column-numbers from typing import List @@ -1539,6 +1757,31 @@ disallow_subclassing_any = True \[mypy-m] disallow_subclassing_any = False +[case testDisallowSubclassingAnyPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import m +import y + +[file m.py] +from typing import Any + +x = None # type: Any + +class ShouldBeFine(x): ... + +[file y.py] +from typing import Any + +x = None # type: Any + +class ShouldNotBeFine(x): ... # E: Class cannot subclass 'x' (has type 'Any') + +[file pyproject.toml] +\[tool.mypy] +disallow_subclassing_any = true +\[tool.mypy-m] +disallow_subclassing_any = false + [case testNoImplicitOptionalPerModule] # flags: --config-file tmp/mypy.ini import m @@ -1568,6 +1811,35 @@ no_implicit_optional = True \[mypy-m] no_implicit_optional = False +[case testNoImplicitOptionalPerModulePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import m + +[file m.py] +def f(a: str = None) -> int: + return 0 + +[file pyproject.toml] +\[tool.mypy] +no_implicit_optional = true +\[tool.mypy-m] +no_implicit_optional = false + +[case testNoImplicitOptionalPerModulePythonPyprojectTOML2] +# flags: --config-file tmp/pyproject.toml --python-version 2.7 +import m + +[file m.py] +def f(a = None): + # type: (str) -> int + return 0 + +[file pyproject.toml] +\[tool.mypy] +no_implicit_optional = true +\[tool.mypy-m] +no_implicit_optional = false + [case testDisableErrorCode] # flags: --disable-error-code attr-defined x = 'should be fine' diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 6d2d7b5c7edf..4f6f30c140c4 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -1812,6 +1812,23 @@ main:3: note: Revealed type is 'builtins.int' [out2] main:3: note: Revealed type is 'Any' +[case testIncrementalFollowImportsVariablePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +reveal_type(a.x) +[file a.py] +x = 0 +[file pyproject.toml] +\[tool.mypy] +follow_imports = "normal" +[file pyproject.toml.2] +\[tool.mypy] +follow_imports = "skip" +[out1] +main:3: note: Revealed type is 'builtins.int' +[out2] +main:3: note: Revealed type is 'Any' + [case testIncrementalNamedTupleInMethod] from ntcrash import nope [file ntcrash.py] @@ -2009,6 +2026,18 @@ warn_no_return = False warn_no_return = True [rechecked] +[case testIncrementalPerFileFlagsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +pass +[file pyproject.toml] +\[tool.mypy] +warn_no_return = false +\[tool.mypy-a] +warn_no_return = true +[rechecked] + [case testIncrementalClassVar] from typing import ClassVar class A: @@ -3665,6 +3694,21 @@ cache_fine_grained = False [rechecked] [stale] +[case testRegularUsesFgCachePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +x = 0 +[file pyproject.toml] +\[tool.mypy] +cache_fine_grained = true +[file pyproject.toml.2] +\[tool.mypy] +cache_fine_grained = false +-- Nothing should get rechecked +[rechecked] +[stale] + [case testFgCacheNeedsFgCache] # flags: --config-file tmp/mypy.ini import a @@ -3680,6 +3724,21 @@ cache_fine_grained = True [stale a, builtins, typing] [builtins fixtures/tuple.pyi] +[case testFgCacheNeedsFgCachePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +x = 0 +[file pyproject.toml] +\[tool.mypy] +cache_fine_grained = false +[file pyproject.toml.2] +\[tool.mypy] +cache_fine_grained = true +[rechecked a, builtins, typing] +[stale a, builtins, typing] +[builtins fixtures/tuple.pyi] + [case testIncrementalPackageNameOverload] # cmd: mypy -m main a # flags: --follow-imports=skip @@ -4660,6 +4719,19 @@ follow_imports_for_stubs = True [stale] [rechecked] +[case testFollowImportSkipNotInvalidatedOnAddedStubOnFollowForStubsPyprojectTOML] +# flags: --follow-imports=skip --ignore-missing-imports --config-file=tmp/pyproject.toml +# cmd: mypy -m main +[file main.py] +import other +[file other.pyi.2] +x = 1 +[file pyproject.toml] +\[tool.mypy] +follow_imports_for_stubs = true +[stale] +[rechecked] + [case testAddedSkippedStubsPackageFrom] # flags: --follow-imports=skip --ignore-missing-imports # cmd: mypy -m main @@ -4919,6 +4991,112 @@ plugins=basic_plugin.py [out2] tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") +[case testChangedPluginsInvalidateCachePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +from b import x +y: int = x + +[file a.py.2] +from b import x +y: int = x +touch = 1 + +[file b.py] +class C: ... +def f() -> C: ... +x = f() + +[file basic_plugin.py] +from mypy.plugin import Plugin + +class MyPlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith('.f'): + return my_hook + assert fullname is not None + return None + +def my_hook(ctx): + return ctx.api.named_generic_type('builtins.int', []) + +def plugin(version): + return MyPlugin + +[file basic_plugin.py.2] +from mypy.plugin import Plugin + +class MyPlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith('.f'): + return my_hook + assert fullname is not None + return None + +def my_hook(ctx): + return ctx.api.named_generic_type('builtins.str', []) + +def plugin(version): + return MyPlugin +[file pyproject.toml] +\[tool.mypy] +plugins = "basic_plugin.py" +[out] +[out2] +tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testChangedPluginsInvalidateCachePyprojectTOML2] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +from b import x +y: int = x + +[file a.py.2] +from b import x +y: int = x +touch = 1 + +[file b.py] +class C: ... +def f() -> C: ... +x = f() + +[file basic_plugin.py] +from mypy.plugin import Plugin +from version_plugin import __version__, choice + +class MyPlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith('.f'): + return my_hook + assert fullname is not None + return None + +def my_hook(ctx): + if choice: + return ctx.api.named_generic_type('builtins.int', []) + else: + return ctx.api.named_generic_type('builtins.str', []) + +def plugin(version): + return MyPlugin + +[file version_plugin.py] +__version__ = 0.1 +choice = True + +[file version_plugin.py.2] +__version__ = 0.2 +choice = False +[file pyproject.toml] +\[tool.mypy] +plugins = "basic_plugin.py" +[out] +[out2] +tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") + [case testAddedPluginsInvalidateCache] # flags: --config-file tmp/mypy.ini import a @@ -4962,6 +5140,49 @@ plugins=basic_plugin.py [out2] tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") +[case testAddedPluginsInvalidateCachePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +from b import x +y: int = x + +[file a.py.2] +from b import x +y: int = x +touch = 1 + +[file b.py] +def f() -> int: ... +x = f() + +[file basic_plugin.py] +from mypy.plugin import Plugin + +class MyPlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith('.f'): + return my_hook + assert fullname is not None + return None + +def my_hook(ctx): + return ctx.api.named_generic_type('builtins.str', []) + +def plugin(version): + return MyPlugin + +[file pyproject.toml] +\[tool.mypy] +python_version = 3.6 +[file pyproject.toml.2] +\[tool.mypy] +python_version = 3.6 +plugins = "basic_plugin.py" +[out] +[out2] +tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") + [case testRemovedPluginsInvalidateCache] # flags: --config-file tmp/mypy.ini import a @@ -5005,6 +5226,49 @@ python_version=3.6 [out2] tmp/a.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") +[case testRemovedPluginsInvalidateCachePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +from b import x +y: str = x + +[file a.py.2] +from b import x +y: str = x +touch = 1 + +[file b.py] +def f() -> int: ... +x = f() + +[file basic_plugin.py] +from mypy.plugin import Plugin + +class MyPlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith('.f'): + return my_hook + assert fullname is not None + return None + +def my_hook(ctx): + return ctx.api.named_generic_type('builtins.str', []) + +def plugin(version): + return MyPlugin + +[file pyproject.toml] +\[tool.mypy] +python_version = 3.6 +plugins = "basic_plugin.py" +[file pyproject.toml.2] +\[tool.mypy] +python_version = 3.6 +[out] +[out2] +tmp/a.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") + [case testPluginConfigData] # flags: --config-file tmp/mypy.ini import a @@ -5023,6 +5287,24 @@ plugins=/test-data/unit/plugins/config_data.py # The config change will force a to be rechecked but not b. [rechecked a] +[case testPluginConfigDataPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +import b +[file a.py] +[file b.py] +[file test.json] +{"a": false, "b": false} +[file test.json.2] +{"a": true, "b": false} + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/config_data.py" + +# The config change will force a to be rechecked but not b. +[rechecked a] + [case testLiteralIncrementalTurningIntoLiteral] import mod reveal_type(mod.a) diff --git a/test-data/unit/check-modules-case.test b/test-data/unit/check-modules-case.test index 521db0833e6e..8c79a07ca6d6 100644 --- a/test-data/unit/check-modules-case.test +++ b/test-data/unit/check-modules-case.test @@ -22,6 +22,22 @@ x = 1 \[mypy] mypy_path = tmp/funky_case +[case testCaseInsensitivityDirPyprojectTOML] +# flags: --config-file tmp/pyproject.toml + +from a import B # E: Module 'a' has no attribute 'B' +from other import x +reveal_type(x) # N: Revealed type is 'builtins.int' + +[file a/__init__.py] +[file a/b/__init__.py] +[file FuNkY_CaSe/other.py] +x = 1 + +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/funky_case" + [case testPreferPackageOverFileCase] # flags: --config-file tmp/mypy.ini import a @@ -34,6 +50,18 @@ pass \[mypy] mypy_path = tmp/funky +[case testPreferPackageOverFileCasePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file funky/a.py] +/ # Deliberate syntax error, this file should not be parsed. +[file FuNkY/a/__init__.py] +pass + +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/funky" + [case testNotPreferPackageOverFileCase] import a [file a.py] @@ -53,6 +81,18 @@ x = '' \[mypy] mypy_path = tmp/xx, tmp/yy +[case testNamespacePackagePickFirstOnMypyPathCasePyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar import x +reveal_type(x) # N: Revealed type is 'builtins.int' +[file XX/foo/bar.py] +x = 0 +[file yy/foo/bar.py] +x = '' +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + [case testClassicPackageInsideNamespacePackageCase] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar.baz.boo import x @@ -67,3 +107,18 @@ x = 0 [file mypy.ini] \[mypy] mypy_path = TmP/xX, TmP/yY + +[case testClassicPackageInsideNamespacePackageCasePyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar.baz.boo import x +reveal_type(x) # N: Revealed type is 'builtins.int' +[file xx/foo/bar/baz/boo.py] +x = '' +[file xx/foo/bar/baz/__init__.py] +[file yy/foo/bar/baz/boo.py] +x = 0 +[file yy/foo/bar/__init__.py] + +[file pyproject.toml] +\[tool.mypy] +mypy_path = "TmP/xX,TmP/yY" diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index b2e10ba14369..6db63591a862 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -2392,6 +2392,26 @@ ignore_missing_imports = True main:3: error: Cannot find implementation or library stub for module named "a.b.d" main:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +[case testModuleGetattrInitPyprojectTOML10] +# flags: --config-file tmp/pyproject.toml +import a.b.c # silenced +import a.b.d # error + +[file a/__init__.pyi] +from typing import Any +def __getattr__(attr: str) -> Any: ... +[file a/b/__init__.pyi] +# empty (i.e. complete subpackage) + +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-a.b.c'] +ignore_missing_imports = true +[builtins fixtures/module.pyi] +[out] +main:3: error: Cannot find implementation or library stub for module named "a.b.d" +main:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports + [case testIndirectFromImportWithinCycleUsedAsBaseClass] import a [file a.py] @@ -2649,6 +2669,24 @@ z = 0 \[mypy] mypy_path = tmp/xx, tmp/yy +[case testNamespacePackageWithMypyPathPyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bax import x +from foo.bay import y +from foo.baz import z +reveal_type(x) # N: Revealed type is 'builtins.int' +reveal_type(y) # N: Revealed type is 'builtins.int' +reveal_type(z) # N: Revealed type is 'builtins.int' +[file xx/foo/bax.py] +x = 0 +[file yy/foo/bay.py] +y = 0 +[file foo/baz.py] +z = 0 +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + [case testClassicPackageIgnoresEarlierNamespacePackage] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar import y @@ -2662,6 +2700,19 @@ y = 0 \[mypy] mypy_path = tmp/xx, tmp/yy +[case testClassicPackageIgnoresEarlierNamespacePackagePyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar import y +reveal_type(y) # N: Revealed type is 'builtins.int' +[file xx/foo/bar.py] +x = '' +[file yy/foo/bar.py] +y = 0 +[file yy/foo/__init__.py] +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + [case testNamespacePackagePickFirstOnMypyPath] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar import x @@ -2674,6 +2725,18 @@ x = '' \[mypy] mypy_path = tmp/xx, tmp/yy +[case testNamespacePackagePickFirstOnMypyPathPyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar import x +reveal_type(x) # N: Revealed type is 'builtins.int' +[file xx/foo/bar.py] +x = 0 +[file yy/foo/bar.py] +x = '' +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + [case testNamespacePackageInsideClassicPackage] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar.baz import x @@ -2687,6 +2750,19 @@ x = 0 \[mypy] mypy_path = tmp/xx, tmp/yy +[case testNamespacePackageInsideClassicPackagePyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar.baz import x +reveal_type(x) # N: Revealed type is 'builtins.int' +[file xx/foo/bar/baz.py] +x = '' +[file yy/foo/bar/baz.py] +x = 0 +[file yy/foo/__init__.py] +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + [case testClassicPackageInsideNamespacePackage] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar.baz.boo import x @@ -2701,6 +2777,20 @@ x = 0 \[mypy] mypy_path = tmp/xx, tmp/yy +[case testClassicPackageInsideNamespacePackagePyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar.baz.boo import x +reveal_type(x) # N: Revealed type is 'builtins.int' +[file xx/foo/bar/baz/boo.py] +x = '' +[file xx/foo/bar/baz/__init__.py] +[file yy/foo/bar/baz/boo.py] +x = 0 +[file yy/foo/bar/__init__.py] +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + [case testNamespacePackagePlainImport] # flags: --namespace-packages import foo.bar.baz diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 1ccdbbce5760..4c0193bf353c 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -1764,6 +1764,34 @@ strict_optional = True \[mypy-b] strict_optional = False +[case testNewAnalyzerTypeArgBoundCheckWithStrictOptionaPyprojectTOMLl] +# flags: --config-file tmp/pyproject.toml +import a + +[file b.py] +from typing import TypeVar, Generic + +x: C[None] +y: C[str] # E: Type argument "builtins.str" of "C" must be a subtype of "builtins.int" +z: C[int] + +T = TypeVar('T', bound=int) +class C(Generic[T]): + pass + +[file a.py] +from b import C + +x: C[None] # E: Type argument "None" of "C" must be a subtype of "builtins.int" +y: C[str] # E: Type argument "builtins.str" of "C" must be a subtype of "builtins.int" +z: C[int] + +[file pyproject.toml] +\[tool.mypy-a] +strict_optional = true +\[tool.mypy-b] +strict_optional = false + [case testNewAnalyzerProperty] class A: @property diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 8d054595d685..8a91b9ddc78c 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -832,3 +832,28 @@ main:4: error: Argument 1 to "asdf" has incompatible type "List[str]"; expected main:4: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance main:4: note: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] + +[case testStrictOptionalCovarianceCrossModulePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from a import asdf +x = ["lol"] +asdf(x) + +[file a.py] +from typing import List, Optional + +def asdf(x: List[Optional[str]]) -> None: + pass + +x = ["lol"] +asdf(x) + +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-a] +strict_optional = false +[out] +main:4: error: Argument 1 to "asdf" has incompatible type "List[str]"; expected "List[Optional[str]]" +main:4: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance +main:4: note: Consider using "Sequence" instead, which is covariant +[builtins fixtures/list.pyi] diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 176069424930..02009753735c 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -152,6 +152,18 @@ def f(): except ZeroDivisionError, err: print err +[case testConfigFilePyprojectTOML] +# cmd: mypy --config-file pyproject.toml main.py +[file pyproject.toml] +\[tool.mypy] +python_version = 2.7 +[file main.py] +def f(): + try: + 1/0 + except ZeroDivisionError, err: + print err + [case testErrorContextConfig] # cmd: mypy main.py [file mypy.ini] @@ -164,6 +176,18 @@ def f() -> None: main.py: note: In function "f": main.py:2: error: Unsupported operand types for + ("int" and "str") +[case testErrorContextConfigPyprojectTOML] +# cmd: mypy main.py +[file pyproject.toml] +\[tool.mypy] +show_error_context = true +[file main.py] +def f() -> None: + 0 + "" +[out] +main.py: note: In function "f": +main.py:2: error: Unsupported operand types for + ("int" and "str") + [case testAltConfigFile] # cmd: mypy --config-file config.ini main.py [file config.ini] @@ -184,6 +208,14 @@ warn_unused_ignores = True [file main.py] # type: ignore +[case testNoConfigFilePyprojectTOML] +# cmd: mypy main.py --config-file= +[file pyproject.toml] +\[tool.mypy] +warn_unused_ignores = true +[file main.py] +# type: ignore + [case testPerFileConfigSection] # cmd: mypy x.py y.py z.py [file mypy.ini] @@ -213,6 +245,35 @@ z.py:1: error: Function is missing a type annotation z.py:4: error: Call to untyped function "f" in typed context x.py:1: error: Function is missing a type annotation +[case testPerFileConfigSectionPyprojectTOML] +# cmd: mypy x.py y.py z.py +[file pyproject.toml] +\[tool.mypy] +disallow_untyped_defs = true +\[tool.mypy-y] +disallow_untyped_defs = false +\[tool.mypy-z] +disallow_untyped_calls = true +[file x.py] +def f(a): + pass +def g(a: int) -> int: + return f(a) +[file y.py] +def f(a): + pass +def g(a: int) -> int: + return f(a) +[file z.py] +def f(a): + pass +def g(a: int) -> int: + return f(a) +[out] +z.py:1: error: Function is missing a type annotation +z.py:4: error: Call to untyped function "f" in typed context +x.py:1: error: Function is missing a type annotation + [case testPerFileConfigSectionMultipleMatchesDisallowed] # cmd: mypy xx.py xy.py yx.py yy.py [file mypy.ini] @@ -238,6 +299,31 @@ mypy.ini: [mypy-*x*]: Patterns must be fully-qualified module names, optionally mypy.ini: [mypy-*y*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) == Return code: 0 +[case testPerFileConfigSectionMultipleMatchesDisallowedPyprojectTOML] +# cmd: mypy xx.py xy.py yx.py yy.py +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-*x*'] +disallow_untyped_defs = true +\[tool.'mypy-*y*'] +disallow_untyped_calls = true +[file xx.py] +def f(a): pass +def g(a: int) -> int: return f(a) +[file xy.py] +def f(a): pass +def g(a: int) -> int: return f(a) +[file yx.py] +def f(a): pass +def g(a: int) -> int: return f(a) +[file yy.py] +def f(a): pass +def g(a: int) -> int: return f(a) +[out] +pyproject.toml: [mypy-*x*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) +pyproject.toml: [mypy-*y*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) +== Return code: 0 + [case testMultipleGlobConfigSection] # cmd: mypy x.py y.py z.py [file mypy.ini] @@ -254,6 +340,22 @@ def f(a): pass z.py:1: error: Function is missing a type annotation x.py:1: error: Function is missing a type annotation +[case testMultipleGlobConfigSectionPyprojectTOML] +# cmd: mypy x.py y.py z.py +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-x.*,z.*'] +disallow_untyped_defs = true +[file x.py] +def f(a): pass +[file y.py] +def f(a): pass +[file z.py] +def f(a): pass +[out] +z.py:1: error: Function is missing a type annotation +x.py:1: error: Function is missing a type annotation + [case testConfigErrorNoSection] # cmd: mypy -c pass [file mypy.ini] @@ -261,6 +363,13 @@ x.py:1: error: Function is missing a type annotation mypy.ini: No [mypy] section in config file == Return code: 0 +[case testConfigErrorNoSectionPyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +[out] +pyproject.toml: No [mypy] section in config file +== Return code: 0 + [case testConfigErrorUnknownFlag] # cmd: mypy -c pass [file mypy.ini] @@ -270,6 +379,15 @@ bad = 0 mypy.ini: [mypy]: Unrecognized option: bad = 0 == Return code: 0 +[case testConfigErrorUnknownFlagPyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +bad = 0 +[out] +pyproject.toml: [mypy]: Unrecognized option: bad = 0 +== Return code: 0 + [case testConfigErrorBadFlag] # cmd: mypy a.py [file mypy.ini] @@ -282,6 +400,18 @@ def f(): mypy.ini: [mypy]: Unrecognized option: disallow-untyped-defs = True == Return code: 0 +[case testConfigErrorBadFlagPyprojectTOML] +# cmd: mypy a.py +[file pyproject.toml] +\[tool.mypy] +disallow-untyped-defs = true +[file a.py] +def f(): + pass +[out] +pyproject.toml: [mypy]: Unrecognized option: disallow-untyped-defs = True +== Return code: 0 + [case testConfigErrorBadBoolean] # cmd: mypy -c pass [file mypy.ini] @@ -291,6 +421,15 @@ ignore_missing_imports = nah mypy.ini: [mypy]: ignore_missing_imports: Not a boolean: nah == Return code: 0 +[case testConfigErrorBadBooleanPyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = "nah" +[out] +pyproject.toml: [mypy]: ignore_missing_imports: Not a boolean: nah +== Return code: 0 + [case testConfigErrorNotPerFile] # cmd: mypy -c pass [file mypy.ini] @@ -301,6 +440,16 @@ python_version = 3.4 mypy.ini: [mypy-*]: Per-module sections should only specify per-module flags (python_version) == Return code: 0 +[case testConfigErrorNotPerFilePyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-*'] +python_version = 3.4 +[out] +pyproject.toml: [mypy-*]: Per-module sections should only specify per-module flags (python_version) +== Return code: 0 + [case testConfigMypyPath] # cmd: mypy file.py [file mypy.ini] @@ -326,6 +475,29 @@ file.py:1: error: Cannot find implementation or library stub for module named "n file.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports file.py:6: error: Argument 1 to "foo" has incompatible type "str"; expected "int" +[case testConfigMypyPathPyprojectTOML] +# cmd: mypy file.py +[file pyproject.toml] +\[tool.mypy] +mypy_path = "foo:bar,baz" +[file foo/foo.pyi] +def foo(x: int) -> str: ... +[file bar/bar.pyi] +def bar(x: str) -> list: ... +[file baz/baz.pyi] +def baz(x: list) -> dict: ... +[file file.py] +import no_stubs +from foo import foo +from bar import bar +from baz import baz +baz(bar(foo(42))) +baz(bar(foo('oof'))) +[out] +file.py:1: error: Cannot find implementation or library stub for module named "no_stubs" +file.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +file.py:6: error: Argument 1 to "foo" has incompatible type "str"; expected "int" + [case testIgnoreErrorsConfig] # cmd: mypy x.py y.py [file mypy.ini] @@ -339,6 +511,19 @@ ignore_errors = True [out] y.py:1: error: Unsupported operand types for + ("str" and "int") +[case testIgnoreErrorsConfigPyprojectTOML] +# cmd: mypy x.py y.py +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-x] +ignore_errors = true +[file x.py] +"" + 0 +[file y.py] +"" + 0 +[out] +y.py:1: error: Unsupported operand types for + ("str" and "int") + [case testConfigFollowImportsNormal] # cmd: mypy main.py [file main.py] @@ -363,6 +548,30 @@ main.py:6: error: Unsupported operand types for + ("int" and "str") main.py:7: error: Module has no attribute "y" main.py:8: error: Unsupported operand types for + (Module and "int") +[case testConfigFollowImportsNormalPyprojectTOML] +# cmd: mypy main.py +[file main.py] +from a import x +x + 0 +x + '' # E +import a +a.x + 0 +a.x + '' # E +a.y # E +a + 0 # E +[file pyproject.toml] +\[tool.mypy] +follow_imports = "normal" +[file a.py] +x = 0 +x += '' # Error reported here +[out] +a.py:2: error: Unsupported operand types for + ("int" and "str") +main.py:3: error: Unsupported operand types for + ("int" and "str") +main.py:6: error: Unsupported operand types for + ("int" and "str") +main.py:7: error: Module has no attribute "y" +main.py:8: error: Unsupported operand types for + (Module and "int") + [case testConfigFollowImportsSilent] # cmd: mypy main.py [file main.py] @@ -384,6 +593,27 @@ main.py:4: error: Unsupported operand types for + ("int" and "str") main.py:5: error: Module has no attribute "y" main.py:6: error: Unsupported operand types for + (Module and "int") +[case testConfigFollowImportsSilentPyprojectTOML] +# cmd: mypy main.py +[file main.py] +from a import x +x + '' +import a +a.x + '' +a.y +a + 0 +[file pyproject.toml] +\[tool.mypy] +follow_imports = "silent" +[file a.py] +x = 0 +x += '' # No error reported +[out] +main.py:2: error: Unsupported operand types for + ("int" and "str") +main.py:4: error: Unsupported operand types for + ("int" and "str") +main.py:5: error: Module has no attribute "y" +main.py:6: error: Unsupported operand types for + (Module and "int") + [case testConfigFollowImportsSkip] # cmd: mypy main.py [file main.py] @@ -400,6 +630,22 @@ follow_imports = skip main.py:2: note: Revealed type is 'Any' main.py:4: note: Revealed type is 'Any' +[case testConfigFollowImportsSkipPyprojectTOML] +# cmd: mypy main.py +[file main.py] +from a import x +reveal_type(x) # Expect Any +import a +reveal_type(a.x) # Expect Any +[file pyproject.toml] +\[tool.mypy] +follow_imports = "skip" +[file a.py] +/ # No error reported +[out] +main.py:2: note: Revealed type is 'Any' +main.py:4: note: Revealed type is 'Any' + [case testConfigFollowImportsError] # cmd: mypy main.py [file main.py] @@ -418,6 +664,24 @@ main.py:1: note: (Using --follow-imports=error, module not passed on command lin main.py:2: note: Revealed type is 'Any' main.py:4: note: Revealed type is 'Any' +[case testConfigFollowImportsErrorPyprojectTOML] +# cmd: mypy main.py +[file main.py] +from a import x +reveal_type(x) # Expect Any +import a # Error reported here +reveal_type(a.x) # Expect Any +[file pyproject.toml] +\[tool.mypy] +follow_imports = "error" +[file a.py] +/ # No error reported +[out] +main.py:1: error: Import of 'a' ignored +main.py:1: note: (Using --follow-imports=error, module not passed on command line) +main.py:2: note: Revealed type is 'Any' +main.py:4: note: Revealed type is 'Any' + [case testConfigFollowImportsSelective] # cmd: mypy main.py [file mypy.ini] @@ -458,6 +722,46 @@ main.py:6: note: Revealed type is 'builtins.int' main.py:7: note: Revealed type is 'Any' main.py:8: note: Revealed type is 'Any' +[case testConfigFollowImportsSelectivePyprojectTOML] +# cmd: mypy main.py +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-normal] +follow_imports = "normal" +\[tool.mypy-silent] +follow_imports = "silent" +\[tool.mypy-skip] +follow_imports = "skip" +\[tool.mypy-error] +follow_imports = "error" +[file main.py] +import normal +import silent +import skip +import error +reveal_type(normal.x) +reveal_type(silent.x) +reveal_type(skip) +reveal_type(error) +[file normal.py] +x = 0 +x += '' +[file silent.py] +x = 0 +x += '' +[file skip.py] +bla bla +[file error.py] +bla bla +[out] +normal.py:2: error: Unsupported operand types for + ("int" and "str") +main.py:4: error: Import of 'error' ignored +main.py:4: note: (Using --follow-imports=error, module not passed on command line) +main.py:5: note: Revealed type is 'builtins.int' +main.py:6: note: Revealed type is 'builtins.int' +main.py:7: note: Revealed type is 'Any' +main.py:8: note: Revealed type is 'Any' + [case testConfigFollowImportsInvalid] # cmd: mypy main.py [file mypy.ini] @@ -468,6 +772,16 @@ follow_imports =True mypy.ini: [mypy]: follow_imports: invalid choice 'True' (choose from 'normal', 'silent', 'skip', 'error') == Return code: 0 +[case testConfigFollowImportsInvalidPyprojectTOML] +# cmd: mypy main.py +[file pyproject.toml] +\[tool.mypy] +follow_imports = true +[file main.py] +[out] +pyproject.toml: [mypy]: follow_imports: invalid choice 'True' (choose from 'normal', 'silent', 'skip', 'error') +== Return code: 0 + [case testConfigSilentMissingImportsOff] # cmd: mypy main.py [file main.py] @@ -481,6 +795,19 @@ main.py:1: error: Cannot find implementation or library stub for module named "m main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports main.py:2: note: Revealed type is 'Any' +[case testConfigSilentMissingImportsOffPyprojectTOML] +# cmd: mypy main.py +[file main.py] +import missing # Expect error here +reveal_type(missing.x) # Expect Any +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = false +[out] +main.py:1: error: Cannot find implementation or library stub for module named "missing" +main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +main.py:2: note: Revealed type is 'Any' + [case testConfigSilentMissingImportsOn] # cmd: mypy main.py [file main.py] @@ -492,6 +819,16 @@ ignore_missing_imports = True [out] main.py:2: note: Revealed type is 'Any' +[case testConfigSilentMissingImportsOnPyprojectTOML] +# cmd: mypy main.py +[file main.py] +import missing # No error here +reveal_type(missing.x) # Expect Any +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = true +[out] +main.py:2: note: Revealed type is 'Any' [case testFailedImportOnWrongCWD] # cmd: mypy main.py @@ -531,6 +868,14 @@ main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#mis x_bad = 0 [out] +[case testConfigNoErrorForUnknownXFlagInSubsectionPyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-foo] +x_bad = 0 +[out] + [case testDotInFilenameOKScript] # cmd: mypy a.b.py c.d.pyi [file a.b.py] @@ -568,6 +913,15 @@ python_version = 1.0 mypy.ini: [mypy]: python_version: Python major version '1' out of range (must be 2 or 3) == Return code: 0 +[case testPythonVersionTooOld10PyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 1.0 +[out] +pyproject.toml: [mypy]: python_version: Python major version '1' out of range (must be 2 or 3) +== Return code: 0 + [case testPythonVersionTooOld26] # cmd: mypy -c pass [file mypy.ini] @@ -577,6 +931,15 @@ python_version = 2.6 mypy.ini: [mypy]: python_version: Python 2.6 is not supported (must be 2.7) == Return code: 0 +[case testPythonVersionTooOld26PyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 2.6 +[out] +pyproject.toml: [mypy]: python_version: Python 2.6 is not supported (must be 2.7) +== Return code: 0 + [case testPythonVersionTooOld33] # cmd: mypy -c pass [file mypy.ini] @@ -586,6 +949,15 @@ python_version = 3.3 mypy.ini: [mypy]: python_version: Python 3.3 is not supported (must be 3.4 or higher) == Return code: 0 +[case testPythonVersionTooOld33PyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 3.3 +[out] +pyproject.toml: [mypy]: python_version: Python 3.3 is not supported (must be 3.4 or higher) +== Return code: 0 + [case testPythonVersionTooNew28] # cmd: mypy -c pass [file mypy.ini] @@ -595,6 +967,15 @@ python_version = 2.8 mypy.ini: [mypy]: python_version: Python 2.8 is not supported (must be 2.7) == Return code: 0 +[case testPythonVersionTooNew28PyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 2.8 +[out] +pyproject.toml: [mypy]: python_version: Python 2.8 is not supported (must be 2.7) +== Return code: 0 + [case testPythonVersionTooNew40] # cmd: mypy -c pass [file mypy.ini] @@ -604,6 +985,15 @@ python_version = 4.0 mypy.ini: [mypy]: python_version: Python major version '4' out of range (must be 2 or 3) == Return code: 0 +[case testPythonVersionTooNew40PyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 4.0 +[out] +pyproject.toml: [mypy]: python_version: Python major version '4' out of range (must be 2 or 3) +== Return code: 0 + [case testPythonVersionAccepted27] # cmd: mypy -c pass [file mypy.ini] @@ -611,6 +1001,13 @@ mypy.ini: [mypy]: python_version: Python major version '4' out of range (must be python_version = 2.7 [out] +[case testPythonVersionAccepted27PyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 2.7 +[out] + [case testPythonVersionAccepted34] # cmd: mypy -c pass [file mypy.ini] @@ -618,6 +1015,13 @@ python_version = 2.7 python_version = 3.4 [out] +[case testPythonVersionAccepted34PyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 3.4 +[out] + [case testPythonVersionAccepted36] # cmd: mypy -c pass [file mypy.ini] @@ -625,6 +1029,13 @@ python_version = 3.4 python_version = 3.6 [out] +[case testPythonVersionAccepted36PyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 3.6 +[out] + -- This should be a dumping ground for tests of plugins that are sensitive to -- typeshed changes. [case testTypeshedSensitivePlugins] @@ -670,6 +1081,29 @@ m.py:5: error: Implicit generic "Any". Use "typing.Dict" and specify generic par m.py:6: error: Implicit generic "Any". Use "typing.Set" and specify generic parameters m.py:7: error: Implicit generic "Any". Use "typing.FrozenSet" and specify generic parameters +[case testDisallowAnyGenericsBuiltinCollectionsPyprojectTOML] +# cmd: mypy m.py +[file pyproject.toml] +\[tool.mypy] +python_version = 3.6 +\[tool.mypy-m] +disallow_any_generics = true + +[file m.py] +s = tuple([1, 2, 3]) # no error + +def f(t: tuple) -> None: pass +def g() -> list: pass +def h(s: dict) -> None: pass +def i(s: set) -> None: pass +def j(s: frozenset) -> None: pass +[out] +m.py:3: error: Implicit generic "Any". Use "typing.Tuple" and specify generic parameters +m.py:4: error: Implicit generic "Any". Use "typing.List" and specify generic parameters +m.py:5: error: Implicit generic "Any". Use "typing.Dict" and specify generic parameters +m.py:6: error: Implicit generic "Any". Use "typing.Set" and specify generic parameters +m.py:7: error: Implicit generic "Any". Use "typing.FrozenSet" and specify generic parameters + [case testDisallowAnyGenericsTypingCollections] # cmd: mypy m.py [file mypy.ini] @@ -692,6 +1126,28 @@ m.py:5: error: Missing type parameters for generic type "Dict" m.py:6: error: Missing type parameters for generic type "Set" m.py:7: error: Missing type parameters for generic type "FrozenSet" +[case testDisallowAnyGenericsTypingCollectionsPyprojectTOML] +# cmd: mypy m.py +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-m] +disallow_any_generics = true + +[file m.py] +from typing import Tuple, List, Dict, Set, FrozenSet + +def f(t: Tuple) -> None: pass +def g() -> List: pass +def h(s: Dict) -> None: pass +def i(s: Set) -> None: pass +def j(s: FrozenSet) -> None: pass +[out] +m.py:3: error: Missing type parameters for generic type "Tuple" +m.py:4: error: Missing type parameters for generic type "List" +m.py:5: error: Missing type parameters for generic type "Dict" +m.py:6: error: Missing type parameters for generic type "Set" +m.py:7: error: Missing type parameters for generic type "FrozenSet" + [case testSectionInheritance] # cmd: mypy a [file a/__init__.py] @@ -727,6 +1183,41 @@ ignore_errors = False a/b/c/d/e/__init__.py:2: error: Missing type parameters for generic type "List" a/b/c/d/e/__init__.py:3: error: Argument 1 to "g" has incompatible type "None"; expected "List[Any]" +[case testSectionInheritancePyprojectTOML] +# cmd: mypy a +[file a/__init__.py] +0() +[file a/foo.py] +0() +[file a/b/__init__.py] +[file a/b/c/__init__.py] +0() +[file a/b/c/d/__init__.py] +[file a/b/c/d/e/__init__.py] +from typing import List +def g(x: List) -> None: pass +g(None) +[file pyproject.toml] +\[tool.mypy] +allow_any_generics = true +\[tool.'mypy-a.*'] +ignore_errors = true +\[tool.'mypy-a.b.*'] +disallow_any_generics = true +ignore_errors = true +\[tool.'mypy-a.b.c.*'] +ignore_errors = true +\[tool.'mypy-a.b.c.d.*'] +ignore_errors = true +\[tool.'mypy-a.b.c.d.e.*'] +ignore_errors = true +strict_optional = true +\[tool.'mypy-a.b.c.d.e'] +ignore_errors = false +[out] +a/b/c/d/e/__init__.py:2: error: Missing type parameters for generic type "List" +a/b/c/d/e/__init__.py:3: error: Argument 1 to "g" has incompatible type "None"; expected "List[Any]" + [case testDisallowUntypedDefsAndGenerics] # cmd: mypy a.py [file mypy.ini] @@ -739,6 +1230,18 @@ def get_tasks(self): [out] a.py:1: error: Function is missing a return type annotation +[case testDisallowUntypedDefsAndGenericsPyprojectTOML] +# cmd: mypy a.py +[file pyproject.toml] +\[tool.mypy] +disallow_untyped_defs = true +disallow_any_generics = true +[file a.py] +def get_tasks(self): + return 'whatever' +[out] +a.py:1: error: Function is missing a return type annotation + [case testMissingFile] # cmd: mypy nope.py [out] @@ -798,6 +1301,19 @@ def bar(a: int, b: int) -> str: [out] src/anamespace/foo/bar.py:2: error: Incompatible return value type (got "int", expected "str") +[case testSrcPEP420PackagesPyprojectTOML] +# cmd: mypy -p anamespace --namespace-packages +[file pyproject.toml] +\[tool.mypy] +mypy_path = "src" +[file src/setup.cfg] +[file src/anamespace/foo/__init__.py] +[file src/anamespace/foo/bar.py] +def bar(a: int, b: int) -> str: + return a + b +[out] +src/anamespace/foo/bar.py:2: error: Incompatible return value type (got "int", expected "str") + [case testNestedPEP420Packages] # cmd: mypy -p pkg --namespace-packages [file pkg/a1/b/c/d/e.py] @@ -832,6 +1348,20 @@ math.frobnicate() main.py:1: error: Import of 'math' ignored main.py:1: note: (Using --follow-imports=error, module not passed on command line) +[case testFollowImportStubsPyprojectTOML1] +# cmd: mypy main.py +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-math.*'] +follow_imports = "error" +follow_imports_for_stubs = true +[file main.py] +import math +math.frobnicate() +[out] +main.py:1: error: Import of 'math' ignored +main.py:1: note: (Using --follow-imports=error, module not passed on command line) + [case testFollowImportStubs2] # cmd: mypy main.py [file mypy.ini] @@ -843,6 +1373,17 @@ follow_imports_for_stubs = True import math math.frobnicate() +[case testFollowImportStubsPyprojectTOML2] +# cmd: mypy main.py +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-math.*'] +follow_imports = "skip" +follow_imports_for_stubs = true +[file main.py] +import math +math.frobnicate() + [case testShadowFile1] # cmd: mypy --shadow-file source.py shadow.py source.py [file source.py] @@ -910,6 +1451,34 @@ incremental = False Warning: unused section(s) in mypy.ini: [mypy-bar], [mypy-baz.*], [mypy-emarg.*], [mypy-emarg.hatch], [mypy-a.*.c], [mypy-a.x.b] == Return code: 0 +[case testConfigWarnUnusedSectionPyprojectTOML1] +# cmd: mypy foo.py quux.py spam/eggs.py +[file pyproject.toml] +\[tool.mypy] +warn_unused_configs = true +incremental = false +\[tool.mypy-bar] +\[tool.mypy-foo] +\[tool.'mypy-baz.*'] +\[tool.'mypy-quux.*'] +\[tool.'mypy-spam.*'] +\[tool.'mypy-spam.eggs'] +\[tool.'mypy-emarg.*'] +\[tool.'mypy-emarg.hatch'] +# Currently we don't treat an unstructured pattern like a.*.b as unused +# if it matches another section (like a.x.b). This would be reasonable +# to change. ' +\[tool.'mypy-a.*.b'] +\[tool.'mypy-a.*.c'] +\[tool.'mypy-a.x.b'] +[file foo.py] +[file quux.py] +[file spam/__init__.py] +[file spam/eggs.py] +[out] +Warning: unused section(s) in pyproject.toml: [mypy-bar], [mypy-baz.*], [mypy-emarg.*], [mypy-emarg.hatch], [mypy-a.*.c], [mypy-a.x.b] +== Return code: 0 + [case testConfigUnstructuredGlob] # cmd: mypy emarg foo [file mypy.ini] @@ -942,6 +1511,38 @@ foo/lol.py:1: error: Name 'fail' is not defined emarg/foo.py:1: error: Name 'fail' is not defined emarg/hatch/villip/mankangulisk.py:1: error: Name 'fail' is not defined +[case testConfigUnstructuredGlobPyprojectTOML] +# cmd: mypy emarg foo +[file pyproject.toml] +\[tool.mypy] +ignore_errors = true +\[tool.'mypy-*.lol'] +ignore_errors = false +\[tool.'mypy-emarg.*'] +ignore_errors = false +\[tool.'mypy-emarg.*.villip.*'] +ignore_errors = true +\[tool.'mypy-emarg.hatch.villip.mankangulisk'] +ignore_errors = false +[file emarg/__init__.py] +[file emarg/foo.py] +fail +[file emarg/villip.py] +fail +[file emarg/hatch/__init__.py] +[file emarg/hatch/villip/__init__.py] +[file emarg/hatch/villip/nus.py] +fail +[file emarg/hatch/villip/mankangulisk.py] +fail +[file foo/__init__.py] +[file foo/lol.py] +fail +[out] +foo/lol.py:1: error: Name 'fail' is not defined +emarg/foo.py:1: error: Name 'fail' is not defined +emarg/hatch/villip/mankangulisk.py:1: error: Name 'fail' is not defined + [case testPackageRootEmpty] # cmd: mypy --package-root= a/b/c.py main.py [file a/b/c.py] @@ -990,6 +1591,22 @@ fail b.py:1: error: Name 'fail' is not defined a.py:1: error: Name 'fail' is not defined +[case testTOMLFiles] +# cmd: mypy +[file pyproject.toml] +\[tool.mypy] +files = [ + "a.py", + "b.py" +] +[file a.py] +fail +[file b.py] +fail +[out] +b.py:1: error: Name 'fail' is not defined +a.py:1: error: Name 'fail' is not defined + [case testIniFilesGlobbing] # cmd: mypy [file mypy.ini] @@ -1003,6 +1620,19 @@ fail a/b.py:1: error: Name 'fail' is not defined c.py:1: error: Name 'fail' is not defined +[case testTOMLFilesGlobbing] +# cmd: mypy +[file pyproject.toml] +\[tool.mypy] +files = "**/*.py" +[file a/b.py] +fail +[file c.py] +fail +[out] +a/b.py:1: error: Name 'fail' is not defined +c.py:1: error: Name 'fail' is not defined + [case testIniFilesCmdlineOverridesConfig] # cmd: mypy override.py [file mypy.ini] @@ -1012,6 +1642,15 @@ files = config.py mypy: can't read file 'override.py': No such file or directory == Return code: 2 +[case testTomlFilesCmdlineOverridesConfig] +# cmd: mypy override.py +[file pyproject.toml] +\[tool.mypy] +files = "config.py" +[out] +mypy: can't read file 'override.py': No such file or directory +== Return code: 2 + [case testErrorSummaryOnSuccess] # cmd: mypy --error-summary good.py [file good.py] @@ -1140,6 +1779,21 @@ import foo.bar src/foo/bar.py: error: Source file found twice under different module names: 'src.foo.bar' and 'foo.bar' == Return code: 2 +[case testDuplicateModulesPyprojectTOML] +# cmd: mypy src +[file pyproject.toml] +\[tool.mypy] +mypy_path = "src" +[file src/__init__.py] +[file src/a.py] +import foo.bar +[file src/foo/__init__.py] +[file src/foo/bar.py] +1+'x' +[out] +src/foo/bar.py: error: Source file found twice under different module names: 'src.foo.bar' and 'foo.bar' +== Return code: 2 + [case testEnableInvalidErrorCode] # cmd: mypy --enable-error-code YOLO test.py [file test.py] @@ -1257,6 +1911,17 @@ y = 0 # type: str [out] pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") +[case testCmdlinePackageAndTomlFiles] +# cmd: mypy -p pkg +[file pyproject.toml] +\[tool.mypy] +files = "file" +[file pkg.py] +x = 0 # type: str +[file file.py] +y = 0 # type: str +[out] +pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") [case testCmdlineModuleAndIniFiles] # cmd: mypy -m pkg @@ -1269,3 +1934,15 @@ x = 0 # type: str y = 0 # type: str [out] pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") + +[case testCmdlineModuleAndTomlFiles] +# cmd: mypy -m pkg +[file pyproject.toml] +\[tool.mypy] +files = "file" +[file pkg.py] +x = 0 # type: str +[file file.py] +y = 0 # type: str +[out] +pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index d7dad66b5ef3..c3cc7fd3b1be 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -35,6 +35,13 @@ Daemon started \[mypy] files = ./foo.py +[case testDaemonIgnoreConfigFilesPyprojectTOML] +$ dmypy start -- --follow-imports=error +Daemon started +[file pyproject.toml] +\[tool.mypy] +files = "./foo.py" + [case testDaemonRunRestart] $ dmypy run -- foo.py --follow-imports=error Daemon started @@ -106,6 +113,27 @@ from mypy.plugin import Plugin class Dummy(Plugin): pass def plugin(version): return Dummy +[case testDaemonRunRestartPluginVersionPyprojectTOML] +$ dmypy run -- foo.py --no-error-summary +Daemon started +$ {python} -c "print(' ')" >> plug.py +$ dmypy run -- foo.py --no-error-summary +Restarting: plugins changed +Daemon stopped +Daemon started +$ dmypy stop +Daemon stopped +[file pyproject.toml] +\[tool.mypy] +follow_imports = "error" +plugins = "plug.py" +[file foo.py] +pass +[file plug.py] +from mypy.plugin import Plugin +class Dummy(Plugin): pass +def plugin(version): return Dummy + [case testDaemonRunRestartGlobs] -- Ensure dmypy is not restarted if the configuration doesn't change and it contains globs -- Note: Backslash path separator in output is replaced with forward slash so the same test succeeds on Windows as well @@ -139,6 +167,39 @@ fail [file foo/ok.py] a: int = 1 +[case testDaemonRunRestartGlobsPyprojectTOML] +-- Ensure dmypy is not restarted if the configuration doesn't change and it contains globs +-- Note: Backslash path separator in output is replaced with forward slash so the same test succeeds on Windows as well +$ dmypy run -- foo --follow-imports=error --python-version=3.6 +Daemon started +foo/lol.py:1: error: Name 'fail' is not defined +Found 1 error in 1 file (checked 3 source files) +== Return code: 1 +$ dmypy run -- foo --follow-imports=error --python-version=3.6 +foo/lol.py:1: error: Name 'fail' is not defined +Found 1 error in 1 file (checked 3 source files) +== Return code: 1 +$ {python} -c "print('[mypy]')" >mypy.ini +$ {python} -c "print('ignore_errors=True')" >>mypy.ini +$ dmypy run -- foo --follow-imports=error --python-version=3.6 +Restarting: configuration changed +Daemon stopped +Daemon started +Success: no issues found in 3 source files +$ dmypy stop +Daemon stopped +[file pyproject.toml] +\[tool.mypy] +ignore_errors = true +\[tool.'mypy-*.lol'] +ignore_errors = false + +[file foo/__init__.py] +[file foo/lol.py] +fail +[file foo/ok.py] +a: int = 1 + [case testDaemonStatusKillRestartRecheck] $ dmypy status No status file found diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index ee3519478c45..46b69524c6cd 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -1146,6 +1146,45 @@ plugins=/test-data/unit/plugins/dyn_class.py __main__.Diff __main__.Diff.x +[case testDynamicBasePluginDiffPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import declarative_base, Column, Instr + +Base = declarative_base() + +class Model(Base): + x: Column[int] +class Other: + x: Column[int] +class Diff: + x: Column[int] +[file next.py] +from mod import declarative_base, Column, Instr + +Base = declarative_base() + +class Model(Base): + x: Column[int] +class Other: + x: Column[int] +class Diff(Base): + x: Column[int] +[file mod.py] +from typing import Generic, TypeVar +def declarative_base(): ... + +T = TypeVar('T') + +class Column(Generic[T]): ... +class Instr(Generic[T]): ... + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/dyn_class.py" +[out] +__main__.Diff +__main__.Diff.x + [case testLiteralTriggersVar] from typing_extensions import Literal diff --git a/test-data/unit/pep561.test b/test-data/unit/pep561.test index e8cb6927d8b8..6f366cbc0bd8 100644 --- a/test-data/unit/pep561.test +++ b/test-data/unit/pep561.test @@ -11,6 +11,19 @@ ignore_missing_imports = True [out] testTypedPkgNoSitePkgsIgnoredImports.py:6: note: Revealed type is 'Any' +[case testTypedPkgNoSitePkgsIgnoredImportsPyprojectTOML] +# pkgs: typedpkg +# flags: --no-site-packages +from typedpkg.sample import ex +from typedpkg import dne +a = ex(['']) +reveal_type(a) +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = true +[out] +testTypedPkgNoSitePkgsIgnoredImports.py:6: note: Revealed type is 'Any' + [case testTypedPkgSimple] # pkgs: typedpkg from typedpkg.sample import ex @@ -40,6 +53,21 @@ testTypedPkg_config_nositepackages.py:2: note: See https://mypy.readthedocs.io/e testTypedPkg_config_nositepackages.py:3: error: Cannot find implementation or library stub for module named "typedpkg" testTypedPkg_config_nositepackages.py:5: note: Revealed type is 'Any' +[case testTypedPkg_config_nositepackagesPyprojectTOML] +# pkgs: typedpkg +from typedpkg.sample import ex +from typedpkg import dne +a = ex(['']) +reveal_type(a) +[file pyproject.toml] +\[tool.mypy] +no_site_packages = true +[out] +testTypedPkg_config_nositepackages.py:2: error: Cannot find implementation or library stub for module named "typedpkg.sample" +testTypedPkg_config_nositepackages.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +testTypedPkg_config_nositepackages.py:3: error: Cannot find implementation or library stub for module named "typedpkg" +testTypedPkg_config_nositepackages.py:5: note: Revealed type is 'Any' + [case testTypedPkg_args_nositepackages] # pkgs: typedpkg # flags: --no-site-packages diff --git a/test-data/unit/reports.test b/test-data/unit/reports.test index 5e0da3f07a98..b783a05e7302 100644 --- a/test-data/unit/reports.test +++ b/test-data/unit/reports.test @@ -11,6 +11,15 @@ bad_report = . mypy.ini: [mypy]: Unrecognized report type: bad_report == Return code: 0 +[case testConfigErrorUnknownReportPyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +bad_report = "." +[out] +pyproject.toml: [mypy]: Unrecognized report type: bad_report +== Return code: 0 + [case testCoberturaParser] # cmd: mypy --cobertura-xml-report build pkg [file pkg/__init__.py] From 6435a9d19812de0af2127a858bccd38c9133dfeb Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Wed, 17 Mar 2021 11:58:26 -0400 Subject: [PATCH 03/39] Allow pyproject.toml with no mypy section --- mypy/config_parser.py | 2 ++ test-data/unit/check-newsemanal.test | 28 ---------------------------- test-data/unit/cmdline.test | 4 +--- 3 files changed, 3 insertions(+), 31 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 42a36a6ae367..ab94542a9ba9 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -146,6 +146,8 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], parser = {key: value for key, value in toml_data.get('tool', {}).items() if key.split('-')[0] == 'mypy'} + if parser.get('mypy') is None: + continue else: config_parser.read(config_file) parser = {key: value for key, value in config_parser.items()} diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 4c0193bf353c..1ccdbbce5760 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -1764,34 +1764,6 @@ strict_optional = True \[mypy-b] strict_optional = False -[case testNewAnalyzerTypeArgBoundCheckWithStrictOptionaPyprojectTOMLl] -# flags: --config-file tmp/pyproject.toml -import a - -[file b.py] -from typing import TypeVar, Generic - -x: C[None] -y: C[str] # E: Type argument "builtins.str" of "C" must be a subtype of "builtins.int" -z: C[int] - -T = TypeVar('T', bound=int) -class C(Generic[T]): - pass - -[file a.py] -from b import C - -x: C[None] # E: Type argument "None" of "C" must be a subtype of "builtins.int" -y: C[str] # E: Type argument "builtins.str" of "C" must be a subtype of "builtins.int" -z: C[int] - -[file pyproject.toml] -\[tool.mypy-a] -strict_optional = true -\[tool.mypy-b] -strict_optional = false - [case testNewAnalyzerProperty] class A: @property diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 02009753735c..aa4dd81c9dfe 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -363,12 +363,10 @@ x.py:1: error: Function is missing a type annotation mypy.ini: No [mypy] section in config file == Return code: 0 -[case testConfigErrorNoSectionPyprojectTOML] +[case testConfigNoErrorNoSectionPyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] [out] -pyproject.toml: No [mypy] section in config file -== Return code: 0 [case testConfigErrorUnknownFlag] # cmd: mypy -c pass From 9f70b2906ad0d4cc0df1ce7230cabf850e61f291 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Wed, 17 Mar 2021 14:55:57 -0400 Subject: [PATCH 04/39] Move pyproject tests to their own test --- mypy/test/testpyproject.py | 133 +++++++ test-data/unit/cmdline.test | 662 -------------------------------- test-data/unit/pyproject.test | 693 ++++++++++++++++++++++++++++++++++ 3 files changed, 826 insertions(+), 662 deletions(-) create mode 100644 mypy/test/testpyproject.py create mode 100644 test-data/unit/pyproject.test diff --git a/mypy/test/testpyproject.py b/mypy/test/testpyproject.py new file mode 100644 index 000000000000..7cc3ba9e74f9 --- /dev/null +++ b/mypy/test/testpyproject.py @@ -0,0 +1,133 @@ +"""Test cases for pyproject.toml configs. + +To begin we test that "mypy [/]" always recurses down the +whole tree. +""" + +import os +import re +import subprocess +import sys + +from typing import List +from typing import Optional + +from mypy.test.config import test_temp_dir, PREFIX +from mypy.test.data import DataDrivenTestCase, DataSuite +from mypy.test.helpers import ( + assert_string_arrays_equal, normalize_error_messages, check_test_output_files +) + +# Path to Python 3 interpreter +python3_path = sys.executable + +# Files containing test case descriptions. +pyproject_files = [ + 'pyproject.test', +] + + +class PythonPyprojectSuite(DataSuite): + files = pyproject_files + native_sep = True + + def run_case(self, testcase: DataDrivenTestCase) -> None: + for step in [1] + sorted(testcase.output2): + test_python_pyproject(testcase, step) + + +def test_python_pyproject(testcase: DataDrivenTestCase, step: int) -> None: + assert testcase.old_cwd is not None, "test was not properly set up" + # Write the program to a file. + program = '_program.py' + program_path = os.path.join(test_temp_dir, program) + with open(program_path, 'w', encoding='utf8') as file: + for s in testcase.input: + file.write('{}\n'.format(s)) + args = parse_args(testcase.input[0]) + custom_cwd = parse_cwd(testcase.input[1]) if len(testcase.input) > 1 else None + args.append('--show-traceback') + if '--error-summary' not in args: + args.append('--no-error-summary') + # Type check the program. + fixed = [python3_path, '-m', 'mypy'] + env = os.environ.copy() + env.pop('COLUMNS', None) + env['PYTHONPATH'] = PREFIX + process = subprocess.Popen(fixed + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=os.path.join( + test_temp_dir, + custom_cwd or "" + ), + env=env) + outb, errb = process.communicate() + result = process.returncode + # Split output into lines. + out = [s.rstrip('\n\r') for s in str(outb, 'utf8').splitlines()] + err = [s.rstrip('\n\r') for s in str(errb, 'utf8').splitlines()] + + if "PYCHARM_HOSTED" in os.environ: + for pos, line in enumerate(err): + if line.startswith('pydev debugger: '): + # Delete the attaching debugger message itself, plus the extra newline added. + del err[pos:pos + 2] + break + + # Remove temp file. + os.remove(program_path) + # Compare actual output to expected. + if testcase.output_files: + # Ignore stdout, but we insist on empty stderr and zero status. + if err or result: + raise AssertionError( + 'Expected zero status and empty stderr%s, got %d and\n%s' % + (' on step %d' % step if testcase.output2 else '', + result, '\n'.join(err + out))) + check_test_output_files(testcase, step) + else: + if testcase.normalize_output: + out = normalize_error_messages(err + out) + obvious_result = 1 if out else 0 + if obvious_result != result: + out.append('== Return code: {}'.format(result)) + expected_out = testcase.output if step == 1 else testcase.output2[step] + # Strip "tmp/" out of the test so that # E: works... + expected_out = [s.replace("tmp" + os.sep, "") for s in expected_out] + assert_string_arrays_equal(expected_out, out, + 'Invalid output ({}, line {}){}'.format( + testcase.file, testcase.line, + ' on step %d' % step if testcase.output2 else '')) + + +def parse_args(line: str) -> List[str]: + """Parse the first line of the program for the command line. + + This should have the form + + # cmd: mypy + + For example: + + # cmd: mypy pkg/ + """ + m = re.match('# cmd: mypy (.*)$', line) + if not m: + return [] # No args; mypy will spit out an error. + return m.group(1).split() + + +def parse_cwd(line: str) -> Optional[str]: + """Parse the second line of the program for the command line. + + This should have the form + + # cwd: + + For example: + + # cwd: main/subdir + """ + m = re.match('# cwd: (.*)$', line) + return m.group(1) if m else None diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index aa4dd81c9dfe..3a47ffd0fa5c 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -152,18 +152,6 @@ def f(): except ZeroDivisionError, err: print err -[case testConfigFilePyprojectTOML] -# cmd: mypy --config-file pyproject.toml main.py -[file pyproject.toml] -\[tool.mypy] -python_version = 2.7 -[file main.py] -def f(): - try: - 1/0 - except ZeroDivisionError, err: - print err - [case testErrorContextConfig] # cmd: mypy main.py [file mypy.ini] @@ -176,18 +164,6 @@ def f() -> None: main.py: note: In function "f": main.py:2: error: Unsupported operand types for + ("int" and "str") -[case testErrorContextConfigPyprojectTOML] -# cmd: mypy main.py -[file pyproject.toml] -\[tool.mypy] -show_error_context = true -[file main.py] -def f() -> None: - 0 + "" -[out] -main.py: note: In function "f": -main.py:2: error: Unsupported operand types for + ("int" and "str") - [case testAltConfigFile] # cmd: mypy --config-file config.ini main.py [file config.ini] @@ -208,14 +184,6 @@ warn_unused_ignores = True [file main.py] # type: ignore -[case testNoConfigFilePyprojectTOML] -# cmd: mypy main.py --config-file= -[file pyproject.toml] -\[tool.mypy] -warn_unused_ignores = true -[file main.py] -# type: ignore - [case testPerFileConfigSection] # cmd: mypy x.py y.py z.py [file mypy.ini] @@ -245,35 +213,6 @@ z.py:1: error: Function is missing a type annotation z.py:4: error: Call to untyped function "f" in typed context x.py:1: error: Function is missing a type annotation -[case testPerFileConfigSectionPyprojectTOML] -# cmd: mypy x.py y.py z.py -[file pyproject.toml] -\[tool.mypy] -disallow_untyped_defs = true -\[tool.mypy-y] -disallow_untyped_defs = false -\[tool.mypy-z] -disallow_untyped_calls = true -[file x.py] -def f(a): - pass -def g(a: int) -> int: - return f(a) -[file y.py] -def f(a): - pass -def g(a: int) -> int: - return f(a) -[file z.py] -def f(a): - pass -def g(a: int) -> int: - return f(a) -[out] -z.py:1: error: Function is missing a type annotation -z.py:4: error: Call to untyped function "f" in typed context -x.py:1: error: Function is missing a type annotation - [case testPerFileConfigSectionMultipleMatchesDisallowed] # cmd: mypy xx.py xy.py yx.py yy.py [file mypy.ini] @@ -299,31 +238,6 @@ mypy.ini: [mypy-*x*]: Patterns must be fully-qualified module names, optionally mypy.ini: [mypy-*y*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) == Return code: 0 -[case testPerFileConfigSectionMultipleMatchesDisallowedPyprojectTOML] -# cmd: mypy xx.py xy.py yx.py yy.py -[file pyproject.toml] -\[tool.mypy] -\[tool.'mypy-*x*'] -disallow_untyped_defs = true -\[tool.'mypy-*y*'] -disallow_untyped_calls = true -[file xx.py] -def f(a): pass -def g(a: int) -> int: return f(a) -[file xy.py] -def f(a): pass -def g(a: int) -> int: return f(a) -[file yx.py] -def f(a): pass -def g(a: int) -> int: return f(a) -[file yy.py] -def f(a): pass -def g(a: int) -> int: return f(a) -[out] -pyproject.toml: [mypy-*x*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) -pyproject.toml: [mypy-*y*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) -== Return code: 0 - [case testMultipleGlobConfigSection] # cmd: mypy x.py y.py z.py [file mypy.ini] @@ -340,22 +254,6 @@ def f(a): pass z.py:1: error: Function is missing a type annotation x.py:1: error: Function is missing a type annotation -[case testMultipleGlobConfigSectionPyprojectTOML] -# cmd: mypy x.py y.py z.py -[file pyproject.toml] -\[tool.mypy] -\[tool.'mypy-x.*,z.*'] -disallow_untyped_defs = true -[file x.py] -def f(a): pass -[file y.py] -def f(a): pass -[file z.py] -def f(a): pass -[out] -z.py:1: error: Function is missing a type annotation -x.py:1: error: Function is missing a type annotation - [case testConfigErrorNoSection] # cmd: mypy -c pass [file mypy.ini] @@ -363,11 +261,6 @@ x.py:1: error: Function is missing a type annotation mypy.ini: No [mypy] section in config file == Return code: 0 -[case testConfigNoErrorNoSectionPyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -[out] - [case testConfigErrorUnknownFlag] # cmd: mypy -c pass [file mypy.ini] @@ -377,15 +270,6 @@ bad = 0 mypy.ini: [mypy]: Unrecognized option: bad = 0 == Return code: 0 -[case testConfigErrorUnknownFlagPyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -bad = 0 -[out] -pyproject.toml: [mypy]: Unrecognized option: bad = 0 -== Return code: 0 - [case testConfigErrorBadFlag] # cmd: mypy a.py [file mypy.ini] @@ -398,18 +282,6 @@ def f(): mypy.ini: [mypy]: Unrecognized option: disallow-untyped-defs = True == Return code: 0 -[case testConfigErrorBadFlagPyprojectTOML] -# cmd: mypy a.py -[file pyproject.toml] -\[tool.mypy] -disallow-untyped-defs = true -[file a.py] -def f(): - pass -[out] -pyproject.toml: [mypy]: Unrecognized option: disallow-untyped-defs = True -== Return code: 0 - [case testConfigErrorBadBoolean] # cmd: mypy -c pass [file mypy.ini] @@ -419,15 +291,6 @@ ignore_missing_imports = nah mypy.ini: [mypy]: ignore_missing_imports: Not a boolean: nah == Return code: 0 -[case testConfigErrorBadBooleanPyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -ignore_missing_imports = "nah" -[out] -pyproject.toml: [mypy]: ignore_missing_imports: Not a boolean: nah -== Return code: 0 - [case testConfigErrorNotPerFile] # cmd: mypy -c pass [file mypy.ini] @@ -438,16 +301,6 @@ python_version = 3.4 mypy.ini: [mypy-*]: Per-module sections should only specify per-module flags (python_version) == Return code: 0 -[case testConfigErrorNotPerFilePyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -\[tool.'mypy-*'] -python_version = 3.4 -[out] -pyproject.toml: [mypy-*]: Per-module sections should only specify per-module flags (python_version) -== Return code: 0 - [case testConfigMypyPath] # cmd: mypy file.py [file mypy.ini] @@ -473,29 +326,6 @@ file.py:1: error: Cannot find implementation or library stub for module named "n file.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports file.py:6: error: Argument 1 to "foo" has incompatible type "str"; expected "int" -[case testConfigMypyPathPyprojectTOML] -# cmd: mypy file.py -[file pyproject.toml] -\[tool.mypy] -mypy_path = "foo:bar,baz" -[file foo/foo.pyi] -def foo(x: int) -> str: ... -[file bar/bar.pyi] -def bar(x: str) -> list: ... -[file baz/baz.pyi] -def baz(x: list) -> dict: ... -[file file.py] -import no_stubs -from foo import foo -from bar import bar -from baz import baz -baz(bar(foo(42))) -baz(bar(foo('oof'))) -[out] -file.py:1: error: Cannot find implementation or library stub for module named "no_stubs" -file.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -file.py:6: error: Argument 1 to "foo" has incompatible type "str"; expected "int" - [case testIgnoreErrorsConfig] # cmd: mypy x.py y.py [file mypy.ini] @@ -509,19 +339,6 @@ ignore_errors = True [out] y.py:1: error: Unsupported operand types for + ("str" and "int") -[case testIgnoreErrorsConfigPyprojectTOML] -# cmd: mypy x.py y.py -[file pyproject.toml] -\[tool.mypy] -\[tool.mypy-x] -ignore_errors = true -[file x.py] -"" + 0 -[file y.py] -"" + 0 -[out] -y.py:1: error: Unsupported operand types for + ("str" and "int") - [case testConfigFollowImportsNormal] # cmd: mypy main.py [file main.py] @@ -546,30 +363,6 @@ main.py:6: error: Unsupported operand types for + ("int" and "str") main.py:7: error: Module has no attribute "y" main.py:8: error: Unsupported operand types for + (Module and "int") -[case testConfigFollowImportsNormalPyprojectTOML] -# cmd: mypy main.py -[file main.py] -from a import x -x + 0 -x + '' # E -import a -a.x + 0 -a.x + '' # E -a.y # E -a + 0 # E -[file pyproject.toml] -\[tool.mypy] -follow_imports = "normal" -[file a.py] -x = 0 -x += '' # Error reported here -[out] -a.py:2: error: Unsupported operand types for + ("int" and "str") -main.py:3: error: Unsupported operand types for + ("int" and "str") -main.py:6: error: Unsupported operand types for + ("int" and "str") -main.py:7: error: Module has no attribute "y" -main.py:8: error: Unsupported operand types for + (Module and "int") - [case testConfigFollowImportsSilent] # cmd: mypy main.py [file main.py] @@ -591,27 +384,6 @@ main.py:4: error: Unsupported operand types for + ("int" and "str") main.py:5: error: Module has no attribute "y" main.py:6: error: Unsupported operand types for + (Module and "int") -[case testConfigFollowImportsSilentPyprojectTOML] -# cmd: mypy main.py -[file main.py] -from a import x -x + '' -import a -a.x + '' -a.y -a + 0 -[file pyproject.toml] -\[tool.mypy] -follow_imports = "silent" -[file a.py] -x = 0 -x += '' # No error reported -[out] -main.py:2: error: Unsupported operand types for + ("int" and "str") -main.py:4: error: Unsupported operand types for + ("int" and "str") -main.py:5: error: Module has no attribute "y" -main.py:6: error: Unsupported operand types for + (Module and "int") - [case testConfigFollowImportsSkip] # cmd: mypy main.py [file main.py] @@ -628,22 +400,6 @@ follow_imports = skip main.py:2: note: Revealed type is 'Any' main.py:4: note: Revealed type is 'Any' -[case testConfigFollowImportsSkipPyprojectTOML] -# cmd: mypy main.py -[file main.py] -from a import x -reveal_type(x) # Expect Any -import a -reveal_type(a.x) # Expect Any -[file pyproject.toml] -\[tool.mypy] -follow_imports = "skip" -[file a.py] -/ # No error reported -[out] -main.py:2: note: Revealed type is 'Any' -main.py:4: note: Revealed type is 'Any' - [case testConfigFollowImportsError] # cmd: mypy main.py [file main.py] @@ -662,24 +418,6 @@ main.py:1: note: (Using --follow-imports=error, module not passed on command lin main.py:2: note: Revealed type is 'Any' main.py:4: note: Revealed type is 'Any' -[case testConfigFollowImportsErrorPyprojectTOML] -# cmd: mypy main.py -[file main.py] -from a import x -reveal_type(x) # Expect Any -import a # Error reported here -reveal_type(a.x) # Expect Any -[file pyproject.toml] -\[tool.mypy] -follow_imports = "error" -[file a.py] -/ # No error reported -[out] -main.py:1: error: Import of 'a' ignored -main.py:1: note: (Using --follow-imports=error, module not passed on command line) -main.py:2: note: Revealed type is 'Any' -main.py:4: note: Revealed type is 'Any' - [case testConfigFollowImportsSelective] # cmd: mypy main.py [file mypy.ini] @@ -720,46 +458,6 @@ main.py:6: note: Revealed type is 'builtins.int' main.py:7: note: Revealed type is 'Any' main.py:8: note: Revealed type is 'Any' -[case testConfigFollowImportsSelectivePyprojectTOML] -# cmd: mypy main.py -[file pyproject.toml] -\[tool.mypy] -\[tool.mypy-normal] -follow_imports = "normal" -\[tool.mypy-silent] -follow_imports = "silent" -\[tool.mypy-skip] -follow_imports = "skip" -\[tool.mypy-error] -follow_imports = "error" -[file main.py] -import normal -import silent -import skip -import error -reveal_type(normal.x) -reveal_type(silent.x) -reveal_type(skip) -reveal_type(error) -[file normal.py] -x = 0 -x += '' -[file silent.py] -x = 0 -x += '' -[file skip.py] -bla bla -[file error.py] -bla bla -[out] -normal.py:2: error: Unsupported operand types for + ("int" and "str") -main.py:4: error: Import of 'error' ignored -main.py:4: note: (Using --follow-imports=error, module not passed on command line) -main.py:5: note: Revealed type is 'builtins.int' -main.py:6: note: Revealed type is 'builtins.int' -main.py:7: note: Revealed type is 'Any' -main.py:8: note: Revealed type is 'Any' - [case testConfigFollowImportsInvalid] # cmd: mypy main.py [file mypy.ini] @@ -770,16 +468,6 @@ follow_imports =True mypy.ini: [mypy]: follow_imports: invalid choice 'True' (choose from 'normal', 'silent', 'skip', 'error') == Return code: 0 -[case testConfigFollowImportsInvalidPyprojectTOML] -# cmd: mypy main.py -[file pyproject.toml] -\[tool.mypy] -follow_imports = true -[file main.py] -[out] -pyproject.toml: [mypy]: follow_imports: invalid choice 'True' (choose from 'normal', 'silent', 'skip', 'error') -== Return code: 0 - [case testConfigSilentMissingImportsOff] # cmd: mypy main.py [file main.py] @@ -793,19 +481,6 @@ main.py:1: error: Cannot find implementation or library stub for module named "m main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports main.py:2: note: Revealed type is 'Any' -[case testConfigSilentMissingImportsOffPyprojectTOML] -# cmd: mypy main.py -[file main.py] -import missing # Expect error here -reveal_type(missing.x) # Expect Any -[file pyproject.toml] -\[tool.mypy] -ignore_missing_imports = false -[out] -main.py:1: error: Cannot find implementation or library stub for module named "missing" -main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -main.py:2: note: Revealed type is 'Any' - [case testConfigSilentMissingImportsOn] # cmd: mypy main.py [file main.py] @@ -817,17 +492,6 @@ ignore_missing_imports = True [out] main.py:2: note: Revealed type is 'Any' -[case testConfigSilentMissingImportsOnPyprojectTOML] -# cmd: mypy main.py -[file main.py] -import missing # No error here -reveal_type(missing.x) # Expect Any -[file pyproject.toml] -\[tool.mypy] -ignore_missing_imports = true -[out] -main.py:2: note: Revealed type is 'Any' - [case testFailedImportOnWrongCWD] # cmd: mypy main.py # cwd: main/subdir1/subdir2 @@ -866,14 +530,6 @@ main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#mis x_bad = 0 [out] -[case testConfigNoErrorForUnknownXFlagInSubsectionPyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -\[tool.mypy-foo] -x_bad = 0 -[out] - [case testDotInFilenameOKScript] # cmd: mypy a.b.py c.d.pyi [file a.b.py] @@ -911,15 +567,6 @@ python_version = 1.0 mypy.ini: [mypy]: python_version: Python major version '1' out of range (must be 2 or 3) == Return code: 0 -[case testPythonVersionTooOld10PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 1.0 -[out] -pyproject.toml: [mypy]: python_version: Python major version '1' out of range (must be 2 or 3) -== Return code: 0 - [case testPythonVersionTooOld26] # cmd: mypy -c pass [file mypy.ini] @@ -929,15 +576,6 @@ python_version = 2.6 mypy.ini: [mypy]: python_version: Python 2.6 is not supported (must be 2.7) == Return code: 0 -[case testPythonVersionTooOld26PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 2.6 -[out] -pyproject.toml: [mypy]: python_version: Python 2.6 is not supported (must be 2.7) -== Return code: 0 - [case testPythonVersionTooOld33] # cmd: mypy -c pass [file mypy.ini] @@ -947,15 +585,6 @@ python_version = 3.3 mypy.ini: [mypy]: python_version: Python 3.3 is not supported (must be 3.4 or higher) == Return code: 0 -[case testPythonVersionTooOld33PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 3.3 -[out] -pyproject.toml: [mypy]: python_version: Python 3.3 is not supported (must be 3.4 or higher) -== Return code: 0 - [case testPythonVersionTooNew28] # cmd: mypy -c pass [file mypy.ini] @@ -965,15 +594,6 @@ python_version = 2.8 mypy.ini: [mypy]: python_version: Python 2.8 is not supported (must be 2.7) == Return code: 0 -[case testPythonVersionTooNew28PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 2.8 -[out] -pyproject.toml: [mypy]: python_version: Python 2.8 is not supported (must be 2.7) -== Return code: 0 - [case testPythonVersionTooNew40] # cmd: mypy -c pass [file mypy.ini] @@ -983,15 +603,6 @@ python_version = 4.0 mypy.ini: [mypy]: python_version: Python major version '4' out of range (must be 2 or 3) == Return code: 0 -[case testPythonVersionTooNew40PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 4.0 -[out] -pyproject.toml: [mypy]: python_version: Python major version '4' out of range (must be 2 or 3) -== Return code: 0 - [case testPythonVersionAccepted27] # cmd: mypy -c pass [file mypy.ini] @@ -999,13 +610,6 @@ pyproject.toml: [mypy]: python_version: Python major version '4' out of range (m python_version = 2.7 [out] -[case testPythonVersionAccepted27PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 2.7 -[out] - [case testPythonVersionAccepted34] # cmd: mypy -c pass [file mypy.ini] @@ -1013,13 +617,6 @@ python_version = 2.7 python_version = 3.4 [out] -[case testPythonVersionAccepted34PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 3.4 -[out] - [case testPythonVersionAccepted36] # cmd: mypy -c pass [file mypy.ini] @@ -1027,13 +624,6 @@ python_version = 3.4 python_version = 3.6 [out] -[case testPythonVersionAccepted36PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 3.6 -[out] - -- This should be a dumping ground for tests of plugins that are sensitive to -- typeshed changes. [case testTypeshedSensitivePlugins] @@ -1079,14 +669,6 @@ m.py:5: error: Implicit generic "Any". Use "typing.Dict" and specify generic par m.py:6: error: Implicit generic "Any". Use "typing.Set" and specify generic parameters m.py:7: error: Implicit generic "Any". Use "typing.FrozenSet" and specify generic parameters -[case testDisallowAnyGenericsBuiltinCollectionsPyprojectTOML] -# cmd: mypy m.py -[file pyproject.toml] -\[tool.mypy] -python_version = 3.6 -\[tool.mypy-m] -disallow_any_generics = true - [file m.py] s = tuple([1, 2, 3]) # no error @@ -1124,28 +706,6 @@ m.py:5: error: Missing type parameters for generic type "Dict" m.py:6: error: Missing type parameters for generic type "Set" m.py:7: error: Missing type parameters for generic type "FrozenSet" -[case testDisallowAnyGenericsTypingCollectionsPyprojectTOML] -# cmd: mypy m.py -[file pyproject.toml] -\[tool.mypy] -\[tool.mypy-m] -disallow_any_generics = true - -[file m.py] -from typing import Tuple, List, Dict, Set, FrozenSet - -def f(t: Tuple) -> None: pass -def g() -> List: pass -def h(s: Dict) -> None: pass -def i(s: Set) -> None: pass -def j(s: FrozenSet) -> None: pass -[out] -m.py:3: error: Missing type parameters for generic type "Tuple" -m.py:4: error: Missing type parameters for generic type "List" -m.py:5: error: Missing type parameters for generic type "Dict" -m.py:6: error: Missing type parameters for generic type "Set" -m.py:7: error: Missing type parameters for generic type "FrozenSet" - [case testSectionInheritance] # cmd: mypy a [file a/__init__.py] @@ -1181,41 +741,6 @@ ignore_errors = False a/b/c/d/e/__init__.py:2: error: Missing type parameters for generic type "List" a/b/c/d/e/__init__.py:3: error: Argument 1 to "g" has incompatible type "None"; expected "List[Any]" -[case testSectionInheritancePyprojectTOML] -# cmd: mypy a -[file a/__init__.py] -0() -[file a/foo.py] -0() -[file a/b/__init__.py] -[file a/b/c/__init__.py] -0() -[file a/b/c/d/__init__.py] -[file a/b/c/d/e/__init__.py] -from typing import List -def g(x: List) -> None: pass -g(None) -[file pyproject.toml] -\[tool.mypy] -allow_any_generics = true -\[tool.'mypy-a.*'] -ignore_errors = true -\[tool.'mypy-a.b.*'] -disallow_any_generics = true -ignore_errors = true -\[tool.'mypy-a.b.c.*'] -ignore_errors = true -\[tool.'mypy-a.b.c.d.*'] -ignore_errors = true -\[tool.'mypy-a.b.c.d.e.*'] -ignore_errors = true -strict_optional = true -\[tool.'mypy-a.b.c.d.e'] -ignore_errors = false -[out] -a/b/c/d/e/__init__.py:2: error: Missing type parameters for generic type "List" -a/b/c/d/e/__init__.py:3: error: Argument 1 to "g" has incompatible type "None"; expected "List[Any]" - [case testDisallowUntypedDefsAndGenerics] # cmd: mypy a.py [file mypy.ini] @@ -1228,18 +753,6 @@ def get_tasks(self): [out] a.py:1: error: Function is missing a return type annotation -[case testDisallowUntypedDefsAndGenericsPyprojectTOML] -# cmd: mypy a.py -[file pyproject.toml] -\[tool.mypy] -disallow_untyped_defs = true -disallow_any_generics = true -[file a.py] -def get_tasks(self): - return 'whatever' -[out] -a.py:1: error: Function is missing a return type annotation - [case testMissingFile] # cmd: mypy nope.py [out] @@ -1299,19 +812,6 @@ def bar(a: int, b: int) -> str: [out] src/anamespace/foo/bar.py:2: error: Incompatible return value type (got "int", expected "str") -[case testSrcPEP420PackagesPyprojectTOML] -# cmd: mypy -p anamespace --namespace-packages -[file pyproject.toml] -\[tool.mypy] -mypy_path = "src" -[file src/setup.cfg] -[file src/anamespace/foo/__init__.py] -[file src/anamespace/foo/bar.py] -def bar(a: int, b: int) -> str: - return a + b -[out] -src/anamespace/foo/bar.py:2: error: Incompatible return value type (got "int", expected "str") - [case testNestedPEP420Packages] # cmd: mypy -p pkg --namespace-packages [file pkg/a1/b/c/d/e.py] @@ -1346,20 +846,6 @@ math.frobnicate() main.py:1: error: Import of 'math' ignored main.py:1: note: (Using --follow-imports=error, module not passed on command line) -[case testFollowImportStubsPyprojectTOML1] -# cmd: mypy main.py -[file pyproject.toml] -\[tool.mypy] -\[tool.'mypy-math.*'] -follow_imports = "error" -follow_imports_for_stubs = true -[file main.py] -import math -math.frobnicate() -[out] -main.py:1: error: Import of 'math' ignored -main.py:1: note: (Using --follow-imports=error, module not passed on command line) - [case testFollowImportStubs2] # cmd: mypy main.py [file mypy.ini] @@ -1371,17 +857,6 @@ follow_imports_for_stubs = True import math math.frobnicate() -[case testFollowImportStubsPyprojectTOML2] -# cmd: mypy main.py -[file pyproject.toml] -\[tool.mypy] -\[tool.'mypy-math.*'] -follow_imports = "skip" -follow_imports_for_stubs = true -[file main.py] -import math -math.frobnicate() - [case testShadowFile1] # cmd: mypy --shadow-file source.py shadow.py source.py [file source.py] @@ -1449,34 +924,6 @@ incremental = False Warning: unused section(s) in mypy.ini: [mypy-bar], [mypy-baz.*], [mypy-emarg.*], [mypy-emarg.hatch], [mypy-a.*.c], [mypy-a.x.b] == Return code: 0 -[case testConfigWarnUnusedSectionPyprojectTOML1] -# cmd: mypy foo.py quux.py spam/eggs.py -[file pyproject.toml] -\[tool.mypy] -warn_unused_configs = true -incremental = false -\[tool.mypy-bar] -\[tool.mypy-foo] -\[tool.'mypy-baz.*'] -\[tool.'mypy-quux.*'] -\[tool.'mypy-spam.*'] -\[tool.'mypy-spam.eggs'] -\[tool.'mypy-emarg.*'] -\[tool.'mypy-emarg.hatch'] -# Currently we don't treat an unstructured pattern like a.*.b as unused -# if it matches another section (like a.x.b). This would be reasonable -# to change. ' -\[tool.'mypy-a.*.b'] -\[tool.'mypy-a.*.c'] -\[tool.'mypy-a.x.b'] -[file foo.py] -[file quux.py] -[file spam/__init__.py] -[file spam/eggs.py] -[out] -Warning: unused section(s) in pyproject.toml: [mypy-bar], [mypy-baz.*], [mypy-emarg.*], [mypy-emarg.hatch], [mypy-a.*.c], [mypy-a.x.b] -== Return code: 0 - [case testConfigUnstructuredGlob] # cmd: mypy emarg foo [file mypy.ini] @@ -1509,38 +956,6 @@ foo/lol.py:1: error: Name 'fail' is not defined emarg/foo.py:1: error: Name 'fail' is not defined emarg/hatch/villip/mankangulisk.py:1: error: Name 'fail' is not defined -[case testConfigUnstructuredGlobPyprojectTOML] -# cmd: mypy emarg foo -[file pyproject.toml] -\[tool.mypy] -ignore_errors = true -\[tool.'mypy-*.lol'] -ignore_errors = false -\[tool.'mypy-emarg.*'] -ignore_errors = false -\[tool.'mypy-emarg.*.villip.*'] -ignore_errors = true -\[tool.'mypy-emarg.hatch.villip.mankangulisk'] -ignore_errors = false -[file emarg/__init__.py] -[file emarg/foo.py] -fail -[file emarg/villip.py] -fail -[file emarg/hatch/__init__.py] -[file emarg/hatch/villip/__init__.py] -[file emarg/hatch/villip/nus.py] -fail -[file emarg/hatch/villip/mankangulisk.py] -fail -[file foo/__init__.py] -[file foo/lol.py] -fail -[out] -foo/lol.py:1: error: Name 'fail' is not defined -emarg/foo.py:1: error: Name 'fail' is not defined -emarg/hatch/villip/mankangulisk.py:1: error: Name 'fail' is not defined - [case testPackageRootEmpty] # cmd: mypy --package-root= a/b/c.py main.py [file a/b/c.py] @@ -1589,22 +1004,6 @@ fail b.py:1: error: Name 'fail' is not defined a.py:1: error: Name 'fail' is not defined -[case testTOMLFiles] -# cmd: mypy -[file pyproject.toml] -\[tool.mypy] -files = [ - "a.py", - "b.py" -] -[file a.py] -fail -[file b.py] -fail -[out] -b.py:1: error: Name 'fail' is not defined -a.py:1: error: Name 'fail' is not defined - [case testIniFilesGlobbing] # cmd: mypy [file mypy.ini] @@ -1618,19 +1017,6 @@ fail a/b.py:1: error: Name 'fail' is not defined c.py:1: error: Name 'fail' is not defined -[case testTOMLFilesGlobbing] -# cmd: mypy -[file pyproject.toml] -\[tool.mypy] -files = "**/*.py" -[file a/b.py] -fail -[file c.py] -fail -[out] -a/b.py:1: error: Name 'fail' is not defined -c.py:1: error: Name 'fail' is not defined - [case testIniFilesCmdlineOverridesConfig] # cmd: mypy override.py [file mypy.ini] @@ -1640,15 +1026,6 @@ files = config.py mypy: can't read file 'override.py': No such file or directory == Return code: 2 -[case testTomlFilesCmdlineOverridesConfig] -# cmd: mypy override.py -[file pyproject.toml] -\[tool.mypy] -files = "config.py" -[out] -mypy: can't read file 'override.py': No such file or directory -== Return code: 2 - [case testErrorSummaryOnSuccess] # cmd: mypy --error-summary good.py [file good.py] @@ -1777,21 +1154,6 @@ import foo.bar src/foo/bar.py: error: Source file found twice under different module names: 'src.foo.bar' and 'foo.bar' == Return code: 2 -[case testDuplicateModulesPyprojectTOML] -# cmd: mypy src -[file pyproject.toml] -\[tool.mypy] -mypy_path = "src" -[file src/__init__.py] -[file src/a.py] -import foo.bar -[file src/foo/__init__.py] -[file src/foo/bar.py] -1+'x' -[out] -src/foo/bar.py: error: Source file found twice under different module names: 'src.foo.bar' and 'foo.bar' -== Return code: 2 - [case testEnableInvalidErrorCode] # cmd: mypy --enable-error-code YOLO test.py [file test.py] @@ -1909,18 +1271,6 @@ y = 0 # type: str [out] pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") -[case testCmdlinePackageAndTomlFiles] -# cmd: mypy -p pkg -[file pyproject.toml] -\[tool.mypy] -files = "file" -[file pkg.py] -x = 0 # type: str -[file file.py] -y = 0 # type: str -[out] -pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") - [case testCmdlineModuleAndIniFiles] # cmd: mypy -m pkg [file mypy.ini] @@ -1932,15 +1282,3 @@ x = 0 # type: str y = 0 # type: str [out] pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") - -[case testCmdlineModuleAndTomlFiles] -# cmd: mypy -m pkg -[file pyproject.toml] -\[tool.mypy] -files = "file" -[file pkg.py] -x = 0 # type: str -[file file.py] -y = 0 # type: str -[out] -pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") diff --git a/test-data/unit/pyproject.test b/test-data/unit/pyproject.test new file mode 100644 index 000000000000..740b2de224eb --- /dev/null +++ b/test-data/unit/pyproject.test @@ -0,0 +1,693 @@ +-- Tests for command line parsing +-- ------------------------------ +-- +-- The initial line specifies the command line, in the format +-- +-- # cmd: mypy +-- +-- Note that # flags: --some-flag IS NOT SUPPORTED. +-- Use # cmd: mypy --some-flag ... +-- +-- '== Return code: ' is added to the output when the process return code +-- is "nonobvious" -- that is, when it is something other than 0 if there are no +-- messages and 1 if there are. + +-- Directories/packages on the command line +-- ---------------------------------------- + +[case testConfigFile] +# cmd: mypy --config-file pyproject.toml main.py +[file pyproject.toml] +\[tool.mypy] +python_version = 2.7 +[file main.py] +def f(): + try: + 1/0 + except ZeroDivisionError, err: + print err + +[case testErrorContextConfig] +# cmd: mypy main.py +[file pyproject.toml] +\[tool.mypy] +show_error_context = true +[file main.py] +def f() -> None: + 0 + "" +[out] +main.py: note: In function "f": +main.py:2: error: Unsupported operand types for + ("int" and "str") + +[case testNoConfigFile] +# cmd: mypy main.py --config-file= +[file pyproject.toml] +\[tool.mypy] +warn_unused_ignores = true +[file main.py] +# type: ignore + +[case testPerFileConfigSection] +# cmd: mypy x.py y.py z.py +[file pyproject.toml] +\[tool.mypy] +disallow_untyped_defs = true +\[tool.mypy-y] +disallow_untyped_defs = false +\[tool.mypy-z] +disallow_untyped_calls = true +[file x.py] +def f(a): + pass +def g(a: int) -> int: + return f(a) +[file y.py] +def f(a): + pass +def g(a: int) -> int: + return f(a) +[file z.py] +def f(a): + pass +def g(a: int) -> int: + return f(a) +[out] +z.py:1: error: Function is missing a type annotation +z.py:4: error: Call to untyped function "f" in typed context +x.py:1: error: Function is missing a type annotation + +[case testPerFileConfigSectionMultipleMatchesDisallowed] +# cmd: mypy xx.py xy.py yx.py yy.py +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-*x*'] +disallow_untyped_defs = true +\[tool.'mypy-*y*'] +disallow_untyped_calls = true +[file xx.py] +def f(a): pass +def g(a: int) -> int: return f(a) +[file xy.py] +def f(a): pass +def g(a: int) -> int: return f(a) +[file yx.py] +def f(a): pass +def g(a: int) -> int: return f(a) +[file yy.py] +def f(a): pass +def g(a: int) -> int: return f(a) +[out] +pyproject.toml: [mypy-*x*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) +pyproject.toml: [mypy-*y*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) +== Return code: 0 + +[case testMultipleGlobConfigSection] +# cmd: mypy x.py y.py z.py +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-x.*,z.*'] +disallow_untyped_defs = true +[file x.py] +def f(a): pass +[file y.py] +def f(a): pass +[file z.py] +def f(a): pass +[out] +z.py:1: error: Function is missing a type annotation +x.py:1: error: Function is missing a type annotation + +[case testConfigNoErrorNoSection] +# cmd: mypy -c pass +[file pyproject.toml] +[out] + +[case testConfigErrorUnknownFlag] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +bad = 0 +[out] +pyproject.toml: [mypy]: Unrecognized option: bad = 0 +== Return code: 0 + +[case testConfigErrorBadFlag] +# cmd: mypy a.py +[file pyproject.toml] +\[tool.mypy] +disallow-untyped-defs = true +[file a.py] +def f(): + pass +[out] +pyproject.toml: [mypy]: Unrecognized option: disallow-untyped-defs = True +== Return code: 0 + +[case testConfigErrorBadBoolean] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = "nah" +[out] +pyproject.toml: [mypy]: ignore_missing_imports: Not a boolean: nah +== Return code: 0 + +[case testConfigErrorNotPerFile] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-*'] +python_version = 3.4 +[out] +pyproject.toml: [mypy-*]: Per-module sections should only specify per-module flags (python_version) +== Return code: 0 + +[case testConfigMypyPath] +# cmd: mypy file.py +[file pyproject.toml] +\[tool.mypy] +mypy_path = "foo:bar,baz" +[file foo/foo.pyi] +def foo(x: int) -> str: ... +[file bar/bar.pyi] +def bar(x: str) -> list: ... +[file baz/baz.pyi] +def baz(x: list) -> dict: ... +[file file.py] +import no_stubs +from foo import foo +from bar import bar +from baz import baz +baz(bar(foo(42))) +baz(bar(foo('oof'))) +[out] +file.py:1: error: Cannot find implementation or library stub for module named "no_stubs" +file.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +file.py:6: error: Argument 1 to "foo" has incompatible type "str"; expected "int" + +[case testIgnoreErrorsConfig] +# cmd: mypy x.py y.py +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-x] +ignore_errors = true +[file x.py] +"" + 0 +[file y.py] +"" + 0 +[out] +y.py:1: error: Unsupported operand types for + ("str" and "int") + +[case testConfigFollowImportsNormal] +# cmd: mypy main.py +[file main.py] +from a import x +x + 0 +x + '' # E +import a +a.x + 0 +a.x + '' # E +a.y # E +a + 0 # E +[file pyproject.toml] +\[tool.mypy] +follow_imports = "normal" +[file a.py] +x = 0 +x += '' # Error reported here +[out] +a.py:2: error: Unsupported operand types for + ("int" and "str") +main.py:3: error: Unsupported operand types for + ("int" and "str") +main.py:6: error: Unsupported operand types for + ("int" and "str") +main.py:7: error: Module has no attribute "y" +main.py:8: error: Unsupported operand types for + (Module and "int") + +[case testConfigFollowImportsSilent] +# cmd: mypy main.py +[file main.py] +from a import x +x + '' +import a +a.x + '' +a.y +a + 0 +[file pyproject.toml] +\[tool.mypy] +follow_imports = "silent" +[file a.py] +x = 0 +x += '' # No error reported +[out] +main.py:2: error: Unsupported operand types for + ("int" and "str") +main.py:4: error: Unsupported operand types for + ("int" and "str") +main.py:5: error: Module has no attribute "y" +main.py:6: error: Unsupported operand types for + (Module and "int") + +[case testConfigFollowImportsSkip] +# cmd: mypy main.py +[file main.py] +from a import x +reveal_type(x) # Expect Any +import a +reveal_type(a.x) # Expect Any +[file pyproject.toml] +\[tool.mypy] +follow_imports = "skip" +[file a.py] +/ # No error reported +[out] +main.py:2: note: Revealed type is 'Any' +main.py:4: note: Revealed type is 'Any' + +[case testConfigFollowImportsError] +# cmd: mypy main.py +[file main.py] +from a import x +reveal_type(x) # Expect Any +import a # Error reported here +reveal_type(a.x) # Expect Any +[file pyproject.toml] +\[tool.mypy] +follow_imports = "error" +[file a.py] +/ # No error reported +[out] +main.py:1: error: Import of 'a' ignored +main.py:1: note: (Using --follow-imports=error, module not passed on command line) +main.py:2: note: Revealed type is 'Any' +main.py:4: note: Revealed type is 'Any' + +[case testConfigFollowImportsSelective] +# cmd: mypy main.py +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-normal] +follow_imports = "normal" +\[tool.mypy-silent] +follow_imports = "silent" +\[tool.mypy-skip] +follow_imports = "skip" +\[tool.mypy-error] +follow_imports = "error" +[file main.py] +import normal +import silent +import skip +import error +reveal_type(normal.x) +reveal_type(silent.x) +reveal_type(skip) +reveal_type(error) +[file normal.py] +x = 0 +x += '' +[file silent.py] +x = 0 +x += '' +[file skip.py] +bla bla +[file error.py] +bla bla +[out] +normal.py:2: error: Unsupported operand types for + ("int" and "str") +main.py:4: error: Import of 'error' ignored +main.py:4: note: (Using --follow-imports=error, module not passed on command line) +main.py:5: note: Revealed type is 'builtins.int' +main.py:6: note: Revealed type is 'builtins.int' +main.py:7: note: Revealed type is 'Any' +main.py:8: note: Revealed type is 'Any' + +[case testConfigFollowImportsInvalid] +# cmd: mypy main.py +[file pyproject.toml] +\[tool.mypy] +follow_imports = true +[file main.py] +[out] +pyproject.toml: [mypy]: follow_imports: invalid choice 'True' (choose from 'normal', 'silent', 'skip', 'error') +== Return code: 0 + +[case testConfigSilentMissingImportsOff] +# cmd: mypy main.py +[file main.py] +import missing # Expect error here +reveal_type(missing.x) # Expect Any +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = false +[out] +main.py:1: error: Cannot find implementation or library stub for module named "missing" +main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +main.py:2: note: Revealed type is 'Any' + +[case testConfigSilentMissingImportsOn] +# cmd: mypy main.py +[file main.py] +import missing # No error here +reveal_type(missing.x) # Expect Any +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = true +[out] +main.py:2: note: Revealed type is 'Any' + +[case testConfigNoErrorForUnknownXFlagInSubsection] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-foo] +x_bad = 0 +[out] + +[case testPythonVersionTooOld10] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 1.0 +[out] +pyproject.toml: [mypy]: python_version: Python major version '1' out of range (must be 2 or 3) +== Return code: 0 + +[case testPythonVersionTooOld26] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 2.6 +[out] +pyproject.toml: [mypy]: python_version: Python 2.6 is not supported (must be 2.7) +== Return code: 0 + +[case testPythonVersionTooOld33] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 3.3 +[out] +pyproject.toml: [mypy]: python_version: Python 3.3 is not supported (must be 3.4 or higher) +== Return code: 0 + +[case testPythonVersionTooNew28] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 2.8 +[out] +pyproject.toml: [mypy]: python_version: Python 2.8 is not supported (must be 2.7) +== Return code: 0 + +[case testPythonVersionTooNew40] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 4.0 +[out] +pyproject.toml: [mypy]: python_version: Python major version '4' out of range (must be 2 or 3) +== Return code: 0 + +[case testPythonVersionAccepted27] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 2.7 +[out] + +[case testPythonVersionAccepted34] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 3.4 +[out] + +[case testPythonVersionAccepted36] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +python_version = 3.6 +[out] + +[case testDisallowAnyGenericsBuiltinCollections] +# cmd: mypy m.py +[file pyproject.toml] +\[tool.mypy] +python_version = 3.6 +\[tool.mypy-m] +disallow_any_generics = true + +[file m.py] +s = tuple([1, 2, 3]) # no error + +def f(t: tuple) -> None: pass +def g() -> list: pass +def h(s: dict) -> None: pass +def i(s: set) -> None: pass +def j(s: frozenset) -> None: pass +[out] +m.py:3: error: Implicit generic "Any". Use "typing.Tuple" and specify generic parameters +m.py:4: error: Implicit generic "Any". Use "typing.List" and specify generic parameters +m.py:5: error: Implicit generic "Any". Use "typing.Dict" and specify generic parameters +m.py:6: error: Implicit generic "Any". Use "typing.Set" and specify generic parameters +m.py:7: error: Implicit generic "Any". Use "typing.FrozenSet" and specify generic parameters + +[case testDisallowAnyGenericsTypingCollections] +# cmd: mypy m.py +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-m] +disallow_any_generics = true + +[file m.py] +from typing import Tuple, List, Dict, Set, FrozenSet + +def f(t: Tuple) -> None: pass +def g() -> List: pass +def h(s: Dict) -> None: pass +def i(s: Set) -> None: pass +def j(s: FrozenSet) -> None: pass +[out] +m.py:3: error: Missing type parameters for generic type "Tuple" +m.py:4: error: Missing type parameters for generic type "List" +m.py:5: error: Missing type parameters for generic type "Dict" +m.py:6: error: Missing type parameters for generic type "Set" +m.py:7: error: Missing type parameters for generic type "FrozenSet" + +[case testSectionInheritance] +# cmd: mypy a +[file a/__init__.py] +0() +[file a/foo.py] +0() +[file a/b/__init__.py] +[file a/b/c/__init__.py] +0() +[file a/b/c/d/__init__.py] +[file a/b/c/d/e/__init__.py] +from typing import List +def g(x: List) -> None: pass +g(None) +[file pyproject.toml] +\[tool.mypy] +allow_any_generics = true +\[tool.'mypy-a.*'] +ignore_errors = true +\[tool.'mypy-a.b.*'] +disallow_any_generics = true +ignore_errors = true +\[tool.'mypy-a.b.c.*'] +ignore_errors = true +\[tool.'mypy-a.b.c.d.*'] +ignore_errors = true +\[tool.'mypy-a.b.c.d.e.*'] +ignore_errors = true +strict_optional = true +\[tool.'mypy-a.b.c.d.e'] +ignore_errors = false +[out] +a/b/c/d/e/__init__.py:2: error: Missing type parameters for generic type "List" +a/b/c/d/e/__init__.py:3: error: Argument 1 to "g" has incompatible type "None"; expected "List[Any]" + +[case testDisallowUntypedDefsAndGenerics] +# cmd: mypy a.py +[file pyproject.toml] +\[tool.mypy] +disallow_untyped_defs = true +disallow_any_generics = true +[file a.py] +def get_tasks(self): + return 'whatever' +[out] +a.py:1: error: Function is missing a return type annotation + +[case testSrcPEP420Packages] +# cmd: mypy -p anamespace --namespace-packages +[file pyproject.toml] +\[tool.mypy] +mypy_path = "src" +[file src/setup.cfg] +[file src/anamespace/foo/__init__.py] +[file src/anamespace/foo/bar.py] +def bar(a: int, b: int) -> str: + return a + b +[out] +src/anamespace/foo/bar.py:2: error: Incompatible return value type (got "int", expected "str") + +[case testFollowImportStubsPyprojectTOML1] +# cmd: mypy main.py +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-math.*'] +follow_imports = "error" +follow_imports_for_stubs = true +[file main.py] +import math +math.frobnicate() +[out] +main.py:1: error: Import of 'math' ignored +main.py:1: note: (Using --follow-imports=error, module not passed on command line) + +[case testFollowImportStubsPyprojectTOML2] +# cmd: mypy main.py +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-math.*'] +follow_imports = "skip" +follow_imports_for_stubs = true +[file main.py] +import math +math.frobnicate() + +[case testConfigWarnUnusedSectionPyprojectTOML1] +# cmd: mypy foo.py quux.py spam/eggs.py +[file pyproject.toml] +\[tool.mypy] +warn_unused_configs = true +incremental = false +\[tool.mypy-bar] +\[tool.mypy-foo] +\[tool.'mypy-baz.*'] +\[tool.'mypy-quux.*'] +\[tool.'mypy-spam.*'] +\[tool.'mypy-spam.eggs'] +\[tool.'mypy-emarg.*'] +\[tool.'mypy-emarg.hatch'] +# Currently we don't treat an unstructured pattern like a.*.b as unused +# if it matches another section (like a.x.b). This would be reasonable +# to change. ' +\[tool.'mypy-a.*.b'] +\[tool.'mypy-a.*.c'] +\[tool.'mypy-a.x.b'] +[file foo.py] +[file quux.py] +[file spam/__init__.py] +[file spam/eggs.py] +[out] +Warning: unused section(s) in pyproject.toml: [mypy-bar], [mypy-baz.*], [mypy-emarg.*], [mypy-emarg.hatch], [mypy-a.*.c], [mypy-a.x.b] +== Return code: 0 + +[case testConfigUnstructuredGlob] +# cmd: mypy emarg foo +[file pyproject.toml] +\[tool.mypy] +ignore_errors = true +\[tool.'mypy-*.lol'] +ignore_errors = false +\[tool.'mypy-emarg.*'] +ignore_errors = false +\[tool.'mypy-emarg.*.villip.*'] +ignore_errors = true +\[tool.'mypy-emarg.hatch.villip.mankangulisk'] +ignore_errors = false +[file emarg/__init__.py] +[file emarg/foo.py] +fail +[file emarg/villip.py] +fail +[file emarg/hatch/__init__.py] +[file emarg/hatch/villip/__init__.py] +[file emarg/hatch/villip/nus.py] +fail +[file emarg/hatch/villip/mankangulisk.py] +fail +[file foo/__init__.py] +[file foo/lol.py] +fail +[out] +foo/lol.py:1: error: Name 'fail' is not defined +emarg/foo.py:1: error: Name 'fail' is not defined +emarg/hatch/villip/mankangulisk.py:1: error: Name 'fail' is not defined + +[case testTomlFiles] +# cmd: mypy +[file pyproject.toml] +\[tool.mypy] +files = [ + "a.py", + "b.py" +] +[file a.py] +fail +[file b.py] +fail +[out] +b.py:1: error: Name 'fail' is not defined +a.py:1: error: Name 'fail' is not defined + +[case testTomlFilesGlobbing] +# cmd: mypy +[file pyproject.toml] +\[tool.mypy] +files = "**/*.py" +[file a/b.py] +fail +[file c.py] +fail +[out] +a/b.py:1: error: Name 'fail' is not defined +c.py:1: error: Name 'fail' is not defined + +[case testTomlFilesCmdlineOverridesConfig] +# cmd: mypy override.py +[file pyproject.toml] +\[tool.mypy] +files = "config.py" +[out] +mypy: can't read file 'override.py': No such file or directory +== Return code: 2 + +[case testDuplicateModules] +# cmd: mypy src +[file pyproject.toml] +\[tool.mypy] +mypy_path = "src" +[file src/__init__.py] +[file src/a.py] +import foo.bar +[file src/foo/__init__.py] +[file src/foo/bar.py] +1+'x' +[out] +src/foo/bar.py: error: Source file found twice under different module names: 'src.foo.bar' and 'foo.bar' +== Return code: 2 + +[case testCmdlinePackageAndTomlFiles] +# cmd: mypy -p pkg +[file pyproject.toml] +\[tool.mypy] +files = "file" +[file pkg.py] +x = 0 # type: str +[file file.py] +y = 0 # type: str +[out] +pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") + +[case testCmdlineModuleAndTomlFiles] +# cmd: mypy -m pkg +[file pyproject.toml] +\[tool.mypy] +files = "file" +[file pkg.py] +x = 0 # type: str +[file file.py] +y = 0 # type: str +[out] +pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") From da965ec5bf6daab763397c7d0976aa9c124692d5 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Wed, 17 Mar 2021 21:26:43 -0400 Subject: [PATCH 05/39] Further delineate pyproject tests --- docs/source/command_line.rst | 2 +- mypy/test/testcheck.py | 7 +- mypy/test/testcmdline.py | 2 + mypy/test/testdaemon.py | 1 + mypy/test/testdiff.py | 5 +- mypy/test/testfinegrained.py | 3 +- mypy/test/testpep561.py | 8 +- mypy/test/testpyproject.py | 133 ---- .../unit/check-custom-plugin.pyproject.test | 737 ++++++++++++++++++ test-data/unit/check-custom-plugin.test | 733 ----------------- test-data/unit/check-flags.pyproject.test | 278 +++++++ test-data/unit/check-flags.test | 275 ------- .../unit/check-incremental.pyproject.test | 306 ++++++++ test-data/unit/check-incremental.test | 315 -------- .../unit/check-modules-case.pyproject.test | 56 ++ test-data/unit/check-modules-case.test | 55 -- test-data/unit/check-modules.pyproject.test | 92 +++ test-data/unit/check-modules.test | 90 --- test-data/unit/check-optional.pyproject.test | 26 + test-data/unit/check-optional.test | 25 - ...{pyproject.test => cmdline.pyproject.test} | 92 +-- test-data/unit/daemon.pyproject.test | 63 ++ test-data/unit/daemon.test | 61 -- test-data/unit/diff.pyproject.test | 41 + test-data/unit/diff.test | 39 - test-data/unit/fine-grained.pyproject.test | 245 ++++++ test-data/unit/pep561.pyproject.test | 27 + test-data/unit/pep561.test | 28 - test-data/unit/reports.pyproject.test | 12 + test-data/unit/reports.test | 9 - 30 files changed, 1953 insertions(+), 1813 deletions(-) delete mode 100644 mypy/test/testpyproject.py create mode 100644 test-data/unit/check-custom-plugin.pyproject.test create mode 100644 test-data/unit/check-flags.pyproject.test create mode 100644 test-data/unit/check-incremental.pyproject.test create mode 100644 test-data/unit/check-modules-case.pyproject.test create mode 100644 test-data/unit/check-modules.pyproject.test create mode 100644 test-data/unit/check-optional.pyproject.test rename test-data/unit/{pyproject.test => cmdline.pyproject.test} (87%) create mode 100644 test-data/unit/daemon.pyproject.test create mode 100644 test-data/unit/diff.pyproject.test create mode 100644 test-data/unit/fine-grained.pyproject.test create mode 100644 test-data/unit/pep561.pyproject.test create mode 100644 test-data/unit/reports.pyproject.test diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index 27b370407d39..a5fa34a9d959 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -97,7 +97,7 @@ Config file This flag makes mypy read configuration settings from the given file. - By default settings are read from ``mypy.ini``, ``.mypy.ini``, or ``setup.cfg`` + By default settings are read from ``mypy.ini``, ``.mypy.ini``, ``pyproject.toml``, or ``setup.cfg`` in the current directory. Settings override mypy's built-in defaults and command line flags can override settings. diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 51f5d71c12ad..ac76b56c8eb1 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -41,6 +41,7 @@ 'check-multiple-inheritance.test', 'check-super.test', 'check-modules.test', + 'check-modules.pyproject.test', 'check-typevar-values.test', 'check-unsupported.test', 'check-unreachable-code.test', @@ -55,10 +56,13 @@ 'check-type-promotion.test', 'check-semanal-error.test', 'check-flags.test', + 'check-flags.pyproject.test', 'check-incremental.test', + 'check-incremental.pyproject.test', 'check-serialize.test', 'check-bound.test', 'check-optional.test', + 'check-optional.pyproject.test', 'check-fastparse.test', 'check-warnings.test', 'check-async-await.test', @@ -79,6 +83,7 @@ 'check-enum.test', 'check-incomplete-fixture.test', 'check-custom-plugin.test', + 'check-custom-plugin.pyproject.test', 'check-default-plugin.test', 'check-attr.test', 'check-ctypes.test', @@ -104,7 +109,7 @@ # Special tests for platforms with case-insensitive filesystems. if sys.platform in ('darwin', 'win32'): - typecheck_files.append('check-modules-case.test') + typecheck_files.extend(['check-modules-case.test', 'check-modules-case.pyproject.test']) class TypeCheckSuite(DataSuite): diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index 85b25cf92545..ec2972fe4001 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -24,7 +24,9 @@ # Files containing test case descriptions. cmdline_files = [ 'cmdline.test', + 'cmdline.pyproject.test', 'reports.test', + 'reports.pyproject.test', 'envvars.test', ] diff --git a/mypy/test/testdaemon.py b/mypy/test/testdaemon.py index 641bd8a70372..fe1e27e6098b 100644 --- a/mypy/test/testdaemon.py +++ b/mypy/test/testdaemon.py @@ -23,6 +23,7 @@ # Files containing test cases descriptions. daemon_files = [ 'daemon.test', + 'daemon.pyproject.test', ] diff --git a/mypy/test/testdiff.py b/mypy/test/testdiff.py index d4617c299b86..0b370414d06f 100644 --- a/mypy/test/testdiff.py +++ b/mypy/test/testdiff.py @@ -16,7 +16,10 @@ class ASTDiffSuite(DataSuite): - files = ['diff.test'] + files = [ + 'diff.test', + 'diff.pyproject.test', + ] def run_case(self, testcase: DataDrivenTestCase) -> None: first_src = '\n'.join(testcase.input) diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index c2737308adc1..264ab04726c0 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -45,6 +45,7 @@ class FineGrainedSuite(DataSuite): files = [ 'fine-grained.test', + 'fine-grained.pyproject.test', 'fine-grained-cycles.test', 'fine-grained-blockers.test', 'fine-grained-modules.test', @@ -159,7 +160,7 @@ def get_options(self, options.follow_imports = 'error' for name, _ in testcase.files: - if 'mypy.ini' in name: + if 'mypy.ini' in name or 'pyproject.toml' in name: parse_config_file(options, lambda: None, name) break diff --git a/mypy/test/testpep561.py b/mypy/test/testpep561.py index 2d0763141ea4..7744c271c8bc 100644 --- a/mypy/test/testpep561.py +++ b/mypy/test/testpep561.py @@ -35,7 +35,10 @@ class PEP561Suite(DataSuite): - files = ['pep561.test'] + files = [ + 'pep561.test', + 'pep561.pyproject.test' + ] def run_case(self, test_case: DataDrivenTestCase) -> None: test_pep561(test_case) @@ -136,6 +139,9 @@ def test_pep561(testcase: DataDrivenTestCase) -> None: if 'mypy.ini' in name: with open('mypy.ini', 'w') as m: m.write(content) + elif 'pyproject.toml' in name: + with open('pyproject.toml', 'w') as m: + m.write(content) output = [] # Type check the module out, err, returncode = mypy.api.run(cmd_line) diff --git a/mypy/test/testpyproject.py b/mypy/test/testpyproject.py deleted file mode 100644 index 7cc3ba9e74f9..000000000000 --- a/mypy/test/testpyproject.py +++ /dev/null @@ -1,133 +0,0 @@ -"""Test cases for pyproject.toml configs. - -To begin we test that "mypy [/]" always recurses down the -whole tree. -""" - -import os -import re -import subprocess -import sys - -from typing import List -from typing import Optional - -from mypy.test.config import test_temp_dir, PREFIX -from mypy.test.data import DataDrivenTestCase, DataSuite -from mypy.test.helpers import ( - assert_string_arrays_equal, normalize_error_messages, check_test_output_files -) - -# Path to Python 3 interpreter -python3_path = sys.executable - -# Files containing test case descriptions. -pyproject_files = [ - 'pyproject.test', -] - - -class PythonPyprojectSuite(DataSuite): - files = pyproject_files - native_sep = True - - def run_case(self, testcase: DataDrivenTestCase) -> None: - for step in [1] + sorted(testcase.output2): - test_python_pyproject(testcase, step) - - -def test_python_pyproject(testcase: DataDrivenTestCase, step: int) -> None: - assert testcase.old_cwd is not None, "test was not properly set up" - # Write the program to a file. - program = '_program.py' - program_path = os.path.join(test_temp_dir, program) - with open(program_path, 'w', encoding='utf8') as file: - for s in testcase.input: - file.write('{}\n'.format(s)) - args = parse_args(testcase.input[0]) - custom_cwd = parse_cwd(testcase.input[1]) if len(testcase.input) > 1 else None - args.append('--show-traceback') - if '--error-summary' not in args: - args.append('--no-error-summary') - # Type check the program. - fixed = [python3_path, '-m', 'mypy'] - env = os.environ.copy() - env.pop('COLUMNS', None) - env['PYTHONPATH'] = PREFIX - process = subprocess.Popen(fixed + args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=os.path.join( - test_temp_dir, - custom_cwd or "" - ), - env=env) - outb, errb = process.communicate() - result = process.returncode - # Split output into lines. - out = [s.rstrip('\n\r') for s in str(outb, 'utf8').splitlines()] - err = [s.rstrip('\n\r') for s in str(errb, 'utf8').splitlines()] - - if "PYCHARM_HOSTED" in os.environ: - for pos, line in enumerate(err): - if line.startswith('pydev debugger: '): - # Delete the attaching debugger message itself, plus the extra newline added. - del err[pos:pos + 2] - break - - # Remove temp file. - os.remove(program_path) - # Compare actual output to expected. - if testcase.output_files: - # Ignore stdout, but we insist on empty stderr and zero status. - if err or result: - raise AssertionError( - 'Expected zero status and empty stderr%s, got %d and\n%s' % - (' on step %d' % step if testcase.output2 else '', - result, '\n'.join(err + out))) - check_test_output_files(testcase, step) - else: - if testcase.normalize_output: - out = normalize_error_messages(err + out) - obvious_result = 1 if out else 0 - if obvious_result != result: - out.append('== Return code: {}'.format(result)) - expected_out = testcase.output if step == 1 else testcase.output2[step] - # Strip "tmp/" out of the test so that # E: works... - expected_out = [s.replace("tmp" + os.sep, "") for s in expected_out] - assert_string_arrays_equal(expected_out, out, - 'Invalid output ({}, line {}){}'.format( - testcase.file, testcase.line, - ' on step %d' % step if testcase.output2 else '')) - - -def parse_args(line: str) -> List[str]: - """Parse the first line of the program for the command line. - - This should have the form - - # cmd: mypy - - For example: - - # cmd: mypy pkg/ - """ - m = re.match('# cmd: mypy (.*)$', line) - if not m: - return [] # No args; mypy will spit out an error. - return m.group(1).split() - - -def parse_cwd(line: str) -> Optional[str]: - """Parse the second line of the program for the command line. - - This should have the form - - # cwd: - - For example: - - # cwd: main/subdir - """ - m = re.match('# cwd: (.*)$', line) - return m.group(1) if m else None diff --git a/test-data/unit/check-custom-plugin.pyproject.test b/test-data/unit/check-custom-plugin.pyproject.test new file mode 100644 index 000000000000..20838d55ec18 --- /dev/null +++ b/test-data/unit/check-custom-plugin.pyproject.test @@ -0,0 +1,737 @@ +-- Test cases for user-defined plugins +-- +-- Note: Plugins used by tests live under test-data/unit/plugins. Defining +-- plugin files in test cases does not work reliably. + +[case testFunctionPluginFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f() -> str: ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/fnplugin.py" + +[case testFunctionPluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f() -> str: ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +[file pyproject.toml] +\[tool.mypy] +plugins = "fnplugin" + +[case testFunctionPluginFullnameIsNotNonePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Callable, TypeVar +f: Callable[[], None] +T = TypeVar('T') +def g(x: T) -> T: return x # This strips out the name of a callable +g(f)() +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/fnplugin.py" + +[case testTwoPluginsPyprojetTOMLPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f(): ... +def g(): ... +def h(): ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +reveal_type(g()) # N: Revealed type is 'builtins.str' +reveal_type(h()) # N: Revealed type is 'Any' +[file pyproject.toml] +\[tool.mypy] +plugins=[ + "/test-data/unit/plugins/fnplugin.py", + "/test-data/unit/plugins/plugin2.py" +] + +[case testTwoPluginsMixedTypePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f(): ... +def g(): ... +def h(): ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +reveal_type(g()) # N: Revealed type is 'builtins.str' +reveal_type(h()) # N: Revealed type is 'Any' +[file pyproject.toml] +\[tool.mypy] +plugins = [ + "/test-data/unit/plugins/fnplugin.py", + "plugin2" +] + +[case testMissingPluginFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +\[tool.mypy] +plugins = "missing.py" +[out] +tmp/pyproject.toml:1: error: Can't find plugin 'tmp/missing.py' +--' (work around syntax highlighting) + +[case testMissingPluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +\[tool.mypy] +plugins = "missing" +[out] +tmp/pyproject.toml:1: error: Error importing plugin 'missing': No module named 'missing' + +[case testMultipleSectionsDefinePluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +\[acme] +plugins = "acmeplugin" +\[tool.mypy] +plugins = "missing.py" +\[another] +plugins = "another_plugin" +[out] +tmp/pyproject.toml:1: error: Can't find plugin 'tmp/missing.py' +--' (work around syntax highlighting) + +[case testInvalidPluginExtensionPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +\[tool.mypy] +plugins = "dir/badext.pyi" +[file dir/badext.pyi] +[out] +tmp/pyproject.toml:1: error: Plugin 'badext.pyi' does not have a .py extension + +[case testMissingPluginEntryPointPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +\[tool.mypy] + plugins = "/test-data/unit/plugins/noentry.py" +[out] +tmp/pyproject.toml:1: error: Plugin '/test-data/unit/plugins/noentry.py' does not define entry point function "plugin" + +[case testCustomPluginEntryPointFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f() -> str: ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/customentry.py:register" + +[case testCustomPluginEntryPointPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f() -> str: ... +reveal_type(f()) # N: Revealed type is 'builtins.int' +[file pyproject.toml] +\[tool.mypy] +plugins = "customentry:register" + +[case testInvalidPluginEntryPointReturnValuePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f(): pass +f() +[file pyproject.toml] +\[tool.mypy] + +plugins = "/test-data/unit/plugins/badreturn.py" +[out] +tmp/pyproject.toml:1: error: Type object expected as the return value of "plugin"; got None (in /test-data/unit/plugins/badreturn.py) + +[case testInvalidPluginEntryPointReturnValue2PyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def f(): pass +f() +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/badreturn2.py" +[out] +tmp/pyproject.toml:1: error: Return value of "plugin" must be a subclass of "mypy.plugin.Plugin" (in /test-data/unit/plugins/badreturn2.py) + +[case testAttributeTypeHookPluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Callable +from m import Signal, DerivedSignal +s: Signal[Callable[[int], None]] = Signal() +s(1) +s('') # E: Argument 1 has incompatible type "str"; expected "int" + +ds: DerivedSignal[Callable[[int], None]] = DerivedSignal() +ds('') # E: Argument 1 has incompatible type "str"; expected "int" +[file m.py] +from typing import TypeVar, Generic, Callable +T = TypeVar('T', bound=Callable[..., None]) +class Signal(Generic[T]): + __call__: Callable[..., None] # This type is replaced by the plugin + +class DerivedSignal(Signal[T]): ... +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/attrhook.py" + +[case testAttributeHookPluginForDynamicClassPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from m import Magic, DerivedMagic + +magic = Magic() +reveal_type(magic.magic_field) # N: Revealed type is 'builtins.str' +reveal_type(magic.non_magic_method()) # N: Revealed type is 'builtins.int' +reveal_type(magic.non_magic_field) # N: Revealed type is 'builtins.int' +magic.nonexistent_field # E: Field does not exist +reveal_type(magic.fallback_example) # N: Revealed type is 'Any' +reveal_type(DerivedMagic().magic_field) # N: Revealed type is 'builtins.str' +[file m.py] +from typing import Any +class Magic: + # Triggers plugin infrastructure: + def __getattr__(self, x: Any) -> Any: ... + def non_magic_method(self) -> int: ... + non_magic_field: int + +class DerivedMagic(Magic): ... + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/attrhook2.py" + +[case testTypeAnalyzeHookPluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Callable +from mypy_extensions import DefaultArg +from m import Signal +s: Signal[[int, DefaultArg(str, 'x')]] = Signal() +reveal_type(s) # N: Revealed type is 'm.Signal[def (builtins.int, x: builtins.str =)]' +s.x # E: "Signal[Callable[[int, str], None]]" has no attribute "x" +ss: Signal[int, str] # E: Invalid "Signal" type (expected "Signal[[t, ...]]") +[file m.py] +from typing import TypeVar, Generic, Callable +T = TypeVar('T', bound=Callable[..., None]) +class Signal(Generic[T]): + __call__: Callable[..., None] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/type_anal_hook.py" +[builtins fixtures/dict.pyi] + +[case testFunctionPluginHookForClassPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import mod +from mod import AttrInt + +Alias = AttrInt +AnotherAlias = mod.Attr + +class C: + x = Alias() + y = mod.AttrInt(required=True) + z = AnotherAlias(int, required=False) + +c = C() +reveal_type(c.x) # N: Revealed type is 'Union[builtins.int, None]' +reveal_type(c.y) # N: Revealed type is 'builtins.int*' +reveal_type(c.z) # N: Revealed type is 'Union[builtins.int*, None]' + +[file mod.py] +from typing import Generic, TypeVar, Type +T = TypeVar('T') + +class Attr(Generic[T]): + def __init__(self, tp: Type[T], required: bool = False) -> None: + pass + def __get__(self, instance: object, owner: type) -> T: + pass + +class AttrInt(Attr[int]): + def __init__(self, required: bool = False) -> None: + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/class_callable.py" +[builtins fixtures/bool.pyi] +[out] + +[case testFunctionPluginHookForReturnedCallablePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from m import decorator1, decorator2 +@decorator1() +def f() -> None: pass +@decorator2() +def g() -> None: pass +reveal_type(f) # N: Revealed type is 'def (*Any, **Any) -> builtins.str' +reveal_type(g) # N: Revealed type is 'def (*Any, **Any) -> builtins.int' +[file m.py] +from typing import Callable +def decorator1() -> Callable[..., Callable[..., int]]: pass +def decorator2() -> Callable[..., Callable[..., int]]: pass +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/named_callable.py" + +[case testFunctionMethodContextsHasArgNamesPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import Class, func + +reveal_type(Class().method(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(Class.myclassmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(Class.mystaticmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(Class.method(self=Class(), arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(func(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' + +[file mod.py] +from typing import Any +class Class: + def method(self, classname: str, arg1: Any, arg2: Any) -> Any: + pass + @classmethod + def myclassmethod(cls, classname: str, arg1: Any, arg2: Any): + pass + @staticmethod + def mystaticmethod(classname: str, arg1: Any, arg2: Any): + pass +def func(classname: str, arg1: Any, arg2: Any) -> Any: + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" +[builtins fixtures/classmethod.pyi] + +[case testFunctionMethodContextsHasArgNamesPositionalsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import Class, func + +reveal_type(Class().method('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' +reveal_type(Class.myclassmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' +reveal_type(Class.mystaticmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' +reveal_type(Class.method(Class(), 'builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' +reveal_type(func('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' + +[file mod.py] +from typing import Any +class Class: + def method(self, classname: str, arg1: Any, arg2: Any) -> Any: + pass + @classmethod + def myclassmethod(cls, classname: str, arg1: Any, arg2: Any): + pass + @staticmethod + def mystaticmethod(classname: str, arg1: Any, arg2: Any): + pass +def func(classname: str, arg1: Any, arg2: Any) -> Any: + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" +[builtins fixtures/classmethod.pyi] + +[case testFunctionMethodContextsHasArgNamesInitMethodPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import ClassInit, Outer + +reveal_type(ClassInit('builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(ClassInit(classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(Outer.NestedClassInit(classname='builtins.str')) # N: Revealed type is 'builtins.str' +[file mod.py] +from typing import Any +class ClassInit: + def __init__(self, classname: str): + pass +class Outer: + class NestedClassInit: + def __init__(self, classname: str): + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" + +[case testFunctionMethodContextsHasArgNamesUnfilledArgumentsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import ClassUnfilled, func_unfilled + +reveal_type(ClassUnfilled().method(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassUnfilled().method(arg2=1, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(ClassUnfilled().method('builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(func_unfilled(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(func_unfilled(arg2=1, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(func_unfilled('builtins.str')) # N: Revealed type is 'builtins.str' + +[file mod.py] +from typing import Any +class ClassUnfilled: + def method(self, classname: str, arg1: Any = None, arg2: Any = None) -> Any: + pass +def func_unfilled(classname: str, arg1: Any = None, arg2: Any = None) -> Any: + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" + +[case testFunctionMethodContextsHasArgNamesStarExpressionsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import ClassStarExpr, func_star_expr + +reveal_type(ClassStarExpr().method(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassStarExpr().method('builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassStarExpr().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassStarExpr().method('builtins.str', 2, 3, 4, arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' +reveal_type(func_star_expr(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(func_star_expr('builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' +reveal_type(func_star_expr('builtins.str', 2, 3, 4, arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' + +[file mod.py] +from typing import Any +class ClassStarExpr: + def method(self, classname: str, *args, **kwargs) -> Any: + pass +def func_star_expr(classname: str, *args, **kwargs) -> Any: + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" +[builtins fixtures/dict.pyi] + +[case testFunctionMethodContextArgNamesForInheritedMethodsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import ClassChild + +reveal_type(ClassChild().method(classname='builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassChild().method(arg1=1, classname='builtins.str', arg2=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassChild().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' +reveal_type(ClassChild.myclassmethod('builtins.str')) # N: Revealed type is 'builtins.str' +[file mod.py] +from typing import Any +class Base: + def method(self, classname: str, arg1: Any, arg2: Any) -> Any: + pass + @classmethod + def myclassmethod(cls, classname: str) -> Any: + pass +class ClassChild(Base): + pass + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_names.py" +[builtins fixtures/classmethod.pyi] + +[case testMethodSignatureHookPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Iterator + +class Foo: + # Test that method signature hooks are applied in various cases: explicit method calls, and + # implicit dunder method calls through language syntax. + # The plugin's method signature hook should turn all str occurrences into int. + def __init__(self) -> None: ... + def __getitem__(self, index: str) -> str: ... + def __setitem__(self, index: str, value: str) -> None: ... + def __iter__(self) -> Iterator[str]: ... + def __next__(self) -> str: ... + def __call__(self, *args: str) -> str: ... + def m(self, arg: str) -> str: ... + +foo = Foo() +reveal_type(foo.m(2)) # N: Revealed type is 'builtins.int' +reveal_type(foo[3]) # N: Revealed type is 'builtins.int' +reveal_type(foo(4, 5, 6)) # N: Revealed type is 'builtins.int' +foo[4] = 5 +for x in foo: + reveal_type(x) # N: Revealed type is 'builtins.int*' + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/method_sig_hook.py" +[builtins fixtures/tuple.pyi] + +[case testMethodSignatureHookNamesFullyQualifiedPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mypy_extensions import TypedDict +from typing import NamedTuple + +class FullyQualifiedTestClass: + @classmethod + def class_method(self) -> str: ... + def instance_method(self) -> str: ... + +class FullyQualifiedTestTypedDict(TypedDict): + foo: str + +FullyQualifiedTestNamedTuple = NamedTuple('FullyQualifiedTestNamedTuple', [('foo', str)]) + +# Check the return types to ensure that the method signature hook is called in each case +reveal_type(FullyQualifiedTestClass.class_method()) # N: Revealed type is 'builtins.int' +reveal_type(FullyQualifiedTestClass().instance_method()) # N: Revealed type is 'builtins.int' +reveal_type(FullyQualifiedTestNamedTuple('')._asdict()) # N: Revealed type is 'builtins.int' + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/fully_qualified_test_hook.py" +[builtins fixtures/classmethod.pyi] + +[case testDynamicClassPluginPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import declarative_base, Column, Instr + +Base = declarative_base() + +class Model(Base): + x: Column[int] +class Other: + x: Column[int] + +reveal_type(Model().x) # N: Revealed type is 'mod.Instr[builtins.int]' +reveal_type(Other().x) # N: Revealed type is 'mod.Column[builtins.int]' +[file mod.py] +from typing import Generic, TypeVar +def declarative_base(): ... + +T = TypeVar('T') + +class Column(Generic[T]): ... +class Instr(Generic[T]): ... + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/dyn_class.py" + +[case testDynamicClassPluginNegativesPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import declarative_base, Column, Instr, non_declarative_base + +Bad1 = non_declarative_base() +Bad2 = Bad3 = declarative_base() + +class C1(Bad1): ... # E: Variable "__main__.Bad1" is not valid as a type \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ + # E: Invalid base class "Bad1" +class C2(Bad2): ... # E: Variable "__main__.Bad2" is not valid as a type \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ + # E: Invalid base class "Bad2" +class C3(Bad3): ... # E: Variable "__main__.Bad3" is not valid as a type \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ + # E: Invalid base class "Bad3" +[file mod.py] +from typing import Generic, TypeVar +def declarative_base(): ... +def non_declarative_base(): ... + +T = TypeVar('T') + +class Column(Generic[T]): ... +class Instr(Generic[T]): ... + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/dyn_class.py" + +[case testDynamicClassHookFromClassMethodPyprojectTOML] +# flags: --config-file tmp/pyproject.toml + +from mod import QuerySet, Manager + +MyManager = Manager.from_queryset(QuerySet) + +reveal_type(MyManager()) # N: Revealed type is '__main__.MyManager' +reveal_type(MyManager().attr) # N: Revealed type is 'builtins.str' + +def func(manager: MyManager) -> None: + reveal_type(manager) # N: Revealed type is '__main__.MyManager' + reveal_type(manager.attr) # N: Revealed type is 'builtins.str' + +func(MyManager()) + +[file mod.py] +from typing import Generic, TypeVar, Type +class QuerySet: + attr: str +class Manager: + @classmethod + def from_queryset(cls, queryset_cls: Type[QuerySet]): ... + +[builtins fixtures/classmethod.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/dyn_class_from_method.py" + +[case testBaseClassPluginHookWorksIncrementalPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a + +[file a.py] +from base import Base +class C(Base): ... + +[file a.py.2] +from base import Base +class C(Base): ... +reveal_type(C().__magic__) +Base.__magic__ + +[file base.py] +from lib import declarative_base +Base = declarative_base() + +[file lib.py] +from typing import Any +def declarative_base() -> Any: ... + +[file pyproject.toml] +\[tool.mypy] +python_version = 3.6 +plugins = "/test-data/unit/plugins/common_api_incremental.py" +[out] +[out2] +tmp/a.py:3: note: Revealed type is 'builtins.str' +tmp/a.py:4: error: "Type[Base]" has no attribute "__magic__" + +[case testArgKindsMethodPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +class Class: + def method(self, *args, **kwargs): + pass + +Class().method(1, *[2], **{'a': 1}) # E: [[0, 2], [4]] +[builtins fixtures/dict.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_kinds.py" + +[case testArgKindsFunctionPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +def func(*args, **kwargs): + pass + +func(1, 2, [3, 4], *[5, 6, 7], **{'a': 1}) # E: [[0, 0, 0, 2], [4]] +[builtins fixtures/dict.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/arg_kinds.py" + +[case testHookCallableInstancePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Generic, TypeVar +T = TypeVar("T") +class Class(Generic[T]): + def __init__(self, one: T): ... + def __call__(self, two: T) -> int: ... +reveal_type(Class("hi")("there")) # N: Revealed type is 'builtins.str*' +instance = Class(3.14) +reveal_type(instance(2)) # N: Revealed type is 'builtins.float*' + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/callable_instance.py" + +[case testGetMethodHooksOnUnionsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml --no-strict-optional +from typing import Union + +class Foo: + def meth(self, x: str) -> str: ... +class Bar: + def meth(self, x: int) -> float: ... +class Other: + meth: int + +x: Union[Foo, Bar, Other] +if isinstance(x.meth, int): + reveal_type(x.meth) # N: Revealed type is 'builtins.int' +else: + reveal_type(x.meth(int())) # N: Revealed type is 'builtins.int' + +[builtins fixtures/isinstancelist.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/union_method.py" + +[case testGetMethodHooksOnUnionsStrictOptionalPyprojectTOML] +# flags: --config-file tmp/pyproject.toml --strict-optional +from typing import Union + +class Foo: + def meth(self, x: str) -> str: ... +class Bar: + def meth(self, x: int) -> float: ... +class Other: + meth: int + +x: Union[Foo, Bar, Other] +if isinstance(x.meth, int): + reveal_type(x.meth) # N: Revealed type is 'builtins.int' +else: + reveal_type(x.meth(int())) # N: Revealed type is 'builtins.int' + +[builtins fixtures/isinstancelist.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/union_method.py" + +[case testGetMethodHooksOnUnionsSpecialPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Union + +class Foo: + def __getitem__(self, x: str) -> str: ... +class Bar: + def __getitem__(self, x: int) -> float: ... + +x: Union[Foo, Bar] +reveal_type(x[int()]) # N: Revealed type is 'builtins.int' + +[builtins fixtures/isinstancelist.pyi] +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/union_method.py" + +[case testPluginDependenciesPyprojectTOML] +# flags: --config-file tmp/pyproject.toml + +# The top level file here doesn't do anything, but the plugin should add +# a dependency on err that will cause err to be processed and an error reported. + +[file err.py] +1 + 'lol' # E: Unsupported operand types for + ("int" and "str") + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/depshook.py" + +[case testCustomizeMroTrivialPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +class A: pass +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/customize_mro.py" + +[case testDescriptorMethodsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml + +class Desc: + def __get__(self, obj, cls): + pass + + def __set__(self, obj, val): + pass + +class Cls: + attr = Desc() + +reveal_type(Cls().attr) # N: Revealed type is 'builtins.int' +reveal_type(Cls.attr) # N: Revealed type is 'builtins.str' + +Cls().attr = 3 +Cls().attr = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "int") + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/descriptor.py" + +[case testFunctionSigPluginFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml + +def dynamic_signature(arg1: str) -> str: ... +reveal_type(dynamic_signature(1)) # N: Revealed type is 'builtins.int' +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/function_sig_hook.py" diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index 952dc17e3d26..645ab50db071 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -11,14 +11,6 @@ reveal_type(f()) # N: Revealed type is 'builtins.int' \[mypy] plugins=/test-data/unit/plugins/fnplugin.py -[case testFunctionPluginFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f() -> str: ... -reveal_type(f()) # N: Revealed type is 'builtins.int' -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/fnplugin.py" - [case testFunctionPlugin] # flags: --config-file tmp/mypy.ini def f() -> str: ... @@ -27,14 +19,6 @@ reveal_type(f()) # N: Revealed type is 'builtins.int' \[mypy] plugins=fnplugin -[case testFunctionPluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f() -> str: ... -reveal_type(f()) # N: Revealed type is 'builtins.int' -[file pyproject.toml] -\[tool.mypy] -plugins = "fnplugin" - [case testFunctionPluginFullnameIsNotNone] # flags: --config-file tmp/mypy.ini from typing import Callable, TypeVar @@ -46,17 +30,6 @@ g(f)() \[mypy] plugins=/test-data/unit/plugins/fnplugin.py -[case testFunctionPluginFullnameIsNotNonePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Callable, TypeVar -f: Callable[[], None] -T = TypeVar('T') -def g(x: T) -> T: return x # This strips out the name of a callable -g(f)() -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/fnplugin.py" - [case testTwoPlugins] # flags: --config-file tmp/mypy.ini def f(): ... @@ -70,21 +43,6 @@ reveal_type(h()) # N: Revealed type is 'Any' plugins=/test-data/unit/plugins/fnplugin.py, /test-data/unit/plugins/plugin2.py -[case testTwoPluginsPyprojetTOML] -# flags: --config-file tmp/pyproject.toml -def f(): ... -def g(): ... -def h(): ... -reveal_type(f()) # N: Revealed type is 'builtins.int' -reveal_type(g()) # N: Revealed type is 'builtins.str' -reveal_type(h()) # N: Revealed type is 'Any' -[file pyproject.toml] -\[tool.mypy] -plugins=[ - "/test-data/unit/plugins/fnplugin.py", - "/test-data/unit/plugins/plugin2.py" -] - [case testTwoPluginsMixedType] # flags: --config-file tmp/mypy.ini def f(): ... @@ -97,21 +55,6 @@ reveal_type(h()) # N: Revealed type is 'Any' \[mypy] plugins=/test-data/unit/plugins/fnplugin.py, plugin2 -[case testTwoPluginsMixedTypePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f(): ... -def g(): ... -def h(): ... -reveal_type(f()) # N: Revealed type is 'builtins.int' -reveal_type(g()) # N: Revealed type is 'builtins.str' -reveal_type(h()) # N: Revealed type is 'Any' -[file pyproject.toml] -\[tool.mypy] -plugins = [ - "/test-data/unit/plugins/fnplugin.py", - "plugin2" -] - [case testMissingPluginFile] # flags: --config-file tmp/mypy.ini [file mypy.ini] @@ -121,15 +64,6 @@ plugins=missing.py tmp/mypy.ini:2: error: Can't find plugin 'tmp/missing.py' --' (work around syntax highlighting) -[case testMissingPluginFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -[file pyproject.toml] -\[tool.mypy] -plugins = "missing.py" -[out] -tmp/pyproject.toml:1: error: Can't find plugin 'tmp/missing.py' ---' (work around syntax highlighting) - [case testMissingPlugin] # flags: --config-file tmp/mypy.ini [file mypy.ini] @@ -138,14 +72,6 @@ plugins=missing [out] tmp/mypy.ini:2: error: Error importing plugin 'missing': No module named 'missing' -[case testMissingPluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -[file pyproject.toml] -\[tool.mypy] -plugins = "missing" -[out] -tmp/pyproject.toml:1: error: Error importing plugin 'missing': No module named 'missing' - [case testMultipleSectionsDefinePlugin] # flags: --config-file tmp/mypy.ini [file mypy.ini] @@ -159,19 +85,6 @@ plugins=another_plugin tmp/mypy.ini:4: error: Can't find plugin 'tmp/missing.py' --' (work around syntax highlighting) -[case testMultipleSectionsDefinePluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -[file pyproject.toml] -\[acme] -plugins = "acmeplugin" -\[tool.mypy] -plugins = "missing.py" -\[another] -plugins = "another_plugin" -[out] -tmp/pyproject.toml:1: error: Can't find plugin 'tmp/missing.py' ---' (work around syntax highlighting) - [case testInvalidPluginExtension] # flags: --config-file tmp/mypy.ini [file mypy.ini] @@ -181,15 +94,6 @@ plugins=dir/badext.pyi [out] tmp/mypy.ini:2: error: Plugin 'badext.pyi' does not have a .py extension -[case testInvalidPluginExtensionPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -[file pyproject.toml] -\[tool.mypy] -plugins = "dir/badext.pyi" -[file dir/badext.pyi] -[out] -tmp/pyproject.toml:1: error: Plugin 'badext.pyi' does not have a .py extension - [case testMissingPluginEntryPoint] # flags: --config-file tmp/mypy.ini [file mypy.ini] @@ -198,14 +102,6 @@ tmp/pyproject.toml:1: error: Plugin 'badext.pyi' does not have a .py extension [out] tmp/mypy.ini:2: error: Plugin '/test-data/unit/plugins/noentry.py' does not define entry point function "plugin" -[case testMissingPluginEntryPointPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -[file pyproject.toml] -\[tool.mypy] - plugins = "/test-data/unit/plugins/noentry.py" -[out] -tmp/pyproject.toml:1: error: Plugin '/test-data/unit/plugins/noentry.py' does not define entry point function "plugin" - [case testCustomPluginEntryPointFile] # flags: --config-file tmp/mypy.ini def f() -> str: ... @@ -214,14 +110,6 @@ reveal_type(f()) # N: Revealed type is 'builtins.int' \[mypy] plugins=/test-data/unit/plugins/customentry.py:register -[case testCustomPluginEntryPointFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f() -> str: ... -reveal_type(f()) # N: Revealed type is 'builtins.int' -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/customentry.py:register" - [case testCustomPluginEntryPoint] # flags: --config-file tmp/mypy.ini def f() -> str: ... @@ -230,14 +118,6 @@ reveal_type(f()) # N: Revealed type is 'builtins.int' \[mypy] plugins=customentry:register -[case testCustomPluginEntryPointPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f() -> str: ... -reveal_type(f()) # N: Revealed type is 'builtins.int' -[file pyproject.toml] -\[tool.mypy] -plugins = "customentry:register" - [case testInvalidPluginEntryPointReturnValue] # flags: --config-file tmp/mypy.ini def f(): pass @@ -259,27 +139,6 @@ plugins=/test-data/unit/plugins/badreturn2.py [out] tmp/mypy.ini:2: error: Return value of "plugin" must be a subclass of "mypy.plugin.Plugin" (in /test-data/unit/plugins/badreturn2.py) -[case testInvalidPluginEntryPointReturnValuePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f(): pass -f() -[file pyproject.toml] -\[tool.mypy] - -plugins = "/test-data/unit/plugins/badreturn.py" -[out] -tmp/pyproject.toml:1: error: Type object expected as the return value of "plugin"; got None (in /test-data/unit/plugins/badreturn.py) - -[case testInvalidPluginEntryPointReturnValuePyprojectTOML2] -# flags: --config-file tmp/pyproject.toml -def f(): pass -f() -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/badreturn2.py" -[out] -tmp/pyproject.toml:1: error: Return value of "plugin" must be a subclass of "mypy.plugin.Plugin" (in /test-data/unit/plugins/badreturn2.py) - [case testAttributeTypeHookPlugin] # flags: --config-file tmp/mypy.ini from typing import Callable @@ -301,27 +160,6 @@ class DerivedSignal(Signal[T]): ... \[mypy] plugins=/test-data/unit/plugins/attrhook.py -[case testAttributeTypeHookPluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Callable -from m import Signal, DerivedSignal -s: Signal[Callable[[int], None]] = Signal() -s(1) -s('') # E: Argument 1 has incompatible type "str"; expected "int" - -ds: DerivedSignal[Callable[[int], None]] = DerivedSignal() -ds('') # E: Argument 1 has incompatible type "str"; expected "int" -[file m.py] -from typing import TypeVar, Generic, Callable -T = TypeVar('T', bound=Callable[..., None]) -class Signal(Generic[T]): - __call__: Callable[..., None] # This type is replaced by the plugin - -class DerivedSignal(Signal[T]): ... -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/attrhook.py" - [case testAttributeHookPluginForDynamicClass] # flags: --config-file tmp/mypy.ini from m import Magic, DerivedMagic @@ -347,31 +185,6 @@ class DerivedMagic(Magic): ... \[mypy] plugins=/test-data/unit/plugins/attrhook2.py -[case testAttributeHookPluginForDynamicClassPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from m import Magic, DerivedMagic - -magic = Magic() -reveal_type(magic.magic_field) # N: Revealed type is 'builtins.str' -reveal_type(magic.non_magic_method()) # N: Revealed type is 'builtins.int' -reveal_type(magic.non_magic_field) # N: Revealed type is 'builtins.int' -magic.nonexistent_field # E: Field does not exist -reveal_type(magic.fallback_example) # N: Revealed type is 'Any' -reveal_type(DerivedMagic().magic_field) # N: Revealed type is 'builtins.str' -[file m.py] -from typing import Any -class Magic: - # Triggers plugin infrastructure: - def __getattr__(self, x: Any) -> Any: ... - def non_magic_method(self) -> int: ... - non_magic_field: int - -class DerivedMagic(Magic): ... - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/attrhook2.py" - [case testTypeAnalyzeHookPlugin] # flags: --config-file tmp/mypy.ini from typing import Callable @@ -391,25 +204,6 @@ class Signal(Generic[T]): plugins=/test-data/unit/plugins/type_anal_hook.py [builtins fixtures/dict.pyi] -[case testTypeAnalyzeHookPluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Callable -from mypy_extensions import DefaultArg -from m import Signal -s: Signal[[int, DefaultArg(str, 'x')]] = Signal() -reveal_type(s) # N: Revealed type is 'm.Signal[def (builtins.int, x: builtins.str =)]' -s.x # E: "Signal[Callable[[int, str], None]]" has no attribute "x" -ss: Signal[int, str] # E: Invalid "Signal" type (expected "Signal[[t, ...]]") -[file m.py] -from typing import TypeVar, Generic, Callable -T = TypeVar('T', bound=Callable[..., None]) -class Signal(Generic[T]): - __call__: Callable[..., None] -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/type_anal_hook.py" -[builtins fixtures/dict.pyi] - [case testFunctionPluginHookForClass] # flags: --config-file tmp/mypy.ini import mod @@ -448,44 +242,6 @@ plugins=/test-data/unit/plugins/class_callable.py [builtins fixtures/bool.pyi] [out] -[case testFunctionPluginHookForClassPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import mod -from mod import AttrInt - -Alias = AttrInt -AnotherAlias = mod.Attr - -class C: - x = Alias() - y = mod.AttrInt(required=True) - z = AnotherAlias(int, required=False) - -c = C() -reveal_type(c.x) # N: Revealed type is 'Union[builtins.int, None]' -reveal_type(c.y) # N: Revealed type is 'builtins.int*' -reveal_type(c.z) # N: Revealed type is 'Union[builtins.int*, None]' - -[file mod.py] -from typing import Generic, TypeVar, Type -T = TypeVar('T') - -class Attr(Generic[T]): - def __init__(self, tp: Type[T], required: bool = False) -> None: - pass - def __get__(self, instance: object, owner: type) -> T: - pass - -class AttrInt(Attr[int]): - def __init__(self, required: bool = False) -> None: - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/class_callable.py" -[builtins fixtures/bool.pyi] -[out] - [case testFunctionPluginHookForReturnedCallable] # flags: --config-file tmp/mypy.ini from m import decorator1, decorator2 @@ -503,23 +259,6 @@ def decorator2() -> Callable[..., Callable[..., int]]: pass \[mypy] plugins=/test-data/unit/plugins/named_callable.py -[case testFunctionPluginHookForReturnedCallablePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from m import decorator1, decorator2 -@decorator1() -def f() -> None: pass -@decorator2() -def g() -> None: pass -reveal_type(f) # N: Revealed type is 'def (*Any, **Any) -> builtins.str' -reveal_type(g) # N: Revealed type is 'def (*Any, **Any) -> builtins.int' -[file m.py] -from typing import Callable -def decorator1() -> Callable[..., Callable[..., int]]: pass -def decorator2() -> Callable[..., Callable[..., int]]: pass -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/named_callable.py" - [case testFunctionMethodContextsHasArgNames] # flags: --config-file tmp/mypy.ini from mod import Class, func @@ -549,35 +288,6 @@ def func(classname: str, arg1: Any, arg2: Any) -> Any: plugins=/test-data/unit/plugins/arg_names.py [builtins fixtures/classmethod.pyi] -[case testFunctionMethodContextsHasArgNamesPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import Class, func - -reveal_type(Class().method(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(Class.myclassmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(Class.mystaticmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(Class.method(self=Class(), arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(func(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' - -[file mod.py] -from typing import Any -class Class: - def method(self, classname: str, arg1: Any, arg2: Any) -> Any: - pass - @classmethod - def myclassmethod(cls, classname: str, arg1: Any, arg2: Any): - pass - @staticmethod - def mystaticmethod(classname: str, arg1: Any, arg2: Any): - pass -def func(classname: str, arg1: Any, arg2: Any) -> Any: - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" -[builtins fixtures/classmethod.pyi] - [case testFunctionMethodContextsHasArgNamesPositionals] # flags: --config-file tmp/mypy.ini from mod import Class, func @@ -607,35 +317,6 @@ def func(classname: str, arg1: Any, arg2: Any) -> Any: plugins=/test-data/unit/plugins/arg_names.py [builtins fixtures/classmethod.pyi] -[case testFunctionMethodContextsHasArgNamesPositionalsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import Class, func - -reveal_type(Class().method('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' -reveal_type(Class.myclassmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' -reveal_type(Class.mystaticmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' -reveal_type(Class.method(Class(), 'builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' -reveal_type(func('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' - -[file mod.py] -from typing import Any -class Class: - def method(self, classname: str, arg1: Any, arg2: Any) -> Any: - pass - @classmethod - def myclassmethod(cls, classname: str, arg1: Any, arg2: Any): - pass - @staticmethod - def mystaticmethod(classname: str, arg1: Any, arg2: Any): - pass -def func(classname: str, arg1: Any, arg2: Any) -> Any: - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" -[builtins fixtures/classmethod.pyi] - [case testFunctionMethodContextsHasArgNamesInitMethod] # flags: --config-file tmp/mypy.ini from mod import ClassInit, Outer @@ -657,27 +338,6 @@ class Outer: \[mypy] plugins=/test-data/unit/plugins/arg_names.py -[case testFunctionMethodContextsHasArgNamesInitMethodPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import ClassInit, Outer - -reveal_type(ClassInit('builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(ClassInit(classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(Outer.NestedClassInit(classname='builtins.str')) # N: Revealed type is 'builtins.str' -[file mod.py] -from typing import Any -class ClassInit: - def __init__(self, classname: str): - pass -class Outer: - class NestedClassInit: - def __init__(self, classname: str): - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" - [case testFunctionMethodContextsHasArgNamesUnfilledArguments] # flags: --config-file tmp/mypy.ini from mod import ClassUnfilled, func_unfilled @@ -701,29 +361,6 @@ def func_unfilled(classname: str, arg1: Any = None, arg2: Any = None) -> Any: \[mypy] plugins=/test-data/unit/plugins/arg_names.py -[case testFunctionMethodContextsHasArgNamesUnfilledArgumentsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import ClassUnfilled, func_unfilled - -reveal_type(ClassUnfilled().method(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassUnfilled().method(arg2=1, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(ClassUnfilled().method('builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(func_unfilled(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(func_unfilled(arg2=1, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(func_unfilled('builtins.str')) # N: Revealed type is 'builtins.str' - -[file mod.py] -from typing import Any -class ClassUnfilled: - def method(self, classname: str, arg1: Any = None, arg2: Any = None) -> Any: - pass -def func_unfilled(classname: str, arg1: Any = None, arg2: Any = None) -> Any: - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" - [case testFunctionMethodContextsHasArgNamesStarExpressions] # flags: --config-file tmp/mypy.ini from mod import ClassStarExpr, func_star_expr @@ -749,31 +386,6 @@ def func_star_expr(classname: str, *args, **kwargs) -> Any: plugins=/test-data/unit/plugins/arg_names.py [builtins fixtures/dict.pyi] -[case testFunctionMethodContextsHasArgNamesStarExpressionsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import ClassStarExpr, func_star_expr - -reveal_type(ClassStarExpr().method(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassStarExpr().method('builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassStarExpr().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassStarExpr().method('builtins.str', 2, 3, 4, arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' -reveal_type(func_star_expr(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(func_star_expr('builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(func_star_expr('builtins.str', 2, 3, 4, arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' - -[file mod.py] -from typing import Any -class ClassStarExpr: - def method(self, classname: str, *args, **kwargs) -> Any: - pass -def func_star_expr(classname: str, *args, **kwargs) -> Any: - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" -[builtins fixtures/dict.pyi] - [case testFunctionMethodContextArgNamesForInheritedMethods] # flags: --config-file tmp/mypy.ini from mod import ClassChild @@ -798,30 +410,6 @@ class ClassChild(Base): plugins=/test-data/unit/plugins/arg_names.py [builtins fixtures/classmethod.pyi] -[case testFunctionMethodContextArgNamesForInheritedMethodsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import ClassChild - -reveal_type(ClassChild().method(classname='builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassChild().method(arg1=1, classname='builtins.str', arg2=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassChild().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassChild.myclassmethod('builtins.str')) # N: Revealed type is 'builtins.str' -[file mod.py] -from typing import Any -class Base: - def method(self, classname: str, arg1: Any, arg2: Any) -> Any: - pass - @classmethod - def myclassmethod(cls, classname: str) -> Any: - pass -class ClassChild(Base): - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" -[builtins fixtures/classmethod.pyi] - [case testMethodSignatureHook] # flags: --config-file tmp/mypy.ini from typing import Iterator @@ -851,35 +439,6 @@ for x in foo: plugins=/test-data/unit/plugins/method_sig_hook.py [builtins fixtures/tuple.pyi] -[case testMethodSignatureHookPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Iterator - -class Foo: - # Test that method signature hooks are applied in various cases: explicit method calls, and - # implicit dunder method calls through language syntax. - # The plugin's method signature hook should turn all str occurrences into int. - def __init__(self) -> None: ... - def __getitem__(self, index: str) -> str: ... - def __setitem__(self, index: str, value: str) -> None: ... - def __iter__(self) -> Iterator[str]: ... - def __next__(self) -> str: ... - def __call__(self, *args: str) -> str: ... - def m(self, arg: str) -> str: ... - -foo = Foo() -reveal_type(foo.m(2)) # N: Revealed type is 'builtins.int' -reveal_type(foo[3]) # N: Revealed type is 'builtins.int' -reveal_type(foo(4, 5, 6)) # N: Revealed type is 'builtins.int' -foo[4] = 5 -for x in foo: - reveal_type(x) # N: Revealed type is 'builtins.int*' - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/method_sig_hook.py" -[builtins fixtures/tuple.pyi] - [case testMethodSignatureHookNamesFullyQualified] # flags: --config-file tmp/mypy.ini from mypy_extensions import TypedDict @@ -905,31 +464,6 @@ reveal_type(FullyQualifiedTestNamedTuple('')._asdict()) # N: Revealed type is 'b plugins=/test-data/unit/plugins/fully_qualified_test_hook.py [builtins fixtures/classmethod.pyi] -[case testMethodSignatureHookNamesFullyQualifiedPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mypy_extensions import TypedDict -from typing import NamedTuple - -class FullyQualifiedTestClass: - @classmethod - def class_method(self) -> str: ... - def instance_method(self) -> str: ... - -class FullyQualifiedTestTypedDict(TypedDict): - foo: str - -FullyQualifiedTestNamedTuple = NamedTuple('FullyQualifiedTestNamedTuple', [('foo', str)]) - -# Check the return types to ensure that the method signature hook is called in each case -reveal_type(FullyQualifiedTestClass.class_method()) # N: Revealed type is 'builtins.int' -reveal_type(FullyQualifiedTestClass().instance_method()) # N: Revealed type is 'builtins.int' -reveal_type(FullyQualifiedTestNamedTuple('')._asdict()) # N: Revealed type is 'builtins.int' - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/fully_qualified_test_hook.py" -[builtins fixtures/classmethod.pyi] - [case testDynamicClassPlugin] # flags: --config-file tmp/mypy.ini from mod import declarative_base, Column, Instr @@ -956,32 +490,6 @@ class Instr(Generic[T]): ... \[mypy] plugins=/test-data/unit/plugins/dyn_class.py -[case testDynamicClassPluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import declarative_base, Column, Instr - -Base = declarative_base() - -class Model(Base): - x: Column[int] -class Other: - x: Column[int] - -reveal_type(Model().x) # N: Revealed type is 'mod.Instr[builtins.int]' -reveal_type(Other().x) # N: Revealed type is 'mod.Column[builtins.int]' -[file mod.py] -from typing import Generic, TypeVar -def declarative_base(): ... - -T = TypeVar('T') - -class Column(Generic[T]): ... -class Instr(Generic[T]): ... - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/dyn_class.py" - [case testDynamicClassPluginNegatives] # flags: --config-file tmp/mypy.ini from mod import declarative_base, Column, Instr, non_declarative_base @@ -1012,36 +520,6 @@ class Instr(Generic[T]): ... \[mypy] plugins=/test-data/unit/plugins/dyn_class.py -[case testDynamicClassPluginNegativesPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import declarative_base, Column, Instr, non_declarative_base - -Bad1 = non_declarative_base() -Bad2 = Bad3 = declarative_base() - -class C1(Bad1): ... # E: Variable "__main__.Bad1" is not valid as a type \ - # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ - # E: Invalid base class "Bad1" -class C2(Bad2): ... # E: Variable "__main__.Bad2" is not valid as a type \ - # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ - # E: Invalid base class "Bad2" -class C3(Bad3): ... # E: Variable "__main__.Bad3" is not valid as a type \ - # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ - # E: Invalid base class "Bad3" -[file mod.py] -from typing import Generic, TypeVar -def declarative_base(): ... -def non_declarative_base(): ... - -T = TypeVar('T') - -class Column(Generic[T]): ... -class Instr(Generic[T]): ... - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/dyn_class.py" - [case testDynamicClassHookFromClassMethod] # flags: --config-file tmp/mypy.ini @@ -1071,35 +549,6 @@ class Manager: \[mypy] plugins=/test-data/unit/plugins/dyn_class_from_method.py -[case testDynamicClassHookFromClassMethodPyprojectTOML] -# flags: --config-file tmp/pyproject.toml - -from mod import QuerySet, Manager - -MyManager = Manager.from_queryset(QuerySet) - -reveal_type(MyManager()) # N: Revealed type is '__main__.MyManager' -reveal_type(MyManager().attr) # N: Revealed type is 'builtins.str' - -def func(manager: MyManager) -> None: - reveal_type(manager) # N: Revealed type is '__main__.MyManager' - reveal_type(manager.attr) # N: Revealed type is 'builtins.str' - -func(MyManager()) - -[file mod.py] -from typing import Generic, TypeVar, Type -class QuerySet: - attr: str -class Manager: - @classmethod - def from_queryset(cls, queryset_cls: Type[QuerySet]): ... - -[builtins fixtures/classmethod.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/dyn_class_from_method.py" - [case testBaseClassPluginHookWorksIncremental] # flags: --config-file tmp/mypy.ini import a @@ -1131,37 +580,6 @@ plugins=/test-data/unit/plugins/common_api_incremental.py tmp/a.py:3: note: Revealed type is 'builtins.str' tmp/a.py:4: error: "Type[Base]" has no attribute "__magic__" -[case testBaseClassPluginHookWorksIncrementalPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a - -[file a.py] -from base import Base -class C(Base): ... - -[file a.py.2] -from base import Base -class C(Base): ... -reveal_type(C().__magic__) -Base.__magic__ - -[file base.py] -from lib import declarative_base -Base = declarative_base() - -[file lib.py] -from typing import Any -def declarative_base() -> Any: ... - -[file pyproject.toml] -\[tool.mypy] -python_version = 3.6 -plugins = "/test-data/unit/plugins/common_api_incremental.py" -[out] -[out2] -tmp/a.py:3: note: Revealed type is 'builtins.str' -tmp/a.py:4: error: "Type[Base]" has no attribute "__magic__" - [case testArgKindsMethod] # flags: --config-file tmp/mypy.ini class Class: @@ -1174,18 +592,6 @@ Class().method(1, *[2], **{'a': 1}) # E: [[0, 2], [4]] \[mypy] plugins=/test-data/unit/plugins/arg_kinds.py -[case testArgKindsMethodPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -class Class: - def method(self, *args, **kwargs): - pass - -Class().method(1, *[2], **{'a': 1}) # E: [[0, 2], [4]] -[builtins fixtures/dict.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/arg_kinds.py" - [case testArgKindsFunction] # flags: --config-file tmp/mypy.ini def func(*args, **kwargs): @@ -1197,17 +603,6 @@ func(1, 2, [3, 4], *[5, 6, 7], **{'a': 1}) # E: [[0, 0, 0, 2], [4]] \[mypy] plugins=/test-data/unit/plugins/arg_kinds.py -[case testArgKindsFunctionPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def func(*args, **kwargs): - pass - -func(1, 2, [3, 4], *[5, 6, 7], **{'a': 1}) # E: [[0, 0, 0, 2], [4]] -[builtins fixtures/dict.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/arg_kinds.py" - [case testHookCallableInstance] # flags: --config-file tmp/mypy.ini from typing import Generic, TypeVar @@ -1223,21 +618,6 @@ reveal_type(instance(2)) # N: Revealed type is 'builtins.float*' \[mypy] plugins=/test-data/unit/plugins/callable_instance.py -[case testHookCallableInstancePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Generic, TypeVar -T = TypeVar("T") -class Class(Generic[T]): - def __init__(self, one: T): ... - def __call__(self, two: T) -> int: ... -reveal_type(Class("hi")("there")) # N: Revealed type is 'builtins.str*' -instance = Class(3.14) -reveal_type(instance(2)) # N: Revealed type is 'builtins.float*' - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/callable_instance.py" - [case testGetMethodHooksOnUnions] # flags: --config-file tmp/mypy.ini --no-strict-optional from typing import Union @@ -1260,28 +640,6 @@ else: \[mypy] plugins=/test-data/unit/plugins/union_method.py -[case testGetMethodHooksOnUnionsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml --no-strict-optional -from typing import Union - -class Foo: - def meth(self, x: str) -> str: ... -class Bar: - def meth(self, x: int) -> float: ... -class Other: - meth: int - -x: Union[Foo, Bar, Other] -if isinstance(x.meth, int): - reveal_type(x.meth) # N: Revealed type is 'builtins.int' -else: - reveal_type(x.meth(int())) # N: Revealed type is 'builtins.int' - -[builtins fixtures/isinstancelist.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/union_method.py" - [case testGetMethodHooksOnUnionsStrictOptional] # flags: --config-file tmp/mypy.ini --strict-optional from typing import Union @@ -1304,28 +662,6 @@ else: \[mypy] plugins=/test-data/unit/plugins/union_method.py -[case testGetMethodHooksOnUnionsStrictOptionalPyprojectTOML] -# flags: --config-file tmp/pyproject.toml --strict-optional -from typing import Union - -class Foo: - def meth(self, x: str) -> str: ... -class Bar: - def meth(self, x: int) -> float: ... -class Other: - meth: int - -x: Union[Foo, Bar, Other] -if isinstance(x.meth, int): - reveal_type(x.meth) # N: Revealed type is 'builtins.int' -else: - reveal_type(x.meth(int())) # N: Revealed type is 'builtins.int' - -[builtins fixtures/isinstancelist.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/union_method.py" - [case testGetMethodHooksOnUnionsSpecial] # flags: --config-file tmp/mypy.ini from typing import Union @@ -1343,23 +679,6 @@ reveal_type(x[int()]) # N: Revealed type is 'builtins.int' \[mypy] plugins=/test-data/unit/plugins/union_method.py -[case testGetMethodHooksOnUnionsSpecialPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Union - -class Foo: - def __getitem__(self, x: str) -> str: ... -class Bar: - def __getitem__(self, x: int) -> float: ... - -x: Union[Foo, Bar] -reveal_type(x[int()]) # N: Revealed type is 'builtins.int' - -[builtins fixtures/isinstancelist.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/union_method.py" - [case testPluginDependencies] # flags: --config-file tmp/mypy.ini @@ -1373,19 +692,6 @@ plugins = "/test-data/unit/plugins/union_method.py" \[mypy] plugins=/test-data/unit/plugins/depshook.py -[case testPluginDependenciesPyprojectTOML] -# flags: --config-file tmp/pyproject.toml - -# The top level file here doesn't do anything, but the plugin should add -# a dependency on err that will cause err to be processed and an error reported. - -[file err.py] -1 + 'lol' # E: Unsupported operand types for + ("int" and "str") - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/depshook.py" - [case testCustomizeMroTrivial] # flags: --config-file tmp/mypy.ini class A: pass @@ -1393,13 +699,6 @@ class A: pass \[mypy] plugins=/test-data/unit/plugins/customize_mro.py -[case testCustomizeMroTrivialPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -class A: pass -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/customize_mro.py" - [case testDescriptorMethods] # flags: --config-file tmp/mypy.ini @@ -1423,29 +722,6 @@ Cls().attr = "foo" # E: Incompatible types in assignment (expression has type " \[mypy] plugins=/test-data/unit/plugins/descriptor.py -[case testDescriptorMethodsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml - -class Desc: - def __get__(self, obj, cls): - pass - - def __set__(self, obj, val): - pass - -class Cls: - attr = Desc() - -reveal_type(Cls().attr) # N: Revealed type is 'builtins.int' -reveal_type(Cls.attr) # N: Revealed type is 'builtins.str' - -Cls().attr = 3 -Cls().attr = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "int") - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/descriptor.py" - [case testFunctionSigPluginFile] # flags: --config-file tmp/mypy.ini @@ -1454,12 +730,3 @@ reveal_type(dynamic_signature(1)) # N: Revealed type is 'builtins.int' [file mypy.ini] \[mypy] plugins=/test-data/unit/plugins/function_sig_hook.py - -[case testFunctionSigPluginFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml - -def dynamic_signature(arg1: str) -> str: ... -reveal_type(dynamic_signature(1)) # N: Revealed type is 'builtins.int' -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/function_sig_hook.py" diff --git a/test-data/unit/check-flags.pyproject.test b/test-data/unit/check-flags.pyproject.test new file mode 100644 index 000000000000..532ff99e248a --- /dev/null +++ b/test-data/unit/check-flags.pyproject.test @@ -0,0 +1,278 @@ +[case testPerFileIncompleteDefsBasicPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, incomplete + +[file standard.py] +def incomplete(x) -> int: + return 0 +[file incomplete.py] +def incomplete(x) -> int: # E: Function is missing a type annotation for one or more arguments + return 0 +[file pyproject.toml] +\[tool.mypy] +disallow_incomplete_defs = false +\[tool.mypy-incomplete] +disallow_incomplete_defs = true + +[case testPerFileStrictOptionalBasicPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +x = 0 +if int(): + x = None +[file optional.py] +x = 0 +if int(): + x = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.mypy-optional] +strict_optional = true + +[case testPerFileStrictOptionalBasicImportStandardPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +from typing import Optional +def f(x: int) -> None: pass +an_int = 0 # type: int +optional_int = None # type: Optional[int] +f(an_int) # ints can be used as ints +f(optional_int) # optional ints can be used as ints in this file + +[file optional.py] +import standard +def f(x: int) -> None: pass +standard.an_int = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") +standard.optional_int = None # OK -- explicitly declared as optional +f(standard.an_int) # ints can be used as ints +f(standard.optional_int) # E: Argument 1 to "f" has incompatible type "None"; expected "int" + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.mypy-optional] +strict_optional = true + +[case testPerFileStrictOptionalBasicImportOptionalPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +import optional +def f(x: int) -> None: pass +f(optional.x) # OK -- in non-strict Optional context +f(optional.y) # OK -- in non-strict Optional context + +[file optional.py] +from typing import Optional +def f(x: int) -> None: pass +x = 0 # type: Optional[int] +y = None # type: None + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.mypy-optional] +strict_optional = true + +[case testPerFileStrictOptionalListItemImportOptionalPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +import optional +from typing import List +def f(x: List[int]) -> None: pass +f(optional.x) # OK -- in non-strict Optional context +f(optional.y) # OK -- in non-strict Optional context + +[file optional.py] +from typing import Optional, List +def f(x: List[int]) -> None: pass +x = [] # type: List[Optional[int]] +y = [] # type: List[int] + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.mypy-optional] +strict_optional = true +[builtins fixtures/list.pyi] + +[case testPerFileStrictOptionalNoneArgumentsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +def f(x: int = None) -> None: pass + +[file optional.py] +import standard +def f(x: int = None) -> None: pass +standard.f(None) + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.mypy-optional] +strict_optional = true + +[case testAlwaysTrueAlwaysFalseConfigFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from somewhere import YOLO, BLAH +if not YOLO: + 1+() +if BLAH: + 1+() +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = true +always_true = [ + "YOLO1", + "YOLO" +] +always_false = [ + "BLAH", + "BLAH1" +] +[builtins fixtures/bool.pyi] + +[case testDisableErrorCodeConfigFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml --disallow-untyped-defs +import foo +def bar(): + pass +[file pyproject.toml] +\[tool.mypy] +disable_error_code = [ + "import", + "no-untyped-def" +] + +[case testStrictInConfigAnyGenericPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import TypeVar, Generic + +T = TypeVar('T') + +class A(Generic[T]): + pass + +def f(c: A) -> None: # E: Missing type parameters for generic type "A" + pass +[file pyproject.toml] +\[tool.mypy] +strict = true +[out] + +[case testStrictFalseInConfigAnyGenericPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import TypeVar, Generic + +T = TypeVar('T') + +class A(Generic[T]): + pass + +def f(c: A) -> None: + pass +[file pyproject.toml] +\[tool.mypy] +strict = false +[out] + +[case testStrictEqualityPerFilePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import b +42 == 'no' # E: Non-overlapping equality check (left operand type: "Literal[42]", right operand type: "Literal['no']") +[file b.py] +42 == 'no' +[file pyproject.toml] +\[tool.mypy] +strict_equality = true +\[tool.mypy-b] +strict_equality = false +[builtins fixtures/bool.pyi] + +[case testNoImplicitReexportPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from other_module_2 import a + +[file other_module_1.py] +a = 5 + +[file other_module_2.py] +from other_module_1 import a + +[file pyproject.toml] +\[tool.mypy] +implicit_reexport = true +\[tool.mypy-other_module_2] +implicit_reexport = false +[out] +main:2: error: Module 'other_module_2' has no attribute 'a' + +[case testDisallowSubclassingAnyPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import m +import y + +[file m.py] +from typing import Any + +x = None # type: Any + +class ShouldBeFine(x): ... + +[file y.py] +from typing import Any + +x = None # type: Any + +class ShouldNotBeFine(x): ... # E: Class cannot subclass 'x' (has type 'Any') + +[file pyproject.toml] +\[tool.mypy] +disallow_subclassing_any = true +\[tool.mypy-m] +disallow_subclassing_any = false + +[case testNoImplicitOptionalPerModulePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import m + +[file m.py] +def f(a: str = None) -> int: + return 0 + +[file pyproject.toml] +\[tool.mypy] +no_implicit_optional = true +\[tool.mypy-m] +no_implicit_optional = false + +[case testNoImplicitOptionalPerModulePython2PyprojectTOML] +# flags: --config-file tmp/pyproject.toml --python-version 2.7 +import m + +[file m.py] +def f(a = None): + # type: (str) -> int + return 0 + +[file pyproject.toml] +\[tool.mypy] +no_implicit_optional = true +\[tool.mypy-m] +no_implicit_optional = false + +[case testDisableErrorCodePyprojectTOML] +# flags: --disable-error-code attr-defined +x = 'should be fine' +x.trim() diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 6dc67d316372..4bf88ac3b981 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -477,22 +477,6 @@ disallow_incomplete_defs = False \[mypy-incomplete] disallow_incomplete_defs = True -[case testPerFileIncompleteDefsBasicPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, incomplete - -[file standard.py] -def incomplete(x) -> int: - return 0 -[file incomplete.py] -def incomplete(x) -> int: # E: Function is missing a type annotation for one or more arguments - return 0 -[file pyproject.toml] -\[tool.mypy] -disallow_incomplete_defs = false -\[tool.mypy-incomplete] -disallow_incomplete_defs = true - [case testPerFileStrictOptionalBasic] # flags: --config-file tmp/mypy.ini import standard, optional @@ -512,25 +496,6 @@ strict_optional = False \[mypy-optional] strict_optional = True -[case testPerFileStrictOptionalBasicPyprojectToml] -# flags: --config-file tmp/pyproject.toml -import standard, optional - -[file standard.py] -x = 0 -if int(): - x = None -[file optional.py] -x = 0 -if int(): - x = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") - -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[tool.mypy-optional] -strict_optional = true - [case testPerFileStrictOptionalBasicImportStandard] # flags: --config-file tmp/mypy.ini import standard, optional @@ -557,33 +522,6 @@ strict_optional = False \[mypy-optional] strict_optional = True -[case testPerFileStrictOptionalBasicImportStandardPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, optional - -[file standard.py] -from typing import Optional -def f(x: int) -> None: pass -an_int = 0 # type: int -optional_int = None # type: Optional[int] -f(an_int) # ints can be used as ints -f(optional_int) # optional ints can be used as ints in this file - -[file optional.py] -import standard -def f(x: int) -> None: pass -standard.an_int = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") -standard.optional_int = None # OK -- explicitly declared as optional -f(standard.an_int) # ints can be used as ints -f(standard.optional_int) # E: Argument 1 to "f" has incompatible type "None"; expected "int" - -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[tool.mypy-optional] -strict_optional = true - - [case testPerFileStrictOptionalBasicImportOptional] # flags: --config-file tmp/mypy.ini import standard, optional @@ -606,28 +544,6 @@ strict_optional = False \[mypy-optional] strict_optional = True -[case testPerFileStrictOptionalBasicImportOptionalPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, optional - -[file standard.py] -import optional -def f(x: int) -> None: pass -f(optional.x) # OK -- in non-strict Optional context -f(optional.y) # OK -- in non-strict Optional context - -[file optional.py] -from typing import Optional -def f(x: int) -> None: pass -x = 0 # type: Optional[int] -y = None # type: None - -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[tool.mypy-optional] -strict_optional = true - [case testPerFileStrictOptionalListItemImportOptional] # flags: --config-file tmp/mypy.ini import standard, optional @@ -652,30 +568,6 @@ strict_optional = False strict_optional = True [builtins fixtures/list.pyi] -[case testPerFileStrictOptionalListItemImportOptionalPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, optional - -[file standard.py] -import optional -from typing import List -def f(x: List[int]) -> None: pass -f(optional.x) # OK -- in non-strict Optional context -f(optional.y) # OK -- in non-strict Optional context - -[file optional.py] -from typing import Optional, List -def f(x: List[int]) -> None: pass -x = [] # type: List[Optional[int]] -y = [] # type: List[int] - -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[tool.mypy-optional] -strict_optional = true -[builtins fixtures/list.pyi] - [case testPerFileStrictOptionalComplicatedList] from typing import Union, Optional, List @@ -701,24 +593,6 @@ strict_optional = False \[mypy-optional] strict_optional = True -[case testPerFileStrictOptionalNoneArgumentsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, optional - -[file standard.py] -def f(x: int = None) -> None: pass - -[file optional.py] -import standard -def f(x: int = None) -> None: pass -standard.f(None) - -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[tool.mypy-optional] -strict_optional = true - [case testDisallowImplicitTypesIgnoreMissingTypes] # flags: --ignore-missing-imports --disallow-any-unimported from missing import MyType @@ -1201,26 +1075,6 @@ always_true = YOLO1, YOLO always_false = BLAH, BLAH1 [builtins fixtures/bool.pyi] -[case testAlwaysTrueAlwaysFalseConfigFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from somewhere import YOLO, BLAH -if not YOLO: - 1+() -if BLAH: - 1+() -[file pyproject.toml] -\[tool.mypy] -ignore_missing_imports = true -always_true = [ - "YOLO1", - "YOLO" -] -always_false = [ - "BLAH", - "BLAH1" -] -[builtins fixtures/bool.pyi] - [case testDisableErrorCodeConfigFile] # flags: --config-file tmp/mypy.ini --disallow-untyped-defs import foo @@ -1230,18 +1084,6 @@ def bar(): \[mypy] disable_error_code = import, no-untyped-def -[case testDisableErrorCodeConfigFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml --disallow-untyped-defs -import foo -def bar(): - pass -[file pyproject.toml] -\[tool.mypy] -disable_error_code = [ - "import", - "no-untyped-def" -] - [case testCheckDisallowAnyGenericsNamedTuple] # flags: --disallow-any-generics from typing import NamedTuple @@ -1345,22 +1187,6 @@ def f(c: A) -> None: # E: Missing type parameters for generic type "A" strict = True [out] -[case testStrictInConfigAnyGenericPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import TypeVar, Generic - -T = TypeVar('T') - -class A(Generic[T]): - pass - -def f(c: A) -> None: # E: Missing type parameters for generic type "A" - pass -[file pyproject.toml] -\[tool.mypy] -strict = true -[out] - [case testStrictFalseInConfigAnyGeneric] # flags: --config-file tmp/mypy.ini from typing import TypeVar, Generic @@ -1377,22 +1203,6 @@ def f(c: A) -> None: strict = False [out] -[case testStrictFalseInConfigAnyGenericPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import TypeVar, Generic - -T = TypeVar('T') - -class A(Generic[T]): - pass - -def f(c: A) -> None: - pass -[file pyproject.toml] -\[tool.mypy] -strict = false -[out] - [case testStrictAndStrictEquality] # flags: --strict x = 0 @@ -1414,19 +1224,6 @@ strict_equality = True strict_equality = False [builtins fixtures/bool.pyi] -[case testStrictEqualityPerFile]PyprojectTOML -# flags: --config-file tmp/pyproject.toml -import b -42 == 'no' # E: Non-overlapping equality check (left operand type: "Literal[42]", right operand type: "Literal['no']") -[file b.py] -42 == 'no' -[file pyproject.toml] -\[toolmypy] -strict_equality = true -\[tool.mypy-b] -strict_equality = false -[builtins fixtures/bool.pyi] - [case testNoImplicitReexport] # flags: --no-implicit-reexport from other_module_2 import a @@ -1505,24 +1302,6 @@ implicit_reexport = False [out] main:2: error: Module 'other_module_2' has no attribute 'a' -[case testNoImplicitReexportPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from other_module_2 import a - -[file other_module_1.py] -a = 5 - -[file other_module_2.py] -from other_module_1 import a - -[file pyproject.toml] -\[tool.mypy] -implicit_reexport = true -\[tool.mypy-other_module_2] -implicit_reexport = false -[out] -main:2: error: Module 'other_module_2' has no attribute 'a' - [case testImplicitAnyOKForNoArgs] # flags: --disallow-any-generics --show-column-numbers from typing import List @@ -1757,31 +1536,6 @@ disallow_subclassing_any = True \[mypy-m] disallow_subclassing_any = False -[case testDisallowSubclassingAnyPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import m -import y - -[file m.py] -from typing import Any - -x = None # type: Any - -class ShouldBeFine(x): ... - -[file y.py] -from typing import Any - -x = None # type: Any - -class ShouldNotBeFine(x): ... # E: Class cannot subclass 'x' (has type 'Any') - -[file pyproject.toml] -\[tool.mypy] -disallow_subclassing_any = true -\[tool.mypy-m] -disallow_subclassing_any = false - [case testNoImplicitOptionalPerModule] # flags: --config-file tmp/mypy.ini import m @@ -1811,35 +1565,6 @@ no_implicit_optional = True \[mypy-m] no_implicit_optional = False -[case testNoImplicitOptionalPerModulePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import m - -[file m.py] -def f(a: str = None) -> int: - return 0 - -[file pyproject.toml] -\[tool.mypy] -no_implicit_optional = true -\[tool.mypy-m] -no_implicit_optional = false - -[case testNoImplicitOptionalPerModulePythonPyprojectTOML2] -# flags: --config-file tmp/pyproject.toml --python-version 2.7 -import m - -[file m.py] -def f(a = None): - # type: (str) -> int - return 0 - -[file pyproject.toml] -\[tool.mypy] -no_implicit_optional = true -\[tool.mypy-m] -no_implicit_optional = false - [case testDisableErrorCode] # flags: --disable-error-code attr-defined x = 'should be fine' diff --git a/test-data/unit/check-incremental.pyproject.test b/test-data/unit/check-incremental.pyproject.test new file mode 100644 index 000000000000..745a1fdeaa8d --- /dev/null +++ b/test-data/unit/check-incremental.pyproject.test @@ -0,0 +1,306 @@ +-- Checks for incremental mode (see testcheck.py). +-- Each test is run at least twice, once with a cold cache, once with a warm cache. +-- Before the tests are run again, in step N any *.py.N files are copied to +-- *.py. There are at least two runs; more as long as there are *.py.N files. +-- +-- You can add an empty section like `[delete mod.py.2]` to delete `mod.py` +-- before the second run. +-- +-- Errors expected in the first run should be in the `[out1]` section, and +-- errors expected in the second run should be in the `[out2]` section, and so on. +-- If a section is omitted, it is expected there are no errors on that run. +-- The number of runs is determined by the highest N in all [outN] sections, but +-- there are always at least two runs. (Note that [out] is equivalent to [out1].) +-- +-- The list of modules to be checked can be specified using +-- # cmd: mypy -m mod1 mod2 mod3 +-- To check a different list on the second run, use +-- # cmd2: mypy -m mod1 mod3 +-- (and cmd3 for the third run, and so on). +-- +-- Extra command line flags may be specified using +-- # flags: --some-flag +-- If the second run requires different flags, those can be specified using +-- # flags2: --another-flag +-- (and flags3 for the third run, and so on). +-- +-- Incremental tests involving plugins that get updated are also supported. +-- All plugin files that are updated *must* end in '_plugin', so they will +-- be unloaded from 'sys.modules' between incremental steps. +-- +-- Any files that we expect to be rechecked should be annotated in the [rechecked] +-- annotation, and any files expect to be stale (aka have a modified interface) +-- should be annotated in the [stale] annotation. Note that a file that ends up +-- producing an error has its caches deleted and is marked stale automatically. +-- Such files do not need to be included in [stale ...] list. +-- +-- The test suite will automatically assume that __main__ is stale and rechecked in +-- all cases so we can avoid constantly having to annotate it. The list of +-- rechecked/stale files can be in any arbitrary order, or can be left empty +-- if no files should be rechecked/stale. +-- +-- There are additional incremental mode test cases in check-serialize.test. + +[case testIncrementalFollowImportsVariablePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +reveal_type(a.x) +[file a.py] +x = 0 +[file pyproject.toml] +\[tool.mypy] +follow_imports = "normal" +[file pyproject.toml.2] +\[tool.mypy] +follow_imports = "skip" +[out1] +main:3: note: Revealed type is 'builtins.int' +[out2] +main:3: note: Revealed type is 'Any' + +[case testIncrementalPerFileFlagsPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +pass +[file pyproject.toml] +\[tool.mypy] +warn_no_return = false +\[tool.mypy-a] +warn_no_return = true +[rechecked] + +[case testRegularUsesFgCachePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +x = 0 +[file pyproject.toml] +\[tool.mypy] +cache_fine_grained = true +[file pyproject.toml.2] +\[tool.mypy] +cache_fine_grained = false +-- Nothing should get rechecked +[rechecked] +[stale] + +[case testFgCacheNeedsFgCachePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +x = 0 +[file pyproject.toml] +\[tool.mypy] +cache_fine_grained = false +[file pyproject.toml.2] +\[tool.mypy] +cache_fine_grained = true +[rechecked a, builtins, typing] +[stale a, builtins, typing] +[builtins fixtures/tuple.pyi] + +[case testFollowImportSkipNotInvalidatedOnAddedStubOnFollowForStubsPyprojectTOML] +# flags: --follow-imports=skip --ignore-missing-imports --config-file=tmp/pyproject.toml +# cmd: mypy -m main +[file main.py] +import other +[file other.pyi.2] +x = 1 +[file pyproject.toml] +\[tool.mypy] +follow_imports_for_stubs = true +[stale] +[rechecked] + +[case testChangedPluginsInvalidateCachePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +from b import x +y: int = x + +[file a.py.2] +from b import x +y: int = x +touch = 1 + +[file b.py] +class C: ... +def f() -> C: ... +x = f() + +[file basic_plugin.py] +from mypy.plugin import Plugin + +class MyPlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith('.f'): + return my_hook + assert fullname is not None + return None + +def my_hook(ctx): + return ctx.api.named_generic_type('builtins.int', []) + +def plugin(version): + return MyPlugin + +[file basic_plugin.py.2] +from mypy.plugin import Plugin + +class MyPlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith('.f'): + return my_hook + assert fullname is not None + return None + +def my_hook(ctx): + return ctx.api.named_generic_type('builtins.str', []) + +def plugin(version): + return MyPlugin +[file pyproject.toml] +\[tool.mypy] +plugins = "basic_plugin.py" +[out] +[out2] +tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testChangedPluginsInvalidateCache2PyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +from b import x +y: int = x + +[file a.py.2] +from b import x +y: int = x +touch = 1 + +[file b.py] +class C: ... +def f() -> C: ... +x = f() + +[file basic_plugin.py] +from mypy.plugin import Plugin +from version_plugin import __version__, choice + +class MyPlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith('.f'): + return my_hook + assert fullname is not None + return None + +def my_hook(ctx): + if choice: + return ctx.api.named_generic_type('builtins.int', []) + else: + return ctx.api.named_generic_type('builtins.str', []) + +def plugin(version): + return MyPlugin + +[file version_plugin.py] +__version__ = 0.1 +choice = True + +[file version_plugin.py.2] +__version__ = 0.2 +choice = False +[file pyproject.toml] +\[tool.mypy] +plugins = "basic_plugin.py" +[out] +[out2] +tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testAddedPluginsInvalidateCachePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +from b import x +y: int = x + +[file a.py.2] +from b import x +y: int = x +touch = 1 + +[file b.py] +def f() -> int: ... +x = f() + +[file basic_plugin.py] +from mypy.plugin import Plugin + +class MyPlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith('.f'): + return my_hook + assert fullname is not None + return None + +def my_hook(ctx): + return ctx.api.named_generic_type('builtins.str', []) + +def plugin(version): + return MyPlugin + +[file pyproject.toml] +\[tool.mypy] +python_version = 3.6 +[file pyproject.toml.2] +\[tool.mypy] +python_version = 3.6 +plugins = "basic_plugin.py" +[out] +[out2] +tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testRemovedPluginsInvalidateCachePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file a.py] +from b import x +y: str = x + +[file a.py.2] +from b import x +y: str = x +touch = 1 + +[file b.py] +def f() -> int: ... +x = f() + +[file basic_plugin.py] +from mypy.plugin import Plugin + +class MyPlugin(Plugin): + def get_function_hook(self, fullname): + if fullname.endswith('.f'): + return my_hook + assert fullname is not None + return None + +def my_hook(ctx): + return ctx.api.named_generic_type('builtins.str', []) + +def plugin(version): + return MyPlugin + +[file pyproject.toml] +\[tool.mypy] +python_version = 3.6 +plugins = "basic_plugin.py" +[file pyproject.toml.2] +\[tool.mypy] +python_version = 3.6 +[out] +[out2] +tmp/a.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 4f6f30c140c4..93ba2162fb39 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -1812,23 +1812,6 @@ main:3: note: Revealed type is 'builtins.int' [out2] main:3: note: Revealed type is 'Any' -[case testIncrementalFollowImportsVariablePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -reveal_type(a.x) -[file a.py] -x = 0 -[file pyproject.toml] -\[tool.mypy] -follow_imports = "normal" -[file pyproject.toml.2] -\[tool.mypy] -follow_imports = "skip" -[out1] -main:3: note: Revealed type is 'builtins.int' -[out2] -main:3: note: Revealed type is 'Any' - [case testIncrementalNamedTupleInMethod] from ntcrash import nope [file ntcrash.py] @@ -2026,18 +2009,6 @@ warn_no_return = False warn_no_return = True [rechecked] -[case testIncrementalPerFileFlagsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -pass -[file pyproject.toml] -\[tool.mypy] -warn_no_return = false -\[tool.mypy-a] -warn_no_return = true -[rechecked] - [case testIncrementalClassVar] from typing import ClassVar class A: @@ -3694,21 +3665,6 @@ cache_fine_grained = False [rechecked] [stale] -[case testRegularUsesFgCachePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -x = 0 -[file pyproject.toml] -\[tool.mypy] -cache_fine_grained = true -[file pyproject.toml.2] -\[tool.mypy] -cache_fine_grained = false --- Nothing should get rechecked -[rechecked] -[stale] - [case testFgCacheNeedsFgCache] # flags: --config-file tmp/mypy.ini import a @@ -3724,21 +3680,6 @@ cache_fine_grained = True [stale a, builtins, typing] [builtins fixtures/tuple.pyi] -[case testFgCacheNeedsFgCachePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -x = 0 -[file pyproject.toml] -\[tool.mypy] -cache_fine_grained = false -[file pyproject.toml.2] -\[tool.mypy] -cache_fine_grained = true -[rechecked a, builtins, typing] -[stale a, builtins, typing] -[builtins fixtures/tuple.pyi] - [case testIncrementalPackageNameOverload] # cmd: mypy -m main a # flags: --follow-imports=skip @@ -4719,19 +4660,6 @@ follow_imports_for_stubs = True [stale] [rechecked] -[case testFollowImportSkipNotInvalidatedOnAddedStubOnFollowForStubsPyprojectTOML] -# flags: --follow-imports=skip --ignore-missing-imports --config-file=tmp/pyproject.toml -# cmd: mypy -m main -[file main.py] -import other -[file other.pyi.2] -x = 1 -[file pyproject.toml] -\[tool.mypy] -follow_imports_for_stubs = true -[stale] -[rechecked] - [case testAddedSkippedStubsPackageFrom] # flags: --follow-imports=skip --ignore-missing-imports # cmd: mypy -m main @@ -4991,112 +4919,6 @@ plugins=basic_plugin.py [out2] tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") -[case testChangedPluginsInvalidateCachePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -from b import x -y: int = x - -[file a.py.2] -from b import x -y: int = x -touch = 1 - -[file b.py] -class C: ... -def f() -> C: ... -x = f() - -[file basic_plugin.py] -from mypy.plugin import Plugin - -class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.endswith('.f'): - return my_hook - assert fullname is not None - return None - -def my_hook(ctx): - return ctx.api.named_generic_type('builtins.int', []) - -def plugin(version): - return MyPlugin - -[file basic_plugin.py.2] -from mypy.plugin import Plugin - -class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.endswith('.f'): - return my_hook - assert fullname is not None - return None - -def my_hook(ctx): - return ctx.api.named_generic_type('builtins.str', []) - -def plugin(version): - return MyPlugin -[file pyproject.toml] -\[tool.mypy] -plugins = "basic_plugin.py" -[out] -[out2] -tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") - -[case testChangedPluginsInvalidateCachePyprojectTOML2] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -from b import x -y: int = x - -[file a.py.2] -from b import x -y: int = x -touch = 1 - -[file b.py] -class C: ... -def f() -> C: ... -x = f() - -[file basic_plugin.py] -from mypy.plugin import Plugin -from version_plugin import __version__, choice - -class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.endswith('.f'): - return my_hook - assert fullname is not None - return None - -def my_hook(ctx): - if choice: - return ctx.api.named_generic_type('builtins.int', []) - else: - return ctx.api.named_generic_type('builtins.str', []) - -def plugin(version): - return MyPlugin - -[file version_plugin.py] -__version__ = 0.1 -choice = True - -[file version_plugin.py.2] -__version__ = 0.2 -choice = False -[file pyproject.toml] -\[tool.mypy] -plugins = "basic_plugin.py" -[out] -[out2] -tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") - [case testAddedPluginsInvalidateCache] # flags: --config-file tmp/mypy.ini import a @@ -5140,49 +4962,6 @@ plugins=basic_plugin.py [out2] tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") -[case testAddedPluginsInvalidateCachePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -from b import x -y: int = x - -[file a.py.2] -from b import x -y: int = x -touch = 1 - -[file b.py] -def f() -> int: ... -x = f() - -[file basic_plugin.py] -from mypy.plugin import Plugin - -class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.endswith('.f'): - return my_hook - assert fullname is not None - return None - -def my_hook(ctx): - return ctx.api.named_generic_type('builtins.str', []) - -def plugin(version): - return MyPlugin - -[file pyproject.toml] -\[tool.mypy] -python_version = 3.6 -[file pyproject.toml.2] -\[tool.mypy] -python_version = 3.6 -plugins = "basic_plugin.py" -[out] -[out2] -tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") - [case testRemovedPluginsInvalidateCache] # flags: --config-file tmp/mypy.ini import a @@ -5226,100 +5005,6 @@ python_version=3.6 [out2] tmp/a.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") -[case testRemovedPluginsInvalidateCachePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -from b import x -y: str = x - -[file a.py.2] -from b import x -y: str = x -touch = 1 - -[file b.py] -def f() -> int: ... -x = f() - -[file basic_plugin.py] -from mypy.plugin import Plugin - -class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.endswith('.f'): - return my_hook - assert fullname is not None - return None - -def my_hook(ctx): - return ctx.api.named_generic_type('builtins.str', []) - -def plugin(version): - return MyPlugin - -[file pyproject.toml] -\[tool.mypy] -python_version = 3.6 -plugins = "basic_plugin.py" -[file pyproject.toml.2] -\[tool.mypy] -python_version = 3.6 -[out] -[out2] -tmp/a.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") - -[case testPluginConfigData] -# flags: --config-file tmp/mypy.ini -import a -import b -[file a.py] -[file b.py] -[file test.json] -{"a": false, "b": false} -[file test.json.2] -{"a": true, "b": false} - -[file mypy.ini] -\[mypy] -plugins=/test-data/unit/plugins/config_data.py - -# The config change will force a to be rechecked but not b. -[rechecked a] - -[case testPluginConfigDataPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -import b -[file a.py] -[file b.py] -[file test.json] -{"a": false, "b": false} -[file test.json.2] -{"a": true, "b": false} - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/config_data.py" - -# The config change will force a to be rechecked but not b. -[rechecked a] - -[case testLiteralIncrementalTurningIntoLiteral] -import mod -reveal_type(mod.a) -[file mod.py] -from typing_extensions import Literal -a = 1 -[file mod.py.2] -from typing_extensions import Literal -a: Literal[2] = 2 -[builtins fixtures/tuple.pyi] -[out] -main:2: note: Revealed type is 'builtins.int' -[out2] -main:2: note: Revealed type is 'Literal[2]' - [case testAddedSubStarImport] # cmd: mypy -m a pack pack.mod b # cmd2: mypy -m other diff --git a/test-data/unit/check-modules-case.pyproject.test b/test-data/unit/check-modules-case.pyproject.test new file mode 100644 index 000000000000..210b56bfa119 --- /dev/null +++ b/test-data/unit/check-modules-case.pyproject.test @@ -0,0 +1,56 @@ +-- Type checker test cases dealing with modules and imports on case-insensitive filesystems. + +[case testCaseInsensitivityDirPyprojectTOML] +# flags: --config-file tmp/pyproject.toml + +from a import B # E: Module 'a' has no attribute 'B' +from other import x +reveal_type(x) # N: Revealed type is 'builtins.int' + +[file a/__init__.py] +[file a/b/__init__.py] +[file FuNkY_CaSe/other.py] +x = 1 + +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/funky_case" + +[case testPreferPackageOverFileCasePyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a +[file funky/a.py] +/ # Deliberate syntax error, this file should not be parsed. +[file FuNkY/a/__init__.py] +pass + +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/funky" + +[case testNamespacePackagePickFirstOnMypyPathCasePyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar import x +reveal_type(x) # N: Revealed type is 'builtins.int' +[file XX/foo/bar.py] +x = 0 +[file yy/foo/bar.py] +x = '' +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + +[case testClassicPackageInsideNamespacePackageCasePyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar.baz.boo import x +reveal_type(x) # N: Revealed type is 'builtins.int' +[file xx/foo/bar/baz/boo.py] +x = '' +[file xx/foo/bar/baz/__init__.py] +[file yy/foo/bar/baz/boo.py] +x = 0 +[file yy/foo/bar/__init__.py] + +[file pyproject.toml] +\[tool.mypy] +mypy_path = "TmP/xX,TmP/yY" diff --git a/test-data/unit/check-modules-case.test b/test-data/unit/check-modules-case.test index 8c79a07ca6d6..521db0833e6e 100644 --- a/test-data/unit/check-modules-case.test +++ b/test-data/unit/check-modules-case.test @@ -22,22 +22,6 @@ x = 1 \[mypy] mypy_path = tmp/funky_case -[case testCaseInsensitivityDirPyprojectTOML] -# flags: --config-file tmp/pyproject.toml - -from a import B # E: Module 'a' has no attribute 'B' -from other import x -reveal_type(x) # N: Revealed type is 'builtins.int' - -[file a/__init__.py] -[file a/b/__init__.py] -[file FuNkY_CaSe/other.py] -x = 1 - -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/funky_case" - [case testPreferPackageOverFileCase] # flags: --config-file tmp/mypy.ini import a @@ -50,18 +34,6 @@ pass \[mypy] mypy_path = tmp/funky -[case testPreferPackageOverFileCasePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file funky/a.py] -/ # Deliberate syntax error, this file should not be parsed. -[file FuNkY/a/__init__.py] -pass - -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/funky" - [case testNotPreferPackageOverFileCase] import a [file a.py] @@ -81,18 +53,6 @@ x = '' \[mypy] mypy_path = tmp/xx, tmp/yy -[case testNamespacePackagePickFirstOnMypyPathCasePyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar import x -reveal_type(x) # N: Revealed type is 'builtins.int' -[file XX/foo/bar.py] -x = 0 -[file yy/foo/bar.py] -x = '' -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - [case testClassicPackageInsideNamespacePackageCase] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar.baz.boo import x @@ -107,18 +67,3 @@ x = 0 [file mypy.ini] \[mypy] mypy_path = TmP/xX, TmP/yY - -[case testClassicPackageInsideNamespacePackageCasePyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar.baz.boo import x -reveal_type(x) # N: Revealed type is 'builtins.int' -[file xx/foo/bar/baz/boo.py] -x = '' -[file xx/foo/bar/baz/__init__.py] -[file yy/foo/bar/baz/boo.py] -x = 0 -[file yy/foo/bar/__init__.py] - -[file pyproject.toml] -\[tool.mypy] -mypy_path = "TmP/xX,TmP/yY" diff --git a/test-data/unit/check-modules.pyproject.test b/test-data/unit/check-modules.pyproject.test new file mode 100644 index 000000000000..633b68b6bf75 --- /dev/null +++ b/test-data/unit/check-modules.pyproject.test @@ -0,0 +1,92 @@ +-- Type checker test cases dealing with modules and imports. +-- Towards the end there are tests for PEP 420 (namespace packages, i.e. __init__.py-less packages). + +[case testModuleGetattrInit10PyprojectTOML] +# flags: --config-file tmp/pyproject.toml +import a.b.c # silenced +import a.b.d # error + +[file a/__init__.pyi] +from typing import Any +def __getattr__(attr: str) -> Any: ... +[file a/b/__init__.pyi] +# empty (i.e. complete subpackage) + +[file pyproject.toml] +\[tool.mypy] +\[tool.'mypy-a.b.c'] +ignore_missing_imports = true +[builtins fixtures/module.pyi] +[out] +main:3: error: Cannot find implementation or library stub for module named "a.b.d" +main:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports + +[case testNamespacePackageWithMypyPathPyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bax import x +from foo.bay import y +from foo.baz import z +reveal_type(x) # N: Revealed type is 'builtins.int' +reveal_type(y) # N: Revealed type is 'builtins.int' +reveal_type(z) # N: Revealed type is 'builtins.int' +[file xx/foo/bax.py] +x = 0 +[file yy/foo/bay.py] +y = 0 +[file foo/baz.py] +z = 0 +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + +[case testClassicPackageIgnoresEarlierNamespacePackagePyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar import y +reveal_type(y) # N: Revealed type is 'builtins.int' +[file xx/foo/bar.py] +x = '' +[file yy/foo/bar.py] +y = 0 +[file yy/foo/__init__.py] +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + +[case testNamespacePackagePickFirstOnMypyPathPyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar import x +reveal_type(x) # N: Revealed type is 'builtins.int' +[file xx/foo/bar.py] +x = 0 +[file yy/foo/bar.py] +x = '' +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + +[case testNamespacePackageInsideClassicPackagePyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar.baz import x +reveal_type(x) # N: Revealed type is 'builtins.int' +[file xx/foo/bar/baz.py] +x = '' +[file yy/foo/bar/baz.py] +x = 0 +[file yy/foo/__init__.py] +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" + +[case testClassicPackageInsideNamespacePackagePyprojectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar.baz.boo import x +reveal_type(x) # N: Revealed type is 'builtins.int' +[file xx/foo/bar/baz/boo.py] +x = '' +[file xx/foo/bar/baz/__init__.py] +[file yy/foo/bar/baz/boo.py] +x = 0 +[file yy/foo/bar/__init__.py] +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/xx,tmp/yy" diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 6db63591a862..b2e10ba14369 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -2392,26 +2392,6 @@ ignore_missing_imports = True main:3: error: Cannot find implementation or library stub for module named "a.b.d" main:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -[case testModuleGetattrInitPyprojectTOML10] -# flags: --config-file tmp/pyproject.toml -import a.b.c # silenced -import a.b.d # error - -[file a/__init__.pyi] -from typing import Any -def __getattr__(attr: str) -> Any: ... -[file a/b/__init__.pyi] -# empty (i.e. complete subpackage) - -[file pyproject.toml] -\[tool.mypy] -\[tool.'mypy-a.b.c'] -ignore_missing_imports = true -[builtins fixtures/module.pyi] -[out] -main:3: error: Cannot find implementation or library stub for module named "a.b.d" -main:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports - [case testIndirectFromImportWithinCycleUsedAsBaseClass] import a [file a.py] @@ -2669,24 +2649,6 @@ z = 0 \[mypy] mypy_path = tmp/xx, tmp/yy -[case testNamespacePackageWithMypyPathPyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bax import x -from foo.bay import y -from foo.baz import z -reveal_type(x) # N: Revealed type is 'builtins.int' -reveal_type(y) # N: Revealed type is 'builtins.int' -reveal_type(z) # N: Revealed type is 'builtins.int' -[file xx/foo/bax.py] -x = 0 -[file yy/foo/bay.py] -y = 0 -[file foo/baz.py] -z = 0 -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - [case testClassicPackageIgnoresEarlierNamespacePackage] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar import y @@ -2700,19 +2662,6 @@ y = 0 \[mypy] mypy_path = tmp/xx, tmp/yy -[case testClassicPackageIgnoresEarlierNamespacePackagePyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar import y -reveal_type(y) # N: Revealed type is 'builtins.int' -[file xx/foo/bar.py] -x = '' -[file yy/foo/bar.py] -y = 0 -[file yy/foo/__init__.py] -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - [case testNamespacePackagePickFirstOnMypyPath] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar import x @@ -2725,18 +2674,6 @@ x = '' \[mypy] mypy_path = tmp/xx, tmp/yy -[case testNamespacePackagePickFirstOnMypyPathPyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar import x -reveal_type(x) # N: Revealed type is 'builtins.int' -[file xx/foo/bar.py] -x = 0 -[file yy/foo/bar.py] -x = '' -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - [case testNamespacePackageInsideClassicPackage] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar.baz import x @@ -2750,19 +2687,6 @@ x = 0 \[mypy] mypy_path = tmp/xx, tmp/yy -[case testNamespacePackageInsideClassicPackagePyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar.baz import x -reveal_type(x) # N: Revealed type is 'builtins.int' -[file xx/foo/bar/baz.py] -x = '' -[file yy/foo/bar/baz.py] -x = 0 -[file yy/foo/__init__.py] -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - [case testClassicPackageInsideNamespacePackage] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar.baz.boo import x @@ -2777,20 +2701,6 @@ x = 0 \[mypy] mypy_path = tmp/xx, tmp/yy -[case testClassicPackageInsideNamespacePackagePyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar.baz.boo import x -reveal_type(x) # N: Revealed type is 'builtins.int' -[file xx/foo/bar/baz/boo.py] -x = '' -[file xx/foo/bar/baz/__init__.py] -[file yy/foo/bar/baz/boo.py] -x = 0 -[file yy/foo/bar/__init__.py] -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - [case testNamespacePackagePlainImport] # flags: --namespace-packages import foo.bar.baz diff --git a/test-data/unit/check-optional.pyproject.test b/test-data/unit/check-optional.pyproject.test new file mode 100644 index 000000000000..eed065332847 --- /dev/null +++ b/test-data/unit/check-optional.pyproject.test @@ -0,0 +1,26 @@ +-- Tests for strict Optional behavior + +[case testStrictOptionalCovarianceCrossModulePyprojectTOMLPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from a import asdf +x = ["lol"] +asdf(x) + +[file a.py] +from typing import List, Optional + +def asdf(x: List[Optional[str]]) -> None: + pass + +x = ["lol"] +asdf(x) + +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-a] +strict_optional = false +[out] +main:4: error: Argument 1 to "asdf" has incompatible type "List[str]"; expected "List[Optional[str]]" +main:4: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance +main:4: note: Consider using "Sequence" instead, which is covariant +[builtins fixtures/list.pyi] diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 8a91b9ddc78c..8d054595d685 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -832,28 +832,3 @@ main:4: error: Argument 1 to "asdf" has incompatible type "List[str]"; expected main:4: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance main:4: note: Consider using "Sequence" instead, which is covariant [builtins fixtures/list.pyi] - -[case testStrictOptionalCovarianceCrossModulePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from a import asdf -x = ["lol"] -asdf(x) - -[file a.py] -from typing import List, Optional - -def asdf(x: List[Optional[str]]) -> None: - pass - -x = ["lol"] -asdf(x) - -[file pyproject.toml] -\[tool.mypy] -\[tool.mypy-a] -strict_optional = false -[out] -main:4: error: Argument 1 to "asdf" has incompatible type "List[str]"; expected "List[Optional[str]]" -main:4: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance -main:4: note: Consider using "Sequence" instead, which is covariant -[builtins fixtures/list.pyi] diff --git a/test-data/unit/pyproject.test b/test-data/unit/cmdline.pyproject.test similarity index 87% rename from test-data/unit/pyproject.test rename to test-data/unit/cmdline.pyproject.test index 740b2de224eb..71e82e069f19 100644 --- a/test-data/unit/pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -15,7 +15,9 @@ -- Directories/packages on the command line -- ---------------------------------------- -[case testConfigFile] +-- Tests mirrored off of cmdline.test + +[case testConfigFilePyprojectTOML] # cmd: mypy --config-file pyproject.toml main.py [file pyproject.toml] \[tool.mypy] @@ -27,7 +29,7 @@ def f(): except ZeroDivisionError, err: print err -[case testErrorContextConfig] +[case testErrorContextConfigPyprojectTOML] # cmd: mypy main.py [file pyproject.toml] \[tool.mypy] @@ -39,7 +41,7 @@ def f() -> None: main.py: note: In function "f": main.py:2: error: Unsupported operand types for + ("int" and "str") -[case testNoConfigFile] +[case testNoConfigFilePyprojectTOML] # cmd: mypy main.py --config-file= [file pyproject.toml] \[tool.mypy] @@ -47,7 +49,7 @@ warn_unused_ignores = true [file main.py] # type: ignore -[case testPerFileConfigSection] +[case testPerFileConfigSectionPyprojectTOML] # cmd: mypy x.py y.py z.py [file pyproject.toml] \[tool.mypy] @@ -76,7 +78,7 @@ z.py:1: error: Function is missing a type annotation z.py:4: error: Call to untyped function "f" in typed context x.py:1: error: Function is missing a type annotation -[case testPerFileConfigSectionMultipleMatchesDisallowed] +[case testPerFileConfigSectionMultipleMatchesDisallowedPyprojectTOML] # cmd: mypy xx.py xy.py yx.py yy.py [file pyproject.toml] \[tool.mypy] @@ -101,7 +103,7 @@ pyproject.toml: [mypy-*x*]: Patterns must be fully-qualified module names, optio pyproject.toml: [mypy-*y*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) == Return code: 0 -[case testMultipleGlobConfigSection] +[case testMultipleGlobConfigSectionPyprojectTOML] # cmd: mypy x.py y.py z.py [file pyproject.toml] \[tool.mypy] @@ -117,12 +119,12 @@ def f(a): pass z.py:1: error: Function is missing a type annotation x.py:1: error: Function is missing a type annotation -[case testConfigNoErrorNoSection] +[case testConfigNoErrorNoSectionPyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] [out] -[case testConfigErrorUnknownFlag] +[case testConfigErrorUnknownFlagPyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] @@ -131,7 +133,7 @@ bad = 0 pyproject.toml: [mypy]: Unrecognized option: bad = 0 == Return code: 0 -[case testConfigErrorBadFlag] +[case testConfigErrorBadFlagPyprojectTOML] # cmd: mypy a.py [file pyproject.toml] \[tool.mypy] @@ -143,7 +145,7 @@ def f(): pyproject.toml: [mypy]: Unrecognized option: disallow-untyped-defs = True == Return code: 0 -[case testConfigErrorBadBoolean] +[case testConfigErrorBadBooleanPyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] @@ -152,7 +154,7 @@ ignore_missing_imports = "nah" pyproject.toml: [mypy]: ignore_missing_imports: Not a boolean: nah == Return code: 0 -[case testConfigErrorNotPerFile] +[case testConfigErrorNotPerFilePyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] @@ -162,7 +164,7 @@ python_version = 3.4 pyproject.toml: [mypy-*]: Per-module sections should only specify per-module flags (python_version) == Return code: 0 -[case testConfigMypyPath] +[case testConfigMypyPathPyprojectTOML] # cmd: mypy file.py [file pyproject.toml] \[tool.mypy] @@ -185,7 +187,7 @@ file.py:1: error: Cannot find implementation or library stub for module named "n file.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports file.py:6: error: Argument 1 to "foo" has incompatible type "str"; expected "int" -[case testIgnoreErrorsConfig] +[case testIgnoreErrorsConfigPyprojectTOML] # cmd: mypy x.py y.py [file pyproject.toml] \[tool.mypy] @@ -198,7 +200,7 @@ ignore_errors = true [out] y.py:1: error: Unsupported operand types for + ("str" and "int") -[case testConfigFollowImportsNormal] +[case testConfigFollowImportsNormalPyprojectTOML] # cmd: mypy main.py [file main.py] from a import x @@ -222,7 +224,7 @@ main.py:6: error: Unsupported operand types for + ("int" and "str") main.py:7: error: Module has no attribute "y" main.py:8: error: Unsupported operand types for + (Module and "int") -[case testConfigFollowImportsSilent] +[case testConfigFollowImportsSilentPyprojectTOML] # cmd: mypy main.py [file main.py] from a import x @@ -243,7 +245,7 @@ main.py:4: error: Unsupported operand types for + ("int" and "str") main.py:5: error: Module has no attribute "y" main.py:6: error: Unsupported operand types for + (Module and "int") -[case testConfigFollowImportsSkip] +[case testConfigFollowImportsSkipPyprojectTOML] # cmd: mypy main.py [file main.py] from a import x @@ -259,7 +261,7 @@ follow_imports = "skip" main.py:2: note: Revealed type is 'Any' main.py:4: note: Revealed type is 'Any' -[case testConfigFollowImportsError] +[case testConfigFollowImportsErrorPyprojectTOML] # cmd: mypy main.py [file main.py] from a import x @@ -277,7 +279,7 @@ main.py:1: note: (Using --follow-imports=error, module not passed on command lin main.py:2: note: Revealed type is 'Any' main.py:4: note: Revealed type is 'Any' -[case testConfigFollowImportsSelective] +[case testConfigFollowImportsSelectivePyprojectTOML] # cmd: mypy main.py [file pyproject.toml] \[tool.mypy] @@ -317,7 +319,7 @@ main.py:6: note: Revealed type is 'builtins.int' main.py:7: note: Revealed type is 'Any' main.py:8: note: Revealed type is 'Any' -[case testConfigFollowImportsInvalid] +[case testConfigFollowImportsInvalidPyprojectTOML] # cmd: mypy main.py [file pyproject.toml] \[tool.mypy] @@ -327,7 +329,7 @@ follow_imports = true pyproject.toml: [mypy]: follow_imports: invalid choice 'True' (choose from 'normal', 'silent', 'skip', 'error') == Return code: 0 -[case testConfigSilentMissingImportsOff] +[case testConfigSilentMissingImportsOffPyprojectTOML] # cmd: mypy main.py [file main.py] import missing # Expect error here @@ -340,7 +342,7 @@ main.py:1: error: Cannot find implementation or library stub for module named "m main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports main.py:2: note: Revealed type is 'Any' -[case testConfigSilentMissingImportsOn] +[case testConfigSilentMissingImportsOnPyprojectTOML] # cmd: mypy main.py [file main.py] import missing # No error here @@ -351,7 +353,7 @@ ignore_missing_imports = true [out] main.py:2: note: Revealed type is 'Any' -[case testConfigNoErrorForUnknownXFlagInSubsection] +[case testConfigNoErrorForUnknownXFlagInSubsectionPyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] @@ -359,7 +361,7 @@ main.py:2: note: Revealed type is 'Any' x_bad = 0 [out] -[case testPythonVersionTooOld10] +[case testPythonVersionTooOld10PyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] @@ -368,7 +370,7 @@ python_version = 1.0 pyproject.toml: [mypy]: python_version: Python major version '1' out of range (must be 2 or 3) == Return code: 0 -[case testPythonVersionTooOld26] +[case testPythonVersionTooOld26PyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] @@ -377,7 +379,7 @@ python_version = 2.6 pyproject.toml: [mypy]: python_version: Python 2.6 is not supported (must be 2.7) == Return code: 0 -[case testPythonVersionTooOld33] +[case testPythonVersionTooOld33PyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] @@ -386,7 +388,7 @@ python_version = 3.3 pyproject.toml: [mypy]: python_version: Python 3.3 is not supported (must be 3.4 or higher) == Return code: 0 -[case testPythonVersionTooNew28] +[case testPythonVersionTooNew28PyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] @@ -395,7 +397,7 @@ python_version = 2.8 pyproject.toml: [mypy]: python_version: Python 2.8 is not supported (must be 2.7) == Return code: 0 -[case testPythonVersionTooNew40] +[case testPythonVersionTooNew40PyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] @@ -404,28 +406,28 @@ python_version = 4.0 pyproject.toml: [mypy]: python_version: Python major version '4' out of range (must be 2 or 3) == Return code: 0 -[case testPythonVersionAccepted27] +[case testPythonVersionAccepted27PyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] python_version = 2.7 [out] -[case testPythonVersionAccepted34] +[case testPythonVersionAccepted34PyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] python_version = 3.4 [out] -[case testPythonVersionAccepted36] +[case testPythonVersionAccepted36PyprojectTOML] # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] python_version = 3.6 [out] -[case testDisallowAnyGenericsBuiltinCollections] +[case testDisallowAnyGenericsBuiltinCollectionsPyprojectTOML] # cmd: mypy m.py [file pyproject.toml] \[tool.mypy] @@ -448,7 +450,7 @@ m.py:5: error: Implicit generic "Any". Use "typing.Dict" and specify generic par m.py:6: error: Implicit generic "Any". Use "typing.Set" and specify generic parameters m.py:7: error: Implicit generic "Any". Use "typing.FrozenSet" and specify generic parameters -[case testDisallowAnyGenericsTypingCollections] +[case testDisallowAnyGenericsTypingCollectionsPyprojectTOML] # cmd: mypy m.py [file pyproject.toml] \[tool.mypy] @@ -470,7 +472,7 @@ m.py:5: error: Missing type parameters for generic type "Dict" m.py:6: error: Missing type parameters for generic type "Set" m.py:7: error: Missing type parameters for generic type "FrozenSet" -[case testSectionInheritance] +[case testSectionInheritancePyprojectTOML] # cmd: mypy a [file a/__init__.py] 0() @@ -505,7 +507,7 @@ ignore_errors = false a/b/c/d/e/__init__.py:2: error: Missing type parameters for generic type "List" a/b/c/d/e/__init__.py:3: error: Argument 1 to "g" has incompatible type "None"; expected "List[Any]" -[case testDisallowUntypedDefsAndGenerics] +[case testDisallowUntypedDefsAndGenericsPyprojectTOML] # cmd: mypy a.py [file pyproject.toml] \[tool.mypy] @@ -517,7 +519,7 @@ def get_tasks(self): [out] a.py:1: error: Function is missing a return type annotation -[case testSrcPEP420Packages] +[case testSrcPEP420PackagesPyprojectTOML] # cmd: mypy -p anamespace --namespace-packages [file pyproject.toml] \[tool.mypy] @@ -530,7 +532,7 @@ def bar(a: int, b: int) -> str: [out] src/anamespace/foo/bar.py:2: error: Incompatible return value type (got "int", expected "str") -[case testFollowImportStubsPyprojectTOML1] +[case testFollowImportStubs1PyprojectTOML] # cmd: mypy main.py [file pyproject.toml] \[tool.mypy] @@ -544,7 +546,7 @@ math.frobnicate() main.py:1: error: Import of 'math' ignored main.py:1: note: (Using --follow-imports=error, module not passed on command line) -[case testFollowImportStubsPyprojectTOML2] +[case testFollowImportStubs2PyprojectTOML] # cmd: mypy main.py [file pyproject.toml] \[tool.mypy] @@ -555,7 +557,7 @@ follow_imports_for_stubs = true import math math.frobnicate() -[case testConfigWarnUnusedSectionPyprojectTOML1] +[case testConfigWarnUnusedSection1PyprojectTOML] # cmd: mypy foo.py quux.py spam/eggs.py [file pyproject.toml] \[tool.mypy] @@ -583,7 +585,7 @@ incremental = false Warning: unused section(s) in pyproject.toml: [mypy-bar], [mypy-baz.*], [mypy-emarg.*], [mypy-emarg.hatch], [mypy-a.*.c], [mypy-a.x.b] == Return code: 0 -[case testConfigUnstructuredGlob] +[case testConfigUnstructuredGlobPyprojectTOML] # cmd: mypy emarg foo [file pyproject.toml] \[tool.mypy] @@ -615,7 +617,7 @@ foo/lol.py:1: error: Name 'fail' is not defined emarg/foo.py:1: error: Name 'fail' is not defined emarg/hatch/villip/mankangulisk.py:1: error: Name 'fail' is not defined -[case testTomlFiles] +[case testTomlFilesPyprojectTOML] # cmd: mypy [file pyproject.toml] \[tool.mypy] @@ -631,7 +633,7 @@ fail b.py:1: error: Name 'fail' is not defined a.py:1: error: Name 'fail' is not defined -[case testTomlFilesGlobbing] +[case testTomlFilesGlobbingPyprojectTOML] # cmd: mypy [file pyproject.toml] \[tool.mypy] @@ -644,7 +646,7 @@ fail a/b.py:1: error: Name 'fail' is not defined c.py:1: error: Name 'fail' is not defined -[case testTomlFilesCmdlineOverridesConfig] +[case testTomlFilesCmdlineOverridesConfigPyprojectTOML] # cmd: mypy override.py [file pyproject.toml] \[tool.mypy] @@ -653,7 +655,7 @@ files = "config.py" mypy: can't read file 'override.py': No such file or directory == Return code: 2 -[case testDuplicateModules] +[case testDuplicateModulesPyprojectTOML] # cmd: mypy src [file pyproject.toml] \[tool.mypy] @@ -668,7 +670,7 @@ import foo.bar src/foo/bar.py: error: Source file found twice under different module names: 'src.foo.bar' and 'foo.bar' == Return code: 2 -[case testCmdlinePackageAndTomlFiles] +[case testCmdlinePackageAndTomlFilesPyprojectTOML] # cmd: mypy -p pkg [file pyproject.toml] \[tool.mypy] @@ -680,7 +682,7 @@ y = 0 # type: str [out] pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") -[case testCmdlineModuleAndTomlFiles] +[case testCmdlineModuleAndTomlFilesPyprojectTOML] # cmd: mypy -m pkg [file pyproject.toml] \[tool.mypy] diff --git a/test-data/unit/daemon.pyproject.test b/test-data/unit/daemon.pyproject.test new file mode 100644 index 000000000000..3e49eef4350c --- /dev/null +++ b/test-data/unit/daemon.pyproject.test @@ -0,0 +1,63 @@ +-- End-to-end test cases for the daemon (dmypy). +-- These are special because they run multiple shell commands. + +[case testDaemonIgnoreConfigFilesPyprojectTOMLPyprojectTOML] +$ dmypy start -- --follow-imports=error +Daemon started +[file pyproject.toml] +\[tool.mypy] +files = "./foo.py" + +[case testDaemonRunRestartPluginVersionPyprojectTOMLPyprojectTOML] +$ dmypy run -- foo.py --no-error-summary +Daemon started +$ {python} -c "print(' ')" >> plug.py +$ dmypy run -- foo.py --no-error-summary +Restarting: plugins changed +Daemon stopped +Daemon started +$ dmypy stop +Daemon stopped +[file pyproject.toml] +\[tool.mypy] +follow_imports = "error" +plugins = "plug.py" +[file foo.py] +pass +[file plug.py] +from mypy.plugin import Plugin +class Dummy(Plugin): pass +def plugin(version): return Dummy + +[case testDaemonRunRestartGlobsPyprojectTOMLPyprojectTOML] +-- Ensure dmypy is not restarted if the configuration doesn't change and it contains globs +-- Note: Backslash path separator in output is replaced with forward slash so the same test succeeds on Windows as well +$ dmypy run -- foo --follow-imports=error --python-version=3.6 +Daemon started +foo/lol.py:1: error: Name 'fail' is not defined +Found 1 error in 1 file (checked 3 source files) +== Return code: 1 +$ dmypy run -- foo --follow-imports=error --python-version=3.6 +foo/lol.py:1: error: Name 'fail' is not defined +Found 1 error in 1 file (checked 3 source files) +== Return code: 1 +$ {python} -c "print('[mypy]')" >mypy.ini +$ {python} -c "print('ignore_errors=True')" >>mypy.ini +$ dmypy run -- foo --follow-imports=error --python-version=3.6 +Restarting: configuration changed +Daemon stopped +Daemon started +Success: no issues found in 3 source files +$ dmypy stop +Daemon stopped +[file pyproject.toml] +\[tool.mypy] +ignore_errors = true +\[tool.'mypy-*.lol'] +ignore_errors = false + +[file foo/__init__.py] +[file foo/lol.py] +fail +[file foo/ok.py] +a: int = 1 diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index c3cc7fd3b1be..d7dad66b5ef3 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -35,13 +35,6 @@ Daemon started \[mypy] files = ./foo.py -[case testDaemonIgnoreConfigFilesPyprojectTOML] -$ dmypy start -- --follow-imports=error -Daemon started -[file pyproject.toml] -\[tool.mypy] -files = "./foo.py" - [case testDaemonRunRestart] $ dmypy run -- foo.py --follow-imports=error Daemon started @@ -113,27 +106,6 @@ from mypy.plugin import Plugin class Dummy(Plugin): pass def plugin(version): return Dummy -[case testDaemonRunRestartPluginVersionPyprojectTOML] -$ dmypy run -- foo.py --no-error-summary -Daemon started -$ {python} -c "print(' ')" >> plug.py -$ dmypy run -- foo.py --no-error-summary -Restarting: plugins changed -Daemon stopped -Daemon started -$ dmypy stop -Daemon stopped -[file pyproject.toml] -\[tool.mypy] -follow_imports = "error" -plugins = "plug.py" -[file foo.py] -pass -[file plug.py] -from mypy.plugin import Plugin -class Dummy(Plugin): pass -def plugin(version): return Dummy - [case testDaemonRunRestartGlobs] -- Ensure dmypy is not restarted if the configuration doesn't change and it contains globs -- Note: Backslash path separator in output is replaced with forward slash so the same test succeeds on Windows as well @@ -167,39 +139,6 @@ fail [file foo/ok.py] a: int = 1 -[case testDaemonRunRestartGlobsPyprojectTOML] --- Ensure dmypy is not restarted if the configuration doesn't change and it contains globs --- Note: Backslash path separator in output is replaced with forward slash so the same test succeeds on Windows as well -$ dmypy run -- foo --follow-imports=error --python-version=3.6 -Daemon started -foo/lol.py:1: error: Name 'fail' is not defined -Found 1 error in 1 file (checked 3 source files) -== Return code: 1 -$ dmypy run -- foo --follow-imports=error --python-version=3.6 -foo/lol.py:1: error: Name 'fail' is not defined -Found 1 error in 1 file (checked 3 source files) -== Return code: 1 -$ {python} -c "print('[mypy]')" >mypy.ini -$ {python} -c "print('ignore_errors=True')" >>mypy.ini -$ dmypy run -- foo --follow-imports=error --python-version=3.6 -Restarting: configuration changed -Daemon stopped -Daemon started -Success: no issues found in 3 source files -$ dmypy stop -Daemon stopped -[file pyproject.toml] -\[tool.mypy] -ignore_errors = true -\[tool.'mypy-*.lol'] -ignore_errors = false - -[file foo/__init__.py] -[file foo/lol.py] -fail -[file foo/ok.py] -a: int = 1 - [case testDaemonStatusKillRestartRecheck] $ dmypy status No status file found diff --git a/test-data/unit/diff.pyproject.test b/test-data/unit/diff.pyproject.test new file mode 100644 index 000000000000..3b97a2c330c1 --- /dev/null +++ b/test-data/unit/diff.pyproject.test @@ -0,0 +1,41 @@ +-- Test cases for taking a diff of two module ASTs/symbol tables. +-- The diffs are used for fined-grained incremental checking. + +[case testDynamicBasePluginDiffPyprojectTOML] +# flags: --config-file tmp/pyproject.toml +from mod import declarative_base, Column, Instr + +Base = declarative_base() + +class Model(Base): + x: Column[int] +class Other: + x: Column[int] +class Diff: + x: Column[int] +[file next.py] +from mod import declarative_base, Column, Instr + +Base = declarative_base() + +class Model(Base): + x: Column[int] +class Other: + x: Column[int] +class Diff(Base): + x: Column[int] +[file mod.py] +from typing import Generic, TypeVar +def declarative_base(): ... + +T = TypeVar('T') + +class Column(Generic[T]): ... +class Instr(Generic[T]): ... + +[file pyproject.toml] +\[tool.mypy] +plugins = "/test-data/unit/plugins/dyn_class.py" +[out] +__main__.Diff +__main__.Diff.x diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index 46b69524c6cd..ee3519478c45 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -1146,45 +1146,6 @@ plugins=/test-data/unit/plugins/dyn_class.py __main__.Diff __main__.Diff.x -[case testDynamicBasePluginDiffPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import declarative_base, Column, Instr - -Base = declarative_base() - -class Model(Base): - x: Column[int] -class Other: - x: Column[int] -class Diff: - x: Column[int] -[file next.py] -from mod import declarative_base, Column, Instr - -Base = declarative_base() - -class Model(Base): - x: Column[int] -class Other: - x: Column[int] -class Diff(Base): - x: Column[int] -[file mod.py] -from typing import Generic, TypeVar -def declarative_base(): ... - -T = TypeVar('T') - -class Column(Generic[T]): ... -class Instr(Generic[T]): ... - -[file pyproject.toml] -\[tool.mypy] -plugins = "/test-data/unit/plugins/dyn_class.py" -[out] -__main__.Diff -__main__.Diff.x - [case testLiteralTriggersVar] from typing_extensions import Literal diff --git a/test-data/unit/fine-grained.pyproject.test b/test-data/unit/fine-grained.pyproject.test new file mode 100644 index 000000000000..a20070dbbf8a --- /dev/null +++ b/test-data/unit/fine-grained.pyproject.test @@ -0,0 +1,245 @@ +-- Test cases for fine-grained incremental checking +-- +-- Test cases may define multiple versions of a file +-- (e.g. m.py, m.py.2). There is always an initial batch +-- pass that processes all files present initially, followed +-- by one or more fine-grained incremental passes that use +-- alternative versions of files, if available. If a file +-- just has a single .py version, it is used for all passes. + +-- TODO: what if version for some passes but not all + +-- Output is laid out like this: +-- +-- [out] +-- +-- == +-- +-- +-- +-- Modules that are expected to be detected as changed by dmypy_server +-- can be checked with [stale ...] +-- Generally this should mean added, deleted, or changed files, though there +-- are important edge cases related to the cache: deleted files won't be detected +-- as changed in the initial run with the cache while modules that depended on them +-- should be. +-- +-- Modules that are require a full-module reprocessing by update can be checked with +-- [rechecked ...]. This should include any files detected as having changed as well +-- as any files that contain targets that need to be reprocessed but which haven't +-- been loaded yet. If there is no [rechecked...] directive, it inherits the value of +-- [stale ...]. +-- +-- Specifications for later runs can be given with [stale2 ...], [stale3 ...], etc. +-- +-- Test runner can parse options from pyproject.toml file. Updating this file in +-- between incremental runs is not yet supported. +-- +-- Each test case run without caching and with caching (if the initial run passes), +-- unless it has one a -only_when_cache or -only_when_nocache arguments. We sometimes +-- skip caching test cases to speed up tests, if the caching variant is not useful. +-- The caching test case variants get an implicit _cached suffix. + +[case testPerFileStrictOptionalModulePyprojectTOML] +import a +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.'mypy-a.*'] +strict_optional = true +[file a.py] +from typing import Optional +import b +x: int +y: int = x +[file b.py] +from typing import Optional +x: int +y: int = x +[file b.py.2] +from typing import Optional +x: Optional[int] +y: int = x +[file a.py.3] +from typing import Optional +import b +x: Optional[int] +y: int = x +[out] +== +== +a.py:4: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") + +[case testPerFileStrictOptionalModuleOnlyPyprojectTOML] +import a +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.'mypy-a.*'] +strict_optional = true +[file a.py] +from typing import Optional +import b +y: int = b.x +class Dummy: + def f(self) -> None: + pass +[file b.py] +from typing import Optional +import c +x: int +y: int = c.x +class Dummy: + def f(self) -> None: + pass +[file c.py] +from typing import Optional +x: int +[file c.py.2] +from typing import Optional +x: Optional[int] +[file b.py.3] +from typing import Optional +import c +x: Optional[int] +y: int = c.x +[file a.py.4] +from typing import Optional +import b +y: Optional[int] = b.x +class Dummy: + def f(self) -> None: + pass +[out] +== +== +a.py:3: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") +== + +[case testPerFileStrictOptionalFunctionPyprojectTOML] +import a +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.'mypy-b.*'] +strict_optional = true +[file a.py] +from typing import Optional +import b +def f() -> None: + x: int + x = b.g(x) +[file b.py] +from typing import Optional +import c +def g(x: Optional[int]) -> Optional[int]: + return c.h(x) +[file c.py] +from typing import Optional +def h(x: Optional[int]) -> int: + pass +[file c.py.2] +from typing import Optional +def h(x: int) -> int: + pass +[file b.py.3] +from typing import Optional +import c +def g(x: int) -> Optional[int]: + return c.h(x) +[out] +== +b.py:4: error: Argument 1 to "h" has incompatible type "Optional[int]"; expected "int" +== + +[case testPerFileStrictOptionalMethodPyprojectTOML] +import a +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[tool.'mypy-b.*'] +strict_optional = true +[file a.py] +from typing import Optional +import b +class A: + def f(self) -> None: + x: int + x = b.B().g(x) +[file b.py] +from typing import Optional +import c +class B: + def g(self, x: Optional[int]) -> Optional[int]: + return c.C().h(x) +[file c.py] +from typing import Optional +class C: + def h(self, x: Optional[int]) -> int: + pass +[file c.py.2] +from typing import Optional +class C: + def h(self, x: int) -> int: + pass +[file b.py.3] +from typing import Optional +import c +class B: + def g(self, x: int) -> Optional[int]: + return c.C().h(x) +[out] +== +b.py:5: error: Argument 1 to "h" of "C" has incompatible type "Optional[int]"; expected "int" +== + +[case testRefreshIgnoreErrors1PyprojectTOML] +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-b] +ignore_errors = true +[file a.py] +y = '1' +[file a.py.2] +y = 1 +[file b.py] +from a import y +def fu() -> None: + 1+'lurr' + y +[out] +== + +[case testRefreshIgnoreErrors2PyprojectTOML] +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy-b] +ignore_errors = true +[file b.py] +def fu() -> int: + 1+'lurr' + return 1 +[file b.py.2] +def fu() -> int: + 1+'lurr' + return 2 +[out] +== + +[case testRefreshOptionsPyprojectTOML] +[file pyproject.toml] +\[tool.mypy] +disallow_any_generics = true +\[tool.mypy-b] +disallow_any_generics = false +[file a.py] +y = '1' +[file a.py.2] +y = 1 +[file b.py] +from typing import List +from a import y +x = [] # type: List +[builtins fixtures/list.pyi] +[out] +== diff --git a/test-data/unit/pep561.pyproject.test b/test-data/unit/pep561.pyproject.test new file mode 100644 index 000000000000..b469edffc92b --- /dev/null +++ b/test-data/unit/pep561.pyproject.test @@ -0,0 +1,27 @@ +[case testTypedPkg_config_nositepackagesPyprojectTOML] +# pkgs: typedpkg +from typedpkg.sample import ex +from typedpkg import dne +a = ex(['']) +reveal_type(a) +[file pyproject.toml] +\[tool.mypy] +no_site_packages = true +[out] +testTypedPkg_config_nositepackages.py:2: error: Cannot find implementation or library stub for module named "typedpkg.sample" +testTypedPkg_config_nositepackages.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +testTypedPkg_config_nositepackages.py:3: error: Cannot find implementation or library stub for module named "typedpkg" +testTypedPkg_config_nositepackages.py:5: note: Revealed type is 'Any' + +[case testTypedPkgNoSitePkgsIgnoredImportsPyprojectTOML] +# pkgs: typedpkg +# flags: --no-site-packages +from typedpkg.sample import ex +from typedpkg import dne +a = ex(['']) +reveal_type(a) +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = true +[out] +testTypedPkgNoSitePkgsIgnoredImports.py:6: note: Revealed type is 'Any' diff --git a/test-data/unit/pep561.test b/test-data/unit/pep561.test index 6f366cbc0bd8..e8cb6927d8b8 100644 --- a/test-data/unit/pep561.test +++ b/test-data/unit/pep561.test @@ -11,19 +11,6 @@ ignore_missing_imports = True [out] testTypedPkgNoSitePkgsIgnoredImports.py:6: note: Revealed type is 'Any' -[case testTypedPkgNoSitePkgsIgnoredImportsPyprojectTOML] -# pkgs: typedpkg -# flags: --no-site-packages -from typedpkg.sample import ex -from typedpkg import dne -a = ex(['']) -reveal_type(a) -[file pyproject.toml] -\[tool.mypy] -ignore_missing_imports = true -[out] -testTypedPkgNoSitePkgsIgnoredImports.py:6: note: Revealed type is 'Any' - [case testTypedPkgSimple] # pkgs: typedpkg from typedpkg.sample import ex @@ -53,21 +40,6 @@ testTypedPkg_config_nositepackages.py:2: note: See https://mypy.readthedocs.io/e testTypedPkg_config_nositepackages.py:3: error: Cannot find implementation or library stub for module named "typedpkg" testTypedPkg_config_nositepackages.py:5: note: Revealed type is 'Any' -[case testTypedPkg_config_nositepackagesPyprojectTOML] -# pkgs: typedpkg -from typedpkg.sample import ex -from typedpkg import dne -a = ex(['']) -reveal_type(a) -[file pyproject.toml] -\[tool.mypy] -no_site_packages = true -[out] -testTypedPkg_config_nositepackages.py:2: error: Cannot find implementation or library stub for module named "typedpkg.sample" -testTypedPkg_config_nositepackages.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -testTypedPkg_config_nositepackages.py:3: error: Cannot find implementation or library stub for module named "typedpkg" -testTypedPkg_config_nositepackages.py:5: note: Revealed type is 'Any' - [case testTypedPkg_args_nositepackages] # pkgs: typedpkg # flags: --no-site-packages diff --git a/test-data/unit/reports.pyproject.test b/test-data/unit/reports.pyproject.test new file mode 100644 index 000000000000..95e41797d296 --- /dev/null +++ b/test-data/unit/reports.pyproject.test @@ -0,0 +1,12 @@ +-- Tests for reports +-- +-- This file follows syntax of cmdline.test. + +[case testConfigErrorUnknownReportPyprojectTOML] +# cmd: mypy -c pass +[file pyproject.toml] +\[tool.mypy] +bad_report = "." +[out] +pyproject.toml: [mypy]: Unrecognized report type: bad_report +== Return code: 0 diff --git a/test-data/unit/reports.test b/test-data/unit/reports.test index b783a05e7302..5e0da3f07a98 100644 --- a/test-data/unit/reports.test +++ b/test-data/unit/reports.test @@ -11,15 +11,6 @@ bad_report = . mypy.ini: [mypy]: Unrecognized report type: bad_report == Return code: 0 -[case testConfigErrorUnknownReportPyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -bad_report = "." -[out] -pyproject.toml: [mypy]: Unrecognized report type: bad_report -== Return code: 0 - [case testCoberturaParser] # cmd: mypy --cobertura-xml-report build pkg [file pkg/__init__.py] From fe201b53aa1530a5122c128619e2b23062258e25 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Wed, 17 Mar 2021 23:41:41 -0400 Subject: [PATCH 06/39] Escape special chars with single quotes --- .gitignore | 1 + mypy/test/testcheck.py | 2 +- ...est => check-custom-pyproject-plugin.test} | 72 +++++++++---------- test-data/unit/diff.pyproject.test | 2 +- 4 files changed, 39 insertions(+), 38 deletions(-) rename test-data/unit/{check-custom-plugin.pyproject.test => check-custom-pyproject-plugin.test} (91%) diff --git a/.gitignore b/.gitignore index 8e967284ecbf..c6238dc32beb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ mypyc/doc/_build *.iml /out/ .venv*/ +venv/ .mypy_cache/ .incremental_checker_cache.json .cache diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index ac76b56c8eb1..4775fa1692a3 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -83,7 +83,7 @@ 'check-enum.test', 'check-incomplete-fixture.test', 'check-custom-plugin.test', - 'check-custom-plugin.pyproject.test', + 'check-custom-pyproject-plugin.test', 'check-default-plugin.test', 'check-attr.test', 'check-ctypes.test', diff --git a/test-data/unit/check-custom-plugin.pyproject.test b/test-data/unit/check-custom-pyproject-plugin.test similarity index 91% rename from test-data/unit/check-custom-plugin.pyproject.test rename to test-data/unit/check-custom-pyproject-plugin.test index 20838d55ec18..4dfc13824018 100644 --- a/test-data/unit/check-custom-plugin.pyproject.test +++ b/test-data/unit/check-custom-pyproject-plugin.test @@ -9,7 +9,7 @@ def f() -> str: ... reveal_type(f()) # N: Revealed type is 'builtins.int' [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/fnplugin.py" +plugins = '/test-data/unit/plugins/fnplugin.py' [case testFunctionPluginPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -28,7 +28,7 @@ def g(x: T) -> T: return x # This strips out the name of a callable g(f)() [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/fnplugin.py" +plugins = '/test-data/unit/plugins/fnplugin.py' [case testTwoPluginsPyprojetTOMLPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -41,8 +41,8 @@ reveal_type(h()) # N: Revealed type is 'Any' [file pyproject.toml] \[tool.mypy] plugins=[ - "/test-data/unit/plugins/fnplugin.py", - "/test-data/unit/plugins/plugin2.py" + '/test-data/unit/plugins/fnplugin.py', + '/test-data/unit/plugins/plugin2.py' ] [case testTwoPluginsMixedTypePyprojectTOML] @@ -56,7 +56,7 @@ reveal_type(h()) # N: Revealed type is 'Any' [file pyproject.toml] \[tool.mypy] plugins = [ - "/test-data/unit/plugins/fnplugin.py", + '/test-data/unit/plugins/fnplugin.py', "plugin2" ] @@ -103,7 +103,7 @@ tmp/pyproject.toml:1: error: Plugin 'badext.pyi' does not have a .py extension # flags: --config-file tmp/pyproject.toml [file pyproject.toml] \[tool.mypy] - plugins = "/test-data/unit/plugins/noentry.py" + plugins = '/test-data/unit/plugins/noentry.py' [out] tmp/pyproject.toml:1: error: Plugin '/test-data/unit/plugins/noentry.py' does not define entry point function "plugin" @@ -113,7 +113,7 @@ def f() -> str: ... reveal_type(f()) # N: Revealed type is 'builtins.int' [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/customentry.py:register" +plugins = '/test-data/unit/plugins/customentry.py:register' [case testCustomPluginEntryPointPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -130,7 +130,7 @@ f() [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/badreturn.py" +plugins = '/test-data/unit/plugins/badreturn.py' [out] tmp/pyproject.toml:1: error: Type object expected as the return value of "plugin"; got None (in /test-data/unit/plugins/badreturn.py) @@ -140,7 +140,7 @@ def f(): pass f() [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/badreturn2.py" +plugins = '/test-data/unit/plugins/badreturn2.py' [out] tmp/pyproject.toml:1: error: Return value of "plugin" must be a subclass of "mypy.plugin.Plugin" (in /test-data/unit/plugins/badreturn2.py) @@ -163,7 +163,7 @@ class Signal(Generic[T]): class DerivedSignal(Signal[T]): ... [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/attrhook.py" +plugins = '/test-data/unit/plugins/attrhook.py' [case testAttributeHookPluginForDynamicClassPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -188,7 +188,7 @@ class DerivedMagic(Magic): ... [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/attrhook2.py" +plugins = '/test-data/unit/plugins/attrhook2.py' [case testTypeAnalyzeHookPluginPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -206,7 +206,7 @@ class Signal(Generic[T]): __call__: Callable[..., None] [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/type_anal_hook.py" +plugins = '/test-data/unit/plugins/type_anal_hook.py' [builtins fixtures/dict.pyi] [case testFunctionPluginHookForClassPyprojectTOML] @@ -243,7 +243,7 @@ class AttrInt(Attr[int]): [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/class_callable.py" +plugins = '/test-data/unit/plugins/class_callable.py' [builtins fixtures/bool.pyi] [out] @@ -262,7 +262,7 @@ def decorator1() -> Callable[..., Callable[..., int]]: pass def decorator2() -> Callable[..., Callable[..., int]]: pass [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/named_callable.py" +plugins = '/test-data/unit/plugins/named_callable.py' [case testFunctionMethodContextsHasArgNamesPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -290,7 +290,7 @@ def func(classname: str, arg1: Any, arg2: Any) -> Any: [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" +plugins = '/test-data/unit/plugins/arg_names.py' [builtins fixtures/classmethod.pyi] [case testFunctionMethodContextsHasArgNamesPositionalsPyprojectTOML] @@ -319,7 +319,7 @@ def func(classname: str, arg1: Any, arg2: Any) -> Any: [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" +plugins = '/test-data/unit/plugins/arg_names.py' [builtins fixtures/classmethod.pyi] [case testFunctionMethodContextsHasArgNamesInitMethodPyprojectTOML] @@ -341,7 +341,7 @@ class Outer: [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" +plugins = '/test-data/unit/plugins/arg_names.py' [case testFunctionMethodContextsHasArgNamesUnfilledArgumentsPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -364,7 +364,7 @@ def func_unfilled(classname: str, arg1: Any = None, arg2: Any = None) -> Any: [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" +plugins = '/test-data/unit/plugins/arg_names.py' [case testFunctionMethodContextsHasArgNamesStarExpressionsPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -388,7 +388,7 @@ def func_star_expr(classname: str, *args, **kwargs) -> Any: [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" +plugins = '/test-data/unit/plugins/arg_names.py' [builtins fixtures/dict.pyi] [case testFunctionMethodContextArgNamesForInheritedMethodsPyprojectTOML] @@ -412,7 +412,7 @@ class ClassChild(Base): [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/arg_names.py" +plugins = '/test-data/unit/plugins/arg_names.py' [builtins fixtures/classmethod.pyi] [case testMethodSignatureHookPyprojectTOML] @@ -441,7 +441,7 @@ for x in foo: [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/method_sig_hook.py" +plugins = '/test-data/unit/plugins/method_sig_hook.py' [builtins fixtures/tuple.pyi] [case testMethodSignatureHookNamesFullyQualifiedPyprojectTOML] @@ -466,7 +466,7 @@ reveal_type(FullyQualifiedTestNamedTuple('')._asdict()) # N: Revealed type is 'b [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/fully_qualified_test_hook.py" +plugins = '/test-data/unit/plugins/fully_qualified_test_hook.py' [builtins fixtures/classmethod.pyi] [case testDynamicClassPluginPyprojectTOML] @@ -493,7 +493,7 @@ class Instr(Generic[T]): ... [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/dyn_class.py" +plugins = '/test-data/unit/plugins/dyn_class.py' [case testDynamicClassPluginNegativesPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -523,7 +523,7 @@ class Instr(Generic[T]): ... [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/dyn_class.py" +plugins = '/test-data/unit/plugins/dyn_class.py' [case testDynamicClassHookFromClassMethodPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -552,7 +552,7 @@ class Manager: [builtins fixtures/classmethod.pyi] [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/dyn_class_from_method.py" +plugins = '/test-data/unit/plugins/dyn_class_from_method.py' [case testBaseClassPluginHookWorksIncrementalPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -579,7 +579,7 @@ def declarative_base() -> Any: ... [file pyproject.toml] \[tool.mypy] python_version = 3.6 -plugins = "/test-data/unit/plugins/common_api_incremental.py" +plugins = '/test-data/unit/plugins/common_api_incremental.py' [out] [out2] tmp/a.py:3: note: Revealed type is 'builtins.str' @@ -595,7 +595,7 @@ Class().method(1, *[2], **{'a': 1}) # E: [[0, 2], [4]] [builtins fixtures/dict.pyi] [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/arg_kinds.py" +plugins = '/test-data/unit/plugins/arg_kinds.py' [case testArgKindsFunctionPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -606,7 +606,7 @@ func(1, 2, [3, 4], *[5, 6, 7], **{'a': 1}) # E: [[0, 0, 0, 2], [4]] [builtins fixtures/dict.pyi] [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/arg_kinds.py" +plugins = '/test-data/unit/plugins/arg_kinds.py' [case testHookCallableInstancePyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -621,7 +621,7 @@ reveal_type(instance(2)) # N: Revealed type is 'builtins.float*' [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/callable_instance.py" +plugins = '/test-data/unit/plugins/callable_instance.py' [case testGetMethodHooksOnUnionsPyprojectTOML] # flags: --config-file tmp/pyproject.toml --no-strict-optional @@ -643,7 +643,7 @@ else: [builtins fixtures/isinstancelist.pyi] [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/union_method.py" +plugins = '/test-data/unit/plugins/union_method.py' [case testGetMethodHooksOnUnionsStrictOptionalPyprojectTOML] # flags: --config-file tmp/pyproject.toml --strict-optional @@ -665,7 +665,7 @@ else: [builtins fixtures/isinstancelist.pyi] [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/union_method.py" +plugins = '/test-data/unit/plugins/union_method.py' [case testGetMethodHooksOnUnionsSpecialPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -682,7 +682,7 @@ reveal_type(x[int()]) # N: Revealed type is 'builtins.int' [builtins fixtures/isinstancelist.pyi] [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/union_method.py" +plugins = '/test-data/unit/plugins/union_method.py' [case testPluginDependenciesPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -695,14 +695,14 @@ plugins = "/test-data/unit/plugins/union_method.py" [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/depshook.py" +plugins = '/test-data/unit/plugins/depshook.py' [case testCustomizeMroTrivialPyprojectTOML] # flags: --config-file tmp/pyproject.toml class A: pass [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/customize_mro.py" +plugins = '/test-data/unit/plugins/customize_mro.py' [case testDescriptorMethodsPyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -725,7 +725,7 @@ Cls().attr = "foo" # E: Incompatible types in assignment (expression has type " [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/descriptor.py" +plugins = '/test-data/unit/plugins/descriptor.py' [case testFunctionSigPluginFilePyprojectTOML] # flags: --config-file tmp/pyproject.toml @@ -734,4 +734,4 @@ def dynamic_signature(arg1: str) -> str: ... reveal_type(dynamic_signature(1)) # N: Revealed type is 'builtins.int' [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/function_sig_hook.py" +plugins = '/test-data/unit/plugins/function_sig_hook.py' diff --git a/test-data/unit/diff.pyproject.test b/test-data/unit/diff.pyproject.test index 3b97a2c330c1..8da407d06fd2 100644 --- a/test-data/unit/diff.pyproject.test +++ b/test-data/unit/diff.pyproject.test @@ -35,7 +35,7 @@ class Instr(Generic[T]): ... [file pyproject.toml] \[tool.mypy] -plugins = "/test-data/unit/plugins/dyn_class.py" +plugins = '/test-data/unit/plugins/dyn_class.py' [out] __main__.Diff __main__.Diff.x From 76d6b6c12b80c1c0287592a88146f9575b77c9c7 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Wed, 17 Mar 2021 23:42:07 -0400 Subject: [PATCH 07/39] Update docs for pyproject.toml --- docs/source/config_file.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 310078adabdc..308fcd2dbdde 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -904,7 +904,7 @@ Instead of using a ``mypy.ini`` file, a ``pyproject.toml`` file (as specified by * The following care should be given to values in the ``pyproject.toml`` files as compared to ``ini`` files: - * Strings must be wrapped in double quotes + * Strings must be wrapped in double quotes, or single quotes if the string contains special characters * Boolean values should be all lower case From 6618f2c94b2633ee03b9a86b52da83458aa8e221 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Thu, 18 Mar 2021 01:17:34 -0400 Subject: [PATCH 08/39] =?UTF-8?q?Fix=20some=20(the=20rest=F0=9F=A4=9E)=20o?= =?UTF-8?q?f=20the=20pyproject=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-data/unit/daemon.pyproject.test | 2 +- test-data/unit/pep561.pyproject.test | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test-data/unit/daemon.pyproject.test b/test-data/unit/daemon.pyproject.test index 3e49eef4350c..2689482b41ee 100644 --- a/test-data/unit/daemon.pyproject.test +++ b/test-data/unit/daemon.pyproject.test @@ -6,7 +6,7 @@ $ dmypy start -- --follow-imports=error Daemon started [file pyproject.toml] \[tool.mypy] -files = "./foo.py" +files = './foo.py' [case testDaemonRunRestartPluginVersionPyprojectTOMLPyprojectTOML] $ dmypy run -- foo.py --no-error-summary diff --git a/test-data/unit/pep561.pyproject.test b/test-data/unit/pep561.pyproject.test index b469edffc92b..31b7206c05f4 100644 --- a/test-data/unit/pep561.pyproject.test +++ b/test-data/unit/pep561.pyproject.test @@ -8,10 +8,10 @@ reveal_type(a) \[tool.mypy] no_site_packages = true [out] -testTypedPkg_config_nositepackages.py:2: error: Cannot find implementation or library stub for module named "typedpkg.sample" -testTypedPkg_config_nositepackages.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -testTypedPkg_config_nositepackages.py:3: error: Cannot find implementation or library stub for module named "typedpkg" -testTypedPkg_config_nositepackages.py:5: note: Revealed type is 'Any' +testTypedPkg_config_nositepackagesPyprojectTOML.py:2: error: Cannot find implementation or library stub for module named "typedpkg.sample" +testTypedPkg_config_nositepackagesPyprojectTOML.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +testTypedPkg_config_nositepackagesPyprojectTOML.py:3: error: Cannot find implementation or library stub for module named "typedpkg" +testTypedPkg_config_nositepackagesPyprojectTOML.py:5: note: Revealed type is 'Any' [case testTypedPkgNoSitePkgsIgnoredImportsPyprojectTOML] # pkgs: typedpkg @@ -24,4 +24,4 @@ reveal_type(a) \[tool.mypy] ignore_missing_imports = true [out] -testTypedPkgNoSitePkgsIgnoredImports.py:6: note: Revealed type is 'Any' +testTypedPkgNoSitePkgsIgnoredImportsPyprojectTOML.py:6: note: Revealed type is 'Any' From 76e11978e3d7f387eb6c827378838d246afbd0fe Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Thu, 18 Mar 2021 15:08:44 -0400 Subject: [PATCH 09/39] Remove unnessecary .test changes --- test-data/unit/check-flags.test | 3 +++ test-data/unit/check-incremental.test | 33 +++++++++++++++++++++++++++ test-data/unit/cmdline.test | 17 ++------------ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 4bf88ac3b981..6a94ded4c489 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -477,6 +477,7 @@ disallow_incomplete_defs = False \[mypy-incomplete] disallow_incomplete_defs = True + [case testPerFileStrictOptionalBasic] # flags: --config-file tmp/mypy.ini import standard, optional @@ -496,6 +497,7 @@ strict_optional = False \[mypy-optional] strict_optional = True + [case testPerFileStrictOptionalBasicImportStandard] # flags: --config-file tmp/mypy.ini import standard, optional @@ -522,6 +524,7 @@ strict_optional = False \[mypy-optional] strict_optional = True + [case testPerFileStrictOptionalBasicImportOptional] # flags: --config-file tmp/mypy.ini import standard, optional diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 93ba2162fb39..6d2d7b5c7edf 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5005,6 +5005,39 @@ python_version=3.6 [out2] tmp/a.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") +[case testPluginConfigData] +# flags: --config-file tmp/mypy.ini +import a +import b +[file a.py] +[file b.py] +[file test.json] +{"a": false, "b": false} +[file test.json.2] +{"a": true, "b": false} + +[file mypy.ini] +\[mypy] +plugins=/test-data/unit/plugins/config_data.py + +# The config change will force a to be rechecked but not b. +[rechecked a] + +[case testLiteralIncrementalTurningIntoLiteral] +import mod +reveal_type(mod.a) +[file mod.py] +from typing_extensions import Literal +a = 1 +[file mod.py.2] +from typing_extensions import Literal +a: Literal[2] = 2 +[builtins fixtures/tuple.pyi] +[out] +main:2: note: Revealed type is 'builtins.int' +[out2] +main:2: note: Revealed type is 'Literal[2]' + [case testAddedSubStarImport] # cmd: mypy -m a pack pack.mod b # cmd2: mypy -m other diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 3a47ffd0fa5c..176069424930 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -492,6 +492,7 @@ ignore_missing_imports = True [out] main.py:2: note: Revealed type is 'Any' + [case testFailedImportOnWrongCWD] # cmd: mypy main.py # cwd: main/subdir1/subdir2 @@ -669,21 +670,6 @@ m.py:5: error: Implicit generic "Any". Use "typing.Dict" and specify generic par m.py:6: error: Implicit generic "Any". Use "typing.Set" and specify generic parameters m.py:7: error: Implicit generic "Any". Use "typing.FrozenSet" and specify generic parameters -[file m.py] -s = tuple([1, 2, 3]) # no error - -def f(t: tuple) -> None: pass -def g() -> list: pass -def h(s: dict) -> None: pass -def i(s: set) -> None: pass -def j(s: frozenset) -> None: pass -[out] -m.py:3: error: Implicit generic "Any". Use "typing.Tuple" and specify generic parameters -m.py:4: error: Implicit generic "Any". Use "typing.List" and specify generic parameters -m.py:5: error: Implicit generic "Any". Use "typing.Dict" and specify generic parameters -m.py:6: error: Implicit generic "Any". Use "typing.Set" and specify generic parameters -m.py:7: error: Implicit generic "Any". Use "typing.FrozenSet" and specify generic parameters - [case testDisallowAnyGenericsTypingCollections] # cmd: mypy m.py [file mypy.ini] @@ -1271,6 +1257,7 @@ y = 0 # type: str [out] pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") + [case testCmdlineModuleAndIniFiles] # cmd: mypy -m pkg [file mypy.ini] From 34f246bd0af6c60f2cecf15d357db261f2e4918f Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Thu, 18 Mar 2021 16:06:45 -0400 Subject: [PATCH 10/39] Restore config_parser functions to original typing and create new ones for TOML parsing --- mypy/config_parser.py | 66 ++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index ab94542a9ba9..61ada692b67f 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -1,5 +1,6 @@ import argparse import configparser +import copy import glob as fileglob from io import StringIO import os @@ -14,9 +15,7 @@ from mypy.options import Options, PER_MODULE_OPTIONS -def parse_version(v: Union[str, float]) -> Tuple[int, int]: - if isinstance(v, float): - v = str(v) +def parse_version(v: str) -> Tuple[int, int]: m = re.match(r'\A(\d)\.(\d+)\Z', v) if not m: raise argparse.ArgumentTypeError( @@ -37,9 +36,11 @@ def parse_version(v: Union[str, float]) -> Tuple[int, int]: return major, minor -def try_split(v: Union[str, Sequence[str]]) -> List[str]: +def try_split(v: Union[str, Sequence[str]], split_regex: str = '[,]') -> List[str]: + """Split and trim a str or list of str into a list of str + """ if isinstance(v, str): - return [p.strip() for p in v.split(',')] + return [p.strip() for p in re.split(split_regex, v)] return [p.strip() for p in v] @@ -52,9 +53,8 @@ def expand_path(path: str) -> str: return os.path.expandvars(os.path.expanduser(path)) -def split_and_match_files(paths: Union[str, Sequence[str]]) -> List[str]: - """Take a string representing a list of files/directories (with support for globbing - through the glob library). +def split_and_match_files_list(paths: Sequence[str]) -> List[str]: + """Take a list of files/directories (with support for globbing through the glob library). Where a path/glob matches no file, we still include the raw path in the resulting list. @@ -62,9 +62,6 @@ def split_and_match_files(paths: Union[str, Sequence[str]]) -> List[str]: """ expanded_paths = [] - if isinstance(paths, str): - paths = paths.split(',') - for path in paths: path = expand_path(path.strip()) globbed_files = fileglob.glob(path, recursive=True) @@ -76,6 +73,18 @@ def split_and_match_files(paths: Union[str, Sequence[str]]) -> List[str]: return expanded_paths +def split_and_match_files(paths: str) -> List[str]: + """Take a string representing a list of files/directories (with support for globbing + through the glob library). + + Where a path/glob matches no file, we still include the raw path in the resulting list. + + Returns a list of file paths + """ + + return split_and_match_files_list(paths.split(',')) + + def check_follow_imports(choice: str) -> str: choices = ['normal', 'silent', 'skip', 'error'] if choice not in choices: @@ -90,7 +99,7 @@ def check_follow_imports(choice: str) -> str: # sufficient, and we don't have to do anything here. This table # exists to specify types for values initialized to None or container # types. -config_types = { +ini_config_types = { 'python_version': parse_version, 'strict_optional_whitelist': lambda s: s.split(), 'custom_typing_module': str, @@ -104,16 +113,31 @@ def check_follow_imports(choice: str) -> str: 'almost_silent': bool, 'follow_imports': check_follow_imports, 'no_site_packages': bool, + 'plugins': lambda s: [p.strip() for p in s.split(',')], + 'always_true': lambda s: [p.strip() for p in s.split(',')], + 'always_false': lambda s: [p.strip() for p in s.split(',')], + 'disable_error_code': lambda s: [p.strip() for p in s.split(',')], + 'enable_error_code': lambda s: [p.strip() for p in s.split(',')], + 'package_root': lambda s: [p.strip() for p in s.split(',')], + 'cache_dir': expand_path, + 'python_executable': expand_path, + 'strict': bool, +} # type: Final + +# Reuse the ini_config_types and overwrite the diff +toml_config_types = copy.deepcopy(ini_config_types) # type: Final +toml_config_types.update({ + 'python_version': lambda s: parse_version(str(s)), + 'strict_optional_whitelist': try_split, + 'mypy_path': lambda s: [expand_path(p) for p in try_split(s, '[,:]')], + 'files': lambda s: split_and_match_files_list(try_split(s)), 'plugins': try_split, 'always_true': try_split, 'always_false': try_split, 'disable_error_code': try_split, 'enable_error_code': try_split, 'package_root': try_split, - 'cache_dir': expand_path, - 'python_executable': expand_path, - 'strict': bool, -} # type: Final +}) def parse_config_file(options: Options, set_strict_flags: Callable[[], None], @@ -148,9 +172,11 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], if key.split('-')[0] == 'mypy'} if parser.get('mypy') is None: continue + config_types = toml_config_types else: config_parser.read(config_file) parser = {key: value for key, value in config_parser.items()} + config_types = ini_config_types except (toml.TomlDecodeError, configparser.Error) as err: print("%s: %s" % (config_file, err), file=stderr) else: @@ -171,7 +197,8 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], else: section = parser['mypy'] prefix = '%s: [%s]: ' % (file_read, 'mypy') - updates, report_dirs = parse_section(prefix, options, set_strict_flags, section, stderr) + updates, report_dirs = parse_section( + prefix, options, set_strict_flags, section, config_types, stderr) for k, v in updates.items(): setattr(options, k, v) options.report_dirs.update(report_dirs) @@ -180,7 +207,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], if name.startswith('mypy-'): prefix = '%s: [%s]: ' % (file_read, name) updates, report_dirs = parse_section( - prefix, options, set_strict_flags, section, stderr) + prefix, options, set_strict_flags, section, config_types, stderr) if report_dirs: print("%sPer-module sections should not specify reports (%s)" % (prefix, ', '.join(s + '_report' for s in sorted(report_dirs))), @@ -210,6 +237,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], def parse_section(prefix: str, template: Options, set_strict_flags: Callable[[], None], section: Mapping[str, str], + config_types: Dict[str, Callable], stderr: TextIO = sys.stderr ) -> Tuple[Dict[str, object], Dict[str, str]]: """Parse one section of a config file. @@ -400,7 +428,7 @@ def set_strict_flags() -> None: strict_found = True new_sections, reports = parse_section( - '', template, set_strict_flags, parser['dummy'], stderr=stderr) + '', template, set_strict_flags, parser['dummy'], ini_config_types, stderr=stderr) errors.extend((lineno, x) for x in stderr.getvalue().strip().split('\n') if x) if reports: errors.append((lineno, "Reports not supported in inline configuration")) From df8db18edd4c44d6ee72c8980382e02e71bdd565 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Thu, 18 Mar 2021 16:21:18 -0400 Subject: [PATCH 11/39] =?UTF-8?q?Helps=20if=20you=20get=20the=20types=20ri?= =?UTF-8?q?ght=20BEFORE=20commiting=20=F0=9F=A4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mypy/config_parser.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 61ada692b67f..0594a8829870 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -8,7 +8,8 @@ import sys import toml -from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, TextIO, Tuple, Union +from typing import (Any, Callable, Dict, List, Mapping, Optional, Sequence, TextIO, Tuple, Type, + Union) from typing_extensions import Final from mypy import defaults @@ -95,6 +96,18 @@ def check_follow_imports(choice: str) -> str: return choice +_ConfigDict = Dict[str, + Union[ + Callable[..., Union[ + List[str], + str, + Tuple[int, int] + ]], + Type[bool] + ] +] + + # For most options, the type of the default value set in options.py is # sufficient, and we don't have to do anything here. This table # exists to specify types for values initialized to None or container @@ -122,10 +135,10 @@ def check_follow_imports(choice: str) -> str: 'cache_dir': expand_path, 'python_executable': expand_path, 'strict': bool, -} # type: Final +} # type: Final[_ConfigDict] # Reuse the ini_config_types and overwrite the diff -toml_config_types = copy.deepcopy(ini_config_types) # type: Final +toml_config_types = copy.deepcopy(ini_config_types) # type: Final[_ConfigDict] toml_config_types.update({ 'python_version': lambda s: parse_version(str(s)), 'strict_optional_whitelist': try_split, @@ -237,7 +250,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], def parse_section(prefix: str, template: Options, set_strict_flags: Callable[[], None], section: Mapping[str, str], - config_types: Dict[str, Callable], + config_types: _ConfigDict, stderr: TextIO = sys.stderr ) -> Tuple[Dict[str, object], Dict[str, str]]: """Parse one section of a config file. From 94de3d5f3f45cb4db11301db25cfba7d3f2b2b38 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Thu, 18 Mar 2021 16:53:01 -0400 Subject: [PATCH 12/39] 3.5 doesn't have typing.Type? --- mypy/config_parser.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 0594a8829870..0393fc81f5a9 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -8,8 +8,7 @@ import sys import toml -from typing import (Any, Callable, Dict, List, Mapping, Optional, Sequence, TextIO, Tuple, Type, - Union) +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, TextIO, Tuple, Union from typing_extensions import Final from mypy import defaults @@ -96,18 +95,6 @@ def check_follow_imports(choice: str) -> str: return choice -_ConfigDict = Dict[str, - Union[ - Callable[..., Union[ - List[str], - str, - Tuple[int, int] - ]], - Type[bool] - ] -] - - # For most options, the type of the default value set in options.py is # sufficient, and we don't have to do anything here. This table # exists to specify types for values initialized to None or container @@ -135,10 +122,10 @@ def check_follow_imports(choice: str) -> str: 'cache_dir': expand_path, 'python_executable': expand_path, 'strict': bool, -} # type: Final[_ConfigDict] +} # type: Final[Dict[str, Any]] # Reuse the ini_config_types and overwrite the diff -toml_config_types = copy.deepcopy(ini_config_types) # type: Final[_ConfigDict] +toml_config_types = copy.deepcopy(ini_config_types) # type: Final[Dict[str, Any]] toml_config_types.update({ 'python_version': lambda s: parse_version(str(s)), 'strict_optional_whitelist': try_split, @@ -250,7 +237,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], def parse_section(prefix: str, template: Options, set_strict_flags: Callable[[], None], section: Mapping[str, str], - config_types: _ConfigDict, + config_types: Dict[str, Any], stderr: TextIO = sys.stderr ) -> Tuple[Dict[str, object], Dict[str, str]]: """Parse one section of a config file. From 60a4558178df616c72bf4d0a227a8d02f480cfea Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Fri, 19 Mar 2021 14:33:59 -0400 Subject: [PATCH 13/39] Deepcopy segfaults mypyc --- mypy/config_parser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 0393fc81f5a9..6553cb341654 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -125,7 +125,7 @@ def check_follow_imports(choice: str) -> str: } # type: Final[Dict[str, Any]] # Reuse the ini_config_types and overwrite the diff -toml_config_types = copy.deepcopy(ini_config_types) # type: Final[Dict[str, Any]] +toml_config_types = copy.copy(ini_config_types) # type: Final[Dict[str, Any]] toml_config_types.update({ 'python_version': lambda s: parse_version(str(s)), 'strict_optional_whitelist': try_split, @@ -236,7 +236,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], def parse_section(prefix: str, template: Options, set_strict_flags: Callable[[], None], - section: Mapping[str, str], + section: Mapping[str, Any], config_types: Dict[str, Any], stderr: TextIO = sys.stderr ) -> Tuple[Dict[str, object], Dict[str, str]]: @@ -262,7 +262,7 @@ def parse_section(prefix: str, template: Options, if key.endswith('_report'): report_type = key[:-7].replace('_', '-') if report_type in defaults.REPORTER_NAMES: - report_dirs[report_type] = section[key] + report_dirs[report_type] = str(section[key]) else: print("%sUnrecognized report type: %s" % (prefix, key), file=stderr) From 02782428a58e8791c9f44b53636d1e0d7fde429e Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Fri, 19 Mar 2021 20:56:16 -0400 Subject: [PATCH 14/39] Fix typing issue found by mypyc --- mypy/config_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 6553cb341654..726f068ebdf9 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -131,6 +131,7 @@ def check_follow_imports(choice: str) -> str: 'strict_optional_whitelist': try_split, 'mypy_path': lambda s: [expand_path(p) for p in try_split(s, '[,:]')], 'files': lambda s: split_and_match_files_list(try_split(s)), + 'follow_imports': lambda s: check_follow_imports(str(s)), 'plugins': try_split, 'always_true': try_split, 'always_false': try_split, From 771964cd6040093fa3f45c344ce7b3ac314875e1 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Thu, 1 Apr 2021 16:23:10 -0400 Subject: [PATCH 15/39] Use OrderedDict to preserve option orders in python 3.5 --- .gitignore | 1 + mypy/config_parser.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index c6238dc32beb..644394bbe3e1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ venv/ .cache dmypy.json .dmypy.json +*.so # Packages *.egg diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 726f068ebdf9..77994be90a6c 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -1,4 +1,5 @@ import argparse +from collections import OrderedDict import configparser import copy import glob as fileglob @@ -166,17 +167,20 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], continue try: if config_file.endswith('.toml'): - toml_data = toml.load(config_file) + toml_data = toml.load(config_file, _dict=OrderedDict) # Filter down to just mypy relevant toml keys - parser = {key: value - for key, value in toml_data.get('tool', {}).items() - if key.split('-')[0] == 'mypy'} - if parser.get('mypy') is None: + toml_data = toml_data.get('tool', {}) + other_keys = [key for key in toml_data.keys() + if key.split('-')[0] != 'mypy'] + for key in other_keys: + del toml_data[key] + if toml_data.get('mypy') is None: continue + parser = toml_data config_types = toml_config_types else: config_parser.read(config_file) - parser = {key: value for key, value in config_parser.items()} + parser = config_parser config_types = ini_config_types except (toml.TomlDecodeError, configparser.Error) as err: print("%s: %s" % (config_file, err), file=stderr) From 109f59049d01e019656d8cf27510182e8f2f58ca Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Thu, 1 Apr 2021 17:55:10 -0400 Subject: [PATCH 16/39] Fix revealed type expected messages to use double quotes --- .../unit/check-custom-pyproject-plugin.test | 150 +++++++++--------- .../unit/check-incremental.pyproject.test | 4 +- .../unit/check-modules-case.pyproject.test | 6 +- test-data/unit/check-modules.pyproject.test | 14 +- test-data/unit/cmdline.pyproject.test | 20 +-- test-data/unit/pep561.pyproject.test | 4 +- 6 files changed, 99 insertions(+), 99 deletions(-) diff --git a/test-data/unit/check-custom-pyproject-plugin.test b/test-data/unit/check-custom-pyproject-plugin.test index 4dfc13824018..c98239cc3ae8 100644 --- a/test-data/unit/check-custom-pyproject-plugin.test +++ b/test-data/unit/check-custom-pyproject-plugin.test @@ -6,7 +6,7 @@ [case testFunctionPluginFilePyprojectTOML] # flags: --config-file tmp/pyproject.toml def f() -> str: ... -reveal_type(f()) # N: Revealed type is 'builtins.int' +reveal_type(f()) # N: Revealed type is "builtins.int" [file pyproject.toml] \[tool.mypy] plugins = '/test-data/unit/plugins/fnplugin.py' @@ -14,7 +14,7 @@ plugins = '/test-data/unit/plugins/fnplugin.py' [case testFunctionPluginPyprojectTOML] # flags: --config-file tmp/pyproject.toml def f() -> str: ... -reveal_type(f()) # N: Revealed type is 'builtins.int' +reveal_type(f()) # N: Revealed type is "builtins.int" [file pyproject.toml] \[tool.mypy] plugins = "fnplugin" @@ -35,9 +35,9 @@ plugins = '/test-data/unit/plugins/fnplugin.py' def f(): ... def g(): ... def h(): ... -reveal_type(f()) # N: Revealed type is 'builtins.int' -reveal_type(g()) # N: Revealed type is 'builtins.str' -reveal_type(h()) # N: Revealed type is 'Any' +reveal_type(f()) # N: Revealed type is "builtins.int" +reveal_type(g()) # N: Revealed type is "builtins.str" +reveal_type(h()) # N: Revealed type is "Any" [file pyproject.toml] \[tool.mypy] plugins=[ @@ -50,9 +50,9 @@ plugins=[ def f(): ... def g(): ... def h(): ... -reveal_type(f()) # N: Revealed type is 'builtins.int' -reveal_type(g()) # N: Revealed type is 'builtins.str' -reveal_type(h()) # N: Revealed type is 'Any' +reveal_type(f()) # N: Revealed type is "builtins.int" +reveal_type(g()) # N: Revealed type is "builtins.str" +reveal_type(h()) # N: Revealed type is "Any" [file pyproject.toml] \[tool.mypy] plugins = [ @@ -110,7 +110,7 @@ tmp/pyproject.toml:1: error: Plugin '/test-data/unit/plugins/noentry.py' d [case testCustomPluginEntryPointFilePyprojectTOML] # flags: --config-file tmp/pyproject.toml def f() -> str: ... -reveal_type(f()) # N: Revealed type is 'builtins.int' +reveal_type(f()) # N: Revealed type is "builtins.int" [file pyproject.toml] \[tool.mypy] plugins = '/test-data/unit/plugins/customentry.py:register' @@ -118,7 +118,7 @@ plugins = '/test-data/unit/plugins/customentry.py:register' [case testCustomPluginEntryPointPyprojectTOML] # flags: --config-file tmp/pyproject.toml def f() -> str: ... -reveal_type(f()) # N: Revealed type is 'builtins.int' +reveal_type(f()) # N: Revealed type is "builtins.int" [file pyproject.toml] \[tool.mypy] plugins = "customentry:register" @@ -170,12 +170,12 @@ plugins = '/test-data/unit/plugins/attrhook.py' from m import Magic, DerivedMagic magic = Magic() -reveal_type(magic.magic_field) # N: Revealed type is 'builtins.str' -reveal_type(magic.non_magic_method()) # N: Revealed type is 'builtins.int' -reveal_type(magic.non_magic_field) # N: Revealed type is 'builtins.int' +reveal_type(magic.magic_field) # N: Revealed type is "builtins.str" +reveal_type(magic.non_magic_method()) # N: Revealed type is "builtins.int" +reveal_type(magic.non_magic_field) # N: Revealed type is "builtins.int" magic.nonexistent_field # E: Field does not exist -reveal_type(magic.fallback_example) # N: Revealed type is 'Any' -reveal_type(DerivedMagic().magic_field) # N: Revealed type is 'builtins.str' +reveal_type(magic.fallback_example) # N: Revealed type is "Any" +reveal_type(DerivedMagic().magic_field) # N: Revealed type is "builtins.str" [file m.py] from typing import Any class Magic: @@ -196,7 +196,7 @@ from typing import Callable from mypy_extensions import DefaultArg from m import Signal s: Signal[[int, DefaultArg(str, 'x')]] = Signal() -reveal_type(s) # N: Revealed type is 'm.Signal[def (builtins.int, x: builtins.str =)]' +reveal_type(s) # N: Revealed type is "m.Signal[def (builtins.int, x: builtins.str =)]" s.x # E: "Signal[Callable[[int, str], None]]" has no attribute "x" ss: Signal[int, str] # E: Invalid "Signal" type (expected "Signal[[t, ...]]") [file m.py] @@ -223,9 +223,9 @@ class C: z = AnotherAlias(int, required=False) c = C() -reveal_type(c.x) # N: Revealed type is 'Union[builtins.int, None]' -reveal_type(c.y) # N: Revealed type is 'builtins.int*' -reveal_type(c.z) # N: Revealed type is 'Union[builtins.int*, None]' +reveal_type(c.x) # N: Revealed type is "Union[builtins.int, None]" +reveal_type(c.y) # N: Revealed type is "builtins.int*" +reveal_type(c.z) # N: Revealed type is "Union[builtins.int*, None]" [file mod.py] from typing import Generic, TypeVar, Type @@ -254,8 +254,8 @@ from m import decorator1, decorator2 def f() -> None: pass @decorator2() def g() -> None: pass -reveal_type(f) # N: Revealed type is 'def (*Any, **Any) -> builtins.str' -reveal_type(g) # N: Revealed type is 'def (*Any, **Any) -> builtins.int' +reveal_type(f) # N: Revealed type is "def (*Any, **Any) -> builtins.str" +reveal_type(g) # N: Revealed type is "def (*Any, **Any) -> builtins.int" [file m.py] from typing import Callable def decorator1() -> Callable[..., Callable[..., int]]: pass @@ -268,11 +268,11 @@ plugins = '/test-data/unit/plugins/named_callable.py' # flags: --config-file tmp/pyproject.toml from mod import Class, func -reveal_type(Class().method(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(Class.myclassmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(Class.mystaticmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(Class.method(self=Class(), arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(func(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(Class().method(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is "builtins.str" +reveal_type(Class.myclassmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is "builtins.str" +reveal_type(Class.mystaticmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is "builtins.str" +reveal_type(Class.method(self=Class(), arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is "builtins.str" +reveal_type(func(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is "builtins.str" [file mod.py] from typing import Any @@ -297,11 +297,11 @@ plugins = '/test-data/unit/plugins/arg_names.py' # flags: --config-file tmp/pyproject.toml from mod import Class, func -reveal_type(Class().method('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' -reveal_type(Class.myclassmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' -reveal_type(Class.mystaticmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' -reveal_type(Class.method(Class(), 'builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' -reveal_type(func('builtins.str', arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' +reveal_type(Class().method('builtins.str', arg1=1, arg2=2)) # N: Revealed type is "builtins.str" +reveal_type(Class.myclassmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is "builtins.str" +reveal_type(Class.mystaticmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is "builtins.str" +reveal_type(Class.method(Class(), 'builtins.str', arg1=1, arg2=2)) # N: Revealed type is "builtins.str" +reveal_type(func('builtins.str', arg1=1, arg2=2)) # N: Revealed type is "builtins.str" [file mod.py] from typing import Any @@ -326,9 +326,9 @@ plugins = '/test-data/unit/plugins/arg_names.py' # flags: --config-file tmp/pyproject.toml from mod import ClassInit, Outer -reveal_type(ClassInit('builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(ClassInit(classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(Outer.NestedClassInit(classname='builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(ClassInit('builtins.str')) # N: Revealed type is "builtins.str" +reveal_type(ClassInit(classname='builtins.str')) # N: Revealed type is "builtins.str" +reveal_type(Outer.NestedClassInit(classname='builtins.str')) # N: Revealed type is "builtins.str" [file mod.py] from typing import Any class ClassInit: @@ -347,12 +347,12 @@ plugins = '/test-data/unit/plugins/arg_names.py' # flags: --config-file tmp/pyproject.toml from mod import ClassUnfilled, func_unfilled -reveal_type(ClassUnfilled().method(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassUnfilled().method(arg2=1, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(ClassUnfilled().method('builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(func_unfilled(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(func_unfilled(arg2=1, classname='builtins.str')) # N: Revealed type is 'builtins.str' -reveal_type(func_unfilled('builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(ClassUnfilled().method(classname='builtins.str', arg1=1)) # N: Revealed type is "builtins.str" +reveal_type(ClassUnfilled().method(arg2=1, classname='builtins.str')) # N: Revealed type is "builtins.str" +reveal_type(ClassUnfilled().method('builtins.str')) # N: Revealed type is "builtins.str" +reveal_type(func_unfilled(classname='builtins.str', arg1=1)) # N: Revealed type is "builtins.str" +reveal_type(func_unfilled(arg2=1, classname='builtins.str')) # N: Revealed type is "builtins.str" +reveal_type(func_unfilled('builtins.str')) # N: Revealed type is "builtins.str" [file mod.py] from typing import Any @@ -370,13 +370,13 @@ plugins = '/test-data/unit/plugins/arg_names.py' # flags: --config-file tmp/pyproject.toml from mod import ClassStarExpr, func_star_expr -reveal_type(ClassStarExpr().method(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassStarExpr().method('builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassStarExpr().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassStarExpr().method('builtins.str', 2, 3, 4, arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' -reveal_type(func_star_expr(classname='builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(func_star_expr('builtins.str', arg1=1)) # N: Revealed type is 'builtins.str' -reveal_type(func_star_expr('builtins.str', 2, 3, 4, arg1=1, arg2=2)) # N: Revealed type is 'builtins.str' +reveal_type(ClassStarExpr().method(classname='builtins.str', arg1=1)) # N: Revealed type is "builtins.str" +reveal_type(ClassStarExpr().method('builtins.str', arg1=1)) # N: Revealed type is "builtins.str" +reveal_type(ClassStarExpr().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is "builtins.str" +reveal_type(ClassStarExpr().method('builtins.str', 2, 3, 4, arg1=1, arg2=1)) # N: Revealed type is "builtins.str" +reveal_type(func_star_expr(classname='builtins.str', arg1=1)) # N: Revealed type is "builtins.str" +reveal_type(func_star_expr('builtins.str', arg1=1)) # N: Revealed type is "builtins.str" +reveal_type(func_star_expr('builtins.str', 2, 3, 4, arg1=1, arg2=2)) # N: Revealed type is "builtins.str" [file mod.py] from typing import Any @@ -395,10 +395,10 @@ plugins = '/test-data/unit/plugins/arg_names.py' # flags: --config-file tmp/pyproject.toml from mod import ClassChild -reveal_type(ClassChild().method(classname='builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassChild().method(arg1=1, classname='builtins.str', arg2=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassChild().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is 'builtins.str' -reveal_type(ClassChild.myclassmethod('builtins.str')) # N: Revealed type is 'builtins.str' +reveal_type(ClassChild().method(classname='builtins.str', arg1=1, arg2=1)) # N: Revealed type is "builtins.str" +reveal_type(ClassChild().method(arg1=1, classname='builtins.str', arg2=1)) # N: Revealed type is "builtins.str" +reveal_type(ClassChild().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is "builtins.str" +reveal_type(ClassChild.myclassmethod('builtins.str')) # N: Revealed type is "builtins.str" [file mod.py] from typing import Any class Base: @@ -432,12 +432,12 @@ class Foo: def m(self, arg: str) -> str: ... foo = Foo() -reveal_type(foo.m(2)) # N: Revealed type is 'builtins.int' -reveal_type(foo[3]) # N: Revealed type is 'builtins.int' -reveal_type(foo(4, 5, 6)) # N: Revealed type is 'builtins.int' +reveal_type(foo.m(2)) # N: Revealed type is "builtins.int" +reveal_type(foo[3]) # N: Revealed type is "builtins.int" +reveal_type(foo(4, 5, 6)) # N: Revealed type is "builtins.int" foo[4] = 5 for x in foo: - reveal_type(x) # N: Revealed type is 'builtins.int*' + reveal_type(x) # N: Revealed type is "builtins.int*" [file pyproject.toml] \[tool.mypy] @@ -460,9 +460,9 @@ class FullyQualifiedTestTypedDict(TypedDict): FullyQualifiedTestNamedTuple = NamedTuple('FullyQualifiedTestNamedTuple', [('foo', str)]) # Check the return types to ensure that the method signature hook is called in each case -reveal_type(FullyQualifiedTestClass.class_method()) # N: Revealed type is 'builtins.int' -reveal_type(FullyQualifiedTestClass().instance_method()) # N: Revealed type is 'builtins.int' -reveal_type(FullyQualifiedTestNamedTuple('')._asdict()) # N: Revealed type is 'builtins.int' +reveal_type(FullyQualifiedTestClass.class_method()) # N: Revealed type is "builtins.int" +reveal_type(FullyQualifiedTestClass().instance_method()) # N: Revealed type is "builtins.int" +reveal_type(FullyQualifiedTestNamedTuple('')._asdict()) # N: Revealed type is "builtins.int" [file pyproject.toml] \[tool.mypy] @@ -480,8 +480,8 @@ class Model(Base): class Other: x: Column[int] -reveal_type(Model().x) # N: Revealed type is 'mod.Instr[builtins.int]' -reveal_type(Other().x) # N: Revealed type is 'mod.Column[builtins.int]' +reveal_type(Model().x) # N: Revealed type is "mod.Instr[builtins.int]" +reveal_type(Other().x) # N: Revealed type is "mod.Column[builtins.int]" [file mod.py] from typing import Generic, TypeVar def declarative_base(): ... @@ -532,12 +532,12 @@ from mod import QuerySet, Manager MyManager = Manager.from_queryset(QuerySet) -reveal_type(MyManager()) # N: Revealed type is '__main__.MyManager' -reveal_type(MyManager().attr) # N: Revealed type is 'builtins.str' +reveal_type(MyManager()) # N: Revealed type is "__main__.MyManager" +reveal_type(MyManager().attr) # N: Revealed type is "builtins.str" def func(manager: MyManager) -> None: - reveal_type(manager) # N: Revealed type is '__main__.MyManager' - reveal_type(manager.attr) # N: Revealed type is 'builtins.str' + reveal_type(manager) # N: Revealed type is "__main__.MyManager" + reveal_type(manager.attr) # N: Revealed type is "builtins.str" func(MyManager()) @@ -582,7 +582,7 @@ python_version = 3.6 plugins = '/test-data/unit/plugins/common_api_incremental.py' [out] [out2] -tmp/a.py:3: note: Revealed type is 'builtins.str' +tmp/a.py:3: note: Revealed type is "builtins.str" tmp/a.py:4: error: "Type[Base]" has no attribute "__magic__" [case testArgKindsMethodPyprojectTOML] @@ -615,9 +615,9 @@ T = TypeVar("T") class Class(Generic[T]): def __init__(self, one: T): ... def __call__(self, two: T) -> int: ... -reveal_type(Class("hi")("there")) # N: Revealed type is 'builtins.str*' +reveal_type(Class("hi")("there")) # N: Revealed type is "builtins.str*" instance = Class(3.14) -reveal_type(instance(2)) # N: Revealed type is 'builtins.float*' +reveal_type(instance(2)) # N: Revealed type is "builtins.float*" [file pyproject.toml] \[tool.mypy] @@ -636,9 +636,9 @@ class Other: x: Union[Foo, Bar, Other] if isinstance(x.meth, int): - reveal_type(x.meth) # N: Revealed type is 'builtins.int' + reveal_type(x.meth) # N: Revealed type is "builtins.int" else: - reveal_type(x.meth(int())) # N: Revealed type is 'builtins.int' + reveal_type(x.meth(int())) # N: Revealed type is "builtins.int" [builtins fixtures/isinstancelist.pyi] [file pyproject.toml] @@ -658,9 +658,9 @@ class Other: x: Union[Foo, Bar, Other] if isinstance(x.meth, int): - reveal_type(x.meth) # N: Revealed type is 'builtins.int' + reveal_type(x.meth) # N: Revealed type is "builtins.int" else: - reveal_type(x.meth(int())) # N: Revealed type is 'builtins.int' + reveal_type(x.meth(int())) # N: Revealed type is "builtins.int" [builtins fixtures/isinstancelist.pyi] [file pyproject.toml] @@ -677,7 +677,7 @@ class Bar: def __getitem__(self, x: int) -> float: ... x: Union[Foo, Bar] -reveal_type(x[int()]) # N: Revealed type is 'builtins.int' +reveal_type(x[int()]) # N: Revealed type is "builtins.int" [builtins fixtures/isinstancelist.pyi] [file pyproject.toml] @@ -717,8 +717,8 @@ class Desc: class Cls: attr = Desc() -reveal_type(Cls().attr) # N: Revealed type is 'builtins.int' -reveal_type(Cls.attr) # N: Revealed type is 'builtins.str' +reveal_type(Cls().attr) # N: Revealed type is "builtins.int" +reveal_type(Cls.attr) # N: Revealed type is "builtins.str" Cls().attr = 3 Cls().attr = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "int") @@ -731,7 +731,7 @@ plugins = '/test-data/unit/plugins/descriptor.py' # flags: --config-file tmp/pyproject.toml def dynamic_signature(arg1: str) -> str: ... -reveal_type(dynamic_signature(1)) # N: Revealed type is 'builtins.int' +reveal_type(dynamic_signature(1)) # N: Revealed type is "builtins.int" [file pyproject.toml] \[tool.mypy] plugins = '/test-data/unit/plugins/function_sig_hook.py' diff --git a/test-data/unit/check-incremental.pyproject.test b/test-data/unit/check-incremental.pyproject.test index 745a1fdeaa8d..fc46de36f631 100644 --- a/test-data/unit/check-incremental.pyproject.test +++ b/test-data/unit/check-incremental.pyproject.test @@ -54,9 +54,9 @@ follow_imports = "normal" \[tool.mypy] follow_imports = "skip" [out1] -main:3: note: Revealed type is 'builtins.int' +main:3: note: Revealed type is "builtins.int" [out2] -main:3: note: Revealed type is 'Any' +main:3: note: Revealed type is "Any" [case testIncrementalPerFileFlagsPyprojectTOML] # flags: --config-file tmp/pyproject.toml diff --git a/test-data/unit/check-modules-case.pyproject.test b/test-data/unit/check-modules-case.pyproject.test index 210b56bfa119..9ee57e59a2e0 100644 --- a/test-data/unit/check-modules-case.pyproject.test +++ b/test-data/unit/check-modules-case.pyproject.test @@ -5,7 +5,7 @@ from a import B # E: Module 'a' has no attribute 'B' from other import x -reveal_type(x) # N: Revealed type is 'builtins.int' +reveal_type(x) # N: Revealed type is "builtins.int" [file a/__init__.py] [file a/b/__init__.py] @@ -31,7 +31,7 @@ mypy_path = "tmp/funky" [case testNamespacePackagePickFirstOnMypyPathCasePyprojectTOML] # flags: --namespace-packages --config-file tmp/pyproject.toml from foo.bar import x -reveal_type(x) # N: Revealed type is 'builtins.int' +reveal_type(x) # N: Revealed type is "builtins.int" [file XX/foo/bar.py] x = 0 [file yy/foo/bar.py] @@ -43,7 +43,7 @@ mypy_path = "tmp/xx,tmp/yy" [case testClassicPackageInsideNamespacePackageCasePyprojectTOML] # flags: --namespace-packages --config-file tmp/pyproject.toml from foo.bar.baz.boo import x -reveal_type(x) # N: Revealed type is 'builtins.int' +reveal_type(x) # N: Revealed type is "builtins.int" [file xx/foo/bar/baz/boo.py] x = '' [file xx/foo/bar/baz/__init__.py] diff --git a/test-data/unit/check-modules.pyproject.test b/test-data/unit/check-modules.pyproject.test index 633b68b6bf75..78b3d28d5e29 100644 --- a/test-data/unit/check-modules.pyproject.test +++ b/test-data/unit/check-modules.pyproject.test @@ -26,9 +26,9 @@ main:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missin from foo.bax import x from foo.bay import y from foo.baz import z -reveal_type(x) # N: Revealed type is 'builtins.int' -reveal_type(y) # N: Revealed type is 'builtins.int' -reveal_type(z) # N: Revealed type is 'builtins.int' +reveal_type(x) # N: Revealed type is "builtins.int" +reveal_type(y) # N: Revealed type is "builtins.int" +reveal_type(z) # N: Revealed type is "builtins.int" [file xx/foo/bax.py] x = 0 [file yy/foo/bay.py] @@ -42,7 +42,7 @@ mypy_path = "tmp/xx,tmp/yy" [case testClassicPackageIgnoresEarlierNamespacePackagePyprojectTOML] # flags: --namespace-packages --config-file tmp/pyproject.toml from foo.bar import y -reveal_type(y) # N: Revealed type is 'builtins.int' +reveal_type(y) # N: Revealed type is "builtins.int" [file xx/foo/bar.py] x = '' [file yy/foo/bar.py] @@ -55,7 +55,7 @@ mypy_path = "tmp/xx,tmp/yy" [case testNamespacePackagePickFirstOnMypyPathPyprojectTOML] # flags: --namespace-packages --config-file tmp/pyproject.toml from foo.bar import x -reveal_type(x) # N: Revealed type is 'builtins.int' +reveal_type(x) # N: Revealed type is "builtins.int" [file xx/foo/bar.py] x = 0 [file yy/foo/bar.py] @@ -67,7 +67,7 @@ mypy_path = "tmp/xx,tmp/yy" [case testNamespacePackageInsideClassicPackagePyprojectTOML] # flags: --namespace-packages --config-file tmp/pyproject.toml from foo.bar.baz import x -reveal_type(x) # N: Revealed type is 'builtins.int' +reveal_type(x) # N: Revealed type is "builtins.int" [file xx/foo/bar/baz.py] x = '' [file yy/foo/bar/baz.py] @@ -80,7 +80,7 @@ mypy_path = "tmp/xx,tmp/yy" [case testClassicPackageInsideNamespacePackagePyprojectTOML] # flags: --namespace-packages --config-file tmp/pyproject.toml from foo.bar.baz.boo import x -reveal_type(x) # N: Revealed type is 'builtins.int' +reveal_type(x) # N: Revealed type is "builtins.int" [file xx/foo/bar/baz/boo.py] x = '' [file xx/foo/bar/baz/__init__.py] diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index 71e82e069f19..7a6e3ef9b9ba 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -258,8 +258,8 @@ follow_imports = "skip" [file a.py] / # No error reported [out] -main.py:2: note: Revealed type is 'Any' -main.py:4: note: Revealed type is 'Any' +main.py:2: note: Revealed type is "Any" +main.py:4: note: Revealed type is "Any" [case testConfigFollowImportsErrorPyprojectTOML] # cmd: mypy main.py @@ -276,8 +276,8 @@ follow_imports = "error" [out] main.py:1: error: Import of 'a' ignored main.py:1: note: (Using --follow-imports=error, module not passed on command line) -main.py:2: note: Revealed type is 'Any' -main.py:4: note: Revealed type is 'Any' +main.py:2: note: Revealed type is "Any" +main.py:4: note: Revealed type is "Any" [case testConfigFollowImportsSelectivePyprojectTOML] # cmd: mypy main.py @@ -314,10 +314,10 @@ bla bla normal.py:2: error: Unsupported operand types for + ("int" and "str") main.py:4: error: Import of 'error' ignored main.py:4: note: (Using --follow-imports=error, module not passed on command line) -main.py:5: note: Revealed type is 'builtins.int' -main.py:6: note: Revealed type is 'builtins.int' -main.py:7: note: Revealed type is 'Any' -main.py:8: note: Revealed type is 'Any' +main.py:5: note: Revealed type is "builtins.int" +main.py:6: note: Revealed type is "builtins.int" +main.py:7: note: Revealed type is "Any" +main.py:8: note: Revealed type is "Any" [case testConfigFollowImportsInvalidPyprojectTOML] # cmd: mypy main.py @@ -340,7 +340,7 @@ ignore_missing_imports = false [out] main.py:1: error: Cannot find implementation or library stub for module named "missing" main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -main.py:2: note: Revealed type is 'Any' +main.py:2: note: Revealed type is "Any" [case testConfigSilentMissingImportsOnPyprojectTOML] # cmd: mypy main.py @@ -351,7 +351,7 @@ reveal_type(missing.x) # Expect Any \[tool.mypy] ignore_missing_imports = true [out] -main.py:2: note: Revealed type is 'Any' +main.py:2: note: Revealed type is "Any" [case testConfigNoErrorForUnknownXFlagInSubsectionPyprojectTOML] # cmd: mypy -c pass diff --git a/test-data/unit/pep561.pyproject.test b/test-data/unit/pep561.pyproject.test index 31b7206c05f4..9cb14ea8eaea 100644 --- a/test-data/unit/pep561.pyproject.test +++ b/test-data/unit/pep561.pyproject.test @@ -11,7 +11,7 @@ no_site_packages = true testTypedPkg_config_nositepackagesPyprojectTOML.py:2: error: Cannot find implementation or library stub for module named "typedpkg.sample" testTypedPkg_config_nositepackagesPyprojectTOML.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports testTypedPkg_config_nositepackagesPyprojectTOML.py:3: error: Cannot find implementation or library stub for module named "typedpkg" -testTypedPkg_config_nositepackagesPyprojectTOML.py:5: note: Revealed type is 'Any' +testTypedPkg_config_nositepackagesPyprojectTOML.py:5: note: Revealed type is "Any" [case testTypedPkgNoSitePkgsIgnoredImportsPyprojectTOML] # pkgs: typedpkg @@ -24,4 +24,4 @@ reveal_type(a) \[tool.mypy] ignore_missing_imports = true [out] -testTypedPkgNoSitePkgsIgnoredImportsPyprojectTOML.py:6: note: Revealed type is 'Any' +testTypedPkgNoSitePkgsIgnoredImportsPyprojectTOML.py:6: note: Revealed type is "Any" From 7e8b7ba64120f86583f3aff0ec186ecd47368fe3 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Mon, 5 Apr 2021 12:22:34 -0400 Subject: [PATCH 17/39] Update single quotes to double quotes in "Name ... is" tests --- test-data/unit/cmdline.pyproject.test | 14 +++++++------- test-data/unit/daemon.pyproject.test | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index 7a6e3ef9b9ba..cfd89dbca4ae 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -613,9 +613,9 @@ fail [file foo/lol.py] fail [out] -foo/lol.py:1: error: Name 'fail' is not defined -emarg/foo.py:1: error: Name 'fail' is not defined -emarg/hatch/villip/mankangulisk.py:1: error: Name 'fail' is not defined +foo/lol.py:1: error: Name "fail" is not defined +emarg/foo.py:1: error: Name "fail" is not defined +emarg/hatch/villip/mankangulisk.py:1: error: Name "fail" is not defined [case testTomlFilesPyprojectTOML] # cmd: mypy @@ -630,8 +630,8 @@ fail [file b.py] fail [out] -b.py:1: error: Name 'fail' is not defined -a.py:1: error: Name 'fail' is not defined +b.py:1: error: Name "fail" is not defined +a.py:1: error: Name "fail" is not defined [case testTomlFilesGlobbingPyprojectTOML] # cmd: mypy @@ -643,8 +643,8 @@ fail [file c.py] fail [out] -a/b.py:1: error: Name 'fail' is not defined -c.py:1: error: Name 'fail' is not defined +a/b.py:1: error: Name "fail" is not defined +c.py:1: error: Name "fail" is not defined [case testTomlFilesCmdlineOverridesConfigPyprojectTOML] # cmd: mypy override.py diff --git a/test-data/unit/daemon.pyproject.test b/test-data/unit/daemon.pyproject.test index 2689482b41ee..206ba0f2e462 100644 --- a/test-data/unit/daemon.pyproject.test +++ b/test-data/unit/daemon.pyproject.test @@ -34,11 +34,11 @@ def plugin(version): return Dummy -- Note: Backslash path separator in output is replaced with forward slash so the same test succeeds on Windows as well $ dmypy run -- foo --follow-imports=error --python-version=3.6 Daemon started -foo/lol.py:1: error: Name 'fail' is not defined +foo/lol.py:1: error: Name "fail" is not defined Found 1 error in 1 file (checked 3 source files) == Return code: 1 $ dmypy run -- foo --follow-imports=error --python-version=3.6 -foo/lol.py:1: error: Name 'fail' is not defined +foo/lol.py:1: error: Name "fail" is not defined Found 1 error in 1 file (checked 3 source files) == Return code: 1 $ {python} -c "print('[mypy]')" >mypy.ini From 7aab519401ffcdd55f0ecef9169193ccfa0e7bc8 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Mon, 12 Apr 2021 13:30:16 -0400 Subject: [PATCH 18/39] Remove *.so from .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 644394bbe3e1..c6238dc32beb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ venv/ .cache dmypy.json .dmypy.json -*.so # Packages *.egg From 9d8d6ec34af34a729f2d5adb4347653902b00c47 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Mon, 12 Apr 2021 13:30:28 -0400 Subject: [PATCH 19/39] Add example pyproject.toml to the docs --- docs/source/config_file.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 308fcd2dbdde..0036dba1f169 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -911,6 +911,32 @@ Instead of using a ``mypy.ini`` file, a ``pyproject.toml`` file (as specified by Please see the `TOML Documentation`_ for more details and information on what is allowed in a ``toml`` file. See `PEP 518`_ for more information on the layout and structure of the ``pyproject.toml`` file. + +Example ``pyproject.toml`` +************************** + +Here is an example of a ``pyproject.toml`` file. To use this config file, place it at the root +of your repo (or append it to the end of an existing ``pyproject.toml`` file) and run mypy. + +.. code-block:: toml + + # mypy global options: + + [tool.mypy] + python_version = "2.7" + warn_return_any = true + warn_unused_configs = true + + # mypy per-module options: + + [tool.'mypy-mycode.foo.*'] + disallow_untyped_defs = true + + [tool.mypy-mycode.bar] + warn_return_any = false + + [tool.mypy-somelibrary] + ignore_missing_imports = true .. _lxml: https://pypi.org/project/lxml/ .. _SQLite: https://www.sqlite.org/ From a29a319ac0453ee272696eb55830217c71ccf090 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Mon, 12 Apr 2021 13:51:44 -0400 Subject: [PATCH 20/39] Fix docstring for try_split --- mypy/config_parser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 77994be90a6c..0d90b5b266ef 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -38,8 +38,7 @@ def parse_version(v: str) -> Tuple[int, int]: def try_split(v: Union[str, Sequence[str]], split_regex: str = '[,]') -> List[str]: - """Split and trim a str or list of str into a list of str - """ + """Split and trim a str or list of str into a list of str""" if isinstance(v, str): return [p.strip() for p in re.split(split_regex, v)] From 68490cc072d9f9d77f931f7d24f664c6ef210f01 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Mon, 12 Apr 2021 13:52:16 -0400 Subject: [PATCH 21/39] Add more specific config value types --- mypy/config_parser.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 0d90b5b266ef..d4804abaebc4 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -15,6 +15,9 @@ from mypy import defaults from mypy.options import Options, PER_MODULE_OPTIONS +_CONFIG_VALUE_TYPES = Union[str, bool, int, float, Dict[str, str], List[str], Tuple[int, int]] +_INI_PARSER_CALLABLE = Callable[[Any], _CONFIG_VALUE_TYPES] + def parse_version(v: str) -> Tuple[int, int]: m = re.match(r'\A(\d)\.(\d+)\Z', v) @@ -122,10 +125,10 @@ def check_follow_imports(choice: str) -> str: 'cache_dir': expand_path, 'python_executable': expand_path, 'strict': bool, -} # type: Final[Dict[str, Any]] +} # type: Final[Dict[str, _INI_PARSER_CALLABLE]] # Reuse the ini_config_types and overwrite the diff -toml_config_types = copy.copy(ini_config_types) # type: Final[Dict[str, Any]] +toml_config_types = ini_config_types.copy() # type: Final[Dict[str, _INI_PARSER_CALLABLE]] toml_config_types.update({ 'python_version': lambda s: parse_version(str(s)), 'strict_optional_whitelist': try_split, From 1686682d4b7d868893c5daa42c386b3408f86bf5 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Mon, 12 Apr 2021 13:52:34 -0400 Subject: [PATCH 22/39] Simpler checking for mypy in toml file --- mypy/config_parser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index d4804abaebc4..62b5f00364b2 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -1,7 +1,6 @@ import argparse from collections import OrderedDict import configparser -import copy import glob as fileglob from io import StringIO import os @@ -176,7 +175,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], if key.split('-')[0] != 'mypy'] for key in other_keys: del toml_data[key] - if toml_data.get('mypy') is None: + if 'mypy' not in toml_data: continue parser = toml_data config_types = toml_config_types From ba2c3aed70b12fe9f445220e660e6a6d99c67802 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Mon, 12 Apr 2021 13:59:28 -0400 Subject: [PATCH 23/39] Fix convert_to_boolean docstring --- mypy/config_parser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 62b5f00364b2..fd5fa0739820 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -342,8 +342,7 @@ def parse_section(prefix: str, template: Options, def convert_to_boolean(value: Optional[Any]) -> bool: - """Return a boolean value translating from other types if necessary. - """ + """Return a boolean value translating from other types if necessary.""" if isinstance(value, bool): return value if not isinstance(value, str): From adeea90b7ea5ada93ca84425b43c28646f4914a2 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Mon, 12 Apr 2021 15:14:55 -0400 Subject: [PATCH 24/39] Fix latest single to doublt quote test changes --- test-data/unit/check-flags.pyproject.test | 2 +- test-data/unit/check-modules-case.pyproject.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-flags.pyproject.test b/test-data/unit/check-flags.pyproject.test index 532ff99e248a..4f0b8d16475d 100644 --- a/test-data/unit/check-flags.pyproject.test +++ b/test-data/unit/check-flags.pyproject.test @@ -216,7 +216,7 @@ implicit_reexport = true \[tool.mypy-other_module_2] implicit_reexport = false [out] -main:2: error: Module 'other_module_2' has no attribute 'a' +main:2: error: Module "other_module_2" has no attribute "a" [case testDisallowSubclassingAnyPyprojectTOML] # flags: --config-file tmp/pyproject.toml diff --git a/test-data/unit/check-modules-case.pyproject.test b/test-data/unit/check-modules-case.pyproject.test index 9ee57e59a2e0..62c68a8d025a 100644 --- a/test-data/unit/check-modules-case.pyproject.test +++ b/test-data/unit/check-modules-case.pyproject.test @@ -3,7 +3,7 @@ [case testCaseInsensitivityDirPyprojectTOML] # flags: --config-file tmp/pyproject.toml -from a import B # E: Module 'a' has no attribute 'B' +from a import B # E: Module "a" has no attribute "B" from other import x reveal_type(x) # N: Revealed type is "builtins.int" From b74d91d4ef6221070c29998869b2641430fde5b7 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Wed, 14 Apr 2021 13:59:11 -0400 Subject: [PATCH 25/39] Modify tests to new override format in pyproject.toml --- test-data/unit/check-flags.pyproject.test | 33 +++-- .../unit/check-incremental.pyproject.test | 3 +- test-data/unit/check-modules.pyproject.test | 3 +- test-data/unit/check-optional.pyproject.test | 3 +- test-data/unit/cmdline.pyproject.test | 114 ++++++++++++------ test-data/unit/daemon.pyproject.test | 3 +- test-data/unit/fine-grained.pyproject.test | 21 ++-- 7 files changed, 121 insertions(+), 59 deletions(-) diff --git a/test-data/unit/check-flags.pyproject.test b/test-data/unit/check-flags.pyproject.test index 4f0b8d16475d..c34eeea1bda5 100644 --- a/test-data/unit/check-flags.pyproject.test +++ b/test-data/unit/check-flags.pyproject.test @@ -11,7 +11,8 @@ def incomplete(x) -> int: # E: Function is missing a type annotation for one or [file pyproject.toml] \[tool.mypy] disallow_incomplete_defs = false -\[tool.mypy-incomplete] +\[[tool.mypy.overrides]] +module = "incomplete" disallow_incomplete_defs = true [case testPerFileStrictOptionalBasicPyprojectTOML] @@ -30,7 +31,8 @@ if int(): [file pyproject.toml] \[tool.mypy] strict_optional = false -\[tool.mypy-optional] +\[[tool.mypy.overrides]] +module = "optional" strict_optional = true [case testPerFileStrictOptionalBasicImportStandardPyprojectTOML] @@ -56,7 +58,8 @@ f(standard.optional_int) # E: Argument 1 to "f" has incompatible type "None"; e [file pyproject.toml] \[tool.mypy] strict_optional = false -\[tool.mypy-optional] +\[[tool.mypy.overrides]] +module = "optional" strict_optional = true [case testPerFileStrictOptionalBasicImportOptionalPyprojectTOML] @@ -78,7 +81,8 @@ y = None # type: None [file pyproject.toml] \[tool.mypy] strict_optional = false -\[tool.mypy-optional] +\[[tool.mypy.overrides]] +module = "optional" strict_optional = true [case testPerFileStrictOptionalListItemImportOptionalPyprojectTOML] @@ -101,7 +105,8 @@ y = [] # type: List[int] [file pyproject.toml] \[tool.mypy] strict_optional = false -\[tool.mypy-optional] +\[[tool.mypy.overrides]] +module = "optional" strict_optional = true [builtins fixtures/list.pyi] @@ -120,7 +125,8 @@ standard.f(None) [file pyproject.toml] \[tool.mypy] strict_optional = false -\[tool.mypy-optional] +\[[tool.mypy.overrides]] +module = "optional" strict_optional = true [case testAlwaysTrueAlwaysFalseConfigFilePyprojectTOML] @@ -196,7 +202,8 @@ import b [file pyproject.toml] \[tool.mypy] strict_equality = true -\[tool.mypy-b] +\[[tool.mypy.overrides]] +module = "b" strict_equality = false [builtins fixtures/bool.pyi] @@ -213,7 +220,8 @@ from other_module_1 import a [file pyproject.toml] \[tool.mypy] implicit_reexport = true -\[tool.mypy-other_module_2] +\[[tool.mypy.overrides]] +module = "other_module_2" implicit_reexport = false [out] main:2: error: Module "other_module_2" has no attribute "a" @@ -240,7 +248,8 @@ class ShouldNotBeFine(x): ... # E: Class cannot subclass 'x' (has type 'Any') [file pyproject.toml] \[tool.mypy] disallow_subclassing_any = true -\[tool.mypy-m] +\[[tool.mypy.overrides]] +module = "m" disallow_subclassing_any = false [case testNoImplicitOptionalPerModulePyprojectTOML] @@ -254,7 +263,8 @@ def f(a: str = None) -> int: [file pyproject.toml] \[tool.mypy] no_implicit_optional = true -\[tool.mypy-m] +\[[tool.mypy.overrides]] +module = "m" no_implicit_optional = false [case testNoImplicitOptionalPerModulePython2PyprojectTOML] @@ -269,7 +279,8 @@ def f(a = None): [file pyproject.toml] \[tool.mypy] no_implicit_optional = true -\[tool.mypy-m] +\[[tool.mypy.overrides]] +module = "m" no_implicit_optional = false [case testDisableErrorCodePyprojectTOML] diff --git a/test-data/unit/check-incremental.pyproject.test b/test-data/unit/check-incremental.pyproject.test index fc46de36f631..9bb23d112767 100644 --- a/test-data/unit/check-incremental.pyproject.test +++ b/test-data/unit/check-incremental.pyproject.test @@ -66,7 +66,8 @@ pass [file pyproject.toml] \[tool.mypy] warn_no_return = false -\[tool.mypy-a] +\[[tool.mypy.overrides]] +module = "a" warn_no_return = true [rechecked] diff --git a/test-data/unit/check-modules.pyproject.test b/test-data/unit/check-modules.pyproject.test index 78b3d28d5e29..7977f5e047e2 100644 --- a/test-data/unit/check-modules.pyproject.test +++ b/test-data/unit/check-modules.pyproject.test @@ -14,7 +14,8 @@ def __getattr__(attr: str) -> Any: ... [file pyproject.toml] \[tool.mypy] -\[tool.'mypy-a.b.c'] +\[[tool.mypy.overrides]] +module = "a.b.c" ignore_missing_imports = true [builtins fixtures/module.pyi] [out] diff --git a/test-data/unit/check-optional.pyproject.test b/test-data/unit/check-optional.pyproject.test index eed065332847..0d013c3750ce 100644 --- a/test-data/unit/check-optional.pyproject.test +++ b/test-data/unit/check-optional.pyproject.test @@ -17,7 +17,8 @@ asdf(x) [file pyproject.toml] \[tool.mypy] -\[tool.mypy-a] +\[[tool.mypy.overrides]] +module = "a" strict_optional = false [out] main:4: error: Argument 1 to "asdf" has incompatible type "List[str]"; expected "List[Optional[str]]" diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index cfd89dbca4ae..852765715073 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -54,9 +54,11 @@ warn_unused_ignores = true [file pyproject.toml] \[tool.mypy] disallow_untyped_defs = true -\[tool.mypy-y] +\[[tool.mypy.overrides]] +module = "y" disallow_untyped_defs = false -\[tool.mypy-z] +\[[tool.mypy.overrides]] +module = "z" disallow_untyped_calls = true [file x.py] def f(a): @@ -82,9 +84,11 @@ x.py:1: error: Function is missing a type annotation # cmd: mypy xx.py xy.py yx.py yy.py [file pyproject.toml] \[tool.mypy] -\[tool.'mypy-*x*'] +\[[tool.mypy.overrides]] +module = "*x*" disallow_untyped_defs = true -\[tool.'mypy-*y*'] +\[[tool.mypy.overrides]] +module = "*y*" disallow_untyped_calls = true [file xx.py] def f(a): pass @@ -107,7 +111,11 @@ pyproject.toml: [mypy-*y*]: Patterns must be fully-qualified module names, optio # cmd: mypy x.py y.py z.py [file pyproject.toml] \[tool.mypy] -\[tool.'mypy-x.*,z.*'] +\[[tool.mypy.overrides]] +module = [ + 'x.*', + 'z.*', +] disallow_untyped_defs = true [file x.py] def f(a): pass @@ -158,7 +166,8 @@ pyproject.toml: [mypy]: ignore_missing_imports: Not a boolean: nah # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] -\[tool.'mypy-*'] +\[[tool.mypy.overrides]] +module = "*" python_version = 3.4 [out] pyproject.toml: [mypy-*]: Per-module sections should only specify per-module flags (python_version) @@ -191,7 +200,8 @@ file.py:6: error: Argument 1 to "foo" has incompatible type "str"; expected "int # cmd: mypy x.py y.py [file pyproject.toml] \[tool.mypy] -\[tool.mypy-x] +\[[tool.mypy.overrides]] +module = "x" ignore_errors = true [file x.py] "" + 0 @@ -283,13 +293,17 @@ main.py:4: note: Revealed type is "Any" # cmd: mypy main.py [file pyproject.toml] \[tool.mypy] -\[tool.mypy-normal] +\[[tool.mypy.overrides]] +module = "normal" follow_imports = "normal" -\[tool.mypy-silent] +\[[tool.mypy.overrides]] +module = "silent" follow_imports = "silent" -\[tool.mypy-skip] +\[[tool.mypy.overrides]] +module = "skip" follow_imports = "skip" -\[tool.mypy-error] +\[[tool.mypy.overrides]] +module = "error" follow_imports = "error" [file main.py] import normal @@ -357,7 +371,8 @@ main.py:2: note: Revealed type is "Any" # cmd: mypy -c pass [file pyproject.toml] \[tool.mypy] -\[tool.mypy-foo] +\[[tool.mypy.overrides]] +module = "foo" x_bad = 0 [out] @@ -432,7 +447,8 @@ python_version = 3.6 [file pyproject.toml] \[tool.mypy] python_version = 3.6 -\[tool.mypy-m] +\[[tool.mypy.overrides]] +module = "m" disallow_any_generics = true [file m.py] @@ -454,7 +470,8 @@ m.py:7: error: Implicit generic "Any". Use "typing.FrozenSet" and specify generi # cmd: mypy m.py [file pyproject.toml] \[tool.mypy] -\[tool.mypy-m] +\[[tool.mypy.overrides]] +module = "m" disallow_any_generics = true [file m.py] @@ -489,19 +506,25 @@ g(None) [file pyproject.toml] \[tool.mypy] allow_any_generics = true -\[tool.'mypy-a.*'] +\[[tool.mypy.overrides]] +module = "a.*" ignore_errors = true -\[tool.'mypy-a.b.*'] +\[[tool.mypy.overrides]] +module = "a.b.*" disallow_any_generics = true ignore_errors = true -\[tool.'mypy-a.b.c.*'] +\[[tool.mypy.overrides]] +module = "a.b.c.*" ignore_errors = true -\[tool.'mypy-a.b.c.d.*'] +\[[tool.mypy.overrides]] +module = "a.b.c.d.*" ignore_errors = true -\[tool.'mypy-a.b.c.d.e.*'] +\[[tool.mypy.overrides]] +module = "a.b.c.d.e.*" ignore_errors = true strict_optional = true -\[tool.'mypy-a.b.c.d.e'] +\[[tool.mypy.overrides]] +module = "a.b.c.d.e" ignore_errors = false [out] a/b/c/d/e/__init__.py:2: error: Missing type parameters for generic type "List" @@ -536,7 +559,8 @@ src/anamespace/foo/bar.py:2: error: Incompatible return value type (got "int", e # cmd: mypy main.py [file pyproject.toml] \[tool.mypy] -\[tool.'mypy-math.*'] +\[[tool.mypy.overrides]] +module = "math.*" follow_imports = "error" follow_imports_for_stubs = true [file main.py] @@ -550,7 +574,8 @@ main.py:1: note: (Using --follow-imports=error, module not passed on command lin # cmd: mypy main.py [file pyproject.toml] \[tool.mypy] -\[tool.'mypy-math.*'] +\[[tool.mypy.overrides]] +module = "math.*" follow_imports = "skip" follow_imports_for_stubs = true [file main.py] @@ -563,20 +588,31 @@ math.frobnicate() \[tool.mypy] warn_unused_configs = true incremental = false -\[tool.mypy-bar] -\[tool.mypy-foo] -\[tool.'mypy-baz.*'] -\[tool.'mypy-quux.*'] -\[tool.'mypy-spam.*'] -\[tool.'mypy-spam.eggs'] -\[tool.'mypy-emarg.*'] -\[tool.'mypy-emarg.hatch'] +\[[tool.mypy.overrides]] +module = "bar" +\[[tool.mypy.overrides]] +module = "foo" +\[[tool.mypy.overrides]] +module = "baz.*" +\[[tool.mypy.overrides]] +module = "quux.*" +\[[tool.mypy.overrides]] +module = "spam.*" +\[[tool.mypy.overrides]] +module = "spam.eggs" +\[[tool.mypy.overrides]] +module = "emarg.*" +\[[tool.mypy.overrides]] +module = "emarg.hatch" # Currently we don't treat an unstructured pattern like a.*.b as unused # if it matches another section (like a.x.b). This would be reasonable # to change. ' -\[tool.'mypy-a.*.b'] -\[tool.'mypy-a.*.c'] -\[tool.'mypy-a.x.b'] +\[[tool.mypy.overrides]] +module = "a.*.b" +\[[tool.mypy.overrides]] +module = "a.*.c" +\[[tool.mypy.overrides]] +module = "a.x.b" [file foo.py] [file quux.py] [file spam/__init__.py] @@ -590,13 +626,17 @@ Warning: unused section(s) in pyproject.toml: [mypy-bar], [mypy-baz.*], [mypy-em [file pyproject.toml] \[tool.mypy] ignore_errors = true -\[tool.'mypy-*.lol'] +\[[tool.mypy.overrides]] +module = "*.lol" ignore_errors = false -\[tool.'mypy-emarg.*'] +\[[tool.mypy.overrides]] +module = "emarg.*" ignore_errors = false -\[tool.'mypy-emarg.*.villip.*'] +\[[tool.mypy.overrides]] +module = "emarg.*.villip.*" ignore_errors = true -\[tool.'mypy-emarg.hatch.villip.mankangulisk'] +\[[tool.mypy.overrides]] +module = "emarg.hatch.villip.mankangulisk" ignore_errors = false [file emarg/__init__.py] [file emarg/foo.py] diff --git a/test-data/unit/daemon.pyproject.test b/test-data/unit/daemon.pyproject.test index 206ba0f2e462..1952b491be33 100644 --- a/test-data/unit/daemon.pyproject.test +++ b/test-data/unit/daemon.pyproject.test @@ -53,7 +53,8 @@ Daemon stopped [file pyproject.toml] \[tool.mypy] ignore_errors = true -\[tool.'mypy-*.lol'] +\[[tool.mypy.overrides]] +module = "*.lol" ignore_errors = false [file foo/__init__.py] diff --git a/test-data/unit/fine-grained.pyproject.test b/test-data/unit/fine-grained.pyproject.test index a20070dbbf8a..d81fcaf89276 100644 --- a/test-data/unit/fine-grained.pyproject.test +++ b/test-data/unit/fine-grained.pyproject.test @@ -45,7 +45,8 @@ import a [file pyproject.toml] \[tool.mypy] strict_optional = false -\[tool.'mypy-a.*'] +\[[tool.mypy.overrides]] +module = "a.*" strict_optional = true [file a.py] from typing import Optional @@ -75,7 +76,8 @@ import a [file pyproject.toml] \[tool.mypy] strict_optional = false -\[tool.'mypy-a.*'] +\[[tool.mypy.overrides]] +module = "a.*" strict_optional = true [file a.py] from typing import Optional @@ -121,7 +123,8 @@ import a [file pyproject.toml] \[tool.mypy] strict_optional = false -\[tool.'mypy-b.*'] +\[[tool.mypy.overrides]] +module = "b.*" strict_optional = true [file a.py] from typing import Optional @@ -157,7 +160,8 @@ import a [file pyproject.toml] \[tool.mypy] strict_optional = false -\[tool.'mypy-b.*'] +\[[tool.mypy.overrides]] +module = "b.*" strict_optional = true [file a.py] from typing import Optional @@ -196,7 +200,8 @@ b.py:5: error: Argument 1 to "h" of "C" has incompatible type "Optional[int]"; e [case testRefreshIgnoreErrors1PyprojectTOML] [file pyproject.toml] \[tool.mypy] -\[tool.mypy-b] +\[[tool.mypy.overrides]] +module = "b" ignore_errors = true [file a.py] y = '1' @@ -213,7 +218,8 @@ def fu() -> None: [case testRefreshIgnoreErrors2PyprojectTOML] [file pyproject.toml] \[tool.mypy] -\[tool.mypy-b] +\[[tool.mypy.overrides]] +module = "b" ignore_errors = true [file b.py] def fu() -> int: @@ -230,7 +236,8 @@ def fu() -> int: [file pyproject.toml] \[tool.mypy] disallow_any_generics = true -\[tool.mypy-b] +\[[tool.mypy.overrides]] +module = "b" disallow_any_generics = false [file a.py] y = '1' From c9ba08115f2c3d66a936ad4356e3dcd25d039e82 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Thu, 15 Apr 2021 17:50:54 -0400 Subject: [PATCH 26/39] Make package overrides in the pyproject.toml a little cleaner --- docs/source/config_file.rst | 24 ++++++----- mypy/config_parser.py | 80 ++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 11 deletions(-) diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 0036dba1f169..7ae4a8c26381 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -892,15 +892,18 @@ Using a pyproject.toml file Instead of using a ``mypy.ini`` file, a ``pyproject.toml`` file (as specified by `PEP 518`_) may be used instead. A few notes on doing so: -* All ``[mypy]`` sections should have ``tool.`` prepended to their name: +* The ``[mypy]`` section should have ``tool.`` prepended to its name: - * ``[mypy]`` would become ``[tool.mypy]`` + * I.e., ``[mypy]`` would become ``[tool.mypy]`` - * For example, ``[mypy-packagename]`` would become ``[tool.mypy-packagename]`` +* The module specific sections should be moved into a `[[tool.mypy.overrides]]` section: -* Any section with special characters (such as ``.`` or ``*``) will need to be wrapped in single quotes: - - * For example, ``[tool.mypy-package.*]`` would become ``[tool.'mypy-package.*']`` + * For example, ``[mypy-packagename]`` would become: + ``` + [[tool.mypy.overrides]] + module = 'packagename + ... + ``` * The following care should be given to values in the ``pyproject.toml`` files as compared to ``ini`` files: @@ -929,13 +932,16 @@ of your repo (or append it to the end of an existing ``pyproject.toml`` file) an # mypy per-module options: - [tool.'mypy-mycode.foo.*'] + [[tool.mypy.overrides]] + module = "mycode.foo.*" disallow_untyped_defs = true - [tool.mypy-mycode.bar] + [[tool.mypy.overrides]] + module = "mycode.bar" warn_return_any = false - [tool.mypy-somelibrary] + [[tool.mypy.overrides]] + module = "somelibrary" ignore_missing_imports = true .. _lxml: https://pypi.org/project/lxml/ diff --git a/mypy/config_parser.py b/mypy/config_parser.py index fd5fa0739820..b8b9f00cb29a 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -1,6 +1,7 @@ import argparse from collections import OrderedDict import configparser +import copy import glob as fileglob from io import StringIO import os @@ -8,7 +9,8 @@ import sys import toml -from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, TextIO, Tuple, Union +from typing import (Any, Callable, Dict, List, Mapping, MutableMapping, Optional, Sequence, + TextIO, Tuple, Union) from typing_extensions import Final from mypy import defaults @@ -177,7 +179,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], del toml_data[key] if 'mypy' not in toml_data: continue - parser = toml_data + parser = destructure_overrides(toml_data) config_types = toml_config_types else: config_parser.read(config_file) @@ -240,6 +242,80 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], options.per_module_options[glob] = updates +def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping[str, Any]: + """Take the new [[tool.mypy.overrides]] section array in the pyproject.toml file, + and convert it back to a flatter structure that the existing config_parser can handle. + + E.g. the following pyproject.toml file: + + [[tool.mypy.overrides]] + module = [ + "a.b", + "b.*" + ] + disallow_untyped_defs = true + + [[tool.mypy.overrides]] + module = 'c' + disallow_untyped_defs = false + + Would map to the following config dict that it would have gotten from parsing an equivalent + ini file: + + { + "mypy-a.b": { + disallow_untyped_defs = true, + }, + "mypy-b.*": { + disallow_untyped_defs = true, + }, + "mypy-c": { + disallow_untyped_defs: false, + }, + } + """ + if 'overrides' not in toml_data['mypy']: + return toml_data + + if not isinstance(toml_data['mypy']['overrides'], list): + raise ValueError("tool.mypy.overrides sections must be an array. Please make sure you are " + "using double brackets like so: [[tool.mypy.overrides]]") + + result = copy.copy(toml_data) + for override in result['mypy']['overrides']: + if 'module' not in override: + raise ValueError("pyproject.toml contains a [[tool.mypy.overrides]] section, but no " + "module to override was specified.") + + if isinstance(override['module'], str): + modules = [override['module']] + elif isinstance(override['module'], list): + modules = override['module'] + else: + raise ValueError("pyproject.toml contains a [[tool.mypy.overrides]] section with " + "a module value that is not a string or a list of strings") + + for module in modules: + module_overrides = override.copy() + del module_overrides['module'] + old_config_name = 'mypy-%s' % module + if old_config_name not in result: + result[old_config_name] = module_overrides + else: + for new_key, new_value in module_overrides.items(): + if (new_key in result[old_config_name] and + result[old_config_name][new_key] != new_value): + raise ValueError("pyproject.toml contains [[tool.mypy.overrides]] " + "sections with conflicting values. Module '%s' " + " has two different values for '%s'" + % (module, new_key)) + result[old_config_name][new_key] = new_value + + del result['mypy']['overrides'] + # print(result) + return result + + def parse_section(prefix: str, template: Options, set_strict_flags: Callable[[], None], section: Mapping[str, Any], From 77a2c041a2565e14156fcf89cc5a3924bdd3de84 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Thu, 15 Apr 2021 20:43:01 -0400 Subject: [PATCH 27/39] Clean up config doc --- docs/source/config_file.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 7ae4a8c26381..ed4396088935 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -896,9 +896,10 @@ Instead of using a ``mypy.ini`` file, a ``pyproject.toml`` file (as specified by * I.e., ``[mypy]`` would become ``[tool.mypy]`` -* The module specific sections should be moved into a `[[tool.mypy.overrides]]` section: +* The module specific sections should be moved into ``[[tool.mypy.overrides]]`` sections: * For example, ``[mypy-packagename]`` would become: + ``` [[tool.mypy.overrides]] module = 'packagename From ec0c061bee42e01e3bdeaf2dc517d351ba905cbf Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Thu, 15 Apr 2021 22:53:11 -0400 Subject: [PATCH 28/39] Add tests for possible errors in the new overrides toml config --- mypy/config_parser.py | 10 ++--- test-data/unit/cmdline.pyproject.test | 58 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index b8b9f00cb29a..84f4b6bd08a1 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -185,7 +185,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], config_parser.read(config_file) parser = config_parser config_types = ini_config_types - except (toml.TomlDecodeError, configparser.Error) as err: + except (toml.TomlDecodeError, configparser.Error, ValueError) as err: print("%s: %s" % (config_file, err), file=stderr) else: if config_file in defaults.SHARED_CONFIG_FILES and 'mypy' not in parser: @@ -284,7 +284,7 @@ def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping result = copy.copy(toml_data) for override in result['mypy']['overrides']: if 'module' not in override: - raise ValueError("pyproject.toml contains a [[tool.mypy.overrides]] section, but no " + raise ValueError("toml config file contains a [[tool.mypy.overrides]] section, but no " "module to override was specified.") if isinstance(override['module'], str): @@ -292,7 +292,7 @@ def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping elif isinstance(override['module'], list): modules = override['module'] else: - raise ValueError("pyproject.toml contains a [[tool.mypy.overrides]] section with " + raise ValueError("toml config file contains a [[tool.mypy.overrides]] section with " "a module value that is not a string or a list of strings") for module in modules: @@ -305,9 +305,9 @@ def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping for new_key, new_value in module_overrides.items(): if (new_key in result[old_config_name] and result[old_config_name][new_key] != new_value): - raise ValueError("pyproject.toml contains [[tool.mypy.overrides]] " + raise ValueError("toml config file contains [[tool.mypy.overrides]] " "sections with conflicting values. Module '%s' " - " has two different values for '%s'" + "has two different values for '%s'" % (module, new_key)) result[old_config_name][new_key] = new_value diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index 852765715073..795b16f3e091 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -530,6 +530,64 @@ ignore_errors = false a/b/c/d/e/__init__.py:2: error: Missing type parameters for generic type "List" a/b/c/d/e/__init__.py:3: error: Argument 1 to "g" has incompatible type "None"; expected "List[Any]" +[case testDuplicateModulesAcrossSectionsPyprojectTOML] +# cmd: mypy a +[file a/__init__.py] +[file pyproject.toml] +\[tool.mypy] +allow_any_generics = true +\[[tool.mypy.overrides]] +module = "a.*" +ignore_errors = true +\[[tool.mypy.overrides]] +module = [ + "a.b.*", + "a.*" +] +disallow_any_generics = true +ignore_errors = false +[out] +pyproject.toml: toml config file contains [[tool.mypy.overrides]] sections with conflicting values. Module 'a.*' has two different values for 'ignore_errors' +== Return code: 0 + +[case testNonArrayOverridesSectionPyprojectTOML] +# cmd: mypy a +[file a/__init__.py] +[file pyproject.toml] +\[tool.mypy] +allow_any_generics = true +\[tool.mypy.overrides] +module = "a.*" +ignore_errors = true +[out] +pyproject.toml: tool.mypy.overrides sections must be an array. Please make sure you are using double brackets like so: [[tool.mypy.overrides]] +== Return code: 0 + +[case testOverridesSectionWithNoModulePyprojectTOML] +# cmd: mypy a +[file a/__init__.py] +[file pyproject.toml] +\[tool.mypy] +allow_any_generics = true +\[[tool.mypy.overrides]] +ignore_errors = true +[out] +pyproject.toml: toml config file contains a [[tool.mypy.overrides]] section, but no module to override was specified. +== Return code: 0 + +[case testOverridesSectionWithInvalidModulePyprojectTOML] +# cmd: mypy a +[file a/__init__.py] +[file pyproject.toml] +\[tool.mypy] +allow_any_generics = true +\[[tool.mypy.overrides]] +module = true +ignore_errors = true +[out] +pyproject.toml: toml config file contains a [[tool.mypy.overrides]] section with a module value that is not a string or a list of strings +== Return code: 0 + [case testDisallowUntypedDefsAndGenericsPyprojectTOML] # cmd: mypy a.py [file pyproject.toml] From 9d213416db2d6838e460c01da73511f614b8c25a Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Fri, 23 Apr 2021 13:35:08 -0400 Subject: [PATCH 29/39] Remove extraneous print --- mypy/config_parser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 84f4b6bd08a1..a914aa709a0d 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -312,7 +312,6 @@ def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping result[old_config_name][new_key] = new_value del result['mypy']['overrides'] - # print(result) return result From 9cbef1680a965ed0fe7d5a4cc15c1051ea8237d8 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Fri, 23 Apr 2021 13:42:18 -0400 Subject: [PATCH 30/39] Add multimodule examples to documenatiton for toml config --- docs/source/config_file.rst | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index ed4396088935..c707d2b162a5 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -900,11 +900,25 @@ Instead of using a ``mypy.ini`` file, a ``pyproject.toml`` file (as specified by * For example, ``[mypy-packagename]`` would become: - ``` +.. code-block:: toml + + [[tool.mypy.overrides]] + module = 'packagename' + ... + +* Multi-module specific sections can be moved into a single ``[[tools.mypy.overrides]]`` section with a + module property set to an array of modules: + + * For example, ``[mypy-packagename,packagename2]`` would become: + +.. code-block:: toml + [[tool.mypy.overrides]] - module = 'packagename + module = [ + 'packagename', + 'packagename2' + ] ... - ``` * The following care should be given to values in the ``pyproject.toml`` files as compared to ``ini`` files: @@ -942,7 +956,10 @@ of your repo (or append it to the end of an existing ``pyproject.toml`` file) an warn_return_any = false [[tool.mypy.overrides]] - module = "somelibrary" + module = [ + "somelibrary", + "some_other_library" + ] ignore_missing_imports = true .. _lxml: https://pypi.org/project/lxml/ From fe357e11c45225219420b424c30624f656bb7758 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Fri, 23 Apr 2021 13:58:20 -0400 Subject: [PATCH 31/39] Remove vestigial string split for toml keys --- mypy/config_parser.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index a914aa709a0d..7d468441992e 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -173,12 +173,9 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], toml_data = toml.load(config_file, _dict=OrderedDict) # Filter down to just mypy relevant toml keys toml_data = toml_data.get('tool', {}) - other_keys = [key for key in toml_data.keys() - if key.split('-')[0] != 'mypy'] - for key in other_keys: - del toml_data[key] if 'mypy' not in toml_data: continue + toml_data = {'mypy': toml_data['mypy']} parser = destructure_overrides(toml_data) config_types = toml_config_types else: From d594d15ed46cd78da874be0a5dd244214ec56b20 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Fri, 23 Apr 2021 18:49:03 -0400 Subject: [PATCH 32/39] Make pyproject errors more tailored to actual toml not ini --- mypy/config_parser.py | 27 +++++++++++++++++++++++++-- mypy/main.py | 10 ++++++---- mypy/options.py | 2 +- test-data/unit/cmdline.pyproject.test | 8 ++++---- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 7d468441992e..6af0b3b64763 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -169,7 +169,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], if not os.path.exists(config_file): continue try: - if config_file.endswith('.toml'): + if is_toml(config_file): toml_data = toml.load(config_file, _dict=OrderedDict) # Filter down to just mypy relevant toml keys toml_data = toml_data.get('tool', {}) @@ -210,7 +210,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], for name, section in parser.items(): if name.startswith('mypy-'): - prefix = '%s: [%s]: ' % (file_read, name) + prefix = get_prefix(file_read, name) updates, report_dirs = parse_section( prefix, options, set_strict_flags, section, config_types, stderr) if report_dirs: @@ -239,6 +239,19 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], options.per_module_options[glob] = updates +def get_prefix(file_read: str, name: str) -> str: + if is_toml(file_read): + module_name_str = 'module = "%s"' % '-'.join(name.split('-')[1:]) + else: + module_name_str = name + + return '%s: [%s]: ' % (file_read, module_name_str) + + +def is_toml(filemame: str) -> bool: + return filemame.lower().endswith('.toml') + + def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping[str, Any]: """Take the new [[tool.mypy.overrides]] section array in the pyproject.toml file, and convert it back to a flatter structure that the existing config_parser can handle. @@ -518,3 +531,13 @@ def set_strict_flags() -> None: sections.update(new_sections) return sections, errors + + +def get_config_module_names(filename: str, modules: List[str]) -> str: + if not modules: + return '' + + if not is_toml(filename): + return ", ".join("[mypy-%s]" % module for module in modules) + + return "module = ['%s']" % ("', '".join(sorted(modules))) diff --git a/mypy/main.py b/mypy/main.py index 98dacceb7588..2e684a22d6e3 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -23,7 +23,7 @@ from mypy.errors import CompileError from mypy.errorcodes import error_codes from mypy.options import Options, BuildType -from mypy.config_parser import parse_version, parse_config_file +from mypy.config_parser import get_config_module_names, parse_version, parse_config_file from mypy.split_namespace import SplitNamespace from mypy.version import __version__ @@ -103,8 +103,9 @@ def flush_errors(new_messages: List[str], serious: bool) -> None: if options.warn_unused_configs and options.unused_configs and not options.incremental: print("Warning: unused section(s) in %s: %s" % (options.config_file, - ", ".join("[mypy-%s]" % glob for glob in options.per_module_options.keys() - if glob in options.unused_configs)), + get_config_module_names(options.config_file, + [glob for glob in options.per_module_options.keys() + if glob in options.unused_configs])), file=stderr) maybe_write_junit_xml(time.time() - t0, serious, messages, options) @@ -452,7 +453,8 @@ def add_invertible_flag(flag: str, help="Configuration file, must have a [mypy] section " "(defaults to {})".format(', '.join(defaults.CONFIG_FILES))) add_invertible_flag('--warn-unused-configs', default=False, strict_flag=True, - help="Warn about unused '[mypy-]' config sections", + help="Warn about unused '[mypy-]' or '[[tool.mypy.overrides]]' " + "config sections", group=config_group) imports_group = parser.add_argument_group( diff --git a/mypy/options.py b/mypy/options.py index a32586fbfc5c..9e41ad7d310d 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -141,7 +141,7 @@ def __init__(self) -> None: # Warn about unused '# type: ignore' comments self.warn_unused_ignores = False - # Warn about unused '[mypy-] config sections + # Warn about unused '[mypy-]' or '[[tool.mypy.overrides]]' config sections self.warn_unused_configs = False # Files in which to ignore all non-fatal errors diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index 795b16f3e091..fabbffe68961 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -103,8 +103,8 @@ def g(a: int) -> int: return f(a) def f(a): pass def g(a: int) -> int: return f(a) [out] -pyproject.toml: [mypy-*x*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) -pyproject.toml: [mypy-*y*]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) +pyproject.toml: [module = "*x*"]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) +pyproject.toml: [module = "*y*"]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) == Return code: 0 [case testMultipleGlobConfigSectionPyprojectTOML] @@ -170,7 +170,7 @@ pyproject.toml: [mypy]: ignore_missing_imports: Not a boolean: nah module = "*" python_version = 3.4 [out] -pyproject.toml: [mypy-*]: Per-module sections should only specify per-module flags (python_version) +pyproject.toml: [module = "*"]: Per-module sections should only specify per-module flags (python_version) == Return code: 0 [case testConfigMypyPathPyprojectTOML] @@ -676,7 +676,7 @@ module = "a.x.b" [file spam/__init__.py] [file spam/eggs.py] [out] -Warning: unused section(s) in pyproject.toml: [mypy-bar], [mypy-baz.*], [mypy-emarg.*], [mypy-emarg.hatch], [mypy-a.*.c], [mypy-a.x.b] +Warning: unused section(s) in pyproject.toml: module = ['a.*.c', 'a.x.b', 'bar', 'baz.*', 'emarg.*', 'emarg.hatch'] == Return code: 0 [case testConfigUnstructuredGlobPyprojectTOML] From b7e02e64229d118b1abc65515f202014cdc18b9b Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Fri, 23 Apr 2021 22:15:00 -0400 Subject: [PATCH 33/39] Convert TOML parse ValueError to custom ValueError --- mypy/config_parser.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 6af0b3b64763..5727b30a5f7e 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -182,7 +182,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], config_parser.read(config_file) parser = config_parser config_types = ini_config_types - except (toml.TomlDecodeError, configparser.Error, ValueError) as err: + except (toml.TomlDecodeError, configparser.Error, ConfigTOMLValueError) as err: print("%s: %s" % (config_file, err), file=stderr) else: if config_file in defaults.SHARED_CONFIG_FILES and 'mypy' not in parser: @@ -288,13 +288,13 @@ def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping return toml_data if not isinstance(toml_data['mypy']['overrides'], list): - raise ValueError("tool.mypy.overrides sections must be an array. Please make sure you are " + raise ConfigTOMLValueError("tool.mypy.overrides sections must be an array. Please make sure you are " "using double brackets like so: [[tool.mypy.overrides]]") result = copy.copy(toml_data) for override in result['mypy']['overrides']: if 'module' not in override: - raise ValueError("toml config file contains a [[tool.mypy.overrides]] section, but no " + raise ConfigTOMLValueError("toml config file contains a [[tool.mypy.overrides]] section, but no " "module to override was specified.") if isinstance(override['module'], str): @@ -302,7 +302,7 @@ def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping elif isinstance(override['module'], list): modules = override['module'] else: - raise ValueError("toml config file contains a [[tool.mypy.overrides]] section with " + raise ConfigTOMLValueError("toml config file contains a [[tool.mypy.overrides]] section with " "a module value that is not a string or a list of strings") for module in modules: @@ -315,7 +315,7 @@ def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping for new_key, new_value in module_overrides.items(): if (new_key in result[old_config_name] and result[old_config_name][new_key] != new_value): - raise ValueError("toml config file contains [[tool.mypy.overrides]] " + raise ConfigTOMLValueError("toml config file contains [[tool.mypy.overrides]] " "sections with conflicting values. Module '%s' " "has two different values for '%s'" % (module, new_key)) @@ -541,3 +541,7 @@ def get_config_module_names(filename: str, modules: List[str]) -> str: return ", ".join("[mypy-%s]" % module for module in modules) return "module = ['%s']" % ("', '".join(sorted(modules))) + + +class ConfigTOMLValueError(ValueError): + pass From 0a444236e914a86997784d08f33521e7f3d5e94d Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Sat, 24 Apr 2021 12:47:10 -0400 Subject: [PATCH 34/39] Add tests to ensure new errors are caught --- mypy/config_parser.py | 21 +++++---- test-data/unit/cmdline.pyproject.test | 66 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 5727b30a5f7e..8594f64cc874 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -288,22 +288,23 @@ def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping return toml_data if not isinstance(toml_data['mypy']['overrides'], list): - raise ConfigTOMLValueError("tool.mypy.overrides sections must be an array. Please make sure you are " - "using double brackets like so: [[tool.mypy.overrides]]") + raise ConfigTOMLValueError("tool.mypy.overrides sections must be an array. Please make " + "sure you are using double brackets like so: [[tool.mypy.overrides]]") - result = copy.copy(toml_data) + result = toml_data.copy() for override in result['mypy']['overrides']: if 'module' not in override: - raise ConfigTOMLValueError("toml config file contains a [[tool.mypy.overrides]] section, but no " - "module to override was specified.") + raise ConfigTOMLValueError("toml config file contains a [[tool.mypy.overrides]] " + "section, but no module to override was specified.") if isinstance(override['module'], str): modules = [override['module']] elif isinstance(override['module'], list): modules = override['module'] else: - raise ConfigTOMLValueError("toml config file contains a [[tool.mypy.overrides]] section with " - "a module value that is not a string or a list of strings") + raise ConfigTOMLValueError("toml config file contains a [[tool.mypy.overrides]] " + "section with a module value that is not a string or a list of " + "strings") for module in modules: module_overrides = override.copy() @@ -315,9 +316,9 @@ def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping for new_key, new_value in module_overrides.items(): if (new_key in result[old_config_name] and result[old_config_name][new_key] != new_value): - raise ConfigTOMLValueError("toml config file contains [[tool.mypy.overrides]] " - "sections with conflicting values. Module '%s' " - "has two different values for '%s'" + raise ConfigTOMLValueError("toml config file contains " + "[[tool.mypy.overrides]] sections with conflicting " + "values. Module '%s' has two different values for '%s'" % (module, new_key)) result[old_config_name][new_key] = new_value diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index fabbffe68961..3130c8128201 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -791,3 +791,69 @@ x = 0 # type: str y = 0 # type: str [out] pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") + +[case testNonArrayOverridesPyprojectTOML] +# cmd: mypy x.py +[file pyproject.toml] +\[tool.mypy] +\[tool.mypy.overrides] +module = "x" +disallow_untyped_defs = false +[file x.py] +def f(a): + pass +def g(a: int) -> int: + return f(a) +[out] +pyproject.toml: tool.mypy.overrides sections must be an array. Please make sure you are using double brackets like so: [[tool.mypy.overrides]] +== Return code: 0 + +[case testNoModuleInOverridePyprojectTOML] +# cmd: mypy x.py +[file pyproject.toml] +\[tool.mypy] +\[[tool.mypy.overrides]] +disallow_untyped_defs = false +[file x.py] +def f(a): + pass +def g(a: int) -> int: + return f(a) +[out] +pyproject.toml: toml config file contains a [[tool.mypy.overrides]] section, but no module to override was specified. +== Return code: 0 + +[case testInvalidModuleInOverridePyprojectTOML] +# cmd: mypy x.py +[file pyproject.toml] +\[tool.mypy] +\[[tool.mypy.overrides]] +module = 0 +disallow_untyped_defs = false +[file x.py] +def f(a): + pass +def g(a: int) -> int: + return f(a) +[out] +pyproject.toml: toml config file contains a [[tool.mypy.overrides]] section with a module value that is not a string or a list of strings +== Return code: 0 + +[case testConflictingModuleInOverridesPyprojectTOML] +# cmd: mypy x.py +[file pyproject.toml] +\[tool.mypy] +\[[tool.mypy.overrides]] +module = 'x' +disallow_untyped_defs = false +\[[tool.mypy.overrides]] +module = ['x'] +disallow_untyped_defs = true +[file x.py] +def f(a): + pass +def g(a: int) -> int: + return f(a) +[out] +pyproject.toml: toml config file contains [[tool.mypy.overrides]] sections with conflicting values. Module 'x' has two different values for 'disallow_untyped_defs' +== Return code: 0 From 8f0e6d226bb3d48d2b81f067d9fb5ebc55ebdfb9 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Sat, 24 Apr 2021 12:47:32 -0400 Subject: [PATCH 35/39] Ensure toml load stays an OrderedDict on copy --- mypy/config_parser.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 8594f64cc874..219905096e8f 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -1,7 +1,6 @@ import argparse from collections import OrderedDict import configparser -import copy import glob as fileglob from io import StringIO import os @@ -9,7 +8,7 @@ import sys import toml -from typing import (Any, Callable, Dict, List, Mapping, MutableMapping, Optional, Sequence, +from typing import (Any, Callable, Dict, List, Mapping, Optional, Sequence, TextIO, Tuple, Union) from typing_extensions import Final @@ -170,7 +169,7 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], continue try: if is_toml(config_file): - toml_data = toml.load(config_file, _dict=OrderedDict) + toml_data = toml.load(config_file, _dict=OrderedDict) # type: OrderedDict # Filter down to just mypy relevant toml keys toml_data = toml_data.get('tool', {}) if 'mypy' not in toml_data: @@ -252,7 +251,7 @@ def is_toml(filemame: str) -> bool: return filemame.lower().endswith('.toml') -def destructure_overrides(toml_data: MutableMapping[str, Any]) -> MutableMapping[str, Any]: +def destructure_overrides(toml_data: "OrderedDict[str, Any]") -> "OrderedDict[str, Any]": """Take the new [[tool.mypy.overrides]] section array in the pyproject.toml file, and convert it back to a flatter structure that the existing config_parser can handle. From 341633799a4338c87ca399e93c9df7109e776f10 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Sat, 24 Apr 2021 13:11:18 -0400 Subject: [PATCH 36/39] Strip pyproject testing down now that we know it works --- mypy/test/testcheck.py | 7 +- mypy/test/testcmdline.py | 1 - mypy/test/testdaemon.py | 1 - mypy/test/testdiff.py | 1 - mypy/test/testfinegrained.py | 1 - mypy/test/testpep561.py | 1 - .../unit/check-custom-pyproject-plugin.test | 737 ----------------- test-data/unit/check-flags.pyproject.test | 289 ------- .../unit/check-incremental.pyproject.test | 307 ------- .../unit/check-modules-case.pyproject.test | 56 -- test-data/unit/check-modules.pyproject.test | 93 --- test-data/unit/check-optional.pyproject.test | 27 - test-data/unit/cmdline.pyproject.test | 777 ------------------ test-data/unit/daemon.pyproject.test | 64 -- test-data/unit/diff.pyproject.test | 41 - test-data/unit/fine-grained.pyproject.test | 252 ------ test-data/unit/pep561.pyproject.test | 27 - test-data/unit/reports.pyproject.test | 12 - 18 files changed, 1 insertion(+), 2693 deletions(-) delete mode 100644 test-data/unit/check-custom-pyproject-plugin.test delete mode 100644 test-data/unit/check-flags.pyproject.test delete mode 100644 test-data/unit/check-incremental.pyproject.test delete mode 100644 test-data/unit/check-modules-case.pyproject.test delete mode 100644 test-data/unit/check-modules.pyproject.test delete mode 100644 test-data/unit/check-optional.pyproject.test delete mode 100644 test-data/unit/daemon.pyproject.test delete mode 100644 test-data/unit/diff.pyproject.test delete mode 100644 test-data/unit/fine-grained.pyproject.test delete mode 100644 test-data/unit/pep561.pyproject.test delete mode 100644 test-data/unit/reports.pyproject.test diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 87b8c792c7c2..8a1fb669276e 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -41,7 +41,6 @@ 'check-multiple-inheritance.test', 'check-super.test', 'check-modules.test', - 'check-modules.pyproject.test', 'check-typevar-values.test', 'check-unsupported.test', 'check-unreachable-code.test', @@ -56,13 +55,10 @@ 'check-type-promotion.test', 'check-semanal-error.test', 'check-flags.test', - 'check-flags.pyproject.test', 'check-incremental.test', - 'check-incremental.pyproject.test', 'check-serialize.test', 'check-bound.test', 'check-optional.test', - 'check-optional.pyproject.test', 'check-fastparse.test', 'check-warnings.test', 'check-async-await.test', @@ -83,7 +79,6 @@ 'check-enum.test', 'check-incomplete-fixture.test', 'check-custom-plugin.test', - 'check-custom-pyproject-plugin.test', 'check-default-plugin.test', 'check-attr.test', 'check-ctypes.test', @@ -110,7 +105,7 @@ # Special tests for platforms with case-insensitive filesystems. if sys.platform in ('darwin', 'win32'): - typecheck_files.extend(['check-modules-case.test', 'check-modules-case.pyproject.test']) + typecheck_files.extend(['check-modules-case.test']) class TypeCheckSuite(DataSuite): diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index ec2972fe4001..9fafb1f36cae 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -26,7 +26,6 @@ 'cmdline.test', 'cmdline.pyproject.test', 'reports.test', - 'reports.pyproject.test', 'envvars.test', ] diff --git a/mypy/test/testdaemon.py b/mypy/test/testdaemon.py index fe1e27e6098b..641bd8a70372 100644 --- a/mypy/test/testdaemon.py +++ b/mypy/test/testdaemon.py @@ -23,7 +23,6 @@ # Files containing test cases descriptions. daemon_files = [ 'daemon.test', - 'daemon.pyproject.test', ] diff --git a/mypy/test/testdiff.py b/mypy/test/testdiff.py index 0b370414d06f..9e1e9097152b 100644 --- a/mypy/test/testdiff.py +++ b/mypy/test/testdiff.py @@ -18,7 +18,6 @@ class ASTDiffSuite(DataSuite): files = [ 'diff.test', - 'diff.pyproject.test', ] def run_case(self, testcase: DataDrivenTestCase) -> None: diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index 264ab04726c0..f95748db0a24 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -45,7 +45,6 @@ class FineGrainedSuite(DataSuite): files = [ 'fine-grained.test', - 'fine-grained.pyproject.test', 'fine-grained-cycles.test', 'fine-grained-blockers.test', 'fine-grained-modules.test', diff --git a/mypy/test/testpep561.py b/mypy/test/testpep561.py index 7744c271c8bc..ba5be621e066 100644 --- a/mypy/test/testpep561.py +++ b/mypy/test/testpep561.py @@ -37,7 +37,6 @@ class PEP561Suite(DataSuite): files = [ 'pep561.test', - 'pep561.pyproject.test' ] def run_case(self, test_case: DataDrivenTestCase) -> None: diff --git a/test-data/unit/check-custom-pyproject-plugin.test b/test-data/unit/check-custom-pyproject-plugin.test deleted file mode 100644 index c98239cc3ae8..000000000000 --- a/test-data/unit/check-custom-pyproject-plugin.test +++ /dev/null @@ -1,737 +0,0 @@ --- Test cases for user-defined plugins --- --- Note: Plugins used by tests live under test-data/unit/plugins. Defining --- plugin files in test cases does not work reliably. - -[case testFunctionPluginFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f() -> str: ... -reveal_type(f()) # N: Revealed type is "builtins.int" -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/fnplugin.py' - -[case testFunctionPluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f() -> str: ... -reveal_type(f()) # N: Revealed type is "builtins.int" -[file pyproject.toml] -\[tool.mypy] -plugins = "fnplugin" - -[case testFunctionPluginFullnameIsNotNonePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Callable, TypeVar -f: Callable[[], None] -T = TypeVar('T') -def g(x: T) -> T: return x # This strips out the name of a callable -g(f)() -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/fnplugin.py' - -[case testTwoPluginsPyprojetTOMLPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f(): ... -def g(): ... -def h(): ... -reveal_type(f()) # N: Revealed type is "builtins.int" -reveal_type(g()) # N: Revealed type is "builtins.str" -reveal_type(h()) # N: Revealed type is "Any" -[file pyproject.toml] -\[tool.mypy] -plugins=[ - '/test-data/unit/plugins/fnplugin.py', - '/test-data/unit/plugins/plugin2.py' -] - -[case testTwoPluginsMixedTypePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f(): ... -def g(): ... -def h(): ... -reveal_type(f()) # N: Revealed type is "builtins.int" -reveal_type(g()) # N: Revealed type is "builtins.str" -reveal_type(h()) # N: Revealed type is "Any" -[file pyproject.toml] -\[tool.mypy] -plugins = [ - '/test-data/unit/plugins/fnplugin.py', - "plugin2" -] - -[case testMissingPluginFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -[file pyproject.toml] -\[tool.mypy] -plugins = "missing.py" -[out] -tmp/pyproject.toml:1: error: Can't find plugin 'tmp/missing.py' ---' (work around syntax highlighting) - -[case testMissingPluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -[file pyproject.toml] -\[tool.mypy] -plugins = "missing" -[out] -tmp/pyproject.toml:1: error: Error importing plugin 'missing': No module named 'missing' - -[case testMultipleSectionsDefinePluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -[file pyproject.toml] -\[acme] -plugins = "acmeplugin" -\[tool.mypy] -plugins = "missing.py" -\[another] -plugins = "another_plugin" -[out] -tmp/pyproject.toml:1: error: Can't find plugin 'tmp/missing.py' ---' (work around syntax highlighting) - -[case testInvalidPluginExtensionPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -[file pyproject.toml] -\[tool.mypy] -plugins = "dir/badext.pyi" -[file dir/badext.pyi] -[out] -tmp/pyproject.toml:1: error: Plugin 'badext.pyi' does not have a .py extension - -[case testMissingPluginEntryPointPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -[file pyproject.toml] -\[tool.mypy] - plugins = '/test-data/unit/plugins/noentry.py' -[out] -tmp/pyproject.toml:1: error: Plugin '/test-data/unit/plugins/noentry.py' does not define entry point function "plugin" - -[case testCustomPluginEntryPointFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f() -> str: ... -reveal_type(f()) # N: Revealed type is "builtins.int" -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/customentry.py:register' - -[case testCustomPluginEntryPointPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f() -> str: ... -reveal_type(f()) # N: Revealed type is "builtins.int" -[file pyproject.toml] -\[tool.mypy] -plugins = "customentry:register" - -[case testInvalidPluginEntryPointReturnValuePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f(): pass -f() -[file pyproject.toml] -\[tool.mypy] - -plugins = '/test-data/unit/plugins/badreturn.py' -[out] -tmp/pyproject.toml:1: error: Type object expected as the return value of "plugin"; got None (in /test-data/unit/plugins/badreturn.py) - -[case testInvalidPluginEntryPointReturnValue2PyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def f(): pass -f() -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/badreturn2.py' -[out] -tmp/pyproject.toml:1: error: Return value of "plugin" must be a subclass of "mypy.plugin.Plugin" (in /test-data/unit/plugins/badreturn2.py) - -[case testAttributeTypeHookPluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Callable -from m import Signal, DerivedSignal -s: Signal[Callable[[int], None]] = Signal() -s(1) -s('') # E: Argument 1 has incompatible type "str"; expected "int" - -ds: DerivedSignal[Callable[[int], None]] = DerivedSignal() -ds('') # E: Argument 1 has incompatible type "str"; expected "int" -[file m.py] -from typing import TypeVar, Generic, Callable -T = TypeVar('T', bound=Callable[..., None]) -class Signal(Generic[T]): - __call__: Callable[..., None] # This type is replaced by the plugin - -class DerivedSignal(Signal[T]): ... -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/attrhook.py' - -[case testAttributeHookPluginForDynamicClassPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from m import Magic, DerivedMagic - -magic = Magic() -reveal_type(magic.magic_field) # N: Revealed type is "builtins.str" -reveal_type(magic.non_magic_method()) # N: Revealed type is "builtins.int" -reveal_type(magic.non_magic_field) # N: Revealed type is "builtins.int" -magic.nonexistent_field # E: Field does not exist -reveal_type(magic.fallback_example) # N: Revealed type is "Any" -reveal_type(DerivedMagic().magic_field) # N: Revealed type is "builtins.str" -[file m.py] -from typing import Any -class Magic: - # Triggers plugin infrastructure: - def __getattr__(self, x: Any) -> Any: ... - def non_magic_method(self) -> int: ... - non_magic_field: int - -class DerivedMagic(Magic): ... - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/attrhook2.py' - -[case testTypeAnalyzeHookPluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Callable -from mypy_extensions import DefaultArg -from m import Signal -s: Signal[[int, DefaultArg(str, 'x')]] = Signal() -reveal_type(s) # N: Revealed type is "m.Signal[def (builtins.int, x: builtins.str =)]" -s.x # E: "Signal[Callable[[int, str], None]]" has no attribute "x" -ss: Signal[int, str] # E: Invalid "Signal" type (expected "Signal[[t, ...]]") -[file m.py] -from typing import TypeVar, Generic, Callable -T = TypeVar('T', bound=Callable[..., None]) -class Signal(Generic[T]): - __call__: Callable[..., None] -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/type_anal_hook.py' -[builtins fixtures/dict.pyi] - -[case testFunctionPluginHookForClassPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import mod -from mod import AttrInt - -Alias = AttrInt -AnotherAlias = mod.Attr - -class C: - x = Alias() - y = mod.AttrInt(required=True) - z = AnotherAlias(int, required=False) - -c = C() -reveal_type(c.x) # N: Revealed type is "Union[builtins.int, None]" -reveal_type(c.y) # N: Revealed type is "builtins.int*" -reveal_type(c.z) # N: Revealed type is "Union[builtins.int*, None]" - -[file mod.py] -from typing import Generic, TypeVar, Type -T = TypeVar('T') - -class Attr(Generic[T]): - def __init__(self, tp: Type[T], required: bool = False) -> None: - pass - def __get__(self, instance: object, owner: type) -> T: - pass - -class AttrInt(Attr[int]): - def __init__(self, required: bool = False) -> None: - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/class_callable.py' -[builtins fixtures/bool.pyi] -[out] - -[case testFunctionPluginHookForReturnedCallablePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from m import decorator1, decorator2 -@decorator1() -def f() -> None: pass -@decorator2() -def g() -> None: pass -reveal_type(f) # N: Revealed type is "def (*Any, **Any) -> builtins.str" -reveal_type(g) # N: Revealed type is "def (*Any, **Any) -> builtins.int" -[file m.py] -from typing import Callable -def decorator1() -> Callable[..., Callable[..., int]]: pass -def decorator2() -> Callable[..., Callable[..., int]]: pass -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/named_callable.py' - -[case testFunctionMethodContextsHasArgNamesPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import Class, func - -reveal_type(Class().method(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is "builtins.str" -reveal_type(Class.myclassmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is "builtins.str" -reveal_type(Class.mystaticmethod(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is "builtins.str" -reveal_type(Class.method(self=Class(), arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is "builtins.str" -reveal_type(func(arg1=1, arg2=2, classname='builtins.str')) # N: Revealed type is "builtins.str" - -[file mod.py] -from typing import Any -class Class: - def method(self, classname: str, arg1: Any, arg2: Any) -> Any: - pass - @classmethod - def myclassmethod(cls, classname: str, arg1: Any, arg2: Any): - pass - @staticmethod - def mystaticmethod(classname: str, arg1: Any, arg2: Any): - pass -def func(classname: str, arg1: Any, arg2: Any) -> Any: - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/arg_names.py' -[builtins fixtures/classmethod.pyi] - -[case testFunctionMethodContextsHasArgNamesPositionalsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import Class, func - -reveal_type(Class().method('builtins.str', arg1=1, arg2=2)) # N: Revealed type is "builtins.str" -reveal_type(Class.myclassmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is "builtins.str" -reveal_type(Class.mystaticmethod('builtins.str', arg1=1, arg2=2)) # N: Revealed type is "builtins.str" -reveal_type(Class.method(Class(), 'builtins.str', arg1=1, arg2=2)) # N: Revealed type is "builtins.str" -reveal_type(func('builtins.str', arg1=1, arg2=2)) # N: Revealed type is "builtins.str" - -[file mod.py] -from typing import Any -class Class: - def method(self, classname: str, arg1: Any, arg2: Any) -> Any: - pass - @classmethod - def myclassmethod(cls, classname: str, arg1: Any, arg2: Any): - pass - @staticmethod - def mystaticmethod(classname: str, arg1: Any, arg2: Any): - pass -def func(classname: str, arg1: Any, arg2: Any) -> Any: - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/arg_names.py' -[builtins fixtures/classmethod.pyi] - -[case testFunctionMethodContextsHasArgNamesInitMethodPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import ClassInit, Outer - -reveal_type(ClassInit('builtins.str')) # N: Revealed type is "builtins.str" -reveal_type(ClassInit(classname='builtins.str')) # N: Revealed type is "builtins.str" -reveal_type(Outer.NestedClassInit(classname='builtins.str')) # N: Revealed type is "builtins.str" -[file mod.py] -from typing import Any -class ClassInit: - def __init__(self, classname: str): - pass -class Outer: - class NestedClassInit: - def __init__(self, classname: str): - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/arg_names.py' - -[case testFunctionMethodContextsHasArgNamesUnfilledArgumentsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import ClassUnfilled, func_unfilled - -reveal_type(ClassUnfilled().method(classname='builtins.str', arg1=1)) # N: Revealed type is "builtins.str" -reveal_type(ClassUnfilled().method(arg2=1, classname='builtins.str')) # N: Revealed type is "builtins.str" -reveal_type(ClassUnfilled().method('builtins.str')) # N: Revealed type is "builtins.str" -reveal_type(func_unfilled(classname='builtins.str', arg1=1)) # N: Revealed type is "builtins.str" -reveal_type(func_unfilled(arg2=1, classname='builtins.str')) # N: Revealed type is "builtins.str" -reveal_type(func_unfilled('builtins.str')) # N: Revealed type is "builtins.str" - -[file mod.py] -from typing import Any -class ClassUnfilled: - def method(self, classname: str, arg1: Any = None, arg2: Any = None) -> Any: - pass -def func_unfilled(classname: str, arg1: Any = None, arg2: Any = None) -> Any: - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/arg_names.py' - -[case testFunctionMethodContextsHasArgNamesStarExpressionsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import ClassStarExpr, func_star_expr - -reveal_type(ClassStarExpr().method(classname='builtins.str', arg1=1)) # N: Revealed type is "builtins.str" -reveal_type(ClassStarExpr().method('builtins.str', arg1=1)) # N: Revealed type is "builtins.str" -reveal_type(ClassStarExpr().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is "builtins.str" -reveal_type(ClassStarExpr().method('builtins.str', 2, 3, 4, arg1=1, arg2=1)) # N: Revealed type is "builtins.str" -reveal_type(func_star_expr(classname='builtins.str', arg1=1)) # N: Revealed type is "builtins.str" -reveal_type(func_star_expr('builtins.str', arg1=1)) # N: Revealed type is "builtins.str" -reveal_type(func_star_expr('builtins.str', 2, 3, 4, arg1=1, arg2=2)) # N: Revealed type is "builtins.str" - -[file mod.py] -from typing import Any -class ClassStarExpr: - def method(self, classname: str, *args, **kwargs) -> Any: - pass -def func_star_expr(classname: str, *args, **kwargs) -> Any: - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/arg_names.py' -[builtins fixtures/dict.pyi] - -[case testFunctionMethodContextArgNamesForInheritedMethodsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import ClassChild - -reveal_type(ClassChild().method(classname='builtins.str', arg1=1, arg2=1)) # N: Revealed type is "builtins.str" -reveal_type(ClassChild().method(arg1=1, classname='builtins.str', arg2=1)) # N: Revealed type is "builtins.str" -reveal_type(ClassChild().method('builtins.str', arg1=1, arg2=1)) # N: Revealed type is "builtins.str" -reveal_type(ClassChild.myclassmethod('builtins.str')) # N: Revealed type is "builtins.str" -[file mod.py] -from typing import Any -class Base: - def method(self, classname: str, arg1: Any, arg2: Any) -> Any: - pass - @classmethod - def myclassmethod(cls, classname: str) -> Any: - pass -class ClassChild(Base): - pass - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/arg_names.py' -[builtins fixtures/classmethod.pyi] - -[case testMethodSignatureHookPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Iterator - -class Foo: - # Test that method signature hooks are applied in various cases: explicit method calls, and - # implicit dunder method calls through language syntax. - # The plugin's method signature hook should turn all str occurrences into int. - def __init__(self) -> None: ... - def __getitem__(self, index: str) -> str: ... - def __setitem__(self, index: str, value: str) -> None: ... - def __iter__(self) -> Iterator[str]: ... - def __next__(self) -> str: ... - def __call__(self, *args: str) -> str: ... - def m(self, arg: str) -> str: ... - -foo = Foo() -reveal_type(foo.m(2)) # N: Revealed type is "builtins.int" -reveal_type(foo[3]) # N: Revealed type is "builtins.int" -reveal_type(foo(4, 5, 6)) # N: Revealed type is "builtins.int" -foo[4] = 5 -for x in foo: - reveal_type(x) # N: Revealed type is "builtins.int*" - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/method_sig_hook.py' -[builtins fixtures/tuple.pyi] - -[case testMethodSignatureHookNamesFullyQualifiedPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mypy_extensions import TypedDict -from typing import NamedTuple - -class FullyQualifiedTestClass: - @classmethod - def class_method(self) -> str: ... - def instance_method(self) -> str: ... - -class FullyQualifiedTestTypedDict(TypedDict): - foo: str - -FullyQualifiedTestNamedTuple = NamedTuple('FullyQualifiedTestNamedTuple', [('foo', str)]) - -# Check the return types to ensure that the method signature hook is called in each case -reveal_type(FullyQualifiedTestClass.class_method()) # N: Revealed type is "builtins.int" -reveal_type(FullyQualifiedTestClass().instance_method()) # N: Revealed type is "builtins.int" -reveal_type(FullyQualifiedTestNamedTuple('')._asdict()) # N: Revealed type is "builtins.int" - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/fully_qualified_test_hook.py' -[builtins fixtures/classmethod.pyi] - -[case testDynamicClassPluginPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import declarative_base, Column, Instr - -Base = declarative_base() - -class Model(Base): - x: Column[int] -class Other: - x: Column[int] - -reveal_type(Model().x) # N: Revealed type is "mod.Instr[builtins.int]" -reveal_type(Other().x) # N: Revealed type is "mod.Column[builtins.int]" -[file mod.py] -from typing import Generic, TypeVar -def declarative_base(): ... - -T = TypeVar('T') - -class Column(Generic[T]): ... -class Instr(Generic[T]): ... - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/dyn_class.py' - -[case testDynamicClassPluginNegativesPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import declarative_base, Column, Instr, non_declarative_base - -Bad1 = non_declarative_base() -Bad2 = Bad3 = declarative_base() - -class C1(Bad1): ... # E: Variable "__main__.Bad1" is not valid as a type \ - # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ - # E: Invalid base class "Bad1" -class C2(Bad2): ... # E: Variable "__main__.Bad2" is not valid as a type \ - # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ - # E: Invalid base class "Bad2" -class C3(Bad3): ... # E: Variable "__main__.Bad3" is not valid as a type \ - # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases \ - # E: Invalid base class "Bad3" -[file mod.py] -from typing import Generic, TypeVar -def declarative_base(): ... -def non_declarative_base(): ... - -T = TypeVar('T') - -class Column(Generic[T]): ... -class Instr(Generic[T]): ... - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/dyn_class.py' - -[case testDynamicClassHookFromClassMethodPyprojectTOML] -# flags: --config-file tmp/pyproject.toml - -from mod import QuerySet, Manager - -MyManager = Manager.from_queryset(QuerySet) - -reveal_type(MyManager()) # N: Revealed type is "__main__.MyManager" -reveal_type(MyManager().attr) # N: Revealed type is "builtins.str" - -def func(manager: MyManager) -> None: - reveal_type(manager) # N: Revealed type is "__main__.MyManager" - reveal_type(manager.attr) # N: Revealed type is "builtins.str" - -func(MyManager()) - -[file mod.py] -from typing import Generic, TypeVar, Type -class QuerySet: - attr: str -class Manager: - @classmethod - def from_queryset(cls, queryset_cls: Type[QuerySet]): ... - -[builtins fixtures/classmethod.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/dyn_class_from_method.py' - -[case testBaseClassPluginHookWorksIncrementalPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a - -[file a.py] -from base import Base -class C(Base): ... - -[file a.py.2] -from base import Base -class C(Base): ... -reveal_type(C().__magic__) -Base.__magic__ - -[file base.py] -from lib import declarative_base -Base = declarative_base() - -[file lib.py] -from typing import Any -def declarative_base() -> Any: ... - -[file pyproject.toml] -\[tool.mypy] -python_version = 3.6 -plugins = '/test-data/unit/plugins/common_api_incremental.py' -[out] -[out2] -tmp/a.py:3: note: Revealed type is "builtins.str" -tmp/a.py:4: error: "Type[Base]" has no attribute "__magic__" - -[case testArgKindsMethodPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -class Class: - def method(self, *args, **kwargs): - pass - -Class().method(1, *[2], **{'a': 1}) # E: [[0, 2], [4]] -[builtins fixtures/dict.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/arg_kinds.py' - -[case testArgKindsFunctionPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -def func(*args, **kwargs): - pass - -func(1, 2, [3, 4], *[5, 6, 7], **{'a': 1}) # E: [[0, 0, 0, 2], [4]] -[builtins fixtures/dict.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/arg_kinds.py' - -[case testHookCallableInstancePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Generic, TypeVar -T = TypeVar("T") -class Class(Generic[T]): - def __init__(self, one: T): ... - def __call__(self, two: T) -> int: ... -reveal_type(Class("hi")("there")) # N: Revealed type is "builtins.str*" -instance = Class(3.14) -reveal_type(instance(2)) # N: Revealed type is "builtins.float*" - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/callable_instance.py' - -[case testGetMethodHooksOnUnionsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml --no-strict-optional -from typing import Union - -class Foo: - def meth(self, x: str) -> str: ... -class Bar: - def meth(self, x: int) -> float: ... -class Other: - meth: int - -x: Union[Foo, Bar, Other] -if isinstance(x.meth, int): - reveal_type(x.meth) # N: Revealed type is "builtins.int" -else: - reveal_type(x.meth(int())) # N: Revealed type is "builtins.int" - -[builtins fixtures/isinstancelist.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/union_method.py' - -[case testGetMethodHooksOnUnionsStrictOptionalPyprojectTOML] -# flags: --config-file tmp/pyproject.toml --strict-optional -from typing import Union - -class Foo: - def meth(self, x: str) -> str: ... -class Bar: - def meth(self, x: int) -> float: ... -class Other: - meth: int - -x: Union[Foo, Bar, Other] -if isinstance(x.meth, int): - reveal_type(x.meth) # N: Revealed type is "builtins.int" -else: - reveal_type(x.meth(int())) # N: Revealed type is "builtins.int" - -[builtins fixtures/isinstancelist.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/union_method.py' - -[case testGetMethodHooksOnUnionsSpecialPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import Union - -class Foo: - def __getitem__(self, x: str) -> str: ... -class Bar: - def __getitem__(self, x: int) -> float: ... - -x: Union[Foo, Bar] -reveal_type(x[int()]) # N: Revealed type is "builtins.int" - -[builtins fixtures/isinstancelist.pyi] -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/union_method.py' - -[case testPluginDependenciesPyprojectTOML] -# flags: --config-file tmp/pyproject.toml - -# The top level file here doesn't do anything, but the plugin should add -# a dependency on err that will cause err to be processed and an error reported. - -[file err.py] -1 + 'lol' # E: Unsupported operand types for + ("int" and "str") - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/depshook.py' - -[case testCustomizeMroTrivialPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -class A: pass -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/customize_mro.py' - -[case testDescriptorMethodsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml - -class Desc: - def __get__(self, obj, cls): - pass - - def __set__(self, obj, val): - pass - -class Cls: - attr = Desc() - -reveal_type(Cls().attr) # N: Revealed type is "builtins.int" -reveal_type(Cls.attr) # N: Revealed type is "builtins.str" - -Cls().attr = 3 -Cls().attr = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "int") - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/descriptor.py' - -[case testFunctionSigPluginFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml - -def dynamic_signature(arg1: str) -> str: ... -reveal_type(dynamic_signature(1)) # N: Revealed type is "builtins.int" -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/function_sig_hook.py' diff --git a/test-data/unit/check-flags.pyproject.test b/test-data/unit/check-flags.pyproject.test deleted file mode 100644 index c34eeea1bda5..000000000000 --- a/test-data/unit/check-flags.pyproject.test +++ /dev/null @@ -1,289 +0,0 @@ -[case testPerFileIncompleteDefsBasicPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, incomplete - -[file standard.py] -def incomplete(x) -> int: - return 0 -[file incomplete.py] -def incomplete(x) -> int: # E: Function is missing a type annotation for one or more arguments - return 0 -[file pyproject.toml] -\[tool.mypy] -disallow_incomplete_defs = false -\[[tool.mypy.overrides]] -module = "incomplete" -disallow_incomplete_defs = true - -[case testPerFileStrictOptionalBasicPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, optional - -[file standard.py] -x = 0 -if int(): - x = None -[file optional.py] -x = 0 -if int(): - x = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") - -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[[tool.mypy.overrides]] -module = "optional" -strict_optional = true - -[case testPerFileStrictOptionalBasicImportStandardPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, optional - -[file standard.py] -from typing import Optional -def f(x: int) -> None: pass -an_int = 0 # type: int -optional_int = None # type: Optional[int] -f(an_int) # ints can be used as ints -f(optional_int) # optional ints can be used as ints in this file - -[file optional.py] -import standard -def f(x: int) -> None: pass -standard.an_int = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") -standard.optional_int = None # OK -- explicitly declared as optional -f(standard.an_int) # ints can be used as ints -f(standard.optional_int) # E: Argument 1 to "f" has incompatible type "None"; expected "int" - -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[[tool.mypy.overrides]] -module = "optional" -strict_optional = true - -[case testPerFileStrictOptionalBasicImportOptionalPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, optional - -[file standard.py] -import optional -def f(x: int) -> None: pass -f(optional.x) # OK -- in non-strict Optional context -f(optional.y) # OK -- in non-strict Optional context - -[file optional.py] -from typing import Optional -def f(x: int) -> None: pass -x = 0 # type: Optional[int] -y = None # type: None - -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[[tool.mypy.overrides]] -module = "optional" -strict_optional = true - -[case testPerFileStrictOptionalListItemImportOptionalPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, optional - -[file standard.py] -import optional -from typing import List -def f(x: List[int]) -> None: pass -f(optional.x) # OK -- in non-strict Optional context -f(optional.y) # OK -- in non-strict Optional context - -[file optional.py] -from typing import Optional, List -def f(x: List[int]) -> None: pass -x = [] # type: List[Optional[int]] -y = [] # type: List[int] - -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[[tool.mypy.overrides]] -module = "optional" -strict_optional = true -[builtins fixtures/list.pyi] - -[case testPerFileStrictOptionalNoneArgumentsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import standard, optional - -[file standard.py] -def f(x: int = None) -> None: pass - -[file optional.py] -import standard -def f(x: int = None) -> None: pass -standard.f(None) - -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[[tool.mypy.overrides]] -module = "optional" -strict_optional = true - -[case testAlwaysTrueAlwaysFalseConfigFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from somewhere import YOLO, BLAH -if not YOLO: - 1+() -if BLAH: - 1+() -[file pyproject.toml] -\[tool.mypy] -ignore_missing_imports = true -always_true = [ - "YOLO1", - "YOLO" -] -always_false = [ - "BLAH", - "BLAH1" -] -[builtins fixtures/bool.pyi] - -[case testDisableErrorCodeConfigFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml --disallow-untyped-defs -import foo -def bar(): - pass -[file pyproject.toml] -\[tool.mypy] -disable_error_code = [ - "import", - "no-untyped-def" -] - -[case testStrictInConfigAnyGenericPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import TypeVar, Generic - -T = TypeVar('T') - -class A(Generic[T]): - pass - -def f(c: A) -> None: # E: Missing type parameters for generic type "A" - pass -[file pyproject.toml] -\[tool.mypy] -strict = true -[out] - -[case testStrictFalseInConfigAnyGenericPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from typing import TypeVar, Generic - -T = TypeVar('T') - -class A(Generic[T]): - pass - -def f(c: A) -> None: - pass -[file pyproject.toml] -\[tool.mypy] -strict = false -[out] - -[case testStrictEqualityPerFilePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import b -42 == 'no' # E: Non-overlapping equality check (left operand type: "Literal[42]", right operand type: "Literal['no']") -[file b.py] -42 == 'no' -[file pyproject.toml] -\[tool.mypy] -strict_equality = true -\[[tool.mypy.overrides]] -module = "b" -strict_equality = false -[builtins fixtures/bool.pyi] - -[case testNoImplicitReexportPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from other_module_2 import a - -[file other_module_1.py] -a = 5 - -[file other_module_2.py] -from other_module_1 import a - -[file pyproject.toml] -\[tool.mypy] -implicit_reexport = true -\[[tool.mypy.overrides]] -module = "other_module_2" -implicit_reexport = false -[out] -main:2: error: Module "other_module_2" has no attribute "a" - -[case testDisallowSubclassingAnyPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import m -import y - -[file m.py] -from typing import Any - -x = None # type: Any - -class ShouldBeFine(x): ... - -[file y.py] -from typing import Any - -x = None # type: Any - -class ShouldNotBeFine(x): ... # E: Class cannot subclass 'x' (has type 'Any') - -[file pyproject.toml] -\[tool.mypy] -disallow_subclassing_any = true -\[[tool.mypy.overrides]] -module = "m" -disallow_subclassing_any = false - -[case testNoImplicitOptionalPerModulePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import m - -[file m.py] -def f(a: str = None) -> int: - return 0 - -[file pyproject.toml] -\[tool.mypy] -no_implicit_optional = true -\[[tool.mypy.overrides]] -module = "m" -no_implicit_optional = false - -[case testNoImplicitOptionalPerModulePython2PyprojectTOML] -# flags: --config-file tmp/pyproject.toml --python-version 2.7 -import m - -[file m.py] -def f(a = None): - # type: (str) -> int - return 0 - -[file pyproject.toml] -\[tool.mypy] -no_implicit_optional = true -\[[tool.mypy.overrides]] -module = "m" -no_implicit_optional = false - -[case testDisableErrorCodePyprojectTOML] -# flags: --disable-error-code attr-defined -x = 'should be fine' -x.trim() diff --git a/test-data/unit/check-incremental.pyproject.test b/test-data/unit/check-incremental.pyproject.test deleted file mode 100644 index 9bb23d112767..000000000000 --- a/test-data/unit/check-incremental.pyproject.test +++ /dev/null @@ -1,307 +0,0 @@ --- Checks for incremental mode (see testcheck.py). --- Each test is run at least twice, once with a cold cache, once with a warm cache. --- Before the tests are run again, in step N any *.py.N files are copied to --- *.py. There are at least two runs; more as long as there are *.py.N files. --- --- You can add an empty section like `[delete mod.py.2]` to delete `mod.py` --- before the second run. --- --- Errors expected in the first run should be in the `[out1]` section, and --- errors expected in the second run should be in the `[out2]` section, and so on. --- If a section is omitted, it is expected there are no errors on that run. --- The number of runs is determined by the highest N in all [outN] sections, but --- there are always at least two runs. (Note that [out] is equivalent to [out1].) --- --- The list of modules to be checked can be specified using --- # cmd: mypy -m mod1 mod2 mod3 --- To check a different list on the second run, use --- # cmd2: mypy -m mod1 mod3 --- (and cmd3 for the third run, and so on). --- --- Extra command line flags may be specified using --- # flags: --some-flag --- If the second run requires different flags, those can be specified using --- # flags2: --another-flag --- (and flags3 for the third run, and so on). --- --- Incremental tests involving plugins that get updated are also supported. --- All plugin files that are updated *must* end in '_plugin', so they will --- be unloaded from 'sys.modules' between incremental steps. --- --- Any files that we expect to be rechecked should be annotated in the [rechecked] --- annotation, and any files expect to be stale (aka have a modified interface) --- should be annotated in the [stale] annotation. Note that a file that ends up --- producing an error has its caches deleted and is marked stale automatically. --- Such files do not need to be included in [stale ...] list. --- --- The test suite will automatically assume that __main__ is stale and rechecked in --- all cases so we can avoid constantly having to annotate it. The list of --- rechecked/stale files can be in any arbitrary order, or can be left empty --- if no files should be rechecked/stale. --- --- There are additional incremental mode test cases in check-serialize.test. - -[case testIncrementalFollowImportsVariablePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -reveal_type(a.x) -[file a.py] -x = 0 -[file pyproject.toml] -\[tool.mypy] -follow_imports = "normal" -[file pyproject.toml.2] -\[tool.mypy] -follow_imports = "skip" -[out1] -main:3: note: Revealed type is "builtins.int" -[out2] -main:3: note: Revealed type is "Any" - -[case testIncrementalPerFileFlagsPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -pass -[file pyproject.toml] -\[tool.mypy] -warn_no_return = false -\[[tool.mypy.overrides]] -module = "a" -warn_no_return = true -[rechecked] - -[case testRegularUsesFgCachePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -x = 0 -[file pyproject.toml] -\[tool.mypy] -cache_fine_grained = true -[file pyproject.toml.2] -\[tool.mypy] -cache_fine_grained = false --- Nothing should get rechecked -[rechecked] -[stale] - -[case testFgCacheNeedsFgCachePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -x = 0 -[file pyproject.toml] -\[tool.mypy] -cache_fine_grained = false -[file pyproject.toml.2] -\[tool.mypy] -cache_fine_grained = true -[rechecked a, builtins, typing] -[stale a, builtins, typing] -[builtins fixtures/tuple.pyi] - -[case testFollowImportSkipNotInvalidatedOnAddedStubOnFollowForStubsPyprojectTOML] -# flags: --follow-imports=skip --ignore-missing-imports --config-file=tmp/pyproject.toml -# cmd: mypy -m main -[file main.py] -import other -[file other.pyi.2] -x = 1 -[file pyproject.toml] -\[tool.mypy] -follow_imports_for_stubs = true -[stale] -[rechecked] - -[case testChangedPluginsInvalidateCachePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -from b import x -y: int = x - -[file a.py.2] -from b import x -y: int = x -touch = 1 - -[file b.py] -class C: ... -def f() -> C: ... -x = f() - -[file basic_plugin.py] -from mypy.plugin import Plugin - -class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.endswith('.f'): - return my_hook - assert fullname is not None - return None - -def my_hook(ctx): - return ctx.api.named_generic_type('builtins.int', []) - -def plugin(version): - return MyPlugin - -[file basic_plugin.py.2] -from mypy.plugin import Plugin - -class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.endswith('.f'): - return my_hook - assert fullname is not None - return None - -def my_hook(ctx): - return ctx.api.named_generic_type('builtins.str', []) - -def plugin(version): - return MyPlugin -[file pyproject.toml] -\[tool.mypy] -plugins = "basic_plugin.py" -[out] -[out2] -tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") - -[case testChangedPluginsInvalidateCache2PyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -from b import x -y: int = x - -[file a.py.2] -from b import x -y: int = x -touch = 1 - -[file b.py] -class C: ... -def f() -> C: ... -x = f() - -[file basic_plugin.py] -from mypy.plugin import Plugin -from version_plugin import __version__, choice - -class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.endswith('.f'): - return my_hook - assert fullname is not None - return None - -def my_hook(ctx): - if choice: - return ctx.api.named_generic_type('builtins.int', []) - else: - return ctx.api.named_generic_type('builtins.str', []) - -def plugin(version): - return MyPlugin - -[file version_plugin.py] -__version__ = 0.1 -choice = True - -[file version_plugin.py.2] -__version__ = 0.2 -choice = False -[file pyproject.toml] -\[tool.mypy] -plugins = "basic_plugin.py" -[out] -[out2] -tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") - -[case testAddedPluginsInvalidateCachePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -from b import x -y: int = x - -[file a.py.2] -from b import x -y: int = x -touch = 1 - -[file b.py] -def f() -> int: ... -x = f() - -[file basic_plugin.py] -from mypy.plugin import Plugin - -class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.endswith('.f'): - return my_hook - assert fullname is not None - return None - -def my_hook(ctx): - return ctx.api.named_generic_type('builtins.str', []) - -def plugin(version): - return MyPlugin - -[file pyproject.toml] -\[tool.mypy] -python_version = 3.6 -[file pyproject.toml.2] -\[tool.mypy] -python_version = 3.6 -plugins = "basic_plugin.py" -[out] -[out2] -tmp/a.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") - -[case testRemovedPluginsInvalidateCachePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file a.py] -from b import x -y: str = x - -[file a.py.2] -from b import x -y: str = x -touch = 1 - -[file b.py] -def f() -> int: ... -x = f() - -[file basic_plugin.py] -from mypy.plugin import Plugin - -class MyPlugin(Plugin): - def get_function_hook(self, fullname): - if fullname.endswith('.f'): - return my_hook - assert fullname is not None - return None - -def my_hook(ctx): - return ctx.api.named_generic_type('builtins.str', []) - -def plugin(version): - return MyPlugin - -[file pyproject.toml] -\[tool.mypy] -python_version = 3.6 -plugins = "basic_plugin.py" -[file pyproject.toml.2] -\[tool.mypy] -python_version = 3.6 -[out] -[out2] -tmp/a.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") diff --git a/test-data/unit/check-modules-case.pyproject.test b/test-data/unit/check-modules-case.pyproject.test deleted file mode 100644 index 62c68a8d025a..000000000000 --- a/test-data/unit/check-modules-case.pyproject.test +++ /dev/null @@ -1,56 +0,0 @@ --- Type checker test cases dealing with modules and imports on case-insensitive filesystems. - -[case testCaseInsensitivityDirPyprojectTOML] -# flags: --config-file tmp/pyproject.toml - -from a import B # E: Module "a" has no attribute "B" -from other import x -reveal_type(x) # N: Revealed type is "builtins.int" - -[file a/__init__.py] -[file a/b/__init__.py] -[file FuNkY_CaSe/other.py] -x = 1 - -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/funky_case" - -[case testPreferPackageOverFileCasePyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a -[file funky/a.py] -/ # Deliberate syntax error, this file should not be parsed. -[file FuNkY/a/__init__.py] -pass - -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/funky" - -[case testNamespacePackagePickFirstOnMypyPathCasePyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar import x -reveal_type(x) # N: Revealed type is "builtins.int" -[file XX/foo/bar.py] -x = 0 -[file yy/foo/bar.py] -x = '' -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - -[case testClassicPackageInsideNamespacePackageCasePyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar.baz.boo import x -reveal_type(x) # N: Revealed type is "builtins.int" -[file xx/foo/bar/baz/boo.py] -x = '' -[file xx/foo/bar/baz/__init__.py] -[file yy/foo/bar/baz/boo.py] -x = 0 -[file yy/foo/bar/__init__.py] - -[file pyproject.toml] -\[tool.mypy] -mypy_path = "TmP/xX,TmP/yY" diff --git a/test-data/unit/check-modules.pyproject.test b/test-data/unit/check-modules.pyproject.test deleted file mode 100644 index 7977f5e047e2..000000000000 --- a/test-data/unit/check-modules.pyproject.test +++ /dev/null @@ -1,93 +0,0 @@ --- Type checker test cases dealing with modules and imports. --- Towards the end there are tests for PEP 420 (namespace packages, i.e. __init__.py-less packages). - -[case testModuleGetattrInit10PyprojectTOML] -# flags: --config-file tmp/pyproject.toml -import a.b.c # silenced -import a.b.d # error - -[file a/__init__.pyi] -from typing import Any -def __getattr__(attr: str) -> Any: ... -[file a/b/__init__.pyi] -# empty (i.e. complete subpackage) - -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "a.b.c" -ignore_missing_imports = true -[builtins fixtures/module.pyi] -[out] -main:3: error: Cannot find implementation or library stub for module named "a.b.d" -main:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports - -[case testNamespacePackageWithMypyPathPyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bax import x -from foo.bay import y -from foo.baz import z -reveal_type(x) # N: Revealed type is "builtins.int" -reveal_type(y) # N: Revealed type is "builtins.int" -reveal_type(z) # N: Revealed type is "builtins.int" -[file xx/foo/bax.py] -x = 0 -[file yy/foo/bay.py] -y = 0 -[file foo/baz.py] -z = 0 -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - -[case testClassicPackageIgnoresEarlierNamespacePackagePyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar import y -reveal_type(y) # N: Revealed type is "builtins.int" -[file xx/foo/bar.py] -x = '' -[file yy/foo/bar.py] -y = 0 -[file yy/foo/__init__.py] -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - -[case testNamespacePackagePickFirstOnMypyPathPyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar import x -reveal_type(x) # N: Revealed type is "builtins.int" -[file xx/foo/bar.py] -x = 0 -[file yy/foo/bar.py] -x = '' -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - -[case testNamespacePackageInsideClassicPackagePyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar.baz import x -reveal_type(x) # N: Revealed type is "builtins.int" -[file xx/foo/bar/baz.py] -x = '' -[file yy/foo/bar/baz.py] -x = 0 -[file yy/foo/__init__.py] -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" - -[case testClassicPackageInsideNamespacePackagePyprojectTOML] -# flags: --namespace-packages --config-file tmp/pyproject.toml -from foo.bar.baz.boo import x -reveal_type(x) # N: Revealed type is "builtins.int" -[file xx/foo/bar/baz/boo.py] -x = '' -[file xx/foo/bar/baz/__init__.py] -[file yy/foo/bar/baz/boo.py] -x = 0 -[file yy/foo/bar/__init__.py] -[file pyproject.toml] -\[tool.mypy] -mypy_path = "tmp/xx,tmp/yy" diff --git a/test-data/unit/check-optional.pyproject.test b/test-data/unit/check-optional.pyproject.test deleted file mode 100644 index 0d013c3750ce..000000000000 --- a/test-data/unit/check-optional.pyproject.test +++ /dev/null @@ -1,27 +0,0 @@ --- Tests for strict Optional behavior - -[case testStrictOptionalCovarianceCrossModulePyprojectTOMLPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from a import asdf -x = ["lol"] -asdf(x) - -[file a.py] -from typing import List, Optional - -def asdf(x: List[Optional[str]]) -> None: - pass - -x = ["lol"] -asdf(x) - -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "a" -strict_optional = false -[out] -main:4: error: Argument 1 to "asdf" has incompatible type "List[str]"; expected "List[Optional[str]]" -main:4: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance -main:4: note: Consider using "Sequence" instead, which is covariant -[builtins fixtures/list.pyi] diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index 3130c8128201..83f4745d0786 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -15,783 +15,6 @@ -- Directories/packages on the command line -- ---------------------------------------- --- Tests mirrored off of cmdline.test - -[case testConfigFilePyprojectTOML] -# cmd: mypy --config-file pyproject.toml main.py -[file pyproject.toml] -\[tool.mypy] -python_version = 2.7 -[file main.py] -def f(): - try: - 1/0 - except ZeroDivisionError, err: - print err - -[case testErrorContextConfigPyprojectTOML] -# cmd: mypy main.py -[file pyproject.toml] -\[tool.mypy] -show_error_context = true -[file main.py] -def f() -> None: - 0 + "" -[out] -main.py: note: In function "f": -main.py:2: error: Unsupported operand types for + ("int" and "str") - -[case testNoConfigFilePyprojectTOML] -# cmd: mypy main.py --config-file= -[file pyproject.toml] -\[tool.mypy] -warn_unused_ignores = true -[file main.py] -# type: ignore - -[case testPerFileConfigSectionPyprojectTOML] -# cmd: mypy x.py y.py z.py -[file pyproject.toml] -\[tool.mypy] -disallow_untyped_defs = true -\[[tool.mypy.overrides]] -module = "y" -disallow_untyped_defs = false -\[[tool.mypy.overrides]] -module = "z" -disallow_untyped_calls = true -[file x.py] -def f(a): - pass -def g(a: int) -> int: - return f(a) -[file y.py] -def f(a): - pass -def g(a: int) -> int: - return f(a) -[file z.py] -def f(a): - pass -def g(a: int) -> int: - return f(a) -[out] -z.py:1: error: Function is missing a type annotation -z.py:4: error: Call to untyped function "f" in typed context -x.py:1: error: Function is missing a type annotation - -[case testPerFileConfigSectionMultipleMatchesDisallowedPyprojectTOML] -# cmd: mypy xx.py xy.py yx.py yy.py -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "*x*" -disallow_untyped_defs = true -\[[tool.mypy.overrides]] -module = "*y*" -disallow_untyped_calls = true -[file xx.py] -def f(a): pass -def g(a: int) -> int: return f(a) -[file xy.py] -def f(a): pass -def g(a: int) -> int: return f(a) -[file yx.py] -def f(a): pass -def g(a: int) -> int: return f(a) -[file yy.py] -def f(a): pass -def g(a: int) -> int: return f(a) -[out] -pyproject.toml: [module = "*x*"]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) -pyproject.toml: [module = "*y*"]: Patterns must be fully-qualified module names, optionally with '*' in some components (e.g spam.*.eggs.*) -== Return code: 0 - -[case testMultipleGlobConfigSectionPyprojectTOML] -# cmd: mypy x.py y.py z.py -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = [ - 'x.*', - 'z.*', -] -disallow_untyped_defs = true -[file x.py] -def f(a): pass -[file y.py] -def f(a): pass -[file z.py] -def f(a): pass -[out] -z.py:1: error: Function is missing a type annotation -x.py:1: error: Function is missing a type annotation - -[case testConfigNoErrorNoSectionPyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -[out] - -[case testConfigErrorUnknownFlagPyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -bad = 0 -[out] -pyproject.toml: [mypy]: Unrecognized option: bad = 0 -== Return code: 0 - -[case testConfigErrorBadFlagPyprojectTOML] -# cmd: mypy a.py -[file pyproject.toml] -\[tool.mypy] -disallow-untyped-defs = true -[file a.py] -def f(): - pass -[out] -pyproject.toml: [mypy]: Unrecognized option: disallow-untyped-defs = True -== Return code: 0 - -[case testConfigErrorBadBooleanPyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -ignore_missing_imports = "nah" -[out] -pyproject.toml: [mypy]: ignore_missing_imports: Not a boolean: nah -== Return code: 0 - -[case testConfigErrorNotPerFilePyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "*" -python_version = 3.4 -[out] -pyproject.toml: [module = "*"]: Per-module sections should only specify per-module flags (python_version) -== Return code: 0 - -[case testConfigMypyPathPyprojectTOML] -# cmd: mypy file.py -[file pyproject.toml] -\[tool.mypy] -mypy_path = "foo:bar,baz" -[file foo/foo.pyi] -def foo(x: int) -> str: ... -[file bar/bar.pyi] -def bar(x: str) -> list: ... -[file baz/baz.pyi] -def baz(x: list) -> dict: ... -[file file.py] -import no_stubs -from foo import foo -from bar import bar -from baz import baz -baz(bar(foo(42))) -baz(bar(foo('oof'))) -[out] -file.py:1: error: Cannot find implementation or library stub for module named "no_stubs" -file.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -file.py:6: error: Argument 1 to "foo" has incompatible type "str"; expected "int" - -[case testIgnoreErrorsConfigPyprojectTOML] -# cmd: mypy x.py y.py -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "x" -ignore_errors = true -[file x.py] -"" + 0 -[file y.py] -"" + 0 -[out] -y.py:1: error: Unsupported operand types for + ("str" and "int") - -[case testConfigFollowImportsNormalPyprojectTOML] -# cmd: mypy main.py -[file main.py] -from a import x -x + 0 -x + '' # E -import a -a.x + 0 -a.x + '' # E -a.y # E -a + 0 # E -[file pyproject.toml] -\[tool.mypy] -follow_imports = "normal" -[file a.py] -x = 0 -x += '' # Error reported here -[out] -a.py:2: error: Unsupported operand types for + ("int" and "str") -main.py:3: error: Unsupported operand types for + ("int" and "str") -main.py:6: error: Unsupported operand types for + ("int" and "str") -main.py:7: error: Module has no attribute "y" -main.py:8: error: Unsupported operand types for + (Module and "int") - -[case testConfigFollowImportsSilentPyprojectTOML] -# cmd: mypy main.py -[file main.py] -from a import x -x + '' -import a -a.x + '' -a.y -a + 0 -[file pyproject.toml] -\[tool.mypy] -follow_imports = "silent" -[file a.py] -x = 0 -x += '' # No error reported -[out] -main.py:2: error: Unsupported operand types for + ("int" and "str") -main.py:4: error: Unsupported operand types for + ("int" and "str") -main.py:5: error: Module has no attribute "y" -main.py:6: error: Unsupported operand types for + (Module and "int") - -[case testConfigFollowImportsSkipPyprojectTOML] -# cmd: mypy main.py -[file main.py] -from a import x -reveal_type(x) # Expect Any -import a -reveal_type(a.x) # Expect Any -[file pyproject.toml] -\[tool.mypy] -follow_imports = "skip" -[file a.py] -/ # No error reported -[out] -main.py:2: note: Revealed type is "Any" -main.py:4: note: Revealed type is "Any" - -[case testConfigFollowImportsErrorPyprojectTOML] -# cmd: mypy main.py -[file main.py] -from a import x -reveal_type(x) # Expect Any -import a # Error reported here -reveal_type(a.x) # Expect Any -[file pyproject.toml] -\[tool.mypy] -follow_imports = "error" -[file a.py] -/ # No error reported -[out] -main.py:1: error: Import of 'a' ignored -main.py:1: note: (Using --follow-imports=error, module not passed on command line) -main.py:2: note: Revealed type is "Any" -main.py:4: note: Revealed type is "Any" - -[case testConfigFollowImportsSelectivePyprojectTOML] -# cmd: mypy main.py -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "normal" -follow_imports = "normal" -\[[tool.mypy.overrides]] -module = "silent" -follow_imports = "silent" -\[[tool.mypy.overrides]] -module = "skip" -follow_imports = "skip" -\[[tool.mypy.overrides]] -module = "error" -follow_imports = "error" -[file main.py] -import normal -import silent -import skip -import error -reveal_type(normal.x) -reveal_type(silent.x) -reveal_type(skip) -reveal_type(error) -[file normal.py] -x = 0 -x += '' -[file silent.py] -x = 0 -x += '' -[file skip.py] -bla bla -[file error.py] -bla bla -[out] -normal.py:2: error: Unsupported operand types for + ("int" and "str") -main.py:4: error: Import of 'error' ignored -main.py:4: note: (Using --follow-imports=error, module not passed on command line) -main.py:5: note: Revealed type is "builtins.int" -main.py:6: note: Revealed type is "builtins.int" -main.py:7: note: Revealed type is "Any" -main.py:8: note: Revealed type is "Any" - -[case testConfigFollowImportsInvalidPyprojectTOML] -# cmd: mypy main.py -[file pyproject.toml] -\[tool.mypy] -follow_imports = true -[file main.py] -[out] -pyproject.toml: [mypy]: follow_imports: invalid choice 'True' (choose from 'normal', 'silent', 'skip', 'error') -== Return code: 0 - -[case testConfigSilentMissingImportsOffPyprojectTOML] -# cmd: mypy main.py -[file main.py] -import missing # Expect error here -reveal_type(missing.x) # Expect Any -[file pyproject.toml] -\[tool.mypy] -ignore_missing_imports = false -[out] -main.py:1: error: Cannot find implementation or library stub for module named "missing" -main.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -main.py:2: note: Revealed type is "Any" - -[case testConfigSilentMissingImportsOnPyprojectTOML] -# cmd: mypy main.py -[file main.py] -import missing # No error here -reveal_type(missing.x) # Expect Any -[file pyproject.toml] -\[tool.mypy] -ignore_missing_imports = true -[out] -main.py:2: note: Revealed type is "Any" - -[case testConfigNoErrorForUnknownXFlagInSubsectionPyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "foo" -x_bad = 0 -[out] - -[case testPythonVersionTooOld10PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 1.0 -[out] -pyproject.toml: [mypy]: python_version: Python major version '1' out of range (must be 2 or 3) -== Return code: 0 - -[case testPythonVersionTooOld26PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 2.6 -[out] -pyproject.toml: [mypy]: python_version: Python 2.6 is not supported (must be 2.7) -== Return code: 0 - -[case testPythonVersionTooOld33PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 3.3 -[out] -pyproject.toml: [mypy]: python_version: Python 3.3 is not supported (must be 3.4 or higher) -== Return code: 0 - -[case testPythonVersionTooNew28PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 2.8 -[out] -pyproject.toml: [mypy]: python_version: Python 2.8 is not supported (must be 2.7) -== Return code: 0 - -[case testPythonVersionTooNew40PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 4.0 -[out] -pyproject.toml: [mypy]: python_version: Python major version '4' out of range (must be 2 or 3) -== Return code: 0 - -[case testPythonVersionAccepted27PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 2.7 -[out] - -[case testPythonVersionAccepted34PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 3.4 -[out] - -[case testPythonVersionAccepted36PyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -python_version = 3.6 -[out] - -[case testDisallowAnyGenericsBuiltinCollectionsPyprojectTOML] -# cmd: mypy m.py -[file pyproject.toml] -\[tool.mypy] -python_version = 3.6 -\[[tool.mypy.overrides]] -module = "m" -disallow_any_generics = true - -[file m.py] -s = tuple([1, 2, 3]) # no error - -def f(t: tuple) -> None: pass -def g() -> list: pass -def h(s: dict) -> None: pass -def i(s: set) -> None: pass -def j(s: frozenset) -> None: pass -[out] -m.py:3: error: Implicit generic "Any". Use "typing.Tuple" and specify generic parameters -m.py:4: error: Implicit generic "Any". Use "typing.List" and specify generic parameters -m.py:5: error: Implicit generic "Any". Use "typing.Dict" and specify generic parameters -m.py:6: error: Implicit generic "Any". Use "typing.Set" and specify generic parameters -m.py:7: error: Implicit generic "Any". Use "typing.FrozenSet" and specify generic parameters - -[case testDisallowAnyGenericsTypingCollectionsPyprojectTOML] -# cmd: mypy m.py -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "m" -disallow_any_generics = true - -[file m.py] -from typing import Tuple, List, Dict, Set, FrozenSet - -def f(t: Tuple) -> None: pass -def g() -> List: pass -def h(s: Dict) -> None: pass -def i(s: Set) -> None: pass -def j(s: FrozenSet) -> None: pass -[out] -m.py:3: error: Missing type parameters for generic type "Tuple" -m.py:4: error: Missing type parameters for generic type "List" -m.py:5: error: Missing type parameters for generic type "Dict" -m.py:6: error: Missing type parameters for generic type "Set" -m.py:7: error: Missing type parameters for generic type "FrozenSet" - -[case testSectionInheritancePyprojectTOML] -# cmd: mypy a -[file a/__init__.py] -0() -[file a/foo.py] -0() -[file a/b/__init__.py] -[file a/b/c/__init__.py] -0() -[file a/b/c/d/__init__.py] -[file a/b/c/d/e/__init__.py] -from typing import List -def g(x: List) -> None: pass -g(None) -[file pyproject.toml] -\[tool.mypy] -allow_any_generics = true -\[[tool.mypy.overrides]] -module = "a.*" -ignore_errors = true -\[[tool.mypy.overrides]] -module = "a.b.*" -disallow_any_generics = true -ignore_errors = true -\[[tool.mypy.overrides]] -module = "a.b.c.*" -ignore_errors = true -\[[tool.mypy.overrides]] -module = "a.b.c.d.*" -ignore_errors = true -\[[tool.mypy.overrides]] -module = "a.b.c.d.e.*" -ignore_errors = true -strict_optional = true -\[[tool.mypy.overrides]] -module = "a.b.c.d.e" -ignore_errors = false -[out] -a/b/c/d/e/__init__.py:2: error: Missing type parameters for generic type "List" -a/b/c/d/e/__init__.py:3: error: Argument 1 to "g" has incompatible type "None"; expected "List[Any]" - -[case testDuplicateModulesAcrossSectionsPyprojectTOML] -# cmd: mypy a -[file a/__init__.py] -[file pyproject.toml] -\[tool.mypy] -allow_any_generics = true -\[[tool.mypy.overrides]] -module = "a.*" -ignore_errors = true -\[[tool.mypy.overrides]] -module = [ - "a.b.*", - "a.*" -] -disallow_any_generics = true -ignore_errors = false -[out] -pyproject.toml: toml config file contains [[tool.mypy.overrides]] sections with conflicting values. Module 'a.*' has two different values for 'ignore_errors' -== Return code: 0 - -[case testNonArrayOverridesSectionPyprojectTOML] -# cmd: mypy a -[file a/__init__.py] -[file pyproject.toml] -\[tool.mypy] -allow_any_generics = true -\[tool.mypy.overrides] -module = "a.*" -ignore_errors = true -[out] -pyproject.toml: tool.mypy.overrides sections must be an array. Please make sure you are using double brackets like so: [[tool.mypy.overrides]] -== Return code: 0 - -[case testOverridesSectionWithNoModulePyprojectTOML] -# cmd: mypy a -[file a/__init__.py] -[file pyproject.toml] -\[tool.mypy] -allow_any_generics = true -\[[tool.mypy.overrides]] -ignore_errors = true -[out] -pyproject.toml: toml config file contains a [[tool.mypy.overrides]] section, but no module to override was specified. -== Return code: 0 - -[case testOverridesSectionWithInvalidModulePyprojectTOML] -# cmd: mypy a -[file a/__init__.py] -[file pyproject.toml] -\[tool.mypy] -allow_any_generics = true -\[[tool.mypy.overrides]] -module = true -ignore_errors = true -[out] -pyproject.toml: toml config file contains a [[tool.mypy.overrides]] section with a module value that is not a string or a list of strings -== Return code: 0 - -[case testDisallowUntypedDefsAndGenericsPyprojectTOML] -# cmd: mypy a.py -[file pyproject.toml] -\[tool.mypy] -disallow_untyped_defs = true -disallow_any_generics = true -[file a.py] -def get_tasks(self): - return 'whatever' -[out] -a.py:1: error: Function is missing a return type annotation - -[case testSrcPEP420PackagesPyprojectTOML] -# cmd: mypy -p anamespace --namespace-packages -[file pyproject.toml] -\[tool.mypy] -mypy_path = "src" -[file src/setup.cfg] -[file src/anamespace/foo/__init__.py] -[file src/anamespace/foo/bar.py] -def bar(a: int, b: int) -> str: - return a + b -[out] -src/anamespace/foo/bar.py:2: error: Incompatible return value type (got "int", expected "str") - -[case testFollowImportStubs1PyprojectTOML] -# cmd: mypy main.py -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "math.*" -follow_imports = "error" -follow_imports_for_stubs = true -[file main.py] -import math -math.frobnicate() -[out] -main.py:1: error: Import of 'math' ignored -main.py:1: note: (Using --follow-imports=error, module not passed on command line) - -[case testFollowImportStubs2PyprojectTOML] -# cmd: mypy main.py -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "math.*" -follow_imports = "skip" -follow_imports_for_stubs = true -[file main.py] -import math -math.frobnicate() - -[case testConfigWarnUnusedSection1PyprojectTOML] -# cmd: mypy foo.py quux.py spam/eggs.py -[file pyproject.toml] -\[tool.mypy] -warn_unused_configs = true -incremental = false -\[[tool.mypy.overrides]] -module = "bar" -\[[tool.mypy.overrides]] -module = "foo" -\[[tool.mypy.overrides]] -module = "baz.*" -\[[tool.mypy.overrides]] -module = "quux.*" -\[[tool.mypy.overrides]] -module = "spam.*" -\[[tool.mypy.overrides]] -module = "spam.eggs" -\[[tool.mypy.overrides]] -module = "emarg.*" -\[[tool.mypy.overrides]] -module = "emarg.hatch" -# Currently we don't treat an unstructured pattern like a.*.b as unused -# if it matches another section (like a.x.b). This would be reasonable -# to change. ' -\[[tool.mypy.overrides]] -module = "a.*.b" -\[[tool.mypy.overrides]] -module = "a.*.c" -\[[tool.mypy.overrides]] -module = "a.x.b" -[file foo.py] -[file quux.py] -[file spam/__init__.py] -[file spam/eggs.py] -[out] -Warning: unused section(s) in pyproject.toml: module = ['a.*.c', 'a.x.b', 'bar', 'baz.*', 'emarg.*', 'emarg.hatch'] -== Return code: 0 - -[case testConfigUnstructuredGlobPyprojectTOML] -# cmd: mypy emarg foo -[file pyproject.toml] -\[tool.mypy] -ignore_errors = true -\[[tool.mypy.overrides]] -module = "*.lol" -ignore_errors = false -\[[tool.mypy.overrides]] -module = "emarg.*" -ignore_errors = false -\[[tool.mypy.overrides]] -module = "emarg.*.villip.*" -ignore_errors = true -\[[tool.mypy.overrides]] -module = "emarg.hatch.villip.mankangulisk" -ignore_errors = false -[file emarg/__init__.py] -[file emarg/foo.py] -fail -[file emarg/villip.py] -fail -[file emarg/hatch/__init__.py] -[file emarg/hatch/villip/__init__.py] -[file emarg/hatch/villip/nus.py] -fail -[file emarg/hatch/villip/mankangulisk.py] -fail -[file foo/__init__.py] -[file foo/lol.py] -fail -[out] -foo/lol.py:1: error: Name "fail" is not defined -emarg/foo.py:1: error: Name "fail" is not defined -emarg/hatch/villip/mankangulisk.py:1: error: Name "fail" is not defined - -[case testTomlFilesPyprojectTOML] -# cmd: mypy -[file pyproject.toml] -\[tool.mypy] -files = [ - "a.py", - "b.py" -] -[file a.py] -fail -[file b.py] -fail -[out] -b.py:1: error: Name "fail" is not defined -a.py:1: error: Name "fail" is not defined - -[case testTomlFilesGlobbingPyprojectTOML] -# cmd: mypy -[file pyproject.toml] -\[tool.mypy] -files = "**/*.py" -[file a/b.py] -fail -[file c.py] -fail -[out] -a/b.py:1: error: Name "fail" is not defined -c.py:1: error: Name "fail" is not defined - -[case testTomlFilesCmdlineOverridesConfigPyprojectTOML] -# cmd: mypy override.py -[file pyproject.toml] -\[tool.mypy] -files = "config.py" -[out] -mypy: can't read file 'override.py': No such file or directory -== Return code: 2 - -[case testDuplicateModulesPyprojectTOML] -# cmd: mypy src -[file pyproject.toml] -\[tool.mypy] -mypy_path = "src" -[file src/__init__.py] -[file src/a.py] -import foo.bar -[file src/foo/__init__.py] -[file src/foo/bar.py] -1+'x' -[out] -src/foo/bar.py: error: Source file found twice under different module names: 'src.foo.bar' and 'foo.bar' -== Return code: 2 - -[case testCmdlinePackageAndTomlFilesPyprojectTOML] -# cmd: mypy -p pkg -[file pyproject.toml] -\[tool.mypy] -files = "file" -[file pkg.py] -x = 0 # type: str -[file file.py] -y = 0 # type: str -[out] -pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") - -[case testCmdlineModuleAndTomlFilesPyprojectTOML] -# cmd: mypy -m pkg -[file pyproject.toml] -\[tool.mypy] -files = "file" -[file pkg.py] -x = 0 # type: str -[file file.py] -y = 0 # type: str -[out] -pkg.py:1: error: Incompatible types in assignment (expression has type "int", variable has type "str") - [case testNonArrayOverridesPyprojectTOML] # cmd: mypy x.py [file pyproject.toml] diff --git a/test-data/unit/daemon.pyproject.test b/test-data/unit/daemon.pyproject.test deleted file mode 100644 index 1952b491be33..000000000000 --- a/test-data/unit/daemon.pyproject.test +++ /dev/null @@ -1,64 +0,0 @@ --- End-to-end test cases for the daemon (dmypy). --- These are special because they run multiple shell commands. - -[case testDaemonIgnoreConfigFilesPyprojectTOMLPyprojectTOML] -$ dmypy start -- --follow-imports=error -Daemon started -[file pyproject.toml] -\[tool.mypy] -files = './foo.py' - -[case testDaemonRunRestartPluginVersionPyprojectTOMLPyprojectTOML] -$ dmypy run -- foo.py --no-error-summary -Daemon started -$ {python} -c "print(' ')" >> plug.py -$ dmypy run -- foo.py --no-error-summary -Restarting: plugins changed -Daemon stopped -Daemon started -$ dmypy stop -Daemon stopped -[file pyproject.toml] -\[tool.mypy] -follow_imports = "error" -plugins = "plug.py" -[file foo.py] -pass -[file plug.py] -from mypy.plugin import Plugin -class Dummy(Plugin): pass -def plugin(version): return Dummy - -[case testDaemonRunRestartGlobsPyprojectTOMLPyprojectTOML] --- Ensure dmypy is not restarted if the configuration doesn't change and it contains globs --- Note: Backslash path separator in output is replaced with forward slash so the same test succeeds on Windows as well -$ dmypy run -- foo --follow-imports=error --python-version=3.6 -Daemon started -foo/lol.py:1: error: Name "fail" is not defined -Found 1 error in 1 file (checked 3 source files) -== Return code: 1 -$ dmypy run -- foo --follow-imports=error --python-version=3.6 -foo/lol.py:1: error: Name "fail" is not defined -Found 1 error in 1 file (checked 3 source files) -== Return code: 1 -$ {python} -c "print('[mypy]')" >mypy.ini -$ {python} -c "print('ignore_errors=True')" >>mypy.ini -$ dmypy run -- foo --follow-imports=error --python-version=3.6 -Restarting: configuration changed -Daemon stopped -Daemon started -Success: no issues found in 3 source files -$ dmypy stop -Daemon stopped -[file pyproject.toml] -\[tool.mypy] -ignore_errors = true -\[[tool.mypy.overrides]] -module = "*.lol" -ignore_errors = false - -[file foo/__init__.py] -[file foo/lol.py] -fail -[file foo/ok.py] -a: int = 1 diff --git a/test-data/unit/diff.pyproject.test b/test-data/unit/diff.pyproject.test deleted file mode 100644 index 8da407d06fd2..000000000000 --- a/test-data/unit/diff.pyproject.test +++ /dev/null @@ -1,41 +0,0 @@ --- Test cases for taking a diff of two module ASTs/symbol tables. --- The diffs are used for fined-grained incremental checking. - -[case testDynamicBasePluginDiffPyprojectTOML] -# flags: --config-file tmp/pyproject.toml -from mod import declarative_base, Column, Instr - -Base = declarative_base() - -class Model(Base): - x: Column[int] -class Other: - x: Column[int] -class Diff: - x: Column[int] -[file next.py] -from mod import declarative_base, Column, Instr - -Base = declarative_base() - -class Model(Base): - x: Column[int] -class Other: - x: Column[int] -class Diff(Base): - x: Column[int] -[file mod.py] -from typing import Generic, TypeVar -def declarative_base(): ... - -T = TypeVar('T') - -class Column(Generic[T]): ... -class Instr(Generic[T]): ... - -[file pyproject.toml] -\[tool.mypy] -plugins = '/test-data/unit/plugins/dyn_class.py' -[out] -__main__.Diff -__main__.Diff.x diff --git a/test-data/unit/fine-grained.pyproject.test b/test-data/unit/fine-grained.pyproject.test deleted file mode 100644 index d81fcaf89276..000000000000 --- a/test-data/unit/fine-grained.pyproject.test +++ /dev/null @@ -1,252 +0,0 @@ --- Test cases for fine-grained incremental checking --- --- Test cases may define multiple versions of a file --- (e.g. m.py, m.py.2). There is always an initial batch --- pass that processes all files present initially, followed --- by one or more fine-grained incremental passes that use --- alternative versions of files, if available. If a file --- just has a single .py version, it is used for all passes. - --- TODO: what if version for some passes but not all - --- Output is laid out like this: --- --- [out] --- --- == --- --- --- --- Modules that are expected to be detected as changed by dmypy_server --- can be checked with [stale ...] --- Generally this should mean added, deleted, or changed files, though there --- are important edge cases related to the cache: deleted files won't be detected --- as changed in the initial run with the cache while modules that depended on them --- should be. --- --- Modules that are require a full-module reprocessing by update can be checked with --- [rechecked ...]. This should include any files detected as having changed as well --- as any files that contain targets that need to be reprocessed but which haven't --- been loaded yet. If there is no [rechecked...] directive, it inherits the value of --- [stale ...]. --- --- Specifications for later runs can be given with [stale2 ...], [stale3 ...], etc. --- --- Test runner can parse options from pyproject.toml file. Updating this file in --- between incremental runs is not yet supported. --- --- Each test case run without caching and with caching (if the initial run passes), --- unless it has one a -only_when_cache or -only_when_nocache arguments. We sometimes --- skip caching test cases to speed up tests, if the caching variant is not useful. --- The caching test case variants get an implicit _cached suffix. - -[case testPerFileStrictOptionalModulePyprojectTOML] -import a -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[[tool.mypy.overrides]] -module = "a.*" -strict_optional = true -[file a.py] -from typing import Optional -import b -x: int -y: int = x -[file b.py] -from typing import Optional -x: int -y: int = x -[file b.py.2] -from typing import Optional -x: Optional[int] -y: int = x -[file a.py.3] -from typing import Optional -import b -x: Optional[int] -y: int = x -[out] -== -== -a.py:4: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") - -[case testPerFileStrictOptionalModuleOnlyPyprojectTOML] -import a -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[[tool.mypy.overrides]] -module = "a.*" -strict_optional = true -[file a.py] -from typing import Optional -import b -y: int = b.x -class Dummy: - def f(self) -> None: - pass -[file b.py] -from typing import Optional -import c -x: int -y: int = c.x -class Dummy: - def f(self) -> None: - pass -[file c.py] -from typing import Optional -x: int -[file c.py.2] -from typing import Optional -x: Optional[int] -[file b.py.3] -from typing import Optional -import c -x: Optional[int] -y: int = c.x -[file a.py.4] -from typing import Optional -import b -y: Optional[int] = b.x -class Dummy: - def f(self) -> None: - pass -[out] -== -== -a.py:3: error: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") -== - -[case testPerFileStrictOptionalFunctionPyprojectTOML] -import a -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[[tool.mypy.overrides]] -module = "b.*" -strict_optional = true -[file a.py] -from typing import Optional -import b -def f() -> None: - x: int - x = b.g(x) -[file b.py] -from typing import Optional -import c -def g(x: Optional[int]) -> Optional[int]: - return c.h(x) -[file c.py] -from typing import Optional -def h(x: Optional[int]) -> int: - pass -[file c.py.2] -from typing import Optional -def h(x: int) -> int: - pass -[file b.py.3] -from typing import Optional -import c -def g(x: int) -> Optional[int]: - return c.h(x) -[out] -== -b.py:4: error: Argument 1 to "h" has incompatible type "Optional[int]"; expected "int" -== - -[case testPerFileStrictOptionalMethodPyprojectTOML] -import a -[file pyproject.toml] -\[tool.mypy] -strict_optional = false -\[[tool.mypy.overrides]] -module = "b.*" -strict_optional = true -[file a.py] -from typing import Optional -import b -class A: - def f(self) -> None: - x: int - x = b.B().g(x) -[file b.py] -from typing import Optional -import c -class B: - def g(self, x: Optional[int]) -> Optional[int]: - return c.C().h(x) -[file c.py] -from typing import Optional -class C: - def h(self, x: Optional[int]) -> int: - pass -[file c.py.2] -from typing import Optional -class C: - def h(self, x: int) -> int: - pass -[file b.py.3] -from typing import Optional -import c -class B: - def g(self, x: int) -> Optional[int]: - return c.C().h(x) -[out] -== -b.py:5: error: Argument 1 to "h" of "C" has incompatible type "Optional[int]"; expected "int" -== - -[case testRefreshIgnoreErrors1PyprojectTOML] -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "b" -ignore_errors = true -[file a.py] -y = '1' -[file a.py.2] -y = 1 -[file b.py] -from a import y -def fu() -> None: - 1+'lurr' - y -[out] -== - -[case testRefreshIgnoreErrors2PyprojectTOML] -[file pyproject.toml] -\[tool.mypy] -\[[tool.mypy.overrides]] -module = "b" -ignore_errors = true -[file b.py] -def fu() -> int: - 1+'lurr' - return 1 -[file b.py.2] -def fu() -> int: - 1+'lurr' - return 2 -[out] -== - -[case testRefreshOptionsPyprojectTOML] -[file pyproject.toml] -\[tool.mypy] -disallow_any_generics = true -\[[tool.mypy.overrides]] -module = "b" -disallow_any_generics = false -[file a.py] -y = '1' -[file a.py.2] -y = 1 -[file b.py] -from typing import List -from a import y -x = [] # type: List -[builtins fixtures/list.pyi] -[out] -== diff --git a/test-data/unit/pep561.pyproject.test b/test-data/unit/pep561.pyproject.test deleted file mode 100644 index 9cb14ea8eaea..000000000000 --- a/test-data/unit/pep561.pyproject.test +++ /dev/null @@ -1,27 +0,0 @@ -[case testTypedPkg_config_nositepackagesPyprojectTOML] -# pkgs: typedpkg -from typedpkg.sample import ex -from typedpkg import dne -a = ex(['']) -reveal_type(a) -[file pyproject.toml] -\[tool.mypy] -no_site_packages = true -[out] -testTypedPkg_config_nositepackagesPyprojectTOML.py:2: error: Cannot find implementation or library stub for module named "typedpkg.sample" -testTypedPkg_config_nositepackagesPyprojectTOML.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -testTypedPkg_config_nositepackagesPyprojectTOML.py:3: error: Cannot find implementation or library stub for module named "typedpkg" -testTypedPkg_config_nositepackagesPyprojectTOML.py:5: note: Revealed type is "Any" - -[case testTypedPkgNoSitePkgsIgnoredImportsPyprojectTOML] -# pkgs: typedpkg -# flags: --no-site-packages -from typedpkg.sample import ex -from typedpkg import dne -a = ex(['']) -reveal_type(a) -[file pyproject.toml] -\[tool.mypy] -ignore_missing_imports = true -[out] -testTypedPkgNoSitePkgsIgnoredImportsPyprojectTOML.py:6: note: Revealed type is "Any" diff --git a/test-data/unit/reports.pyproject.test b/test-data/unit/reports.pyproject.test deleted file mode 100644 index 95e41797d296..000000000000 --- a/test-data/unit/reports.pyproject.test +++ /dev/null @@ -1,12 +0,0 @@ --- Tests for reports --- --- This file follows syntax of cmdline.test. - -[case testConfigErrorUnknownReportPyprojectTOML] -# cmd: mypy -c pass -[file pyproject.toml] -\[tool.mypy] -bad_report = "." -[out] -pyproject.toml: [mypy]: Unrecognized report type: bad_report -== Return code: 0 From ddb3922081e57e70a315e5650bca9485e7788842 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Sat, 24 Apr 2021 14:29:26 -0400 Subject: [PATCH 37/39] Fix some mistyping --- mypy/config_parser.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 219905096e8f..76aa629cbec3 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -8,8 +8,8 @@ import sys import toml -from typing import (Any, Callable, Dict, List, Mapping, Optional, Sequence, - TextIO, Tuple, Union) +from typing import (Any, Callable, Dict, List, Mapping, MutableMapping, Optional, Sequence, + TextIO, Tuple, Union, cast) from typing_extensions import Final from mypy import defaults @@ -169,13 +169,14 @@ def parse_config_file(options: Options, set_strict_flags: Callable[[], None], continue try: if is_toml(config_file): - toml_data = toml.load(config_file, _dict=OrderedDict) # type: OrderedDict + toml_data = cast("OrderedDict[str, Any]", + toml.load(config_file, _dict=OrderedDict)) # Filter down to just mypy relevant toml keys toml_data = toml_data.get('tool', {}) if 'mypy' not in toml_data: continue - toml_data = {'mypy': toml_data['mypy']} - parser = destructure_overrides(toml_data) + toml_data = OrderedDict({'mypy': toml_data['mypy']}) + parser = destructure_overrides(toml_data) # type: MutableMapping[str, Any] config_types = toml_config_types else: config_parser.read(config_file) @@ -533,8 +534,8 @@ def set_strict_flags() -> None: return sections, errors -def get_config_module_names(filename: str, modules: List[str]) -> str: - if not modules: +def get_config_module_names(filename: Optional[str], modules: List[str]) -> str: + if not filename or not modules: return '' if not is_toml(filename): From b4f7d798e1c01078bca730c1fa30a09f939c56cb Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Fri, 30 Apr 2021 14:49:07 -0400 Subject: [PATCH 38/39] Flesh out some feature tests of the pyproject.toml config --- test-data/unit/check-custom-plugin.test | 53 ++++ test-data/unit/check-flags.test | 315 ++++++++++++++++++++++++ test-data/unit/check-incremental.test | 24 ++ test-data/unit/check-modules-case.test | 73 ++++++ test-data/unit/check-modules.test | 62 +++++ test-data/unit/check-newsemanal.test | 32 +++ 6 files changed, 559 insertions(+) diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index 580310e70af7..180b5c4a642b 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -11,6 +11,14 @@ reveal_type(f()) # N: Revealed type is "builtins.int" \[mypy] plugins=/test-data/unit/plugins/fnplugin.py +[case testFunctionPluginFilePyProjectTOML] +# flags: --config-file tmp/pyproject.toml +def f() -> str: ... +reveal_type(f()) # N: Revealed type is "builtins.int" +[file pyproject.toml] +\[tool.mypy] +plugins="/test-data/unit/plugins/fnplugin.py" + [case testFunctionPlugin] # flags: --config-file tmp/mypy.ini def f() -> str: ... @@ -19,6 +27,14 @@ reveal_type(f()) # N: Revealed type is "builtins.int" \[mypy] plugins=fnplugin +[case testFunctionPluginPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +def f() -> str: ... +reveal_type(f()) # N: Revealed type is "builtins.int" +[file pyproject.toml] +\[tool.mypy] +plugins = 'fnplugin' + [case testFunctionPluginFullnameIsNotNone] # flags: --config-file tmp/mypy.ini from typing import Callable, TypeVar @@ -30,6 +46,17 @@ g(f)() \[mypy] plugins=/test-data/unit/plugins/fnplugin.py +[case testFunctionPluginFullnameIsNotNonePyProjectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import Callable, TypeVar +f: Callable[[], None] +T = TypeVar('T') +def g(x: T) -> T: return x # This strips out the name of a callable +g(f)() +[file pyproject.toml] +\[tool.mypy] +plugins="/test-data/unit/plugins/fnplugin.py" + [case testTwoPlugins] # flags: --config-file tmp/mypy.ini def f(): ... @@ -43,6 +70,20 @@ reveal_type(h()) # N: Revealed type is "Any" plugins=/test-data/unit/plugins/fnplugin.py, /test-data/unit/plugins/plugin2.py +[case testTwoPluginsPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +def f(): ... +def g(): ... +def h(): ... +reveal_type(f()) # N: Revealed type is "builtins.int" +reveal_type(g()) # N: Revealed type is "builtins.str" +reveal_type(h()) # N: Revealed type is "Any" +[file pyproject.toml] +\[tool.mypy] +plugins=["/test-data/unit/plugins/fnplugin.py", + "/test-data/unit/plugins/plugin2.py" +] + [case testTwoPluginsMixedType] # flags: --config-file tmp/mypy.ini def f(): ... @@ -55,6 +96,18 @@ reveal_type(h()) # N: Revealed type is "Any" \[mypy] plugins=/test-data/unit/plugins/fnplugin.py, plugin2 +[case testTwoPluginsMixedTypePyProjectTOML] +# flags: --config-file tmp/pyproject.toml +def f(): ... +def g(): ... +def h(): ... +reveal_type(f()) # N: Revealed type is "builtins.int" +reveal_type(g()) # N: Revealed type is "builtins.str" +reveal_type(h()) # N: Revealed type is "Any" +[file pyproject.toml] +\[tool.mypy] +plugins=["/test-data/unit/plugins/fnplugin.py", 'plugin2'] + [case testMissingPluginFile] # flags: --config-file tmp/mypy.ini [file mypy.ini] diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index aa6b54068aa8..7099f5b0ec19 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -478,6 +478,24 @@ disallow_incomplete_defs = False disallow_incomplete_defs = True +[case testPerFileIncompleteDefsBasicPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, incomplete + +[file standard.py] +def incomplete(x) -> int: + return 0 +[file incomplete.py] +def incomplete(x) -> int: # E: Function is missing a type annotation for one or more arguments + return 0 +[file pyproject.toml] +\[tool.mypy] +disallow_incomplete_defs = false +\[[tool.mypy.overrides]] +module = 'incomplete' +disallow_incomplete_defs = true + + [case testPerFileStrictOptionalBasic] # flags: --config-file tmp/mypy.ini import standard, optional @@ -498,6 +516,27 @@ strict_optional = False strict_optional = True +[case testPerFileStrictOptionalBasicPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +x = 0 +if int(): + x = None +[file optional.py] +x = 0 +if int(): + x = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[[tool.mypy.overrides]] +module = 'optional' +strict_optional = true + + [case testPerFileStrictOptionalBasicImportStandard] # flags: --config-file tmp/mypy.ini import standard, optional @@ -525,6 +564,34 @@ strict_optional = False strict_optional = True +[case testPerFileStrictOptionalBasicImportStandardPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +from typing import Optional +def f(x: int) -> None: pass +an_int = 0 # type: int +optional_int = None # type: Optional[int] +f(an_int) # ints can be used as ints +f(optional_int) # optional ints can be used as ints in this file + +[file optional.py] +import standard +def f(x: int) -> None: pass +standard.an_int = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") +standard.optional_int = None # OK -- explicitly declared as optional +f(standard.an_int) # ints can be used as ints +f(standard.optional_int) # E: Argument 1 to "f" has incompatible type "None"; expected "int" + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[[tool.mypy.overrides]] +module = 'optional' +strict_optional = true + + [case testPerFileStrictOptionalBasicImportOptional] # flags: --config-file tmp/mypy.ini import standard, optional @@ -547,6 +614,31 @@ strict_optional = False \[mypy-optional] strict_optional = True + +[case testPerFileStrictOptionalBasicImportOptionalPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +import optional +def f(x: int) -> None: pass +f(optional.x) # OK -- in non-strict Optional context +f(optional.y) # OK -- in non-strict Optional context + +[file optional.py] +from typing import Optional +def f(x: int) -> None: pass +x = 0 # type: Optional[int] +y = None # type: None + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[[tool.mypy.overrides]] +module = 'optional' +strict_optional = true + + [case testPerFileStrictOptionalListItemImportOptional] # flags: --config-file tmp/mypy.ini import standard, optional @@ -571,6 +663,34 @@ strict_optional = False strict_optional = True [builtins fixtures/list.pyi] + +[case testPerFileStrictOptionalListItemImportOptionalPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +import optional +from typing import List +def f(x: List[int]) -> None: pass +f(optional.x) # OK -- in non-strict Optional context +f(optional.y) # OK -- in non-strict Optional context + +[file optional.py] +from typing import Optional, List +def f(x: List[int]) -> None: pass +x = [] # type: List[Optional[int]] +y = [] # type: List[int] + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[[tool.mypy.overrides]] +module = 'optional' +strict_optional = true + +[builtins fixtures/list.pyi] + + [case testPerFileStrictOptionalComplicatedList] from typing import Union, Optional, List @@ -596,6 +716,27 @@ strict_optional = False \[mypy-optional] strict_optional = True + +[case testPerFileStrictOptionalNoneArgumentsPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import standard, optional + +[file standard.py] +def f(x: int = None) -> None: pass + +[file optional.py] +import standard +def f(x: int = None) -> None: pass +standard.f(None) + +[file pyproject.toml] +\[tool.mypy] +strict_optional = false +\[[tool.mypy.overrides]] +module = 'optional' +strict_optional = true + + [case testDisallowImplicitTypesIgnoreMissingTypes] # flags: --ignore-missing-imports --disallow-any-unimported from missing import MyType @@ -1078,6 +1219,24 @@ always_true = YOLO1, YOLO always_false = BLAH, BLAH1 [builtins fixtures/bool.pyi] + +[case testAlwaysTrueAlwaysFalseConfigFilePyProjectTOML] +# flags: --config-file tmp/pyproject.toml +from somewhere import YOLO, BLAH +if not YOLO: + 1+() +if BLAH: + 1+() + +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = true +always_true = ['YOLO1', 'YOLO'] +always_false = ['BLAH', 'BLAH1'] + +[builtins fixtures/bool.pyi] + + [case testDisableErrorCodeConfigFile] # flags: --config-file tmp/mypy.ini --disallow-untyped-defs import foo @@ -1087,6 +1246,18 @@ def bar(): \[mypy] disable_error_code = import, no-untyped-def + +[case testDisableErrorCodeConfigFilePyProjectTOML] +# flags: --config-file tmp/pyproject.toml --disallow-untyped-defs +import foo +def bar(): + pass + +[file pyproject.toml] +\[tool.mypy] +disable_error_code = ['import', 'no-untyped-def'] + + [case testCheckDisallowAnyGenericsNamedTuple] # flags: --disallow-any-generics from typing import NamedTuple @@ -1190,6 +1361,26 @@ def f(c: A) -> None: # E: Missing type parameters for generic type "A" strict = True [out] + +[case testStrictInConfigAnyGenericPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import TypeVar, Generic + +T = TypeVar('T') + +class A(Generic[T]): + pass + +def f(c: A) -> None: # E: Missing type parameters for generic type "A" + pass + +[file pyproject.toml] +\[tool.mypy] +strict = true + +[out] + + [case testStrictFalseInConfigAnyGeneric] # flags: --config-file tmp/mypy.ini from typing import TypeVar, Generic @@ -1206,6 +1397,26 @@ def f(c: A) -> None: strict = False [out] + +[case testStrictFalseInConfigAnyGenericPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +from typing import TypeVar, Generic + +T = TypeVar('T') + +class A(Generic[T]): + pass + +def f(c: A) -> None: + pass + +[file pyproject.toml] +\[tool.mypy] +strict = false + +[out] + + [case testStrictAndStrictEquality] # flags: --strict x = 0 @@ -1227,6 +1438,25 @@ strict_equality = True strict_equality = False [builtins fixtures/bool.pyi] + +[case testStrictEqualityPerFilePyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import b +42 == 'no' # E: Non-overlapping equality check (left operand type: "Literal[42]", right operand type: "Literal['no']") + +[file b.py] +42 == 'no' + +[file pyproject.toml] +\[tool.mypy] +strict_equality = true +\[[tool.mypy.overrides]] +module = 'b' +strict_equality = false + +[builtins fixtures/bool.pyi] + + [case testNoImplicitReexport] # flags: --no-implicit-reexport from other_module_2 import a @@ -1305,6 +1535,28 @@ implicit_reexport = False [out] main:2: error: Module "other_module_2" has no attribute "a" + +[case testNoImplicitReexportPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +from other_module_2 import a + +[file other_module_1.py] +a = 5 + +[file other_module_2.py] +from other_module_1 import a + +[file pyproject.toml] +\[tool.mypy] +implicit_reexport = true +\[[tool.mypy.overrides]] +module = 'other_module_2' +implicit_reexport = false + +[out] +main:2: error: Module "other_module_2" has no attribute "a" + + [case testImplicitAnyOKForNoArgs] # flags: --disallow-any-generics --show-column-numbers from typing import List @@ -1539,6 +1791,34 @@ disallow_subclassing_any = True \[mypy-m] disallow_subclassing_any = False + +[case testDisallowSubclassingAnyPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import m +import y + +[file m.py] +from typing import Any + +x = None # type: Any + +class ShouldBeFine(x): ... + +[file y.py] +from typing import Any + +x = None # type: Any + +class ShouldNotBeFine(x): ... # E: Class cannot subclass 'x' (has type 'Any') + +[file pyproject.toml] +\[tool.mypy] +disallow_subclassing_any = true +\[[tool.mypy.overrides]] +module = 'm' +disallow_subclassing_any = false + + [case testNoImplicitOptionalPerModule] # flags: --config-file tmp/mypy.ini import m @@ -1553,6 +1833,23 @@ no_implicit_optional = True \[mypy-m] no_implicit_optional = False + +[case testNoImplicitOptionalPerModulePyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import m + +[file m.py] +def f(a: str = None) -> int: + return 0 + +[file pyproject.toml] +\[tool.mypy] +no_implicit_optional = true +\[[tool.mypy.overrides]] +module = 'm' +no_implicit_optional = false + + [case testNoImplicitOptionalPerModulePython2] # flags: --config-file tmp/mypy.ini --python-version 2.7 import m @@ -1568,6 +1865,24 @@ no_implicit_optional = True \[mypy-m] no_implicit_optional = False + +[case testNoImplicitOptionalPerModulePython2PyProjectTOML] +# flags: --config-file tmp/pyproject.toml --python-version 2.7 +import m + +[file m.py] +def f(a = None): + # type: (str) -> int + return 0 + +[file pyproject.toml] +\[tool.mypy] +no_implicit_optional = true +\[[tool.mypy.overrides]] +module = 'm' +no_implicit_optional = false + + [case testDisableErrorCode] # flags: --disable-error-code attr-defined x = 'should be fine' diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 90a13c23132b..076757110c2f 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -1812,6 +1812,30 @@ main:3: note: Revealed type is "builtins.int" [out2] main:3: note: Revealed type is "Any" + +[case testIncrementalFollowImportsVariablePyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import a +reveal_type(a.x) + +[file a.py] +x = 0 + +[file pyproject.toml] +\[tool.mypy] +follow_imports = 'normal' + +[file pyproject.toml.2] +\[tool.mypy] +follow_imports = 'skip' + +[out1] +main:3: note: Revealed type is "builtins.int" + +[out2] +main:3: note: Revealed type is "Any" + + [case testIncrementalNamedTupleInMethod] from ntcrash import nope [file ntcrash.py] diff --git a/test-data/unit/check-modules-case.test b/test-data/unit/check-modules-case.test index ebe0e31674b2..bc2e91aacfdc 100644 --- a/test-data/unit/check-modules-case.test +++ b/test-data/unit/check-modules-case.test @@ -22,6 +22,26 @@ x = 1 \[mypy] mypy_path = tmp/funky_case + +[case testCaseInsensitivityDirPyProjectTOML] +# flags: --config-file tmp/pyproject.toml + +from a import B # E: Module "a" has no attribute "B" +from other import x +reveal_type(x) # N: Revealed type is "builtins.int" + +[file a/__init__.py] + +[file a/b/__init__.py] + +[file FuNkY_CaSe/other.py] +x = 1 + +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/funky_case" + + [case testPreferPackageOverFileCase] # flags: --config-file tmp/mypy.ini import a @@ -34,6 +54,22 @@ pass \[mypy] mypy_path = tmp/funky + +[case testPreferPackageOverFileCasePyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import a + +[file funky/a.py] +/ # Deliberate syntax error, this file should not be parsed. + +[file FuNkY/a/__init__.py] +pass + +[file pyproject.toml] +\[tool.mypy] +mypy_path = "tmp/funky" + + [case testNotPreferPackageOverFileCase] import a [file a.py] @@ -53,6 +89,23 @@ x = '' \[mypy] mypy_path = tmp/xx, tmp/yy + +[case testNamespacePackagePickFirstOnMypyPathCasePyProjectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar import x +reveal_type(x) # N: Revealed type is "builtins.int" + +[file XX/foo/bar.py] +x = 0 + +[file yy/foo/bar.py] +x = '' + +[file pyproject.toml] +\[tool.mypy] +mypy_path = ["tmp/xx", "tmp/yy"] + + [case testClassicPackageInsideNamespacePackageCase] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar.baz.boo import x @@ -67,3 +120,23 @@ x = 0 [file mypy.ini] \[mypy] mypy_path = TmP/xX, TmP/yY + + +[case testClassicPackageInsideNamespacePackageCasePyProjectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bar.baz.boo import x +reveal_type(x) # N: Revealed type is "builtins.int" + +[file xx/foo/bar/baz/boo.py] +x = '' + +[file xx/foo/bar/baz/__init__.py] + +[file yy/foo/bar/baz/boo.py] +x = 0 + +[file yy/foo/bar/__init__.py] + +[file pyproject.toml] +\[tool.mypy] +mypy_path = ["TmP/xX", "TmP/yY"] diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 824ff96e5fa2..559b3868ea9b 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -2392,6 +2392,44 @@ ignore_missing_imports = True main:3: error: Cannot find implementation or library stub for module named "a.b.d" main:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports + +[case testModuleGetattrInit10PyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import a.b.c # silenced +import a.b.d # error + +[file a/__init__.pyi] +from typing import Any +def __getattr__(attr: str) -> Any: ... + +[file a/b/__init__.pyi] +# empty (i.e. complete subpackage) + +[file pyproject.toml] +\[tool.mypy] +\[[tool.mypy.overrides]] +module = 'a.b.c' +ignore_missing_imports = true + +[builtins fixtures/module.pyi] + +[out] +main:3: error: Cannot find implementation or library stub for module named "a.b.d" +main:3: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports + + +[case testMultipleModulesInOverridePyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import a +import b + +[file pyproject.toml] +\[tool.mypy] +\[[tool.mypy.overrides]] +module = ['a', 'b'] +ignore_missing_imports = true + + [case testIndirectFromImportWithinCycleUsedAsBaseClass] import a [file a.py] @@ -2649,6 +2687,30 @@ z = 0 \[mypy] mypy_path = tmp/xx, tmp/yy + +[case testNamespacePackageWithMypyPathPyProjectTOML] +# flags: --namespace-packages --config-file tmp/pyproject.toml +from foo.bax import x +from foo.bay import y +from foo.baz import z +reveal_type(x) # N: Revealed type is "builtins.int" +reveal_type(y) # N: Revealed type is "builtins.int" +reveal_type(z) # N: Revealed type is "builtins.int" + +[file xx/foo/bax.py] +x = 0 + +[file yy/foo/bay.py] +y = 0 + +[file foo/baz.py] +z = 0 + +[file pyproject.toml] +\[tool.mypy] +mypy_path = ["tmp/xx", "tmp/yy"] + + [case testClassicPackageIgnoresEarlierNamespacePackage] # flags: --namespace-packages --config-file tmp/mypy.ini from foo.bar import y diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index d2061a2594a3..bf997169b2b5 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -1764,6 +1764,38 @@ strict_optional = True \[mypy-b] strict_optional = False + +[case testNewAnalyzerTypeArgBoundCheckWithStrictOptionalPyProjectTOML] +# flags: --config-file tmp/pyproject.toml +import a + +[file b.py] +from typing import TypeVar, Generic + +x: C[None] +y: C[str] # E: Type argument "builtins.str" of "C" must be a subtype of "builtins.int" +z: C[int] + +T = TypeVar('T', bound=int) +class C(Generic[T]): + pass + +[file a.py] +from b import C + +x: C[None] # E: Type argument "None" of "C" must be a subtype of "builtins.int" +y: C[str] # E: Type argument "builtins.str" of "C" must be a subtype of "builtins.int" +z: C[int] + +[file pyproject.toml] +\[[tool.mypy.overrides]] +module = 'a' +strict_optional = true +\[[tool.mypy.overrides]] +module = 'b' +strict_optional = false + + [case testNewAnalyzerProperty] class A: @property From d9d091e7a7cf73a045c29f892b21dd66ab38bf56 Mon Sep 17 00:00:00 2001 From: Adam Weeden Date: Sun, 2 May 2021 00:39:54 -0400 Subject: [PATCH 39/39] Fix double to single quotes for test paths --- test-data/unit/check-custom-plugin.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index 180b5c4a642b..c66c9f36a260 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -17,7 +17,7 @@ def f() -> str: ... reveal_type(f()) # N: Revealed type is "builtins.int" [file pyproject.toml] \[tool.mypy] -plugins="/test-data/unit/plugins/fnplugin.py" +plugins='/test-data/unit/plugins/fnplugin.py' [case testFunctionPlugin] # flags: --config-file tmp/mypy.ini @@ -80,8 +80,8 @@ reveal_type(g()) # N: Revealed type is "builtins.str" reveal_type(h()) # N: Revealed type is "Any" [file pyproject.toml] \[tool.mypy] -plugins=["/test-data/unit/plugins/fnplugin.py", - "/test-data/unit/plugins/plugin2.py" +plugins=['/test-data/unit/plugins/fnplugin.py', + '/test-data/unit/plugins/plugin2.py' ] [case testTwoPluginsMixedType] @@ -106,7 +106,7 @@ reveal_type(g()) # N: Revealed type is "builtins.str" reveal_type(h()) # N: Revealed type is "Any" [file pyproject.toml] \[tool.mypy] -plugins=["/test-data/unit/plugins/fnplugin.py", 'plugin2'] +plugins=['/test-data/unit/plugins/fnplugin.py', 'plugin2'] [case testMissingPluginFile] # flags: --config-file tmp/mypy.ini