259 lines
8.2 KiB
Python
259 lines
8.2 KiB
Python
|
"""Shared definitions used by different parts of semantic analysis."""
|
||
|
|
||
|
from abc import abstractmethod
|
||
|
|
||
|
from typing import Optional, List, Callable, Union
|
||
|
from typing_extensions import Final, Protocol
|
||
|
from mypy_extensions import trait
|
||
|
|
||
|
from mypy.nodes import (
|
||
|
Context, SymbolTableNode, FuncDef, Node, TypeInfo, Expression,
|
||
|
SymbolNode, SymbolTable
|
||
|
)
|
||
|
from mypy.types import (
|
||
|
Type, FunctionLike, Instance, TupleType, TPDICT_FB_NAMES, ProperType, get_proper_type,
|
||
|
ParamSpecType, ParamSpecFlavor, Parameters, TypeVarId
|
||
|
)
|
||
|
from mypy.tvar_scope import TypeVarLikeScope
|
||
|
from mypy.errorcodes import ErrorCode
|
||
|
from mypy import join
|
||
|
|
||
|
# 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) -> Optional[SymbolTableNode]:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
@abstractmethod
|
||
|
def lookup_fully_qualified(self, name: str) -> SymbolTableNode:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
@abstractmethod
|
||
|
def lookup_fully_qualified_or_none(self, name: str) -> Optional[SymbolTableNode]:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
@abstractmethod
|
||
|
def fail(self, msg: str, ctx: Context, serious: bool = False, *,
|
||
|
blocker: bool = False, code: Optional[ErrorCode] = None) -> None:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
@abstractmethod
|
||
|
def note(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
@abstractmethod
|
||
|
def record_incomplete_ref(self) -> None:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
@abstractmethod
|
||
|
def defer(self) -> 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
|
||
|
|
||
|
|
||
|
@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
|
||
|
"""
|
||
|
|
||
|
@abstractmethod
|
||
|
def lookup(self, name: str, ctx: Context,
|
||
|
suppress_errors: bool = False) -> Optional[SymbolTableNode]:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
@abstractmethod
|
||
|
def named_type(self, fullname: str,
|
||
|
args: Optional[List[Type]] = None) -> Instance:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
@abstractmethod
|
||
|
def named_type_or_none(self, fullname: str,
|
||
|
args: Optional[List[Type]] = None) -> Optional[Instance]:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
@abstractmethod
|
||
|
def accept(self, node: Node) -> None:
|
||
|
raise NotImplementedError
|
||
|
|
||
|
@abstractmethod
|
||
|
def anal_type(self, t: Type, *,
|
||
|
tvar_scope: Optional[TypeVarLikeScope] = None,
|
||
|
allow_tuple_literal: bool = False,
|
||
|
allow_unbound_tvars: bool = False,
|
||
|
allow_required: bool = False,
|
||
|
report_invalid_types: bool = True) -> Optional[Type]:
|
||
|
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) -> Optional[bool]:
|
||
|
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 is_func_scope(self) -> bool:
|
||
|
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: Optional[List[Type]] = None
|
||
|
) -> Instance: ...
|
||
|
|
||
|
|
||
|
def paramspec_args(
|
||
|
name: str, fullname: str, id: Union[TypeVarId, int], *,
|
||
|
named_type_func: _NamedTypeCallback, line: int = -1, column: int = -1,
|
||
|
prefix: Optional[Parameters] = None
|
||
|
) -> ParamSpecType:
|
||
|
return ParamSpecType(
|
||
|
name,
|
||
|
fullname,
|
||
|
id,
|
||
|
flavor=ParamSpecFlavor.ARGS,
|
||
|
upper_bound=named_type_func('builtins.tuple', [named_type_func('builtins.object')]),
|
||
|
line=line,
|
||
|
column=column,
|
||
|
prefix=prefix
|
||
|
)
|
||
|
|
||
|
|
||
|
def paramspec_kwargs(
|
||
|
name: str, fullname: str, id: Union[TypeVarId, int], *,
|
||
|
named_type_func: _NamedTypeCallback, line: int = -1, column: int = -1,
|
||
|
prefix: Optional[Parameters] = 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')]
|
||
|
),
|
||
|
line=line,
|
||
|
column=column,
|
||
|
prefix=prefix
|
||
|
)
|