Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google sync #1578

Merged
merged 9 commits into from
Feb 8, 2024
2 changes: 1 addition & 1 deletion pytype/blocks/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class OrderedCode:
localsplus: Tuple[str, ...]
exception_table: Tuple[Any, ...]
order: List[Block]
python_version = Tuple[int, int]
python_version: Tuple[int, int]

def __init__(self, code, bytecode, order):
assert hasattr(code, "co_code")
Expand Down
7 changes: 4 additions & 3 deletions pytype/overlays/attr_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,12 @@ def decorate(self, node, cls):
def to_metadata(self):
# For simplicity, we give all attrs decorators with the same behavior as
# attr.s the same tag.
args = self._current_args or self.DEFAULT_ARGS
return {
"tag": "attr.s",
"init": self._current_args["init"],
"kw_only": self._current_args["kw_only"],
"auto_attribs": self._current_args["auto_attribs"]
"init": args["init"],
"kw_only": args["kw_only"],
"auto_attribs": args["auto_attribs"]
}


Expand Down
8 changes: 7 additions & 1 deletion pytype/overlays/typed_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,13 @@ def get_slot(self, node, key_var, default_var=None):
str_key = self._check_key(key_var)
except TypedDictKeyMissing:
return node, default_var or self.ctx.convert.none.to_variable(node)
return node, self.pyval[str_key]
if str_key in self.pyval:
return node, self.pyval[str_key]
else:
# If total=False the key might not be in self.pyval.
# TODO(mdemello): Should we return `self.props[key].typ | default | None`
# here, or just `default | None`?
return node, default_var or self.ctx.convert.none.to_variable(node)

def merge_instance_type_parameter(self, node, name, value):
_, _, short_name = name.rpartition(".")
Expand Down
15 changes: 11 additions & 4 deletions pytype/pattern_matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def cover_instance(self, val) -> List[_Value]:
opt.values.remove(val)
return [val]
else:
return []
return [val] if opt.indefinite else []

def cover_type(self, val) -> List[_Value]:
"""Remove a class and any associated instances from the match options."""
Expand Down Expand Up @@ -165,7 +165,10 @@ def __init__(self, match_var, ctx):
self.is_valid: bool = True

for d in match_var.data:
if isinstance(d, abstract.Instance):
if isinstance(d, abstract.Unsolvable):
self.is_valid = False
self.could_contain_anything = True
elif isinstance(d, abstract.Instance):
self.options.add_instance(d)
else:
self.options.add_type(d)
Expand Down Expand Up @@ -206,12 +209,17 @@ def cover_from_cmp(self, line, case_var) -> List[_Value]:
# what we have matched against.
ret += self.cover_type(d)
self.invalidate()
else:
elif isinstance(d, abstract.Instance):
ret += self.options.cover_instance(d)
self.cases[line].add_instance(d)
if isinstance(d, abstract.ConcreteValue) and d.pyval is None:
# Need to special-case `case None` since it's compiled differently.
ret += self.options.cover_type(d.cls)
else:
# We do not handle whatever case this is; just invalidate the tracker
# TODO(mdemello): This is probably an error in the user's code; we
# should figure out a way to report it.
self.invalidate()
return ret

