Skip to content

Commit

Permalink
Merge branch 'master' into fix-enum-with-property-match-exhaustion
Browse files Browse the repository at this point in the history
  • Loading branch information
terencehonles committed Sep 19, 2024
2 parents 50d0967 + a646f33 commit cdbbbb5
Show file tree
Hide file tree
Showing 122 changed files with 2,419 additions and 704 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.8'
python-version: '3.12'
- name: Install tox
run: pip install tox==4.11.0
- name: Setup tox environment
Expand Down
310 changes: 155 additions & 155 deletions CHANGELOG.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/requirements-docs.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
sphinx>=5.1.0
furo>=2022.3.4
myst-parser>=4.0.0
3 changes: 3 additions & 0 deletions docs/source/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<!-- This file includes mypy/CHANGELOG.md into mypy documentation -->
```{include} ../../CHANGELOG.md
```
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ["sphinx.ext.intersphinx", "docs.source.html_builder"]
extensions = ["sphinx.ext.intersphinx", "docs.source.html_builder", "myst_parser"]

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
Expand Down
28 changes: 28 additions & 0 deletions docs/source/error_code_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,34 @@ types you expect.

See :ref:`overloading <function-overloading>` for more explanation.


.. _code-overload-cannot-match:

Check for overload signatures that cannot match [overload-cannot-match]
--------------------------------------------------------------------------

Warn if an ``@overload`` variant can never be matched, because an earlier
overload has a wider signature. For example, this can happen if the two
overloads accept the same parameters and each parameter on the first overload
has the same type or a wider type than the corresponding parameter on the second
overload.

Example:

.. code-block:: python
from typing import overload, Union
@overload
def process(response1: object, response2: object) -> object:
...
@overload
def process(response1: int, response2: int) -> int: # E: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader [overload-cannot-match]
...
def process(response1: object, response2: object) -> object:
return response1 + response2
.. _code-annotation-unchecked:

Notify about an annotation in an unchecked function [annotation-unchecked]
Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Contents
error_code_list2
additional_features
faq
changelog

