Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e6c37ac
feat: 3.12 support for the PositionNodeFinder
15r10nk May 18, 2023
486a40c
feat: support LOAD_CONST
15r10nk Sep 20, 2023
bb04189
test: verify exception generation for asserts >= python 3.11.2
15r10nk Mar 30, 2023
acd860e
test: skip test of module_files for the SentinelNodeFinder
15r10nk Mar 10, 2023
558f378
test: do not check qualnames for files with syntax errors
15r10nk May 19, 2023
348d8ec
fix: handle List/Set/DictComp inlining
15r10nk Sep 10, 2023
76a067e
fix: handle super optimization
15r10nk Sep 11, 2023
7f555b7
fix: type parameter related things
15r10nk Sep 12, 2023
93f4427
fix: f-string
15r10nk Sep 13, 2023
b209631
fix: remove python 2.7 from ci tests
15r10nk Sep 17, 2023
64c0fc0
fix: disabled type checking for 3.12
15r10nk Sep 17, 2023
fdb436b
test: added test results for 3.12
15r10nk Sep 17, 2023
6163fa2
fix: fix f-strings for cpython 3.9
15r10nk Sep 18, 2023
8576249
fix: deadcode detection for NamedExpr
15r10nk Sep 20, 2023
c8d6b8e
fix: workaround for a bug in six
15r10nk Sep 21, 2023
4c008ff
Don't skip test_sys_modules
alexmojaki Sep 22, 2023
d71acda
Don't test pypy2
alexmojaki Sep 22, 2023
81438ba
3.11-dev -> 3.11
alexmojaki Sep 22, 2023
d224166
Add another exception for STORE_ATTR in TestFiles
alexmojaki Sep 22, 2023
50eeb36
remove python2 code
alexmojaki Sep 23, 2023
9ec8ed9
remove mypy-py2 job
alexmojaki Sep 23, 2023
4af67e4
text_type -> str
alexmojaki Sep 23, 2023
c668b55
feat: support for __iter__
15r10nk Sep 24, 2023
81888a3
feat: support for __next__
15r10nk Sep 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 4 additions & 24 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, '3.10', 3.11-dev, pypy2, pypy-3.6]
python-version: [3.5, 3.6, 3.7, 3.8, 3.9, '3.10', 3.11, 3.12-dev, pypy-3.6]

