diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index 41a27a6d1b54..a96ccc1b0098 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -390,29 +390,23 @@ None and Optional handling The following flags adjust how mypy handles values of type ``None``. For more details, see :ref:`no_strict_optional`. -.. _no-implicit-optional: +.. _implicit-optional: -.. option:: --no-implicit-optional +.. option:: --implicit-optional - This flag causes mypy to stop treating arguments with a ``None`` + This flag causes mypy to treat arguments with a ``None`` default value as having an implicit :py:data:`~typing.Optional` type. - For example, by default mypy will assume that the ``x`` parameter - is of type ``Optional[int]`` in the code snippet below since - the default parameter is ``None``: + For example, if this flag is set, mypy would assume that the ``x`` + parameter is actually of type ``Optional[int]`` in the code snippet below + since the default parameter is ``None``: .. code-block:: python def foo(x: int = None) -> None: print(x) - If this flag is set, the above snippet will no longer type check: - we must now explicitly indicate that the type is ``Optional[int]``: - - .. code-block:: python - - def foo(x: Optional[int] = None) -> None: - print(x) + **Note:** This was disabled by default starting in mypy 0.980. .. option:: --no-strict-optional diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 34158ac791db..807304483e10 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -503,13 +503,15 @@ None and Optional handling For more information, see the :ref:`None and Optional handling ` section of the command line docs. -.. confval:: no_implicit_optional +.. confval:: implicit_optional :type: boolean :default: False - Changes the treatment of arguments with a default value of ``None`` by not implicitly - making their type :py:data:`~typing.Optional`. + Causes mypy to treat arguments with a ``None`` + default value as having an implicit :py:data:`~typing.Optional` type. + + **Note:** This was True by default in mypy versions 0.980 and earlier. .. confval:: strict_optional diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index b9ddaf88ad74..b575a6eac4c5 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -388,12 +388,8 @@ case you should add an explicit ``Optional[...]`` annotation (or type comment). .. note:: ``Optional[...]`` *does not* mean a function argument with a default value. - However, if the default value of an argument is ``None``, you can use - an optional type for the argument, but it's not enforced by default. - You can use the :option:`--no-implicit-optional ` command-line option to stop - treating arguments with a ``None`` default value as having an implicit - ``Optional[...]`` type. It's possible that this will become the default - behavior in the future. + It simply means that ``None`` is a valid value for the argument. This is + a common confusion because ``None`` is a common default value for arguments. .. _alternative_union_syntax: diff --git a/mypy/fastparse.py b/mypy/fastparse.py index f54f60310714..a5bd152a643c 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -1003,7 +1003,7 @@ def do_func_def( return retval def set_type_optional(self, type: Type | None, initializer: Expression | None) -> None: - if self.options.no_implicit_optional: + if not self.options.implicit_optional: return # Indicate that type should be wrapped in an Optional if arg is initialized to None. optional = isinstance(initializer, NameExpr) and initializer.name == "None" diff --git a/mypy/main.py b/mypy/main.py index 3dce045be75b..737c0dece0bc 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -720,10 +720,9 @@ def add_invertible_flag( "https://mypy.readthedocs.io/en/stable/kinds_of_types.html#no-strict-optional", ) add_invertible_flag( - "--no-implicit-optional", + "--implicit-optional", default=False, - strict_flag=True, - help="Don't assume arguments with default values of None are Optional", + help="Assume arguments with default values of None are Optional", group=none_group, ) none_group.add_argument("--strict-optional", action="store_true", help=argparse.SUPPRESS) diff --git a/mypy/options.py b/mypy/options.py index fb7bb8e43bbb..8be59f25dfa3 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -39,14 +39,14 @@ class BuildType: "disallow_untyped_defs", "enable_error_code", "enabled_error_codes", - "follow_imports", "follow_imports_for_stubs", + "follow_imports", "ignore_errors", "ignore_missing_imports", + "implicit_optional", "implicit_reexport", "local_partial_types", "mypyc", - "no_implicit_optional", "strict_concatenate", "strict_equality", "strict_optional", @@ -160,8 +160,8 @@ def __init__(self) -> None: self.color_output = True self.error_summary = True - # Don't assume arguments with default values of None are Optional - self.no_implicit_optional = False + # Assume arguments with default values of None are Optional + self.implicit_optional = False # Don't re-export names unless they are imported with `from ... as ...` self.implicit_reexport = True diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a3c0b79e01bd..eae75e6766ef 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2933,8 +2933,9 @@ class B: pass [case testConstructInstanceWith__new__] +from typing import Optional class C: - def __new__(cls, foo: int = None) -> 'C': + def __new__(cls, foo: Optional[int] = None) -> 'C': obj = object.__new__(cls) return obj diff --git a/test-data/unit/check-fastparse.test b/test-data/unit/check-fastparse.test index 848d91b1659d..f172a9727d49 100644 --- a/test-data/unit/check-fastparse.test +++ b/test-data/unit/check-fastparse.test @@ -106,6 +106,7 @@ class C: [builtins fixtures/property.pyi] [case testFastParsePerArgumentAnnotations] +# flags: --implicit-optional class A: pass class B: pass @@ -130,6 +131,7 @@ def f(a, # type: A [out] [case testFastParsePerArgumentAnnotationsWithReturn] +# flags: --implicit-optional class A: pass class B: pass diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 9fdd5ea2232c..cc1e46d86caa 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -843,6 +843,7 @@ standard.f(None) [file mypy.ini] \[mypy] strict_optional = False +implicit_optional = true \[mypy-optional] strict_optional = True @@ -862,6 +863,7 @@ standard.f(None) [file pyproject.toml] \[tool.mypy] strict_optional = false +implicit_optional = true \[[tool.mypy.overrides]] module = 'optional' strict_optional = true diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 61f6c8ad02fc..bbea884c95b3 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -465,6 +465,7 @@ if int(): [case testCallingFunctionsWithDefaultArgumentValues] +# flags: --implicit-optional --no-strict-optional a, b = None, None # type: (A, B) if int(): diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 9f8de1265ee7..e59c295b58ac 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -24,6 +24,7 @@ class A: pass class B: pass [case testOneOfSeveralOptionalKeywordArguments] +# flags: --implicit-optional import typing def f(a: 'A' = None, b: 'B' = None, c: 'C' = None) -> None: pass f(a=A()) @@ -219,6 +220,7 @@ f(a, **b) [builtins fixtures/dict.pyi] [case testKeywordArgAfterVarArgs] +# flags: --implicit-optional import typing def f(*a: 'A', b: 'B' = None) -> None: pass f() @@ -235,6 +237,7 @@ class B: pass [builtins fixtures/list.pyi] [case testKeywordArgAfterVarArgsWithBothCallerAndCalleeVarArgs] +# flags: --implicit-optional --no-strict-optional from typing import List def f(*a: 'A', b: 'B' = None) -> None: pass a = None # type: List[A] diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 03b076fb09db..d40ebf993581 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -127,6 +127,7 @@ def f(x: None) -> None: pass f(None) [case testInferOptionalFromDefaultNone] +# flags: --implicit-optional def f(x: int = None) -> None: x + 1 # E: Unsupported left operand type for + ("None") \ # N: Left operand is of type "Optional[int]" @@ -140,6 +141,7 @@ def f(x: int = None) -> None: # E: Incompatible default for argument "x" (defau [out] [case testInferOptionalFromDefaultNoneComment] +# flags: --implicit-optional def f(x=None): # type: (int) -> None x + 1 # E: Unsupported left operand type for + ("None") \ diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index ac68e20028a7..d5c60bcf450e 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -82,6 +82,7 @@ class C: pass [builtins fixtures/list.pyi] [case testCallingVarArgsFunctionWithDefaultArgs] +# flags: --implicit-optional --no-strict-optional a = None # type: A b = None # type: B @@ -388,12 +389,14 @@ class B(A): pass [builtins fixtures/list.pyi] [case testCallerVarArgsAndDefaultArgs] +# flags: --implicit-optional --no-strict-optional a, b = None, None # type: (A, B) -f(*()) # Fail -f(a, *[a]) # Fail -f(a, b, *[a]) # Fail -f(*(a, a, b)) # Fail +f(*()) # E: Too few arguments for "f" +f(a, *[a]) # E: Argument 2 to "f" has incompatible type "*List[A]"; expected "Optional[B]" \ + # E: Argument 2 to "f" has incompatible type "*List[A]"; expected "B" +f(a, b, *[a]) # E: Argument 3 to "f" has incompatible type "*List[A]"; expected "B" +f(*(a, a, b)) # E: Argument 1 to "f" has incompatible type "*Tuple[A, A, B]"; expected "Optional[B]" f(*(a,)) f(*(a, b)) f(*(a, b, b, b)) @@ -407,12 +410,6 @@ def f(a: 'A', b: 'B' = None, *c: 'B') -> None: class A: pass class B: pass [builtins fixtures/list.pyi] -[out] -main:3: error: Too few arguments for "f" -main:4: error: Argument 2 to "f" has incompatible type "*List[A]"; expected "Optional[B]" -main:4: error: Argument 2 to "f" has incompatible type "*List[A]"; expected "B" -main:5: error: Argument 3 to "f" has incompatible type "*List[A]"; expected "B" -main:6: error: Argument 1 to "f" has incompatible type "*Tuple[A, A, B]"; expected "Optional[B]" [case testVarArgsAfterKeywordArgInCall1] # see: mypy issue #2729 diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 9d8857301425..cb4cffa519e6 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -7942,7 +7942,7 @@ class Foo(a.I): == [case testImplicitOptionalRefresh1] -# flags: --strict-optional +# flags: --strict-optional --implicit-optional from x import f def foo(x: int = None) -> None: f() diff --git a/test-data/unit/fixtures/tuple.pyi b/test-data/unit/fixtures/tuple.pyi index 6f40356bb5f0..a919b5a37b28 100644 --- a/test-data/unit/fixtures/tuple.pyi +++ b/test-data/unit/fixtures/tuple.pyi @@ -1,6 +1,6 @@ # Builtins stub used in tuple-related test cases. -from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Any, overload, Tuple, Type +from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Optional, overload, Tuple, Type T = TypeVar("T") Tco = TypeVar('Tco', covariant=True) @@ -47,6 +47,6 @@ class list(Sequence[T], Generic[T]): def isinstance(x: object, t: type) -> bool: pass -def sum(iterable: Iterable[T], start: T = None) -> T: pass +def sum(iterable: Iterable[T], start: Optional[T] = None) -> T: pass class BaseException: pass diff --git a/test-data/unit/fixtures/typing-namedtuple.pyi b/test-data/unit/fixtures/typing-namedtuple.pyi index 3404dc69de44..d51134ead599 100644 --- a/test-data/unit/fixtures/typing-namedtuple.pyi +++ b/test-data/unit/fixtures/typing-namedtuple.pyi @@ -4,6 +4,7 @@ Any = 0 overload = 0 Type = 0 Literal = 0 +Optional = 0 T_co = TypeVar('T_co', covariant=True) KT = TypeVar('KT')