Repository URL to install this package:
|
Version:
2022.2.8 ▾
|
pytype
/
context.py
|
|---|
"""Container of things that should be accessible to all abstract values."""
import contextlib
import logging
from typing import Dict, List, Tuple
from pytype import annotation_utils
from pytype import attribute
from pytype import config
from pytype import convert
from pytype import errors
from pytype import load_pytd
from pytype import matcher
from pytype import output
from pytype import special_builtins
from pytype import tracer_vm
from pytype import vm_utils
from pytype.abstract import abstract
from pytype.abstract import abstract_utils
from pytype.typegraph import cfg
from pytype.typegraph import cfg_utils
log = logging.getLogger(__name__)
class Context:
"""An abstract context."""
def __init__(
self,
errorlog: errors.ErrorLog,
options: config.Options,
loader: load_pytd.Loader,
generate_unknowns=False,
store_all_calls=False,
):
# Inputs
self.errorlog = errorlog
self.options = options
self.python_version: Tuple[int, int] = self.options.python_version
self.loader = loader
self.generate_unknowns = generate_unknowns
self.store_all_calls = store_all_calls
# Typegraph
self.program = cfg.Program()
self.root_node: cfg.CFGNode = self.program.NewCFGNode("root")
self.program.entrypoint = self.root_node
# Represents the program exit. Needs to be set before analyze_types.
self.exitpoint: cfg.CFGNode = None
# Helper classes/modules
self.vm = tracer_vm.CallTracer(self)
self.annotation_utils = annotation_utils.AnnotationUtils(self)
self.attribute_handler = attribute.AbstractAttributeHandler(self)
self.converter_minimally_initialized = False
self.convert = convert.Converter(self)
self.pytd_convert = output.Converter(self)
self.program.default_data = self.convert.unsolvable
# Other context
self.callself_stack: List[cfg.Variable] = []
# Map from builtin names to canonical objects.
self.special_builtins: Dict[str, abstract.BaseValue] = {
# The super() function.
"super": self.convert.super_type,
# The object type.
"object": self.convert.object_type,
# for more pretty branching tests.
"__random__": self.convert.primitive_class_instances[bool],
# for debugging
"reveal_type": special_builtins.RevealType(self),
# boolean values.
"True": self.convert.true,
"False": self.convert.false,
# builtin classes
"property": special_builtins.Property(self),
"staticmethod": special_builtins.StaticMethod(self),
"classmethod": special_builtins.ClassMethod(self),
"dict": special_builtins.Dict(self),
}
# builtin functions
for cls in (
special_builtins.Abs,
special_builtins.AssertType,
special_builtins.HasAttr,
special_builtins.IsCallable,
special_builtins.IsInstance,
special_builtins.IsSubclass,
special_builtins.Next,
):
self.special_builtins[cls.name] = cls.make(self)
# If set, allow construction of recursive values, setting the
# self-referential field to Any
self.recursion_allowed = False
def matcher(self, node):
return matcher.AbstractMatcher(node, self)
@contextlib.contextmanager
def allow_recursive_convert(self):
old = self.recursion_allowed
self.recursion_allowed = True
try:
yield
finally:
self.recursion_allowed = old
def new_unsolvable(self, node):
"""Create a new unsolvable variable at node."""
return self.convert.unsolvable.to_variable(node)
def join_cfg_nodes(self, nodes):
"""Get a new node to which the given nodes have been joined."""
assert nodes
if len(nodes) == 1:
return nodes[0]
else:
ret = self.program.NewCFGNode(self.vm.frame and
self.vm.frame.current_opcode and
self.vm.frame.current_opcode.line)
for node in nodes:
node.ConnectTo(ret)
return ret
def join_variables(self, node, variables):
return cfg_utils.merge_variables(self.program, node, variables)
def join_bindings(self, node, bindings):
return cfg_utils.merge_bindings(self.program, node, bindings)
# TODO(b/202335303): The only reason this method exists is so that
# abstract/mixin.py can construct an abstract.NativeFunction.
def make_native_function(self, name, method):
return abstract.NativeFunction(name, method, self)
def make_class(self, node, name_var, bases, class_dict_var, cls_var,
new_class_var=None, is_decorated=False, class_type=None):
return vm_utils.make_class(node, name_var, bases, class_dict_var, cls_var,
new_class_var, is_decorated, class_type, self)
def check_annotation_type_mismatch(
self, node, name, typ, value, stack, allow_none, details=None):
"""Checks for a mismatch between a variable's annotation and value.
Args:
node: node
name: variable name
typ: variable annotation
value: variable value
stack: a frame stack for error reporting
allow_none: whether a value of None is allowed for any type
details: any additional details to add to the error message
"""
if not typ or not value:
return
if (value.data == [self.convert.ellipsis] or
allow_none and value.data == [self.convert.none]):
return
contained_type = abstract_utils.match_type_container(
typ, ("typing.ClassVar", "dataclasses.InitVar"))
if contained_type:
typ = contained_type
bad = self.matcher(node).bad_matches(value, typ)
for view, error_details in bad:
binding = view[value]
self.errorlog.annotation_type_mismatch(
stack, typ, binding, name, error_details, details)