steps:
- uses: actions/checkout@v2
Expand All @@ -26,15 +26,15 @@ jobs:
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) }}
if: ${{ !contains(fromJson('["pypy-3.6", "3.11","3.12-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) }}
# TODO: enable typechecking for 3.12
if: ${{ contains(fromJson('["3.11"]'), matrix.python-version) }}
# only >=3.11 use _position_node_finder.py
- name: Test
env:
Expand All @@ -56,23 +56,3 @@ 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
225 changes: 212 additions & 13 deletions executing/_position_node_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ def mangled_name(node: EnhancedAST) -> str:
elif isinstance(node, ast.ExceptHandler):
assert node.name
name = node.name
elif sys.version_info >= (3,12) and isinstance(node,ast.TypeVar):
name=node.name
else:
raise TypeError("no node to mangle")
raise TypeError("no node to mangle for type "+repr(type(node)))

if name.startswith("__") and not name.endswith("__"):

Expand Down Expand Up @@ -176,21 +178,24 @@ def test_for_decorator(self, node: EnhancedAST, index: int) -> None:

# index opname
# ------------------
# index-4 PRECALL
# index-4 PRECALL (only in 3.11)
# index-2 CACHE
# index CALL <- the call instruction
# ... CACHE some CACHE instructions

# maybe multiple other bytecode blocks for other decorators
# index-4 PRECALL
# index-4 PRECALL (only in 3.11)
# index-2 CACHE
# index CALL <- index of the next loop
# ... CACHE some CACHE instructions

# index+x STORE_* the ast-node of this instruction points to the decorated thing

if self.opname(index - 4) != "PRECALL" or self.opname(index) != "CALL": # pragma: no mutate
break # pragma: no mutate
if not (
(self.opname(index - 4) == "PRECALL" or sys.version_info >= (3, 12))
and self.opname(index) == "CALL"
): # pragma: no mutate
break # pragma: no mutate

index += 2

Expand All @@ -205,7 +210,8 @@ def test_for_decorator(self, node: EnhancedAST, index: int) -> None:
self.decorator = node
return

index += 4
if sys.version_info < (3, 12):
index += 4

def known_issues(self, node: EnhancedAST, instruction: dis.Instruction) -> None:
if instruction.opname in ("COMPARE_OP", "IS_OP", "CONTAINS_OP") and isinstance(
Expand Down Expand Up @@ -259,6 +265,37 @@ def known_issues(self, node: EnhancedAST, instruction: dis.Instruction) -> None:
# TODO: investigate
raise KnownIssue("pattern matching ranges seems to be wrong")

if (
sys.version_info >= (3, 12)
and isinstance(node, ast.Call)
and isinstance(node.func, ast.Name)
and node.func.id == "super"
):
# super is optimized to some instructions which do not map nicely to a Call

# find the enclosing function
func = node.parent
while hasattr(func, "parent") and not isinstance(
func, (ast.AsyncFunctionDef, ast.FunctionDef)
):

func = func.parent

# get the first function argument (self/cls)
first_arg = None

if hasattr(func, "args"):
args = [*func.args.posonlyargs, *func.args.args]
if args:
first_arg = args[0].arg

if (instruction.opname, instruction.argval) in [
("LOAD_DEREF", "__class__"),
("LOAD_FAST", first_arg),
("LOAD_DEREF", first_arg),
]:
raise KnownIssue("super optimization")

if self.is_except_cleanup(instruction, node):
raise KnownIssue("exeption cleanup does not belong to the last node in a except block")

Expand All @@ -279,6 +316,14 @@ def known_issues(self, node: EnhancedAST, instruction: dis.Instruction) -> None:

raise KnownIssue("store __classcell__")

if (
instruction.opname == "CALL"
and not isinstance(node,ast.Call)
and any(isinstance(p, ast.Assert) for p in parents(node))
and sys.version_info >= (3, 11, 2)
):
raise KnownIssue("exception generation maps to condition")

@staticmethod
def is_except_cleanup(inst: dis.Instruction, node: EnhancedAST) -> bool:
if inst.opname not in (
Expand Down Expand Up @@ -392,6 +437,13 @@ def node_match(node_type: Union[Type, Tuple[Type, ...]], **kwargs: Any) -> bool:
# call to the generator function
return

if (
sys.version_info >= (3, 12)
and inst_match(("LOAD_FAST_AND_CLEAR", "STORE_FAST"))
and node_match((ast.ListComp, ast.SetComp, ast.DictComp))
):
return

if inst_match(("CALL", "CALL_FUNCTION_EX")) and node_match(
(ast.ClassDef, ast.Call)
):
Expand All @@ -410,6 +462,7 @@ def node_match(node_type: Union[Type, Tuple[Type, ...]], **kwargs: Any) -> bool:
if (
(
inst_match("LOAD_METHOD", argval="join")
or inst_match("LOAD_ATTR", argval="join") # 3.12
or inst_match(("CALL", "BUILD_STRING"))
)
and node_match(ast.BinOp, left=ast.Constant, op=ast.Mod)
Expand Down Expand Up @@ -495,9 +548,14 @@ def node_match(node_type: Union[Type, Tuple[Type, ...]], **kwargs: Any) -> bool:
):
return

if inst_match(("JUMP_IF_TRUE_OR_POP", "JUMP_IF_FALSE_OR_POP")) and node_match(
ast.BoolOp
):
if inst_match(
(
"JUMP_IF_TRUE_OR_POP",
"JUMP_IF_FALSE_OR_POP",
"POP_JUMP_IF_TRUE",
"POP_JUMP_IF_FALSE",
)
) and node_match(ast.BoolOp):
# and/or short circuit
return

Expand All @@ -511,7 +569,15 @@ def node_match(node_type: Union[Type, Tuple[Type, ...]], **kwargs: Any) -> bool:
and isinstance(node.parent, ast.AugAssign)
)
) and inst_match(
("LOAD_NAME", "LOAD_FAST", "LOAD_GLOBAL", "LOAD_DEREF"), argval=mangled_name(node)
(
"LOAD_NAME",
"LOAD_FAST",
"LOAD_FAST_CHECK",
"LOAD_GLOBAL",
"LOAD_DEREF",
"LOAD_FROM_DICT_OR_DEREF",
),
argval=mangled_name(node),
):
return

Expand All @@ -520,6 +586,124 @@ def node_match(node_type: Union[Type, Tuple[Type, ...]], **kwargs: Any) -> bool:
):
return

if node_match(ast.Constant) and inst_match(
"LOAD_CONST", argval=cast(ast.Constant, node).value
):
return

if node_match(
(ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp, ast.For)
) and inst_match(("GET_ITER", "FOR_ITER")):
return

if sys.version_info >= (3, 12):
if node_match(ast.UnaryOp, op=ast.UAdd) and inst_match(
"CALL_INTRINSIC_1", argrepr="INTRINSIC_UNARY_POSITIVE"
):
return

if node_match(ast.Subscript) and inst_match("BINARY_SLICE"):
return

if node_match(ast.ImportFrom) and inst_match(
"CALL_INTRINSIC_1", argrepr="INTRINSIC_IMPORT_STAR"
):
return

if (
node_match(ast.Yield) or isinstance(node.parent, ast.GeneratorExp)
) and inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_ASYNC_GEN_WRAP"):
return

if node_match(ast.Name) and inst_match("LOAD_DEREF",argval="__classdict__"):
return

if node_match(ast.TypeVar) and (
inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEVAR")
or inst_match(
"CALL_INTRINSIC_2", argrepr="INTRINSIC_TYPEVAR_WITH_BOUND"
)
or inst_match(
"CALL_INTRINSIC_2", argrepr="INTRINSIC_TYPEVAR_WITH_CONSTRAINTS"
)
or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=mangled_name(node))
):
return

if node_match(ast.TypeVarTuple) and (
inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEVARTUPLE")
or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=node.name)
):
return

if node_match(ast.ParamSpec) and (
inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_PARAMSPEC")

or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=node.name)):
return


