Skip to content

Use --no-implicit-optional by default #13401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 5 additions & 3 deletions docs/source/config_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -503,13 +503,15 @@ None and Optional handling
For more information, see the :ref:`None and Optional handling <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

Expand Down
8 changes: 2 additions & 6 deletions docs/source/kinds_of_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <mypy --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:

Expand Down
2 changes: 1 addition & 1 deletion mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 2 additions & 3 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions test-data/unit/check-fastparse.test
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class C:
[builtins fixtures/property.pyi]

[case testFastParsePerArgumentAnnotations]
# flags: --implicit-optional

class A: pass
class B: pass
Expand All @@ -130,6 +131,7 @@ def f(a, # type: A
[out]

[case testFastParsePerArgumentAnnotationsWithReturn]
# flags: --implicit-optional

class A: pass
class B: pass
Expand Down
2 changes: 2 additions & 0 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,7 @@ standard.f(None)
[file mypy.ini]
\[mypy]
strict_optional = False
implicit_optional = true
\[mypy-optional]
strict_optional = True

Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ if int():


[case testCallingFunctionsWithDefaultArgumentValues]
# flags: --implicit-optional --no-strict-optional

a, b = None, None # type: (A, B)
if int():
Expand Down
3 changes: 3 additions & 0 deletions test-data/unit/check-kwargs.test
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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()
Expand All @@ -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]
Expand Down
2 changes: 2 additions & 0 deletions test-data/unit/check-optional.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
Expand All @@ -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") \
Expand Down
17 changes: 7 additions & 10 deletions test-data/unit/check-varargs.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/fixtures/tuple.pyi
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions test-data/unit/fixtures/typing-namedtuple.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Any = 0
overload = 0
Type = 0
Literal = 0
Optional = 0

T_co = TypeVar('T_co', covariant=True)
KT = TypeVar('KT')
Expand Down