Skip to content

Commit

Permalink
Merge branch 'master' into variance-self-type
Browse files Browse the repository at this point in the history
  • Loading branch information
JukkaL committed Sep 20, 2024
2 parents 5cc88e8 + 5dfc7d9 commit d201e50
Show file tree
Hide file tree
Showing 18 changed files with 242 additions and 291 deletions.
15 changes: 1 addition & 14 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,7 @@ format into the specified directory.
Enabling incomplete/experimental features
*****************************************

.. option:: --enable-incomplete-feature {PreciseTupleTypes, NewGenericSyntax, InlineTypedDict}
.. option:: --enable-incomplete-feature {PreciseTupleTypes, InlineTypedDict}

Some features may require several mypy releases to implement, for example
due to their complexity, potential for backwards incompatibility, or
Expand Down Expand Up @@ -1055,19 +1055,6 @@ List of currently incomplete/experimental features:
# Without PreciseTupleTypes: tuple[int, ...]
# With PreciseTupleTypes: tuple[()] | tuple[int] | tuple[int, int]
* ``NewGenericSyntax``: this feature enables support for syntax defined
by :pep:`695`. For example:

.. code-block:: python
class Container[T]: # defines a generic class
content: T
def first[T](items: list[T]) -> T: # defines a generic function
return items[0]
type Items[T] = list[tuple[T, T]] # defines a generic type alias
* ``InlineTypedDict``: this feature enables non-standard syntax for inline
:ref:`TypedDicts <typeddict>`, for example:

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
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
66 changes: 14 additions & 52 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
YieldFromExpr,
check_arg_names,
)
from mypy.options import NEW_GENERIC_SYNTAX, Options
from mypy.options import Options
from mypy.patterns import (
AsPattern,
ClassPattern,
Expand Down Expand Up @@ -965,19 +965,7 @@ def do_func_def(
return_type = AnyType(TypeOfAny.from_error)
else:
if sys.version_info >= (3, 12) and n.type_params:
if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature:
explicit_type_params = self.translate_type_params(n.type_params)
else:
self.fail(
ErrorMessage(
"PEP 695 generics are not yet supported. "
"Use --enable-incomplete-feature=NewGenericSyntax for experimental support",
code=codes.VALID_TYPE,
),
n.type_params[0].lineno,
n.type_params[0].col_offset,
blocker=False,
)
explicit_type_params = self.translate_type_params(n.type_params)

arg_types = [a.type_annotation for a in args]
return_type = TypeConverter(
Expand Down Expand Up @@ -1157,19 +1145,7 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
explicit_type_params: list[TypeParam] | None = None

if sys.version_info >= (3, 12) and n.type_params:
if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature:
explicit_type_params = self.translate_type_params(n.type_params)
else:
self.fail(
ErrorMessage(
"PEP 695 generics are not yet supported. "
"Use --enable-incomplete-feature=NewGenericSyntax for experimental support",
code=codes.VALID_TYPE,
),
n.type_params[0].lineno,
n.type_params[0].col_offset,
blocker=False,
)
explicit_type_params = self.translate_type_params(n.type_params)

cdef = ClassDef(
n.name,
Expand Down Expand Up @@ -1843,31 +1819,17 @@ def validate_type_alias(self, n: ast_TypeAlias) -> None:
# TypeAlias(identifier name, type_param* type_params, expr value)
def visit_TypeAlias(self, n: ast_TypeAlias) -> TypeAliasStmt | AssignmentStmt:
node: TypeAliasStmt | AssignmentStmt
if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature:
type_params = self.translate_type_params(n.type_params)
self.validate_type_alias(n)
value = self.visit(n.value)
# Since the value is evaluated lazily, wrap the value inside a lambda.
# This helps mypyc.
ret = ReturnStmt(value)
self.set_line(ret, n.value)
value_func = LambdaExpr(body=Block([ret]))
self.set_line(value_func, n.value)
node = TypeAliasStmt(self.visit_Name(n.name), type_params, value_func)
return self.set_line(node, n)
else:
self.fail(
ErrorMessage(
"PEP 695 type aliases are not yet supported. "
"Use --enable-incomplete-feature=NewGenericSyntax for experimental support",
code=codes.VALID_TYPE,
),
n.lineno,
n.col_offset,
blocker=False,
)
node = AssignmentStmt([NameExpr(n.name.id)], self.visit(n.value))
return self.set_line(node, n)
type_params = self.translate_type_params(n.type_params)
self.validate_type_alias(n)
value = self.visit(n.value)
# Since the value is evaluated lazily, wrap the value inside a lambda.
# This helps mypyc.
ret = ReturnStmt(value)
self.set_line(ret, n.value)
value_func = LambdaExpr(body=Block([ret]))
self.set_line(value_func, n.value)
node = TypeAliasStmt(self.visit_Name(n.name), type_params, value_func)
return self.set_line(node, n)


class TypeConverter:
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
4 changes: 2 additions & 2 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ class BuildType:
PRECISE_TUPLE_TYPES: Final = "PreciseTupleTypes"
NEW_GENERIC_SYNTAX: Final = "NewGenericSyntax"
INLINE_TYPEDDICT: Final = "InlineTypedDict"
INCOMPLETE_FEATURES: Final = frozenset((PRECISE_TUPLE_TYPES, NEW_GENERIC_SYNTAX, INLINE_TYPEDDICT))
COMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE, UNPACK))
INCOMPLETE_FEATURES: Final = frozenset((PRECISE_TUPLE_TYPES, INLINE_TYPEDDICT))
COMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE, UNPACK, NEW_GENERIC_SYNTAX))


