From 0925b896debbf4a2583c090e0e6a171b9ce6de9e Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Thu, 5 Dec 2024 15:21:13 -0800 Subject: [PATCH 1/5] More detailed checking of type objects in stubtest This uses checkmember.type_object_type and context to produce better types of type objects. --- mypy/stubtest.py | 59 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 36cd0a213d4d..8edbc337ed88 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -32,6 +32,9 @@ from typing_extensions import get_origin, is_typeddict import mypy.build +import mypy.checkexpr +import mypy.checkmember +import mypy.erasetype import mypy.modulefinder import mypy.nodes import mypy.state @@ -670,7 +673,11 @@ def _verify_arg_default_value( "has a default value but stub argument does not" ) else: - runtime_type = get_mypy_type_of_runtime_value(runtime_arg.default) + type_context = stub_arg.variable.type + runtime_type = get_mypy_type_of_runtime_value( + runtime_arg.default, type_context=type_context + ) + # Fallback to the type annotation type if var type is missing. The type annotation # is an UnboundType, but I don't know enough to know what the pros and cons here are. # UnboundTypes have ugly question marks following them, so default to var type. @@ -1097,7 +1104,7 @@ def verify_var( ): yield Error(object_path, "is read-only at runtime but not in the stub", stub, runtime) - runtime_type = get_mypy_type_of_runtime_value(runtime) + runtime_type = get_mypy_type_of_runtime_value(runtime, type_context=stub.type) if ( runtime_type is not None and stub.type is not None @@ -1586,7 +1593,9 @@ def is_subtype_helper(left: mypy.types.Type, right: mypy.types.Type) -> bool: return mypy.subtypes.is_subtype(left, right) -def get_mypy_type_of_runtime_value(runtime: Any) -> mypy.types.Type | None: +def get_mypy_type_of_runtime_value( + runtime: Any, type_context: mypy.types.Type | None = None +) -> mypy.types.Type | None: """Returns a mypy type object representing the type of ``runtime``. Returns None if we can't find something that works. @@ -1647,7 +1656,49 @@ def anytype() -> mypy.types.AnyType: is_ellipsis_args=True, ) - # Try and look up a stub for the runtime object + if type_context: + # Don't attempt to account for context if the context is generic + # This is related to issue #3737 + if isinstance(type_context, mypy.types.CallableType): + if isinstance(type_context.ret_type, mypy.types.TypeVarType): + type_context = None + if isinstance(type_context, mypy.types.TypeType): + if isinstance(type_context.item, mypy.types.TypeVarType): + type_context = None + + if type_context: + + def _named_type(name: str) -> mypy.types.Instance: + parts = name.rsplit(".", maxsplit=1) + stub = get_stub(parts[0]) + if stub is not None: + if parts[1] in stub.names: + node = stub.names[parts[1]] + assert isinstance(node.node, nodes.TypeInfo) + any_type = mypy.types.AnyType(mypy.types.TypeOfAny.special_form) + return mypy.types.Instance( + node.node, [any_type] * len(node.node.defn.type_vars) + ) + + any_type = mypy.types.AnyType(mypy.types.TypeOfAny.from_error) + return mypy.types.Instance(node.node, []) + + if isinstance(runtime, type): + # Try and look up a stub for the runtime object itself + # The logic here is similar to ExpressionChecker.analyze_ref_expr + stub = get_stub(runtime.__module__) + if stub is not None: + if runtime.__name__ in stub.names: + type_info = stub.names[runtime.__name__].node + if isinstance(type_info, nodes.TypeInfo): + result = mypy.checkmember.type_object_type(type_info, _named_type) + if mypy.checkexpr.is_type_type_context(type_context): + # This is the type in a type[] expression, so substitute type + # variables with Any. + result = mypy.erasetype.erase_typevars(result) + return result + + # Try and look up a stub for the runtime object's type stub = get_stub(type(runtime).__module__) if stub is None: return None From b851fab8bece7e88c2ce4d744e1bccf1b69e084c Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Thu, 5 Dec 2024 16:03:53 -0800 Subject: [PATCH 2/5] fix typing --- mypy/stubtest.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 8edbc337ed88..2f5f4307882d 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1659,6 +1659,7 @@ def anytype() -> mypy.types.AnyType: if type_context: # Don't attempt to account for context if the context is generic # This is related to issue #3737 + type_context = mypy.types.get_proper_type(type_context) if isinstance(type_context, mypy.types.CallableType): if isinstance(type_context.ret_type, mypy.types.TypeVarType): type_context = None @@ -1671,17 +1672,12 @@ def anytype() -> mypy.types.AnyType: def _named_type(name: str) -> mypy.types.Instance: parts = name.rsplit(".", maxsplit=1) stub = get_stub(parts[0]) - if stub is not None: - if parts[1] in stub.names: - node = stub.names[parts[1]] - assert isinstance(node.node, nodes.TypeInfo) - any_type = mypy.types.AnyType(mypy.types.TypeOfAny.special_form) - return mypy.types.Instance( - node.node, [any_type] * len(node.node.defn.type_vars) - ) - - any_type = mypy.types.AnyType(mypy.types.TypeOfAny.from_error) - return mypy.types.Instance(node.node, []) + assert stub is not None + assert parts[1] in stub.names + node = stub.names[parts[1]] + assert isinstance(node.node, nodes.TypeInfo) + any_type = mypy.types.AnyType(mypy.types.TypeOfAny.special_form) + return mypy.types.Instance(node.node, [any_type] * len(node.node.defn.type_vars)) if isinstance(runtime, type): # Try and look up a stub for the runtime object itself @@ -1691,6 +1687,7 @@ def _named_type(name: str) -> mypy.types.Instance: if runtime.__name__ in stub.names: type_info = stub.names[runtime.__name__].node if isinstance(type_info, nodes.TypeInfo): + result: mypy.types.Type | None = None result = mypy.checkmember.type_object_type(type_info, _named_type) if mypy.checkexpr.is_type_type_context(type_context): # This is the type in a type[] expression, so substitute type From b45915891c00bdff034d13877faacfd302eca89c Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Thu, 5 Dec 2024 17:26:10 -0800 Subject: [PATCH 3/5] rework logic for clarity --- mypy/stubtest.py | 68 +++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 2f5f4307882d..8551b1872ddd 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1593,6 +1593,15 @@ def is_subtype_helper(left: mypy.types.Type, right: mypy.types.Type) -> bool: return mypy.subtypes.is_subtype(left, right) +def get_mypy_node_for_name(module: str, type_name: str) -> mypy.nodes.SymbolNode | None: + stub = get_stub(module) + if stub is None: + return None + if type_name not in stub.names: + return None + return stub.names[type_name].node + + def get_mypy_type_of_runtime_value( runtime: Any, type_context: mypy.types.Type | None = None ) -> mypy.types.Type | None: @@ -1656,53 +1665,48 @@ def anytype() -> mypy.types.AnyType: is_ellipsis_args=True, ) + skip_type_object_type = False if type_context: - # Don't attempt to account for context if the context is generic + # Don't attempt to process the type object when context is generic # This is related to issue #3737 type_context = mypy.types.get_proper_type(type_context) + # Callable types with a generic return value if isinstance(type_context, mypy.types.CallableType): if isinstance(type_context.ret_type, mypy.types.TypeVarType): - type_context = None + skip_type_object_type = True + # Type[x] where x is generic if isinstance(type_context, mypy.types.TypeType): if isinstance(type_context.item, mypy.types.TypeVarType): - type_context = None + skip_type_object_type = True - if type_context: + if isinstance(runtime, type) and not skip_type_object_type: def _named_type(name: str) -> mypy.types.Instance: parts = name.rsplit(".", maxsplit=1) - stub = get_stub(parts[0]) - assert stub is not None - assert parts[1] in stub.names - node = stub.names[parts[1]] - assert isinstance(node.node, nodes.TypeInfo) + node = get_mypy_node_for_name(parts[0], parts[1]) + assert isinstance(node, nodes.TypeInfo) any_type = mypy.types.AnyType(mypy.types.TypeOfAny.special_form) - return mypy.types.Instance(node.node, [any_type] * len(node.node.defn.type_vars)) - - if isinstance(runtime, type): - # Try and look up a stub for the runtime object itself - # The logic here is similar to ExpressionChecker.analyze_ref_expr - stub = get_stub(runtime.__module__) - if stub is not None: - if runtime.__name__ in stub.names: - type_info = stub.names[runtime.__name__].node - if isinstance(type_info, nodes.TypeInfo): - result: mypy.types.Type | None = None - result = mypy.checkmember.type_object_type(type_info, _named_type) - if mypy.checkexpr.is_type_type_context(type_context): - # This is the type in a type[] expression, so substitute type - # variables with Any. - result = mypy.erasetype.erase_typevars(result) - return result + return mypy.types.Instance(node, [any_type] * len(node.defn.type_vars)) + + # Try and look up a stub for the runtime object itself + # The logic here is similar to ExpressionChecker.analyze_ref_expr + stub = get_stub(runtime.__module__) + if stub is not None: + if runtime.__name__ in stub.names: + type_info = stub.names[runtime.__name__].node + if isinstance(type_info, nodes.TypeInfo): + result: mypy.types.Type | None = None + result = mypy.checkmember.type_object_type(type_info, _named_type) + if mypy.checkexpr.is_type_type_context(type_context): + # This is the type in a type[] expression, so substitute type + # variables with Any. + result = mypy.erasetype.erase_typevars(result) + return result # Try and look up a stub for the runtime object's type - stub = get_stub(type(runtime).__module__) - if stub is None: - return None - type_name = type(runtime).__name__ - if type_name not in stub.names: + type_info = get_mypy_node_for_name(type(runtime).__module__, type(runtime).__name__) + if type_info is None: return None - type_info = stub.names[type_name].node if isinstance(type_info, nodes.Var): return type_info.type if not isinstance(type_info, nodes.TypeInfo): From 8d09eca98262f09dbd4deb864eff80edb1feee8f Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Thu, 5 Dec 2024 19:45:30 -0800 Subject: [PATCH 4/5] additional simplification --- mypy/stubtest.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 8551b1872ddd..286e13ee1754 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1690,18 +1690,15 @@ def _named_type(name: str) -> mypy.types.Instance: # Try and look up a stub for the runtime object itself # The logic here is similar to ExpressionChecker.analyze_ref_expr - stub = get_stub(runtime.__module__) - if stub is not None: - if runtime.__name__ in stub.names: - type_info = stub.names[runtime.__name__].node - if isinstance(type_info, nodes.TypeInfo): - result: mypy.types.Type | None = None - result = mypy.checkmember.type_object_type(type_info, _named_type) - if mypy.checkexpr.is_type_type_context(type_context): - # This is the type in a type[] expression, so substitute type - # variables with Any. - result = mypy.erasetype.erase_typevars(result) - return result + type_info = get_mypy_node_for_name(runtime.__module__, runtime.__name__) + if isinstance(type_info, nodes.TypeInfo): + result: mypy.types.Type | None = None + result = mypy.checkmember.type_object_type(type_info, _named_type) + if mypy.checkexpr.is_type_type_context(type_context): + # This is the type in a type[] expression, so substitute type + # variables with Any. + result = mypy.erasetype.erase_typevars(result) + return result # Try and look up a stub for the runtime object's type type_info = get_mypy_node_for_name(type(runtime).__module__, type(runtime).__name__) From 6dcb76022ab4ac3382cf4e456f5a79d9d5074867 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Sat, 7 Dec 2024 17:25:07 -0800 Subject: [PATCH 5/5] add a test case based on argparse.ArgumentParser --- mypy/test/teststubtest.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index fcbf07b4d371..7011112972e2 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -2401,6 +2401,31 @@ def func2() -> None: ... error="func2", ) + @collect_cases + def test_type_default_protocol(self) -> Iterator[Case]: + yield Case( + stub=""" + from typing import Protocol + + class _FormatterClass(Protocol): + def __call__(self, *, prog: str) -> HelpFormatter: ... + + class ArgumentParser: + def __init__(self, formatter_class: _FormatterClass = ...) -> None: ... + + class HelpFormatter: + def __init__(self, prog: str, indent_increment: int = 2) -> None: ... + """, + runtime=""" + class HelpFormatter: + def __init__(self, prog, indent_increment=2) -> None: ... + + class ArgumentParser: + def __init__(self, formatter_class=HelpFormatter): ... + """, + error=None, + ) + def remove_color_code(s: str) -> str: return re.sub("\\x1b.*?m", "", s) # this works! pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy