Skip to content

Commit

Permalink
Merge fb45bf0 into f562c57
Browse files Browse the repository at this point in the history
  • Loading branch information
palfrey committed Oct 9, 2022
2 parents f562c57 + fb45bf0 commit e85cada
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 95 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ jobs:
pip install -U pip
pip install --upgrade coveralls setuptools setuptools_scm pep517 .[tests]
pip install .
- name: Mypy testing (<3.11)
run: |
pip install mypy==0.910
python -m mypy executing --exclude=executing/_position_node_finder.py
# fromJson because https://github.community/t/passing-an-array-literal-to-contains-function-causes-syntax-error/17213/3
if: ${{ !contains(fromJson('["2.7", "pypy2", "pypy-3.6", "3.11-dev"]'), matrix.python-version) }}
# pypy < 3.8 very doesn't work
# 2.7 is tested separately in mypy-py2, as we need to run mypy under Python 3.x
- name: Mypy testing (3.11)
run: |
pip install mypy==0.971
python -m mypy executing
# fromJson because https://github.community/t/passing-an-array-literal-to-contains-function-causes-syntax-error/17213/3
if: ${{ contains(fromJson('["3.11-dev"]'), matrix.python-version) }}
# only >=3.11 use _position_node_finder.py
- name: Test
env:
EXECUTING_SLOW_TESTS: 1
Expand All @@ -41,3 +56,23 @@ jobs:
uses: AndreMiras/coveralls-python-action@v20201129
with:
parallel-finished: true

# Can't run mypy on Python 2.7, but can run it in Python 2 mode
mypy-py2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9

- name: Install dependencies
run: |
pip install --upgrade setuptools setuptools_scm pep517
pip install .[tests]
pip install mypy[python2]==0.910
- name: Mypy testing for Python 2
run: |
python -m mypy --py2 executing --exclude=executing/_position_node_finder.py
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,7 @@ venv.bak/

# mypy
.mypy_cache/
.dmypy.json

# VSCode
.vscode/
4 changes: 2 additions & 2 deletions executing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
"""

from collections import namedtuple
_VersionInfo = namedtuple('VersionInfo', ('major', 'minor', 'micro'))
_VersionInfo = namedtuple('_VersionInfo', ('major', 'minor', 'micro'))
from .executing import Source, Executing, only, NotOneValueFound, cache, future_flags
try:
from .version import __version__
from .version import __version__ # type: ignore[import]
if "dev" in __version__:
raise ValueError
except Exception:
Expand Down
3 changes: 2 additions & 1 deletion executing/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class VerifierFailure(Exception):
"""

def __init__(self, title, node, instruction):
# type: (object, object, object) -> None
self.node = node
self.instruction = instruction

super().__init__(title)
super().__init__(title) # type: ignore[call-arg]
70 changes: 37 additions & 33 deletions executing/_position_node_finder.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import ast
import dis
from .executing import NotOneValueFound, only, function_node_types, assert_
from types import CodeType, FrameType
from typing import Any, Callable, Iterator, Optional, Sequence, Set, Tuple, Type, Union, cast
from .executing import EnhancedAST, NotOneValueFound, Source, only, function_node_types, assert_
from ._exceptions import KnownIssue, VerifierFailure

from functools import lru_cache

# the code in this module can use all python>=3.11 features


def parents(node):
def parents(node: EnhancedAST) -> Iterator[EnhancedAST]:
while True:
if hasattr(node, "parent"):
node = node.parent
yield node
else:
break


def node_and_parents(node):
def node_and_parents(node: EnhancedAST) -> Iterator[EnhancedAST]:
yield node
yield from parents(node)


def mangled_name(node):
def mangled_name(node: EnhancedAST) -> str:
"""
Parameters:
Expand Down Expand Up @@ -64,7 +65,7 @@ def mangled_name(node):


@lru_cache(128)
def get_instructions(code):
def get_instructions(code: CodeType) -> list[dis.Instruction]:
return list(dis.get_instructions(code, show_caches=True))


Expand Down Expand Up @@ -106,11 +107,11 @@ class PositionNodeFinder(object):
There are only some exceptions for methods and attributes.
"""