.. toctree::
:hidden:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
From 44bc98bd50e7170887f0740b53ed95a8eb04f00e Mon Sep 17 00:00:00 2001
From 58c6a6ab863c1c38e95ccafaf13792ed9c00e499 Mon Sep 17 00:00:00 2001
From: Shantanu <[email protected]>
Date: Sat, 29 Oct 2022 12:47:21 -0700
Subject: [PATCH] Revert sum literal integer change (#13961)
Expand All @@ -19,18 +19,18 @@ within mypy, I might pursue upstreaming this in typeshed.
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi
index 99919c64c..680cd5561 100644
index ea9f8c894..a6065cc67 100644
--- a/mypy/typeshed/stdlib/builtins.pyi
+++ b/mypy/typeshed/stdlib/builtins.pyi
@@ -1596,7 +1596,7 @@ _SupportsSumNoDefaultT = TypeVar("_SupportsSumNoDefaultT", bound=_SupportsSumWit
@@ -1653,7 +1653,7 @@ _SupportsSumNoDefaultT = TypeVar("_SupportsSumNoDefaultT", bound=_SupportsSumWit
# without creating many false-positive errors (see #7578).
# Instead, we special-case the most common examples of this: bool and literal integers.
@overload
-def sum(iterable: Iterable[bool | _LiteralInteger], /, start: int = 0) -> int: ... # type: ignore[overload-overlap]
+def sum(iterable: Iterable[bool], /, start: int = 0) -> int: ... # type: ignore[overload-overlap]
-def sum(iterable: Iterable[bool | _LiteralInteger], /, start: int = 0) -> int: ...
+def sum(iterable: Iterable[bool], /, start: int = 0) -> int: ...
@overload
def sum(iterable: Iterable[_SupportsSumNoDefaultT], /) -> _SupportsSumNoDefaultT | Literal[0]: ...
@overload
--
2.39.3 (Apple Git-146)
2.46.0

15 changes: 13 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,8 @@ def extract_callable_type(self, inner_type: Type | None, ctx: Context) -> Callab
inner_type = get_proper_type(inner_type)
outer_type: CallableType | None = None
if inner_type is not None and not isinstance(inner_type, AnyType):
if isinstance(inner_type, TypeVarLikeType):
inner_type = get_proper_type(inner_type.upper_bound)
if isinstance(inner_type, TypeType):
if isinstance(inner_type.item, Instance):
inner_type = expand_type_by_instance(
Expand Down Expand Up @@ -5649,7 +5651,16 @@ def _is_truthy_type(self, t: ProperType) -> bool:
)
)

def _check_for_truthy_type(self, t: Type, expr: Expression) -> None:
def check_for_truthy_type(self, t: Type, expr: Expression) -> None:
"""
Check if a type can have a truthy value.
Used in checks like::
if x: # <---
not x # <---
"""
if not state.strict_optional:
return # if everything can be None, all bets are off

Expand Down Expand Up @@ -6143,7 +6154,7 @@ def has_no_custom_eq_checks(t: Type) -> bool:
if in_boolean_context:
# We don't check `:=` values in expressions like `(a := A())`,
# because they produce two error messages.
self._check_for_truthy_type(original_vartype, node)
self.check_for_truthy_type(original_vartype, node)
vartype = try_expanding_sum_type_to_union(original_vartype, "builtins.bool")

if_type = true_only(vartype)
Expand Down
1 change: 1 addition & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4256,6 +4256,7 @@ def visit_unary_expr(self, e: UnaryExpr) -> Type:
op = e.op
if op == "not":
result: Type = self.bool_type()
self.chk.check_for_truthy_type(operand_type, e.expr)
else:
method = operators.unary_op_methods[op]
result, method_type = self.check_method_call_by_name(method, operand_type, [], [], e)
Expand Down
8 changes: 8 additions & 0 deletions mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,14 @@ def __hash__(self) -> int:
# This is a catch-all for remaining uncategorized errors.
MISC: Final[ErrorCode] = ErrorCode("misc", "Miscellaneous other checks", "General")

OVERLOAD_CANNOT_MATCH: Final[ErrorCode] = ErrorCode(
"overload-cannot-match",
"Warn if an @overload signature can never be matched",
"General",
sub_code_of=MISC,
)


OVERLOAD_OVERLAP: Final[ErrorCode] = ErrorCode(
"overload-overlap",
"Warn if multiple @overload variants overlap in unsafe ways",
Expand Down
39 changes: 27 additions & 12 deletions mypy/exprtotype.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

from __future__ import annotations

from typing import Callable

from mypy.fastparse import parse_type_string
from mypy.nodes import (
MISSING_FALLBACK,
BytesExpr,
CallExpr,
ComplexExpr,
Context,
DictExpr,
EllipsisExpr,
Expression,
Expand All @@ -21,6 +24,7 @@
RefExpr,
StarExpr,
StrExpr,
SymbolTableNode,
TupleExpr,
UnaryExpr,
get_member_expr_fullname,
Expand Down Expand Up @@ -63,12 +67,16 @@ def expr_to_unanalyzed_type(
allow_new_syntax: bool = False,
_parent: Expression | None = None,
allow_unpack: bool = False,
lookup_qualified: Callable[[str, Context], SymbolTableNode | None] | None = None,
) -> ProperType:
"""Translate an expression to the corresponding type.
The result is not semantically analyzed. It can be UnboundType or TypeList.
Raise TypeTranslationError if the expression cannot represent a type.
If lookup_qualified is not provided, the expression is expected to be semantically
analyzed.
If allow_new_syntax is True, allow all type syntax independent of the target
Python version (used in stubs).
Expand Down Expand Up @@ -101,19 +109,26 @@ def expr_to_unanalyzed_type(
else:
args = [expr.index]

if isinstance(expr.base, RefExpr) and expr.base.fullname in ANNOTATED_TYPE_NAMES:
# TODO: this is not the optimal solution as we are basically getting rid
# of the Annotation definition and only returning the type information,
# losing all the annotations.
if isinstance(expr.base, RefExpr):
# Check if the type is Annotated[...]. For this we need the fullname,
# which must be looked up if the expression hasn't been semantically analyzed.
base_fullname = None
if lookup_qualified is not None:
sym = lookup_qualified(base.name, expr)
if sym and sym.node:
base_fullname = sym.node.fullname
else:
base_fullname = expr.base.fullname

return expr_to_unanalyzed_type(args[0], options, allow_new_syntax, expr)
else:
base.args = tuple(
expr_to_unanalyzed_type(
arg, options, allow_new_syntax, expr, allow_unpack=True
)
for arg in args
)
if base_fullname is not None and base_fullname in ANNOTATED_TYPE_NAMES:
# TODO: this is not the optimal solution as we are basically getting rid
# of the Annotation definition and only returning the type information,
# losing all the annotations.
return expr_to_unanalyzed_type(args[0], options, allow_new_syntax, expr)
base.args = tuple(
expr_to_unanalyzed_type(arg, options, allow_new_syntax, expr, allow_unpack=True)
for arg in args
)
if not base.args:
base.empty_tuple_index = True
return base
Expand Down
4 changes: 1 addition & 3 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,10 @@ def parse_type_string(
"""
try:
_, node = parse_type_comment(f"({expr_string})", line=line, column=column, errors=None)
if isinstance(node, UnboundType) and node.original_str_expr is None:
if isinstance(node, (UnboundType, UnionType)) and node.original_str_expr is None:
node.original_str_expr = expr_string
node.original_str_fallback = expr_fallback_name
return node
elif isinstance(node, UnionType):
return node
else:
return RawExpressionType(expr_string, expr_fallback_name, line, column)
except (SyntaxError, ValueError):
Expand Down
1 change: 1 addition & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,7 @@ def overloaded_signature_will_never_match(
index1=index1, index2=index2
),
context,
code=codes.OVERLOAD_CANNOT_MATCH,
)

def overloaded_signatures_typevar_specific(self, index: int, context: Context) -> None:
Expand Down
2 changes: 2 additions & 0 deletions mypy/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,8 @@ def on_finish(self) -> None:
self.root_package.covered_lines, self.root_package.total_lines
)
self.root.attrib["branch-rate"] = "0"
self.root.attrib["lines-covered"] = str(self.root_package.covered_lines)
self.root.attrib["lines-valid"] = str(self.root_package.total_lines)
sources = etree.SubElement(self.root, "sources")
source_element = etree.SubElement(sources, "source")
source_element.text = os.getcwd()
Expand Down
10 changes: 9 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,11 @@ def file_context(
self.num_incomplete_refs = 0

if active_type:
enclosing_fullname = active_type.fullname.rsplit(".", 1)[0]
if "." in enclosing_fullname:
enclosing_node = self.lookup_fully_qualified_or_none(enclosing_fullname)
if enclosing_node and isinstance(enclosing_node.node, TypeInfo):
self._type = enclosing_node.node
self.push_type_args(active_type.defn.type_args, active_type.defn)
self.incomplete_type_stack.append(False)
scope.enter_class(active_type)
Expand Down Expand Up @@ -3744,7 +3749,9 @@ def analyze_alias(
dynamic = bool(self.function_stack and self.function_stack[-1].is_dynamic())
global_scope = not self.type and not self.function_stack
try:
typ = expr_to_unanalyzed_type(rvalue, self.options, self.is_stub_file)
typ = expr_to_unanalyzed_type(
rvalue, self.options, self.is_stub_file, lookup_qualified=self.lookup_qualified
)
except TypeTranslationError:
self.fail(
"Invalid type alias: expression is not a valid type", rvalue, code=codes.VALID_TYPE
Expand Down Expand Up @@ -6951,6 +6958,7 @@ def name_not_defined(self, name: str, ctx: Context, namespace: str | None = None
namespace is None
and self.type
and not self.is_func_scope()
and self.incomplete_type_stack
and self.incomplete_type_stack[-1]
and not self.final_iteration
):
Expand Down
39 changes: 34 additions & 5 deletions mypy/stubgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import mypy.parse
import mypy.traverser
import mypy.util
import mypy.version
from mypy.build import build
from mypy.errors import CompileError, Errors
from mypy.find_sources import InvalidSourceList, create_source_list
Expand Down Expand Up @@ -304,9 +305,26 @@ def visit_name_expr(self, node: NameExpr) -> str:
def visit_member_expr(self, o: MemberExpr) -> str:
return self._visit_ref_expr(o)

def visit_str_expr(self, node: StrExpr) -> str:
def _visit_literal_node(
self, node: StrExpr | BytesExpr | IntExpr | FloatExpr | ComplexExpr
) -> str:
return repr(node.value)

def visit_str_expr(self, node: StrExpr) -> str:
return self._visit_literal_node(node)

def visit_bytes_expr(self, node: BytesExpr) -> str:
return f"b{self._visit_literal_node(node)}"

def visit_int_expr(self, node: IntExpr) -> str:
return self._visit_literal_node(node)

def visit_float_expr(self, node: FloatExpr) -> str:
return self._visit_literal_node(node)

def visit_complex_expr(self, node: ComplexExpr) -> str:
return self._visit_literal_node(node)

def visit_index_expr(self, node: IndexExpr) -> str:
base_fullname = self.stubgen.get_fullname(node.base)
if base_fullname == "typing.Union":
Expand Down Expand Up @@ -570,8 +588,8 @@ def _get_func_return(self, o: FuncDef, ctx: FunctionContext) -> str | None:
if has_yield_expression(o) or has_yield_from_expression(o):
generator_name = self.add_name("collections.abc.Generator")
yield_name = "None"
send_name = "None"
return_name = "None"
send_name: str | None = None
return_name: str | None = None
if has_yield_from_expression(o):
yield_name = send_name = self.add_name("_typeshed.Incomplete")
else:
Expand All @@ -582,7 +600,14 @@ def _get_func_return(self, o: FuncDef, ctx: FunctionContext) -> str | None:
send_name = self.add_name("_typeshed.Incomplete")
if has_return_statement(o):
return_name = self.add_name("_typeshed.Incomplete")
return f"{generator_name}[{yield_name}, {send_name}, {return_name}]"
if return_name is not None:
if send_name is None:
send_name = "None"
return f"{generator_name}[{yield_name}, {send_name}, {return_name}]"
elif send_name is not None:
return f"{generator_name}[{yield_name}, {send_name}]"
else:
return f"{generator_name}[{yield_name}]"
if not has_return_statement(o) and o.abstract_status == NOT_ABSTRACT:
return "None"
return None
Expand Down Expand Up @@ -804,7 +829,8 @@ def get_base_types(self, cdef: ClassDef) -> list[str]:
for name, value in cdef.keywords.items():
if name == "metaclass":
continue # handled separately
base_types.append(f"{name}={value.accept(p)}")
processed_value = value.accept(p) or "..." # at least, don't crash
base_types.append(f"{name}={processed_value}")
return base_types

def get_class_decorators(self, cdef: ClassDef) -> list[str]:
Expand Down Expand Up @@ -1847,6 +1873,9 @@ def parse_options(args: list[str]) -> Options:
dest="files",
help="generate stubs for given files or directories",
)
parser.add_argument(
"--version", action="version", version="%(prog)s " + mypy.version.__version__
)

ns = parser.parse_args(args)

Expand Down
3 changes: 3 additions & 0 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,9 @@ def _verify_readonly_property(stub: nodes.Decorator, runtime: Any) -> Iterator[s
if isinstance(runtime, property):
yield from _verify_final_method(stub.func, runtime.fget, MISSING)
return
if isinstance(runtime, functools.cached_property):
yield from _verify_final_method(stub.func, runtime.func, MISSING)
return
if inspect.isdatadescriptor(runtime):
# It's enough like a property...
return
Expand Down
Loading

0 comments on commit cdbbbb5

Please sign in to comment.