def cover_from_none(self, line) -> List[_Value]:
Expand Down Expand Up @@ -391,7 +399,6 @@ def add_cmp_branch(
if op.line not in self.matches.match_cases:
return None
tracker = self.get_current_type_tracker(op, match_var)

# If we are not part of a class match, check if we have an exhaustive match
# (enum or union of literals) that we are tracking.
if not tracker:
Expand Down
22 changes: 22 additions & 0 deletions pytype/pytd/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,25 @@ def VisitNamedType(self, t):
module_name = module.name
if module_name == self.name: # dotted local reference
return t
# It's possible that module_name.cls_prefix is actually an alias to another
# module. In that case, we need to unwrap the alias, so we can look up
# aliased_module.name.
if cls_prefix:
try:
# cls_prefix includes the trailing period.
maybe_alias = pytd.LookupItemRecursive(module, cls_prefix[:-1])
except KeyError:
pass
else:
if isinstance(maybe_alias, pytd.Alias) and isinstance(
maybe_alias.type, pytd.Module
):
if maybe_alias.type.module_name not in self._module_map:
raise KeyError(
f"{t.name} refers to unknown module {maybe_alias.name}"
)
module = self._module_map[maybe_alias.type.module_name]
cls_prefix = ""
name = cls_prefix + name
try:
if name == "*":
Expand Down Expand Up @@ -1507,6 +1526,9 @@ def EnterNamedType(self, node):
def EnterLateType(self, node):
self._ProcessName(node.name, self.late_dependencies)

def EnterModule(self, node):
self._ProcessName(node.module_name, self.dependencies)


def ExpandSignature(sig):
"""Expand a single signature.
Expand Down
44 changes: 44 additions & 0 deletions pytype/rewrite/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,28 @@ py_library(
NAME
rewrite
DEPS
.abstract
.analyze
.frame
.vm
)

py_library(
NAME
abstract
SRCS
abstract.py
)

py_test(
NAME
abstract_test
SRCS
abstract_test.py
DEPS
.abstract
)

py_library(
NAME
analyze
Expand Down Expand Up @@ -35,12 +53,38 @@ py_test(
pytype.pytd.pytd
)

py_library(
NAME
frame
SRCS
frame.py
DEPS
.abstract
pytype.blocks.blocks
pytype.rewrite.flow.flow
)

py_test(
NAME
frame_test
SRCS
frame_test.py
DEPS
.abstract
.frame
pytype.blocks.blocks
pytype.pyc.pyc
pytype.rewrite.flow.flow
pytype.rewrite.tests.test_utils
)

py_library(
NAME
vm
SRCS
vm.py
DEPS
.frame
pytype.blocks.blocks
pytype.rewrite.flow.flow
)
Expand Down
17 changes: 17 additions & 0 deletions pytype/rewrite/abstract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Abstract representations of Python values."""


class BaseValue:
pass


class PythonConstant(BaseValue):

def __init__(self, constant):
self.constant = constant

def __repr__(self):
return f'PythonConstant({self.constant!r})'

def __eq__(self, other):
return type(self) == type(other) and self.constant == other.constant # pylint: disable=unidiomatic-typecheck
20 changes: 20 additions & 0 deletions pytype/rewrite/abstract_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from pytype.rewrite import abstract

import unittest


class PythonConstantTest(unittest.TestCase):

def test_equal(self):
c1 = abstract.PythonConstant('a')
c2 = abstract.PythonConstant('a')
self.assertEqual(c1, c2)

def test_not_equal(self):
c1 = abstract.PythonConstant('a')
c2 = abstract.PythonConstant('b')
self.assertNotEqual(c1, c2)


if __name__ == '__main__':
unittest.main()
6 changes: 3 additions & 3 deletions pytype/rewrite/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pytype.blocks import blocks
from pytype.pyc import pyc
from pytype.pytd import pytd
from pytype.rewrite import vm
from pytype.rewrite import vm as vm_lib

# How deep to follow call chains:
_INIT_MAXIMUM_DEPTH = 4 # during module loading
Expand Down Expand Up @@ -75,8 +75,8 @@ def _analyze(
code = _get_bytecode(src, options)
# TODO(b/241479600): Populate globals from builtins.
globals_ = {}
module_vm = vm.VM(code, initial_locals=globals_, globals_=globals_)
module_vm.run()
vm = vm_lib.VirtualMachine(code, globals_)
vm.run()
# TODO(b/241479600): Analyze classes and functions.


Expand Down
12 changes: 6 additions & 6 deletions pytype/rewrite/flow/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ py_library(
flow
DEPS
.conditions
.frame_base
.state
.variables
.vm_base
)

py_library(
Expand Down Expand Up @@ -68,9 +68,9 @@ py_test(

py_library(
NAME
vm_base
frame_base
SRCS
vm_base.py
frame_base.py
DEPS
.state
.variables
Expand All @@ -79,13 +79,13 @@ py_library(

py_test(
NAME
vm_base_test
frame_base_test
SRCS
vm_base_test.py
frame_base_test.py
DEPS
.conditions
.frame_base
.state
.vm_base
pytype.pyc.pyc
pytype.rewrite.tests.test_utils
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Base implementation of an abstract virtual machine for bytecode.
"""Base implementation of a frame of an abstract virtual machine for bytecode.

This module contains a VmBase class, which provides a base implementation of a
VM that analyzes bytecode one instruction (i.e., opcode) at a time, tracking
variables and conditions. Use VmBase by subclassing it and adding a
This module contains a FrameBase class, which provides a base implementation of
a frame that analyzes bytecode one instruction (i.e., opcode) at a time,
tracking variables and conditions. Use FrameBase by subclassing it and adding a
byte_{opcode_name} method implementing each opcode.
"""

Expand All @@ -16,18 +16,22 @@

@dataclasses.dataclass
class _Step:
"""Block and opcode indices for a VM step."""
"""Block and opcode indices for a frame step."""

block: int
opcode: int


class VmConsumedError(Exception):
"""Raised when step() is called on a VM with no more opcodes to execute."""
class FrameConsumedError(Exception):
"""Raised when step() is called on a frame with no more opcodes to execute."""


class VmBase:
"""Virtual machine."""
class FrameBase:
"""Virtual machine frame.

Attributes:
final_locals: The frame's `locals` dictionary after it finishes execution.
"""

def __init__(
self, code: blocks.OrderedCode,
Expand All @@ -36,7 +40,11 @@ def __init__(
# Sanity check: non-empty code
assert code.order and all(block.code for block in code.order)
self._code = code # bytecode
self._initial_locals = initial_locals # locally scoped names before VM runs

# Local names before and after frame runs
self._initial_locals = initial_locals
self.final_locals: Dict[str, variables.Variable] = None

self._current_step = _Step(0, 0) # current block and opcode indices

self._states: Dict[int, state.BlockState] = {} # block id to state
Expand All @@ -50,7 +58,7 @@ def step(self) -> None:
# Grab the current block and opcode.
block_index = self._current_step.block
if block_index == -1:
raise VmConsumedError()
raise FrameConsumedError()
opcode_index = self._current_step.opcode
block = self._code.order[block_index]
opcode = block[opcode_index]
Expand All @@ -72,6 +80,7 @@ def step(self) -> None:
self._merge_state_into(self._current_state, opcode.next.index)
if block is self._code.order[-1]:
self._current_step.block = -1
self.final_locals = self._current_state.get_locals()
else:
self._current_step.block += 1
self._current_step.opcode = 0
Expand Down
Loading
Loading