def __init__(self, frame, stmts, tree, lasti, source):
def __init__(self, frame: FrameType, stmts: Set[EnhancedAST], tree: ast.Module, lasti: int, source: Source):
self.bc_list = get_instructions(frame.f_code)

self.source = source
self.decorator = None
self.decorator: Optional[EnhancedAST] = None

# work around for https://github.com/python/cpython/issues/96970
while self.opname(lasti) == "CACHE":
Expand All @@ -120,6 +121,7 @@ def __init__(self, frame, stmts, tree, lasti, source):
# try to map with all match_positions
self.result = self.find_node(lasti)
except NotOneValueFound:
typ: tuple[Type]
# LOAD_METHOD could load "".join for long "..."%(...) BinOps
# this can only be associated by using all positions
if self.opname(lasti) in (
Expand Down Expand Up @@ -155,10 +157,10 @@ def __init__(self, frame, stmts, tree, lasti, source):
if self.decorator is None:
self.verify(self.result, self.instruction(lasti))

def test_for_decorator(self, node, index):
def test_for_decorator(self, node: EnhancedAST, index: int) -> None:
if (
isinstance(node.parent, (ast.ClassDef, function_node_types))
and node in node.parent.decorator_list
and node in node.parent.decorator_list # type: ignore[attr-defined]
):
node_func = node.parent

Expand Down Expand Up @@ -198,7 +200,7 @@ def test_for_decorator(self, node, index):

index += 4

def known_issues(self, node, instruction):
def known_issues(self, node: EnhancedAST, instruction: dis.Instruction) -> None:
if instruction.opname in ("COMPARE_OP", "IS_OP", "CONTAINS_OP") and isinstance(
node, types_cmp_issue
):
Expand All @@ -213,14 +215,14 @@ def known_issues(self, node, instruction):

comparisons = [
n
for n in ast.walk(node.test)
for n in ast.walk(node.test) # type: ignore[attr-defined]
if isinstance(n, ast.Compare) and len(n.ops) > 1
]

assert_(comparisons, "expected at least one comparison")

if len(comparisons) == 1:
node = self.result = comparisons[0]
node = self.result = cast(EnhancedAST, comparisons[0])
else:
raise KnownIssue(
"multiple chain comparison inside %s can not be fixed" % (node)
Expand Down Expand Up @@ -258,7 +260,7 @@ def known_issues(self, node, instruction):
raise KnownIssue("store __classcell__")

@staticmethod
def is_except_cleanup(inst, node):
def is_except_cleanup(inst: dis.Instruction, node: EnhancedAST) -> bool:
if inst.opname not in (
"STORE_NAME",
"STORE_FAST",
Expand Down Expand Up @@ -294,16 +296,16 @@ def is_except_cleanup(inst, node):
for n in parents(node)
)

def verify(self, node, instruction):
def verify(self, node: EnhancedAST, instruction: dis.Instruction) -> None:
"""
checks if this node could gererate this instruction
"""

op_name = instruction.opname
extra_filter = lambda e: True
ctx = type(None)
extra_filter: Callable[[EnhancedAST], bool] = lambda e: True
ctx: Type = type(None)

def inst_match(opnames, **kwargs):
def inst_match(opnames: Union[str, Sequence[str]], **kwargs: Any) -> bool:
"""
match instruction
Expand All @@ -322,7 +324,7 @@ def inst_match(opnames, **kwargs):
k: getattr(instruction, k) for k in kwargs
}

def node_match(node_type, **kwargs):
def node_match(node_type: Union[Type, Tuple[Type, ...]], **kwargs: Any) -> bool:
"""
match the ast-node
Expand Down Expand Up @@ -372,7 +374,7 @@ def node_match(node_type, **kwargs):
or inst_match(("CALL", "BUILD_STRING"))
)
and node_match(ast.BinOp, left=ast.Constant, op=ast.Mod)
and isinstance(node.left.value, str)
and isinstance(cast(ast.Constant, cast(ast.BinOp, node).left).value, str)
):
# "..."%(...) uses "".join
return
Expand Down Expand Up @@ -414,7 +416,7 @@ def node_match(node_type, **kwargs):
if (
inst_match(("STORE_NAME", "STORE_FAST", "STORE_DEREF", "STORE_GLOBAL"))
and node_match((ast.Import, ast.ImportFrom))
and any(mangled_name(alias) == instruction.argval for alias in node.names)
and any(mangled_name(cast(EnhancedAST, alias)) == instruction.argval for alias in cast(ast.Import, node).names)
):
# store imported module in variable
return
Expand Down Expand Up @@ -477,15 +479,16 @@ def node_match(node_type, **kwargs):