if node_match(ast.TypeAlias):
if(
inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEALIAS")
or inst_match(
("STORE_NAME", "STORE_FAST", "STORE_DEREF"), argrepr=node.name.id
)
or inst_match("CALL")
):
return


if node_match(ast.ClassDef) and node.type_params:
if inst_match(
("STORE_DEREF", "LOAD_DEREF", "LOAD_FROM_DICT_OR_DEREF"),
argrepr=".type_params",
):
return

if inst_match(("STORE_FAST", "LOAD_FAST"), argrepr=".generic_base"):
return

if inst_match(
"CALL_INTRINSIC_1", argrepr="INTRINSIC_SUBSCRIPT_GENERIC"
):
return

if inst_match("LOAD_DEREF",argval="__classdict__"):
return

if node_match((ast.FunctionDef,ast.AsyncFunctionDef)) and node.type_params:
if inst_match("CALL"):
return

if inst_match(
"CALL_INTRINSIC_2", argrepr="INTRINSIC_SET_FUNCTION_TYPE_PARAMS"
):
return

if inst_match("LOAD_FAST",argval=".defaults"):
return

if inst_match("LOAD_FAST",argval=".kwdefaults"):
return

if inst_match("STORE_NAME", argval="__classdictcell__"):
# this is a general thing
return


# f-strings

if node_match(ast.JoinedStr) and (
inst_match("LOAD_ATTR", argval="join")
or inst_match(("LIST_APPEND", "CALL"))
):
return

if node_match(ast.FormattedValue) and inst_match("FORMAT_VALUE"):
return


# old verifier

typ: Type = type(None)
Expand All @@ -541,7 +725,7 @@ def node_match(node_type: Union[Type, Tuple[Type, ...]], **kwargs: Any) -> bool:
UNARY_INVERT=ast.Invert,
)[op_name]
extra_filter = lambda e: isinstance(cast(ast.UnaryOp, e).op, op_type)
elif op_name in ("LOAD_ATTR", "LOAD_METHOD", "LOOKUP_METHOD"):
elif op_name in ("LOAD_ATTR", "LOAD_METHOD", "LOOKUP_METHOD","LOAD_SUPER_ATTR"):
typ = ast.Attribute
ctx = ast.Load
extra_filter = lambda e: mangled_name(e) == instruction.argval
Expand Down Expand Up @@ -593,11 +777,26 @@ def instruction(self, index: int) -> dis.Instruction:
def opname(self, index: int) -> str:
return self.instruction(index).opname

extra_node_types=()
if sys.version_info >= (3,12):
extra_node_types = (ast.type_param,)

def find_node(
self,
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),
match_positions: Sequence[str] = (
"lineno",
"end_lineno",
"col_offset",
"end_col_offset",
),
typ: tuple[Type, ...] = (
ast.expr,
ast.stmt,
ast.excepthandler,
ast.pattern,
*extra_node_types,
),
) -> EnhancedAST:
position = self.instruction(index).positions
assert position is not None and position.lineno is not None
Expand Down
Loading