Major fixes and new features
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
475
venv/lib/python3.12/site-packages/mypy/semanal_shared.py
Normal file
475
venv/lib/python3.12/site-packages/mypy/semanal_shared.py
Normal file
@@ -0,0 +1,475 @@
|
||||
"""Shared definitions used by different parts of semantic analysis."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import Callable, Final, overload
|
||||
from typing_extensions import Literal, Protocol
|
||||
|
||||
from mypy_extensions import trait
|
||||
|
||||
from mypy import join
|
||||
from mypy.errorcodes import LITERAL_REQ, ErrorCode
|
||||
from mypy.nodes import (
|
||||
CallExpr,
|
||||
ClassDef,
|
||||
Context,
|
||||
DataclassTransformSpec,
|
||||
Decorator,
|
||||
Expression,
|
||||
FuncDef,
|
||||
NameExpr,
|
||||
Node,
|
||||
OverloadedFuncDef,
|
||||
RefExpr,
|
||||
SymbolNode,
|
||||
SymbolTable,
|
||||
SymbolTableNode,
|
||||
TypeInfo,
|
||||
)
|
||||
from mypy.plugin import SemanticAnalyzerPluginInterface
|
||||
from mypy.tvar_scope import TypeVarLikeScope
|
||||
from mypy.type_visitor import ANY_STRATEGY, BoolTypeQuery
|
||||
from mypy.types import (
|
||||
TPDICT_FB_NAMES,
|
||||
AnyType,
|
||||
FunctionLike,
|
||||
Instance,
|
||||
Parameters,
|
||||
ParamSpecFlavor,
|
||||
ParamSpecType,
|
||||
PlaceholderType,
|
||||
ProperType,
|
||||
TupleType,
|
||||
Type,
|
||||
TypeOfAny,
|
||||
TypeVarId,
|
||||
TypeVarLikeType,
|
||||
get_proper_type,
|
||||
)
|
||||
|
||||
# Subclasses can override these Var attributes with incompatible types. This can also be
|
||||
# set for individual attributes using 'allow_incompatible_override' of Var.
|
||||
ALLOW_INCOMPATIBLE_OVERRIDE: Final = ("__slots__", "__deletable__", "__match_args__")
|
||||
|
||||
|
||||
# Priorities for ordering of patches within the "patch" phase of semantic analysis
|
||||
# (after the main pass):
|
||||
|
||||
# Fix fallbacks (does joins)
|
||||
PRIORITY_FALLBACKS: Final = 1
|
||||
|
||||
|
||||
@trait
|
||||
class SemanticAnalyzerCoreInterface:
|
||||
"""A core abstract interface to generic semantic analyzer functionality.
|
||||
|
||||
This is implemented by both semantic analyzer passes 2 and 3.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def lookup_qualified(
|
||||
self, name: str, ctx: Context, suppress_errors: bool = False
|
||||
) -> SymbolTableNode | None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def lookup_fully_qualified(self, name: str) -> SymbolTableNode:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def lookup_fully_qualified_or_none(self, name: str) -> SymbolTableNode | None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def fail(
|
||||
self,
|
||||
msg: str,
|
||||
ctx: Context,
|
||||
serious: bool = False,
|
||||
*,
|
||||
blocker: bool = False,
|
||||
code: ErrorCode | None = None,
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def note(self, msg: str, ctx: Context, *, code: ErrorCode | None = None) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def incomplete_feature_enabled(self, feature: str, ctx: Context) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def record_incomplete_ref(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def defer(self, debug_context: Context | None = None, force_progress: bool = False) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def is_incomplete_namespace(self, fullname: str) -> bool:
|
||||
"""Is a module or class namespace potentially missing some definitions?"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def final_iteration(self) -> bool:
|
||||
"""Is this the final iteration of semantic analysis?"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def is_future_flag_set(self, flag: str) -> bool:
|
||||
"""Is the specific __future__ feature imported"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def is_stub_file(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def is_func_scope(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def type(self) -> TypeInfo | None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@trait
|
||||
class SemanticAnalyzerInterface(SemanticAnalyzerCoreInterface):
|
||||
"""A limited abstract interface to some generic semantic analyzer pass 2 functionality.
|
||||
|
||||
We use this interface for various reasons:
|
||||
|
||||
* Looser coupling
|
||||
* Cleaner import graph
|
||||
* Less need to pass around callback functions
|
||||
"""
|
||||
|
||||
tvar_scope: TypeVarLikeScope
|
||||
|
||||
@abstractmethod
|
||||
def lookup(
|
||||
self, name: str, ctx: Context, suppress_errors: bool = False
|
||||
) -> SymbolTableNode | None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def named_type(self, fullname: str, args: list[Type] | None = None) -> Instance:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def named_type_or_none(self, fullname: str, args: list[Type] | None = None) -> Instance | None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def accept(self, node: Node) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def anal_type(
|
||||
self,
|
||||
t: Type,
|
||||
*,
|
||||
tvar_scope: TypeVarLikeScope | None = None,
|
||||
allow_tuple_literal: bool = False,
|
||||
allow_unbound_tvars: bool = False,
|
||||
allow_required: bool = False,
|
||||
allow_placeholder: bool = False,
|
||||
report_invalid_types: bool = True,
|
||||
prohibit_self_type: str | None = None,
|
||||
) -> Type | None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_and_bind_all_tvars(self, type_exprs: list[Expression]) -> list[TypeVarLikeType]:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance, line: int) -> TypeInfo:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def schedule_patch(self, priority: int, fn: Callable[[], None]) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def add_symbol_table_node(self, name: str, stnode: SymbolTableNode) -> bool:
|
||||
"""Add node to the current symbol table."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def current_symbol_table(self) -> SymbolTable:
|
||||
"""Get currently active symbol table.
|
||||
|
||||
May be module, class, or local namespace.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def add_symbol(
|
||||
self,
|
||||
name: str,
|
||||
node: SymbolNode,
|
||||
context: Context,
|
||||
module_public: bool = True,
|
||||
module_hidden: bool = False,
|
||||
can_defer: bool = True,
|
||||
) -> bool:
|
||||
"""Add symbol to the current symbol table."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def add_symbol_skip_local(self, name: str, node: SymbolNode) -> None:
|
||||
"""Add symbol to the current symbol table, skipping locals.
|
||||
|
||||
This is used to store symbol nodes in a symbol table that
|
||||
is going to be serialized (local namespaces are not serialized).
|
||||
See implementation docstring for more details.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def parse_bool(self, expr: Expression) -> bool | None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def qualified_name(self, n: str) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def is_typeshed_stub_file(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def process_placeholder(
|
||||
self, name: str | None, kind: str, ctx: Context, force_progress: bool = False
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def set_callable_name(sig: Type, fdef: FuncDef) -> ProperType:
|
||||
sig = get_proper_type(sig)
|
||||
if isinstance(sig, FunctionLike):
|
||||
if fdef.info:
|
||||
if fdef.info.fullname in TPDICT_FB_NAMES:
|
||||
# Avoid exposing the internal _TypedDict name.
|
||||
class_name = "TypedDict"
|
||||
else:
|
||||
class_name = fdef.info.name
|
||||
return sig.with_name(f"{fdef.name} of {class_name}")
|
||||
else:
|
||||
return sig.with_name(fdef.name)
|
||||
else:
|
||||
return sig
|
||||
|
||||
|
||||
def calculate_tuple_fallback(typ: TupleType) -> None:
|
||||
"""Calculate a precise item type for the fallback of a tuple type.
|
||||
|
||||
This must be called only after the main semantic analysis pass, since joins
|
||||
aren't available before that.
|
||||
|
||||
Note that there is an apparent chicken and egg problem with respect
|
||||
to verifying type arguments against bounds. Verifying bounds might
|
||||
require fallbacks, but we might use the bounds to calculate the
|
||||
fallbacks. In practice this is not a problem, since the worst that
|
||||
can happen is that we have invalid type argument values, and these
|
||||
can happen in later stages as well (they will generate errors, but
|
||||
we don't prevent their existence).
|
||||
"""
|
||||
fallback = typ.partial_fallback
|
||||
assert fallback.type.fullname == "builtins.tuple"
|
||||
fallback.args = (join.join_type_list(list(typ.items)),) + fallback.args[1:]
|
||||
|
||||
|
||||
class _NamedTypeCallback(Protocol):
|
||||
def __call__(self, fully_qualified_name: str, args: list[Type] | None = None) -> Instance:
|
||||
...
|
||||
|
||||
|
||||
def paramspec_args(
|
||||
name: str,
|
||||
fullname: str,
|
||||
id: TypeVarId | int,
|
||||
*,
|
||||
named_type_func: _NamedTypeCallback,
|
||||
line: int = -1,
|
||||
column: int = -1,
|
||||
prefix: Parameters | None = None,
|
||||
) -> ParamSpecType:
|
||||
return ParamSpecType(
|
||||
name,
|
||||
fullname,
|
||||
id,
|
||||
flavor=ParamSpecFlavor.ARGS,
|
||||
upper_bound=named_type_func("builtins.tuple", [named_type_func("builtins.object")]),
|
||||
default=AnyType(TypeOfAny.from_omitted_generics),
|
||||
line=line,
|
||||
column=column,
|
||||
prefix=prefix,
|
||||
)
|
||||
|
||||
|
||||
def paramspec_kwargs(
|
||||
name: str,
|
||||
fullname: str,
|
||||
id: TypeVarId | int,
|
||||
*,
|
||||
named_type_func: _NamedTypeCallback,
|
||||
line: int = -1,
|
||||
column: int = -1,
|
||||
prefix: Parameters | None = None,
|
||||
) -> ParamSpecType:
|
||||
return ParamSpecType(
|
||||
name,
|
||||
fullname,
|
||||
id,
|
||||
flavor=ParamSpecFlavor.KWARGS,
|
||||
upper_bound=named_type_func(
|
||||
"builtins.dict", [named_type_func("builtins.str"), named_type_func("builtins.object")]
|
||||
),
|
||||
default=AnyType(TypeOfAny.from_omitted_generics),
|
||||
line=line,
|
||||
column=column,
|
||||
prefix=prefix,
|
||||
)
|
||||
|
||||
|
||||
class HasPlaceholders(BoolTypeQuery):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(ANY_STRATEGY)
|
||||
|
||||
def visit_placeholder_type(self, t: PlaceholderType) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def has_placeholder(typ: Type) -> bool:
|
||||
"""Check if a type contains any placeholder types (recursively)."""
|
||||
return typ.accept(HasPlaceholders())
|
||||
|
||||
|
||||
def find_dataclass_transform_spec(node: Node | None) -> DataclassTransformSpec | None:
|
||||
"""
|
||||
Find the dataclass transform spec for the given node, if any exists.
|
||||
|
||||
Per PEP 681 (https://peps.python.org/pep-0681/#the-dataclass-transform-decorator), dataclass
|
||||
transforms can be specified in multiple ways, including decorator functions and
|
||||
metaclasses/base classes. This function resolves the spec from any of these variants.
|
||||
"""
|
||||
|
||||
# The spec only lives on the function/class definition itself, so we need to unwrap down to that
|
||||
# point
|
||||
if isinstance(node, CallExpr):
|
||||
# Like dataclasses.dataclass, transform-based decorators can be applied either with or
|
||||
# without parameters; ie, both of these forms are accepted:
|
||||
#
|
||||
# @typing.dataclass_transform
|
||||
# class Foo: ...
|
||||
# @typing.dataclass_transform(eq=True, order=True, ...)
|
||||
# class Bar: ...
|
||||
#
|
||||
# We need to unwrap the call for the second variant.
|
||||
node = node.callee
|
||||
|
||||
if isinstance(node, RefExpr):
|
||||
node = node.node
|
||||
|
||||
if isinstance(node, Decorator):
|
||||
# typing.dataclass_transform usage must always result in a Decorator; it always uses the
|
||||
# `@dataclass_transform(...)` syntax and never `@dataclass_transform`
|
||||
node = node.func
|
||||
|
||||
if isinstance(node, OverloadedFuncDef):
|
||||
# The dataclass_transform decorator may be attached to any single overload, so we must
|
||||
# search them all.
|
||||
# Note that using more than one decorator is undefined behavior, so we can just take the
|
||||
# first that we find.
|
||||
for candidate in node.items:
|
||||
spec = find_dataclass_transform_spec(candidate)
|
||||
if spec is not None:
|
||||
return spec
|
||||
return find_dataclass_transform_spec(node.impl)
|
||||
|
||||
# For functions, we can directly consult the AST field for the spec
|
||||
if isinstance(node, FuncDef):
|
||||
return node.dataclass_transform_spec
|
||||
|
||||
if isinstance(node, ClassDef):
|
||||
node = node.info
|
||||
if isinstance(node, TypeInfo):
|
||||
# Search all parent classes to see if any are decorated with `typing.dataclass_transform`
|
||||
for base in node.mro[1:]:
|
||||
if base.dataclass_transform_spec is not None:
|
||||
return base.dataclass_transform_spec
|
||||
|
||||
# Check if there is a metaclass that is decorated with `typing.dataclass_transform`
|
||||
#
|
||||
# Note that PEP 681 only discusses using a metaclass that is directly decorated with
|
||||
# `typing.dataclass_transform`; subclasses thereof should be treated with dataclass
|
||||
# semantics rather than as transforms:
|
||||
#
|
||||
# > If dataclass_transform is applied to a class, dataclass-like semantics will be assumed
|
||||
# > for any class that directly or indirectly derives from the decorated class or uses the
|
||||
# > decorated class as a metaclass.
|
||||
#
|
||||
# The wording doesn't make this entirely explicit, but Pyright (the reference
|
||||
# implementation for this PEP) only handles directly-decorated metaclasses.
|
||||
metaclass_type = node.metaclass_type
|
||||
if metaclass_type is not None and metaclass_type.type.dataclass_transform_spec is not None:
|
||||
return metaclass_type.type.dataclass_transform_spec
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# Never returns `None` if a default is given
|
||||
@overload
|
||||
def require_bool_literal_argument(
|
||||
api: SemanticAnalyzerInterface | SemanticAnalyzerPluginInterface,
|
||||
expression: Expression,
|
||||
name: str,
|
||||
default: Literal[True] | Literal[False],
|
||||
) -> bool:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def require_bool_literal_argument(
|
||||
api: SemanticAnalyzerInterface | SemanticAnalyzerPluginInterface,
|
||||
expression: Expression,
|
||||
name: str,
|
||||
default: None = None,
|
||||
) -> bool | None:
|
||||
...
|
||||
|
||||
|
||||
def require_bool_literal_argument(
|
||||
api: SemanticAnalyzerInterface | SemanticAnalyzerPluginInterface,
|
||||
expression: Expression,
|
||||
name: str,
|
||||
default: bool | None = None,
|
||||
) -> bool | None:
|
||||
"""Attempt to interpret an expression as a boolean literal, and fail analysis if we can't."""
|
||||
value = parse_bool(expression)
|
||||
if value is None:
|
||||
api.fail(
|
||||
f'"{name}" argument must be a True or False literal', expression, code=LITERAL_REQ
|
||||
)
|
||||
return default
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def parse_bool(expr: Expression) -> bool | None:
|
||||
if isinstance(expr, NameExpr):
|
||||
if expr.fullname == "builtins.True":
|
||||
return True
|
||||
if expr.fullname == "builtins.False":
|
||||
return False
|
||||
return None
|
||||
Reference in New Issue
Block a user