# old verifier

typ = type(None)
typ: Type = type(None)
op_type: Type = type(None)

if op_name.startswith(("BINARY_SUBSCR", "SLICE+")):
typ = ast.Subscript
ctx = ast.Load
elif op_name.startswith("BINARY_"):
typ = ast.BinOp
op_type = op_type_map[instruction.argrepr]
extra_filter = lambda e: isinstance(e.op, op_type)
extra_filter = lambda e: isinstance(cast(ast.BinOp, e).op, op_type)
elif op_name.startswith("UNARY_"):
typ = ast.UnaryOp
op_type = dict(
Expand All @@ -494,7 +497,7 @@ def node_match(node_type, **kwargs):
UNARY_NOT=ast.Not,
UNARY_INVERT=ast.Invert,
)[op_name]
extra_filter = lambda e: isinstance(e.op, op_type)
extra_filter = lambda e: isinstance(cast(ast.UnaryOp, e).op, op_type)
elif op_name in ("LOAD_ATTR", "LOAD_METHOD", "LOOKUP_METHOD"):
typ = ast.Attribute
ctx = ast.Load
Expand All @@ -508,10 +511,10 @@ def node_match(node_type, **kwargs):
):
typ = ast.Name
ctx = ast.Load
extra_filter = lambda e: e.id == instruction.argval
extra_filter = lambda e: cast(ast.Name, e).id == instruction.argval
elif op_name in ("COMPARE_OP", "IS_OP", "CONTAINS_OP"):
typ = ast.Compare
extra_filter = lambda e: len(e.ops) == 1
extra_filter = lambda e: len(cast(ast.Compare, e).ops) == 1
elif op_name.startswith(("STORE_SLICE", "STORE_SUBSCR")):
ctx = ast.Store
typ = ast.Subscript
Expand Down Expand Up @@ -541,22 +544,23 @@ def node_match(node_type, **kwargs):

raise VerifierFailure(title, node, instruction)

def instruction(self, index):
def instruction(self, index: int) -> dis.Instruction:
return self.bc_list[index // 2]

def opname(self, index):
def opname(self, index: int) -> str:
return self.instruction(index).opname

def find_node(
self,
index,
match_positions=("lineno", "end_lineno", "col_offset", "end_col_offset"),
typ=(ast.expr, ast.stmt, ast.excepthandler, ast.pattern),
):
index: int,
match_positions: Sequence[str]=("lineno", "end_lineno", "col_offset", "end_col_offset"),
typ: tuple[Type, ...]=(ast.expr, ast.stmt, ast.excepthandler, ast.pattern),
) -> EnhancedAST:
position = self.instruction(index).positions
assert position is not None and position.lineno is not None

return only(
node
cast(EnhancedAST, node)
for node in self.source._nodes_by_line[position.lineno]
if isinstance(node, typ)
if not isinstance(node, ast.Expr)
Expand Down
Loading

0 comments on commit e85cada

Please sign in to comment.