From bf2a0b36984543471871191a90e6e9a24ca027e7 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 13 Nov 2022 13:57:15 -0800 Subject: [PATCH 1/8] stubtest: if a default is present in the stub, check that it is correct Helps with python/typeshed#8988. --- mypy/stubtest.py | 191 ++++++++++++++++++++++++++++++++++++++ mypy/test/teststubtest.py | 55 ++++++++++- 2 files changed, 245 insertions(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 87ccbd3176df4..e0ad7d18773f3 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -7,6 +7,7 @@ from __future__ import annotations import argparse +import ast import collections.abc import copy import enum @@ -29,6 +30,7 @@ import mypy.build import mypy.modulefinder +import mypy.nodes import mypy.state import mypy.types import mypy.version @@ -36,6 +38,7 @@ from mypy.config_parser import parse_config_file from mypy.options import Options from mypy.util import FancyFormatter, bytes_to_human_readable_repr, is_dunder, plural_s +from mypy.visitor import ExpressionVisitor class Missing: @@ -540,6 +543,179 @@ def names_approx_match(a: str, b: str) -> bool: ) +class _NodeEvaluator(ExpressionVisitor[object]): + def visit_int_expr(self, o: mypy.nodes.IntExpr) -> int: + return o.value + + def visit_str_expr(self, o: mypy.nodes.StrExpr) -> str: + return o.value + + def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> bytes: + try: + return ast.literal_eval(f"b'{o.value}'") + except SyntaxError: + return ast.literal_eval(f'b"{o.value}"') + + def visit_float_expr(self, o: mypy.nodes.FloatExpr) -> float: + return o.value + + def visit_complex_expr(self, o: mypy.nodes.ComplexExpr) -> object: + return o.value + + def visit_ellipsis(self, o: mypy.nodes.EllipsisExpr) -> object: + return Ellipsis + + def visit_star_expr(self, o: mypy.nodes.StarExpr) -> object: + return MISSING + + def visit_name_expr(self, o: mypy.nodes.NameExpr) -> object: + if o.name == "True": + return True + elif o.name == "False": + return False + elif o.name == "None": + return None + return MISSING + + def visit_member_expr(self, o: mypy.nodes.MemberExpr) -> object: + return MISSING + + def visit_yield_from_expr(self, o: mypy.nodes.YieldFromExpr) -> object: + return MISSING + + def visit_yield_expr(self, o: mypy.nodes.YieldExpr) -> object: + return MISSING + + def visit_call_expr(self, o: mypy.nodes.CallExpr) -> object: + return MISSING + + def visit_op_expr(self, o: mypy.nodes.OpExpr) -> object: + return MISSING + + def visit_comparison_expr(self, o: mypy.nodes.ComparisonExpr) -> object: + return MISSING + + def visit_cast_expr(self, o: mypy.nodes.CastExpr) -> object: + return o.expr.accept(self) + + def visit_assert_type_expr(self, o: mypy.nodes.AssertTypeExpr) -> object: + return o.expr.accept(self) + + def visit_reveal_expr(self, o: mypy.nodes.RevealExpr) -> object: + return MISSING + + def visit_super_expr(self, o: mypy.nodes.SuperExpr) -> object: + return MISSING + + def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> object: + operand = o.expr.accept(self) + if operand is MISSING: + return MISSING + if o.op == "-": + if isinstance(operand, (int, float, complex)): + return -operand + elif o.op == "+": + if isinstance(operand, (int, float, complex)): + return +operand + elif o.op == "~": + if isinstance(operand, int): + return ~operand + elif o.op == "not": + if isinstance(operand, (bool, int, float, str, bytes)): + return not operand + return MISSING + + def visit_assignment_expr(self, o: mypy.nodes.AssignmentExpr) -> object: + return o.value.accept(self) + + def visit_list_expr(self, o: mypy.nodes.ListExpr) -> object: + items = [item.accept(self) for item in o.items] + if all(item is not MISSING for item in items): + return items + return MISSING + + def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> object: + items = [ + (MISSING if key is None else key.accept(self), value.accept(self)) + for key, value in o.items + ] + if all(key is not MISSING and value is not None for key, value in items): + return dict(items) + return MISSING + + def visit_tuple_expr(self, o: mypy.nodes.TupleExpr) -> object: + items = [item.accept(self) for item in o.items] + if all(item is not MISSING for item in items): + return tuple(items) + return MISSING + + def visit_set_expr(self, o: mypy.nodes.SetExpr) -> object: + items = [item.accept(self) for item in o.items] + if all(item is not MISSING for item in items): + return set(items) + return MISSING + + def visit_index_expr(self, o: mypy.nodes.IndexExpr) -> object: + return MISSING + + def visit_type_application(self, o: mypy.nodes.TypeApplication) -> object: + return MISSING + + def visit_lambda_expr(self, o: mypy.nodes.LambdaExpr) -> object: + return MISSING + + def visit_list_comprehension(self, o: mypy.nodes.ListComprehension) -> object: + return MISSING + + def visit_set_comprehension(self, o: mypy.nodes.SetComprehension) -> object: + return MISSING + + def visit_dictionary_comprehension(self, o: mypy.nodes.DictionaryComprehension) -> object: + return MISSING + + def visit_generator_expr(self, o: mypy.nodes.GeneratorExpr) -> object: + return MISSING + + def visit_slice_expr(self, o: mypy.nodes.SliceExpr) -> object: + return MISSING + + def visit_conditional_expr(self, o: mypy.nodes.ConditionalExpr) -> object: + return MISSING + + def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> object: + return MISSING + + def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> object: + return MISSING + + def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> object: + return MISSING + + def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> object: + return MISSING + + def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> object: + return MISSING + + def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> object: + return MISSING + + def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> object: + return MISSING + + def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> object: + return MISSING + + def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> object: + return MISSING + + def visit_await_expr(self, o: mypy.nodes.AwaitExpr) -> object: + return MISSING + + def visit_temp_node(self, o: mypy.nodes.TempNode) -> object: + return MISSING + + def _verify_arg_default_value( stub_arg: nodes.Argument, runtime_arg: inspect.Parameter ) -> Iterator[str]: @@ -573,6 +749,21 @@ def _verify_arg_default_value( f"has a default value of type {runtime_type}, " f"which is incompatible with stub argument type {stub_type}" ) + if runtime_arg.default is not ... and stub_arg.initializer is not None: + stub_default = stub_arg.initializer.accept(_NodeEvaluator()) + if ( + stub_default is not MISSING + and stub_default is not ... + and ( + stub_default != runtime_arg.default + or type(stub_default) is not type(runtime_arg.default) + ) + ): + yield ( + f'runtime argument "{runtime_arg.name}" ' + f"has a default value of {runtime_arg.default!r}, " + f"which is different from stub argument default {stub_default!r}" + ) else: if stub_arg.kind.is_optional(): yield ( diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 5a6904bfaaf4a..e863f4f575688 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -302,7 +302,7 @@ def test_arg_kind(self) -> Iterator[Case]: ) @collect_cases - def test_default_value(self) -> Iterator[Case]: + def test_default_presence(self) -> Iterator[Case]: yield Case( stub="def f1(text: str = ...) -> None: ...", runtime="def f1(text = 'asdf'): pass", @@ -336,6 +336,59 @@ def f6(text: _T = ...) -> None: ... error="f6", ) + @collect_cases + def test_default_value(self) -> Iterator[Case]: + yield Case( + stub="def f1(text: str = 'x') -> None: ...", + runtime="def f1(text = 'y'): pass", + error="f1", + ) + yield Case( + stub='def f2(text: bytes = b"x\'") -> None: ...', + runtime='def f2(text = b"x\'"): pass', + error=None, + ) + yield Case( + stub='def f3(text: bytes = b"y\'") -> None: ...', + runtime='def f3(text = b"x\'"): pass', + error="f3", + ) + yield Case( + stub="def f4(text: object = 1) -> None: ...", + runtime="def f4(text = 1.0): pass", + error="f4", + ) + yield Case( + stub="def f5(text: object = True) -> None: ...", + runtime="def f5(text = 1): pass", + error="f5", + ) + yield Case( + stub="def f6(text: object = True) -> None: ...", + runtime="def f6(text = True): pass", + error=None, + ) + yield Case( + stub="def f7(text: object = not True) -> None: ...", + runtime="def f7(text = False): pass", + error=None, + ) + yield Case( + stub="def f8(text: object = not True) -> None: ...", + runtime="def f8(text = True): pass", + error="f8", + ) + yield Case( + stub="def f9(text: object = {1: 2}) -> None: ...", + runtime="def f9(text = {1: 3}): pass", + error="f9", + ) + yield Case( + stub="def f10(text: object = [1, 2]) -> None: ...", + runtime="def f10(text = [1, 2]): pass", + error=None, + ) + @collect_cases def test_static_class_method(self) -> Iterator[Case]: yield Case( From 85714c1b51d34a04d52cafc7dcdf73dc11dd8c6d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 13 Nov 2022 14:02:47 -0800 Subject: [PATCH 2/8] some comments --- mypy/stubtest.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index e0ad7d18773f3..38ee6e30c2f12 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -551,6 +551,8 @@ def visit_str_expr(self, o: mypy.nodes.StrExpr) -> str: return o.value def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> bytes: + # The value of a BytesExpr is a string created from the repr() + # of the bytes object. Get the original bytes back. try: return ast.literal_eval(f"b'{o.value}'") except SyntaxError: @@ -575,6 +577,8 @@ def visit_name_expr(self, o: mypy.nodes.NameExpr) -> object: return False elif o.name == "None": return None + # TODO: Handle more names by figuring out a way to hook into the + # symbol table. return MISSING def visit_member_expr(self, o: mypy.nodes.MemberExpr) -> object: @@ -756,7 +760,9 @@ def _verify_arg_default_value( and stub_default is not ... and ( stub_default != runtime_arg.default - or type(stub_default) is not type(runtime_arg.default) + # We want the types to match exactly, e.g. in case the stub has + # True and the runtime has 1 (or vice versa). + or type(stub_default) is not type(runtime_arg.default) # noqa: E721 ) ): yield ( From 78bcef1e50f0bc770fb68300e15412a02fe3c083 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 13 Nov 2022 14:03:20 -0800 Subject: [PATCH 3/8] fix self check --- mypy/stubtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 38ee6e30c2f12..06e778d0f04e5 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -550,7 +550,7 @@ def visit_int_expr(self, o: mypy.nodes.IntExpr) -> int: def visit_str_expr(self, o: mypy.nodes.StrExpr) -> str: return o.value - def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> bytes: + def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> object: # The value of a BytesExpr is a string created from the repr() # of the bytes object. Get the original bytes back. try: From de8ccd547b74a919de75597d44809cca00bbff76 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 13 Nov 2022 16:25:57 -0800 Subject: [PATCH 4/8] try this --- mypy/stubtest.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 06e778d0f04e5..8076cbc1f0bbb 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -720,6 +720,9 @@ def visit_temp_node(self, o: mypy.nodes.TempNode) -> object: return MISSING +_evaluator: typing_extensions.Final = _NodeEvaluator() + + def _verify_arg_default_value( stub_arg: nodes.Argument, runtime_arg: inspect.Parameter ) -> Iterator[str]: @@ -753,8 +756,8 @@ def _verify_arg_default_value( f"has a default value of type {runtime_type}, " f"which is incompatible with stub argument type {stub_type}" ) - if runtime_arg.default is not ... and stub_arg.initializer is not None: - stub_default = stub_arg.initializer.accept(_NodeEvaluator()) + if stub_arg.initializer is not None: + stub_default = stub_arg.initializer.accept(_evaluator) if ( stub_default is not MISSING and stub_default is not ... From 2f6e6e2b653ed1274135bc47c50b7429adc3cb7d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 14 Nov 2022 17:30:32 -0800 Subject: [PATCH 5/8] generic solution --- mypy/stubtest.py | 92 ++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 8076cbc1f0bbb..dd23beddaaab8 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -543,14 +543,14 @@ def names_approx_match(a: str, b: str) -> bool: ) -class _NodeEvaluator(ExpressionVisitor[object]): - def visit_int_expr(self, o: mypy.nodes.IntExpr) -> int: +class _NodeEvaluator(ExpressionVisitor[T]): + def visit_int_expr(self, o: mypy.nodes.IntExpr) -> Any: return o.value - def visit_str_expr(self, o: mypy.nodes.StrExpr) -> str: + def visit_str_expr(self, o: mypy.nodes.StrExpr) -> Any: return o.value - def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> object: + def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> Any: # The value of a BytesExpr is a string created from the repr() # of the bytes object. Get the original bytes back. try: @@ -558,19 +558,19 @@ def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> object: except SyntaxError: return ast.literal_eval(f'b"{o.value}"') - def visit_float_expr(self, o: mypy.nodes.FloatExpr) -> float: + def visit_float_expr(self, o: mypy.nodes.FloatExpr) -> Any: return o.value - def visit_complex_expr(self, o: mypy.nodes.ComplexExpr) -> object: + def visit_complex_expr(self, o: mypy.nodes.ComplexExpr) -> Any: return o.value - def visit_ellipsis(self, o: mypy.nodes.EllipsisExpr) -> object: + def visit_ellipsis(self, o: mypy.nodes.EllipsisExpr) -> Any: return Ellipsis - def visit_star_expr(self, o: mypy.nodes.StarExpr) -> object: + def visit_star_expr(self, o: mypy.nodes.StarExpr) -> Any: return MISSING - def visit_name_expr(self, o: mypy.nodes.NameExpr) -> object: + def visit_name_expr(self, o: mypy.nodes.NameExpr) -> Any: if o.name == "True": return True elif o.name == "False": @@ -581,37 +581,37 @@ def visit_name_expr(self, o: mypy.nodes.NameExpr) -> object: # symbol table. return MISSING - def visit_member_expr(self, o: mypy.nodes.MemberExpr) -> object: + def visit_member_expr(self, o: mypy.nodes.MemberExpr) -> Any: return MISSING - def visit_yield_from_expr(self, o: mypy.nodes.YieldFromExpr) -> object: + def visit_yield_from_expr(self, o: mypy.nodes.YieldFromExpr) -> Any: return MISSING - def visit_yield_expr(self, o: mypy.nodes.YieldExpr) -> object: + def visit_yield_expr(self, o: mypy.nodes.YieldExpr) -> Any: return MISSING - def visit_call_expr(self, o: mypy.nodes.CallExpr) -> object: + def visit_call_expr(self, o: mypy.nodes.CallExpr) -> Any: return MISSING - def visit_op_expr(self, o: mypy.nodes.OpExpr) -> object: + def visit_op_expr(self, o: mypy.nodes.OpExpr) -> Any: return MISSING - def visit_comparison_expr(self, o: mypy.nodes.ComparisonExpr) -> object: + def visit_comparison_expr(self, o: mypy.nodes.ComparisonExpr) -> Any: return MISSING - def visit_cast_expr(self, o: mypy.nodes.CastExpr) -> object: + def visit_cast_expr(self, o: mypy.nodes.CastExpr) -> Any: return o.expr.accept(self) - def visit_assert_type_expr(self, o: mypy.nodes.AssertTypeExpr) -> object: + def visit_assert_type_expr(self, o: mypy.nodes.AssertTypeExpr) -> Any: return o.expr.accept(self) - def visit_reveal_expr(self, o: mypy.nodes.RevealExpr) -> object: + def visit_reveal_expr(self, o: mypy.nodes.RevealExpr) -> Any: return MISSING - def visit_super_expr(self, o: mypy.nodes.SuperExpr) -> object: + def visit_super_expr(self, o: mypy.nodes.SuperExpr) -> Any: return MISSING - def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> object: + def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> Any: operand = o.expr.accept(self) if operand is MISSING: return MISSING @@ -629,16 +629,16 @@ def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> object: return not operand return MISSING - def visit_assignment_expr(self, o: mypy.nodes.AssignmentExpr) -> object: + def visit_assignment_expr(self, o: mypy.nodes.AssignmentExpr) -> Any: return o.value.accept(self) - def visit_list_expr(self, o: mypy.nodes.ListExpr) -> object: + def visit_list_expr(self, o: mypy.nodes.ListExpr) -> Any: items = [item.accept(self) for item in o.items] if all(item is not MISSING for item in items): return items return MISSING - def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> object: + def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> Any: items = [ (MISSING if key is None else key.accept(self), value.accept(self)) for key, value in o.items @@ -647,80 +647,80 @@ def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> object: return dict(items) return MISSING - def visit_tuple_expr(self, o: mypy.nodes.TupleExpr) -> object: + def visit_tuple_expr(self, o: mypy.nodes.TupleExpr) -> Any: items = [item.accept(self) for item in o.items] if all(item is not MISSING for item in items): return tuple(items) return MISSING - def visit_set_expr(self, o: mypy.nodes.SetExpr) -> object: + def visit_set_expr(self, o: mypy.nodes.SetExpr) -> Any: items = [item.accept(self) for item in o.items] if all(item is not MISSING for item in items): return set(items) return MISSING - def visit_index_expr(self, o: mypy.nodes.IndexExpr) -> object: + def visit_index_expr(self, o: mypy.nodes.IndexExpr) -> Any: return MISSING - def visit_type_application(self, o: mypy.nodes.TypeApplication) -> object: + def visit_type_application(self, o: mypy.nodes.TypeApplication) -> Any: return MISSING - def visit_lambda_expr(self, o: mypy.nodes.LambdaExpr) -> object: + def visit_lambda_expr(self, o: mypy.nodes.LambdaExpr) -> Any: return MISSING - def visit_list_comprehension(self, o: mypy.nodes.ListComprehension) -> object: + def visit_list_comprehension(self, o: mypy.nodes.ListComprehension) -> Any: return MISSING - def visit_set_comprehension(self, o: mypy.nodes.SetComprehension) -> object: + def visit_set_comprehension(self, o: mypy.nodes.SetComprehension) -> Any: return MISSING - def visit_dictionary_comprehension(self, o: mypy.nodes.DictionaryComprehension) -> object: + def visit_dictionary_comprehension(self, o: mypy.nodes.DictionaryComprehension) -> Any: return MISSING - def visit_generator_expr(self, o: mypy.nodes.GeneratorExpr) -> object: + def visit_generator_expr(self, o: mypy.nodes.GeneratorExpr) -> Any: return MISSING - def visit_slice_expr(self, o: mypy.nodes.SliceExpr) -> object: + def visit_slice_expr(self, o: mypy.nodes.SliceExpr) -> Any: return MISSING - def visit_conditional_expr(self, o: mypy.nodes.ConditionalExpr) -> object: + def visit_conditional_expr(self, o: mypy.nodes.ConditionalExpr) -> Any: return MISSING - def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> object: + def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> Any: return MISSING - def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> object: + def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> Any: return MISSING - def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> object: + def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> Any: return MISSING - def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> object: + def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> Any: return MISSING - def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> object: + def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> Any: return MISSING - def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> object: + def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> Any: return MISSING - def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> object: + def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> Any: return MISSING - def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> object: + def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> Any: return MISSING - def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> object: + def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> Any: return MISSING - def visit_await_expr(self, o: mypy.nodes.AwaitExpr) -> object: + def visit_await_expr(self, o: mypy.nodes.AwaitExpr) -> Any: return MISSING - def visit_temp_node(self, o: mypy.nodes.TempNode) -> object: + def visit_temp_node(self, o: mypy.nodes.TempNode) -> Any: return MISSING -_evaluator: typing_extensions.Final = _NodeEvaluator() +_evaluator: typing_extensions.Final[_NodeEvaluator[object]] = _NodeEvaluator() def _verify_arg_default_value( From fefb522b55d0e791cd3e1686ea49671eb169ac99 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 14 Nov 2022 19:31:19 -0800 Subject: [PATCH 6/8] you want multiple inheritance, you get multiple inheritance --- mypy/stubtest.py | 96 +++++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index dd23beddaaab8..31a1593d13ac0 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -543,14 +543,18 @@ def names_approx_match(a: str, b: str) -> bool: ) -class _NodeEvaluator(ExpressionVisitor[T]): - def visit_int_expr(self, o: mypy.nodes.IntExpr) -> Any: +class _Base: + pass + + +class _NodeEvaluator(_Base, ExpressionVisitor[object]): + def visit_int_expr(self, o: mypy.nodes.IntExpr) -> int: return o.value - def visit_str_expr(self, o: mypy.nodes.StrExpr) -> Any: + def visit_str_expr(self, o: mypy.nodes.StrExpr) -> str: return o.value - def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> Any: + def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> object: # The value of a BytesExpr is a string created from the repr() # of the bytes object. Get the original bytes back. try: @@ -558,19 +562,19 @@ def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> Any: except SyntaxError: return ast.literal_eval(f'b"{o.value}"') - def visit_float_expr(self, o: mypy.nodes.FloatExpr) -> Any: + def visit_float_expr(self, o: mypy.nodes.FloatExpr) -> float: return o.value - def visit_complex_expr(self, o: mypy.nodes.ComplexExpr) -> Any: + def visit_complex_expr(self, o: mypy.nodes.ComplexExpr) -> object: return o.value - def visit_ellipsis(self, o: mypy.nodes.EllipsisExpr) -> Any: + def visit_ellipsis(self, o: mypy.nodes.EllipsisExpr) -> object: return Ellipsis - def visit_star_expr(self, o: mypy.nodes.StarExpr) -> Any: + def visit_star_expr(self, o: mypy.nodes.StarExpr) -> object: return MISSING - def visit_name_expr(self, o: mypy.nodes.NameExpr) -> Any: + def visit_name_expr(self, o: mypy.nodes.NameExpr) -> object: if o.name == "True": return True elif o.name == "False": @@ -581,37 +585,37 @@ def visit_name_expr(self, o: mypy.nodes.NameExpr) -> Any: # symbol table. return MISSING - def visit_member_expr(self, o: mypy.nodes.MemberExpr) -> Any: + def visit_member_expr(self, o: mypy.nodes.MemberExpr) -> object: return MISSING - def visit_yield_from_expr(self, o: mypy.nodes.YieldFromExpr) -> Any: + def visit_yield_from_expr(self, o: mypy.nodes.YieldFromExpr) -> object: return MISSING - def visit_yield_expr(self, o: mypy.nodes.YieldExpr) -> Any: + def visit_yield_expr(self, o: mypy.nodes.YieldExpr) -> object: return MISSING - def visit_call_expr(self, o: mypy.nodes.CallExpr) -> Any: + def visit_call_expr(self, o: mypy.nodes.CallExpr) -> object: return MISSING - def visit_op_expr(self, o: mypy.nodes.OpExpr) -> Any: + def visit_op_expr(self, o: mypy.nodes.OpExpr) -> object: return MISSING - def visit_comparison_expr(self, o: mypy.nodes.ComparisonExpr) -> Any: + def visit_comparison_expr(self, o: mypy.nodes.ComparisonExpr) -> object: return MISSING - def visit_cast_expr(self, o: mypy.nodes.CastExpr) -> Any: + def visit_cast_expr(self, o: mypy.nodes.CastExpr) -> object: return o.expr.accept(self) - def visit_assert_type_expr(self, o: mypy.nodes.AssertTypeExpr) -> Any: + def visit_assert_type_expr(self, o: mypy.nodes.AssertTypeExpr) -> object: return o.expr.accept(self) - def visit_reveal_expr(self, o: mypy.nodes.RevealExpr) -> Any: + def visit_reveal_expr(self, o: mypy.nodes.RevealExpr) -> object: return MISSING - def visit_super_expr(self, o: mypy.nodes.SuperExpr) -> Any: + def visit_super_expr(self, o: mypy.nodes.SuperExpr) -> object: return MISSING - def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> Any: + def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> object: operand = o.expr.accept(self) if operand is MISSING: return MISSING @@ -629,16 +633,16 @@ def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> Any: return not operand return MISSING - def visit_assignment_expr(self, o: mypy.nodes.AssignmentExpr) -> Any: + def visit_assignment_expr(self, o: mypy.nodes.AssignmentExpr) -> object: return o.value.accept(self) - def visit_list_expr(self, o: mypy.nodes.ListExpr) -> Any: + def visit_list_expr(self, o: mypy.nodes.ListExpr) -> object: items = [item.accept(self) for item in o.items] if all(item is not MISSING for item in items): return items return MISSING - def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> Any: + def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> object: items = [ (MISSING if key is None else key.accept(self), value.accept(self)) for key, value in o.items @@ -647,80 +651,80 @@ def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> Any: return dict(items) return MISSING - def visit_tuple_expr(self, o: mypy.nodes.TupleExpr) -> Any: + def visit_tuple_expr(self, o: mypy.nodes.TupleExpr) -> object: items = [item.accept(self) for item in o.items] if all(item is not MISSING for item in items): return tuple(items) return MISSING - def visit_set_expr(self, o: mypy.nodes.SetExpr) -> Any: + def visit_set_expr(self, o: mypy.nodes.SetExpr) -> object: items = [item.accept(self) for item in o.items] if all(item is not MISSING for item in items): return set(items) return MISSING - def visit_index_expr(self, o: mypy.nodes.IndexExpr) -> Any: + def visit_index_expr(self, o: mypy.nodes.IndexExpr) -> object: return MISSING - def visit_type_application(self, o: mypy.nodes.TypeApplication) -> Any: + def visit_type_application(self, o: mypy.nodes.TypeApplication) -> object: return MISSING - def visit_lambda_expr(self, o: mypy.nodes.LambdaExpr) -> Any: + def visit_lambda_expr(self, o: mypy.nodes.LambdaExpr) -> object: return MISSING - def visit_list_comprehension(self, o: mypy.nodes.ListComprehension) -> Any: + def visit_list_comprehension(self, o: mypy.nodes.ListComprehension) -> object: return MISSING - def visit_set_comprehension(self, o: mypy.nodes.SetComprehension) -> Any: + def visit_set_comprehension(self, o: mypy.nodes.SetComprehension) -> object: return MISSING - def visit_dictionary_comprehension(self, o: mypy.nodes.DictionaryComprehension) -> Any: + def visit_dictionary_comprehension(self, o: mypy.nodes.DictionaryComprehension) -> object: return MISSING - def visit_generator_expr(self, o: mypy.nodes.GeneratorExpr) -> Any: + def visit_generator_expr(self, o: mypy.nodes.GeneratorExpr) -> object: return MISSING - def visit_slice_expr(self, o: mypy.nodes.SliceExpr) -> Any: + def visit_slice_expr(self, o: mypy.nodes.SliceExpr) -> object: return MISSING - def visit_conditional_expr(self, o: mypy.nodes.ConditionalExpr) -> Any: + def visit_conditional_expr(self, o: mypy.nodes.ConditionalExpr) -> object: return MISSING - def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> Any: + def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> object: return MISSING - def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> Any: + def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> object: return MISSING - def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> Any: + def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> object: return MISSING - def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> Any: + def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> object: return MISSING - def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> Any: + def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> object: return MISSING - def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> Any: + def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> object: return MISSING - def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> Any: + def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> object: return MISSING - def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> Any: + def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> object: return MISSING - def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> Any: + def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> object: return MISSING - def visit_await_expr(self, o: mypy.nodes.AwaitExpr) -> Any: + def visit_await_expr(self, o: mypy.nodes.AwaitExpr) -> object: return MISSING - def visit_temp_node(self, o: mypy.nodes.TempNode) -> Any: + def visit_temp_node(self, o: mypy.nodes.TempNode) -> object: return MISSING -_evaluator: typing_extensions.Final[_NodeEvaluator[object]] = _NodeEvaluator() +_evaluator: typing_extensions.Final = _NodeEvaluator() def _verify_arg_default_value( From 71a79f0bbbe6818db4f4d9d1f8a4aacc7338dbf4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 24 Nov 2022 20:26:57 -0800 Subject: [PATCH 7/8] separate file --- mypy/evalexpr.py | 203 +++++++++++++++++++++++++++++++++++++++++++++++ mypy/stubtest.py | 191 +------------------------------------------- 2 files changed, 206 insertions(+), 188 deletions(-) create mode 100644 mypy/evalexpr.py diff --git a/mypy/evalexpr.py b/mypy/evalexpr.py new file mode 100644 index 0000000000000..77fe60de9e313 --- /dev/null +++ b/mypy/evalexpr.py @@ -0,0 +1,203 @@ +""" + +Evaluate an expression. + +Used by stubtest; in a separate file because things break if we don't +put it in a mypyc-compiled file. + +""" +import ast +import mypy.nodes +from mypy.visitor import ExpressionVisitor +from typing_extensions import Final + +UNKNOWN = object() + + +class _NodeEvaluator(ExpressionVisitor[object]): + def visit_int_expr(self, o: mypy.nodes.IntExpr) -> int: + return o.value + + def visit_str_expr(self, o: mypy.nodes.StrExpr) -> str: + return o.value + + def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> object: + # The value of a BytesExpr is a string created from the repr() + # of the bytes object. Get the original bytes back. + try: + return ast.literal_eval(f"b'{o.value}'") + except SyntaxError: + return ast.literal_eval(f'b"{o.value}"') + + def visit_float_expr(self, o: mypy.nodes.FloatExpr) -> float: + return o.value + + def visit_complex_expr(self, o: mypy.nodes.ComplexExpr) -> object: + return o.value + + def visit_ellipsis(self, o: mypy.nodes.EllipsisExpr) -> object: + return Ellipsis + + def visit_star_expr(self, o: mypy.nodes.StarExpr) -> object: + return UNKNOWN + + def visit_name_expr(self, o: mypy.nodes.NameExpr) -> object: + if o.name == "True": + return True + elif o.name == "False": + return False + elif o.name == "None": + return None + # TODO: Handle more names by figuring out a way to hook into the + # symbol table. + return UNKNOWN + + def visit_member_expr(self, o: mypy.nodes.MemberExpr) -> object: + return UNKNOWN + + def visit_yield_from_expr(self, o: mypy.nodes.YieldFromExpr) -> object: + return UNKNOWN + + def visit_yield_expr(self, o: mypy.nodes.YieldExpr) -> object: + return UNKNOWN + + def visit_call_expr(self, o: mypy.nodes.CallExpr) -> object: + return UNKNOWN + + def visit_op_expr(self, o: mypy.nodes.OpExpr) -> object: + return UNKNOWN + + def visit_comparison_expr(self, o: mypy.nodes.ComparisonExpr) -> object: + return UNKNOWN + + def visit_cast_expr(self, o: mypy.nodes.CastExpr) -> object: + return o.expr.accept(self) + + def visit_assert_type_expr(self, o: mypy.nodes.AssertTypeExpr) -> object: + return o.expr.accept(self) + + def visit_reveal_expr(self, o: mypy.nodes.RevealExpr) -> object: + return UNKNOWN + + def visit_super_expr(self, o: mypy.nodes.SuperExpr) -> object: + return UNKNOWN + + def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> object: + operand = o.expr.accept(self) + if operand is UNKNOWN: + return UNKNOWN + if o.op == "-": + if isinstance(operand, (int, float, complex)): + return -operand + elif o.op == "+": + if isinstance(operand, (int, float, complex)): + return +operand + elif o.op == "~": + if isinstance(operand, int): + return ~operand + elif o.op == "not": + if isinstance(operand, (bool, int, float, str, bytes)): + return not operand + return UNKNOWN + + def visit_assignment_expr(self, o: mypy.nodes.AssignmentExpr) -> object: + return o.value.accept(self) + + def visit_list_expr(self, o: mypy.nodes.ListExpr) -> object: + items = [item.accept(self) for item in o.items] + if all(item is not UNKNOWN for item in items): + return items + return UNKNOWN + + def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> object: + items = [ + (UNKNOWN if key is None else key.accept(self), value.accept(self)) + for key, value in o.items + ] + if all(key is not UNKNOWN and value is not None for key, value in items): + return dict(items) + return UNKNOWN + + def visit_tuple_expr(self, o: mypy.nodes.TupleExpr) -> object: + items = [item.accept(self) for item in o.items] + if all(item is not UNKNOWN for item in items): + return tuple(items) + return UNKNOWN + + def visit_set_expr(self, o: mypy.nodes.SetExpr) -> object: + items = [item.accept(self) for item in o.items] + if all(item is not UNKNOWN for item in items): + return set(items) + return UNKNOWN + + def visit_index_expr(self, o: mypy.nodes.IndexExpr) -> object: + return UNKNOWN + + def visit_type_application(self, o: mypy.nodes.TypeApplication) -> object: + return UNKNOWN + + def visit_lambda_expr(self, o: mypy.nodes.LambdaExpr) -> object: + return UNKNOWN + + def visit_list_comprehension(self, o: mypy.nodes.ListComprehension) -> object: + return UNKNOWN + + def visit_set_comprehension(self, o: mypy.nodes.SetComprehension) -> object: + return UNKNOWN + + def visit_dictionary_comprehension(self, o: mypy.nodes.DictionaryComprehension) -> object: + return UNKNOWN + + def visit_generator_expr(self, o: mypy.nodes.GeneratorExpr) -> object: + return UNKNOWN + + def visit_slice_expr(self, o: mypy.nodes.SliceExpr) -> object: + return UNKNOWN + + def visit_conditional_expr(self, o: mypy.nodes.ConditionalExpr) -> object: + return UNKNOWN + + def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> object: + return UNKNOWN + + def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> object: + return UNKNOWN + + def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> object: + return UNKNOWN + + def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> object: + return UNKNOWN + + def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> object: + return UNKNOWN + + def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> object: + return UNKNOWN + + def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> object: + return UNKNOWN + + def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> object: + return UNKNOWN + + def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> object: + return UNKNOWN + + def visit_await_expr(self, o: mypy.nodes.AwaitExpr) -> object: + return UNKNOWN + + def visit_temp_node(self, o: mypy.nodes.TempNode) -> object: + return UNKNOWN + + +_evaluator: Final = _NodeEvaluator() + + +def evaluate_expression(expr: mypy.nodes.Expression) -> object: + """Evaluate an expression at runtime. + + Return the result of the expression, or UNKNOWN if the expression cannot be + evaluated. + """ + return expr.accept(_evaluator) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 31a1593d13ac0..2eeda9d268343 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -7,7 +7,6 @@ from __future__ import annotations import argparse -import ast import collections.abc import copy import enum @@ -36,9 +35,9 @@ import mypy.version from mypy import nodes from mypy.config_parser import parse_config_file +from mypy.evalexpr import evaluate_expression, UNKNOWN from mypy.options import Options from mypy.util import FancyFormatter, bytes_to_human_readable_repr, is_dunder, plural_s -from mypy.visitor import ExpressionVisitor class Missing: @@ -543,190 +542,6 @@ def names_approx_match(a: str, b: str) -> bool: ) -class _Base: - pass - - -class _NodeEvaluator(_Base, ExpressionVisitor[object]): - def visit_int_expr(self, o: mypy.nodes.IntExpr) -> int: - return o.value - - def visit_str_expr(self, o: mypy.nodes.StrExpr) -> str: - return o.value - - def visit_bytes_expr(self, o: mypy.nodes.BytesExpr) -> object: - # The value of a BytesExpr is a string created from the repr() - # of the bytes object. Get the original bytes back. - try: - return ast.literal_eval(f"b'{o.value}'") - except SyntaxError: - return ast.literal_eval(f'b"{o.value}"') - - def visit_float_expr(self, o: mypy.nodes.FloatExpr) -> float: - return o.value - - def visit_complex_expr(self, o: mypy.nodes.ComplexExpr) -> object: - return o.value - - def visit_ellipsis(self, o: mypy.nodes.EllipsisExpr) -> object: - return Ellipsis - - def visit_star_expr(self, o: mypy.nodes.StarExpr) -> object: - return MISSING - - def visit_name_expr(self, o: mypy.nodes.NameExpr) -> object: - if o.name == "True": - return True - elif o.name == "False": - return False - elif o.name == "None": - return None - # TODO: Handle more names by figuring out a way to hook into the - # symbol table. - return MISSING - - def visit_member_expr(self, o: mypy.nodes.MemberExpr) -> object: - return MISSING - - def visit_yield_from_expr(self, o: mypy.nodes.YieldFromExpr) -> object: - return MISSING - - def visit_yield_expr(self, o: mypy.nodes.YieldExpr) -> object: - return MISSING - - def visit_call_expr(self, o: mypy.nodes.CallExpr) -> object: - return MISSING - - def visit_op_expr(self, o: mypy.nodes.OpExpr) -> object: - return MISSING - - def visit_comparison_expr(self, o: mypy.nodes.ComparisonExpr) -> object: - return MISSING - - def visit_cast_expr(self, o: mypy.nodes.CastExpr) -> object: - return o.expr.accept(self) - - def visit_assert_type_expr(self, o: mypy.nodes.AssertTypeExpr) -> object: - return o.expr.accept(self) - - def visit_reveal_expr(self, o: mypy.nodes.RevealExpr) -> object: - return MISSING - - def visit_super_expr(self, o: mypy.nodes.SuperExpr) -> object: - return MISSING - - def visit_unary_expr(self, o: mypy.nodes.UnaryExpr) -> object: - operand = o.expr.accept(self) - if operand is MISSING: - return MISSING - if o.op == "-": - if isinstance(operand, (int, float, complex)): - return -operand - elif o.op == "+": - if isinstance(operand, (int, float, complex)): - return +operand - elif o.op == "~": - if isinstance(operand, int): - return ~operand - elif o.op == "not": - if isinstance(operand, (bool, int, float, str, bytes)): - return not operand - return MISSING - - def visit_assignment_expr(self, o: mypy.nodes.AssignmentExpr) -> object: - return o.value.accept(self) - - def visit_list_expr(self, o: mypy.nodes.ListExpr) -> object: - items = [item.accept(self) for item in o.items] - if all(item is not MISSING for item in items): - return items - return MISSING - - def visit_dict_expr(self, o: mypy.nodes.DictExpr) -> object: - items = [ - (MISSING if key is None else key.accept(self), value.accept(self)) - for key, value in o.items - ] - if all(key is not MISSING and value is not None for key, value in items): - return dict(items) - return MISSING - - def visit_tuple_expr(self, o: mypy.nodes.TupleExpr) -> object: - items = [item.accept(self) for item in o.items] - if all(item is not MISSING for item in items): - return tuple(items) - return MISSING - - def visit_set_expr(self, o: mypy.nodes.SetExpr) -> object: - items = [item.accept(self) for item in o.items] - if all(item is not MISSING for item in items): - return set(items) - return MISSING - - def visit_index_expr(self, o: mypy.nodes.IndexExpr) -> object: - return MISSING - - def visit_type_application(self, o: mypy.nodes.TypeApplication) -> object: - return MISSING - - def visit_lambda_expr(self, o: mypy.nodes.LambdaExpr) -> object: - return MISSING - - def visit_list_comprehension(self, o: mypy.nodes.ListComprehension) -> object: - return MISSING - - def visit_set_comprehension(self, o: mypy.nodes.SetComprehension) -> object: - return MISSING - - def visit_dictionary_comprehension(self, o: mypy.nodes.DictionaryComprehension) -> object: - return MISSING - - def visit_generator_expr(self, o: mypy.nodes.GeneratorExpr) -> object: - return MISSING - - def visit_slice_expr(self, o: mypy.nodes.SliceExpr) -> object: - return MISSING - - def visit_conditional_expr(self, o: mypy.nodes.ConditionalExpr) -> object: - return MISSING - - def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> object: - return MISSING - - def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> object: - return MISSING - - def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> object: - return MISSING - - def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> object: - return MISSING - - def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> object: - return MISSING - - def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> object: - return MISSING - - def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> object: - return MISSING - - def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> object: - return MISSING - - def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> object: - return MISSING - - def visit_await_expr(self, o: mypy.nodes.AwaitExpr) -> object: - return MISSING - - def visit_temp_node(self, o: mypy.nodes.TempNode) -> object: - return MISSING - - -_evaluator: typing_extensions.Final = _NodeEvaluator() - - def _verify_arg_default_value( stub_arg: nodes.Argument, runtime_arg: inspect.Parameter ) -> Iterator[str]: @@ -761,9 +576,9 @@ def _verify_arg_default_value( f"which is incompatible with stub argument type {stub_type}" ) if stub_arg.initializer is not None: - stub_default = stub_arg.initializer.accept(_evaluator) + stub_default = evaluate_expression(stub_arg.initializer) if ( - stub_default is not MISSING + stub_default is not UNKNOWN and stub_default is not ... and ( stub_default != runtime_arg.default From 6035620246bcefe0063d065ba909b33bf7b8b952 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 25 Nov 2022 07:00:36 -0800 Subject: [PATCH 8/8] isort --- mypy/evalexpr.py | 3 ++- mypy/stubtest.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/evalexpr.py b/mypy/evalexpr.py index 77fe60de9e313..2bc6966fa2fac 100644 --- a/mypy/evalexpr.py +++ b/mypy/evalexpr.py @@ -7,9 +7,10 @@ """ import ast +from typing_extensions import Final + import mypy.nodes from mypy.visitor import ExpressionVisitor -from typing_extensions import Final UNKNOWN = object() diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 2eeda9d268343..06457bf862997 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -35,7 +35,7 @@ import mypy.version from mypy import nodes from mypy.config_parser import parse_config_file -from mypy.evalexpr import evaluate_expression, UNKNOWN +from mypy.evalexpr import UNKNOWN, evaluate_expression from mypy.options import Options from mypy.util import FancyFormatter, bytes_to_human_readable_repr, is_dunder, plural_s