class Options:
Expand Down
16 changes: 16 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1824,6 +1824,8 @@ def analyze_class(self, defn: ClassDef) -> None:
defn, bases, context=defn
)

self.check_type_alias_bases(bases)

for tvd in tvar_defs:
if isinstance(tvd, TypeVarType) and any(
has_placeholder(t) for t in [tvd.upper_bound] + tvd.values
Expand Down Expand Up @@ -1895,6 +1897,19 @@ def analyze_class(self, defn: ClassDef) -> None:

self.analyze_class_body_common(defn)

def check_type_alias_bases(self, bases: list[Expression]) -> None:
for base in bases:
if isinstance(base, IndexExpr):
base = base.base
if (
isinstance(base, RefExpr)
and isinstance(base.node, TypeAlias)
and base.node.python_3_12_type_alias
):
self.fail(
'Type alias defined using "type" statement not valid as base class', base
)

def setup_type_vars(self, defn: ClassDef, tvar_defs: list[TypeVarLikeType]) -> None:
defn.type_vars = tvar_defs
defn.info.type_vars = []
Expand Down Expand Up @@ -6955,6 +6970,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
9 changes: 9 additions & 0 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2041,6 +2041,15 @@ def infer_variance(info: TypeInfo, i: int) -> bool:
contra = False
if settable:
co = False

# Infer variance from base classes, in case they have explicit variances
for base in info.bases:
base2 = expand_type(base, {tvar.id: object_type})
if not is_subtype(base, base2):
co = False
if not is_subtype(base2, base):
contra = False

if co:
v = COVARIANT
elif contra:
Expand Down
2 changes: 0 additions & 2 deletions mypyc/test-data/run-python312.test
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[case testPEP695Basics]
# flags: --enable-incomplete-feature=NewGenericSyntax
from typing import Any, TypeAliasType, cast

from testutil import assertRaises
Expand Down Expand Up @@ -192,7 +191,6 @@ def test_recursive_type_alias() -> None:
[typing fixtures/typing-full.pyi]

[case testPEP695GenericTypeAlias]
# flags: --enable-incomplete-feature=NewGenericSyntax
from typing import Callable
from types import GenericAlias

Expand Down
1 change: 0 additions & 1 deletion mypyc/test/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) ->
options.preserve_asts = True
options.allow_empty_bodies = True
options.incremental = self.separate
options.enable_incomplete_feature.append("NewGenericSyntax")

# Avoid checking modules/packages named 'unchecked', to provide a way
# to test interacting with code we don't have types for.
Expand Down
14 changes: 14 additions & 0 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1222,3 +1222,17 @@ def f(x: str) -> TypeIs[int]: # E: Narrowed type "int" is not a subtype of inpu
pass

[builtins fixtures/tuple.pyi]


[case testOverloadedFunctionSignature]
from typing import overload, Union

@overload
def process(response1: float,response2: float) -> float:
...
@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,response2)-> Union[float,int]:
return response1 + response2
Loading

0 comments on commit d201e50

Please sign in to comment.