Skip to content

Commit d7b67e4

Browse files
committed
feat: enhanced Subscript Support for Dynamic Indexing for varname (#119)
1 parent 577c803 commit d7b67e4

File tree

2 files changed

+82
-3
lines changed

2 files changed

+82
-3
lines changed

tests/test_varname.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,14 @@ def function():
122122
assert (a, b, x.c) == ("a", "b", "x.c")
123123

124124
# Not all LHS are variables
125+
def fn(x):
126+
return 1
125127
y = {}
126128
with pytest.raises(
127129
ImproperUseError,
128-
match=r"Node 'y\[BinOp\]' detected",
130+
match=r"Node 'y\[Call\]' detected",
129131
):
130-
y[1 + 1] = function()
132+
y[fn(a)] = function()
131133

132134

133135
def test_raise_exc():
@@ -251,6 +253,26 @@ def func():
251253
assert c.value == "c[:4]"
252254
c[:] = func()
253255
assert c.value == "c[:]"
256+
# BinOp in subscript
257+
c[b + 1] = func()
258+
assert c.value == "c[b + 1]"
259+
c[b is not c] = func()
260+
# Compare in subscript
261+
assert c.value == "c[b is not c]"
262+
c[b == 1] = func()
263+
assert c.value == "c[b == 1]"
264+
c[b < 1] = func()
265+
assert c.value == "c[b < 1]"
266+
# UnaryOp in subscript
267+
c[-b] = func()
268+
assert c.value == "c[-b]"
269+
c[~b] = func()
270+
assert c.value == "c[~b]"
271+
# BoolOp in subscript
272+
c[b < 1 and b > 0] = func()
273+
assert c.value == "c[b < 1 and b > 0]"
274+
c[b < 1 or b > 0] = func()
275+
assert c.value == "c[b < 1 or b > 0]"
254276

255277

256278
def test_unusual():

varname/utils.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,42 @@
4343
ast.MatMult: "__matmul__",
4444
}
4545

46+
OP2SYMBOL = {
47+
# BinOp
48+
ast.Add: "+",
49+
ast.Sub: "-",
50+
ast.Mult: "*",
51+
ast.Div: "/",
52+
ast.FloorDiv: "//",
53+
ast.Mod: "%",
54+
ast.Pow: "**",
55+
ast.LShift: "<<",
56+
ast.RShift: ">>",
57+
ast.BitOr: "|",
58+
ast.BitXor: "^",
59+
ast.BitAnd: "&",
60+
ast.MatMult: "@",
61+
# BoolOp
62+
ast.And: "and",
63+
ast.Or: "or",
64+
# UnaryOp
65+
ast.UAdd: "+",
66+
ast.USub: "-",
67+
ast.Invert: "~",
68+
ast.Not: "not ",
69+
# Compare
70+
ast.Eq: "==",
71+
ast.NotEq: "!=",
72+
ast.Lt: "<",
73+
ast.LtE: "<=",
74+
ast.Gt: ">",
75+
ast.GtE: ">=",
76+
ast.Is: "is",
77+
ast.IsNot: "is not",
78+
ast.In: "in",
79+
ast.NotIn: "not in",
80+
}
81+
4682
CMP2MAGIC = {
4783
ast.Eq: "__eq__",
4884
ast.NotEq: "__ne__",
@@ -229,6 +265,25 @@ def node_name(
229265
if node.upper is not None
230266
else ":"
231267
)
268+
if isinstance(node, ast.BinOp):
269+
return (
270+
f"{node_name(node.left)} {OP2SYMBOL[type(node.op)]} {node_name(node.right)}"
271+
)
272+
if isinstance(node, ast.BoolOp):
273+
return f" {OP2SYMBOL[type(node.op)]} ".join(
274+
node_name(value) for value in node.values
275+
)
276+
if isinstance(node, ast.Compare):
277+
# When the node is identified by executing, len(ops) is always 1.
278+
# Otherwise, the node cannot be identified.
279+
assert len(node.ops) == 1
280+
return (
281+
f"{node_name(node.left)} "
282+
f"{OP2SYMBOL[type(node.ops[0])]} "
283+
f"{node_name(node.comparators[0])}"
284+
)
285+
if isinstance(node, ast.UnaryOp):
286+
return f"{OP2SYMBOL[type(node.op)]}{node_name(node.operand)}"
232287

233288
name = type(node).__name__
234289
if isinstance(node, ast.Subscript):
@@ -245,7 +300,9 @@ def node_name(
245300
" - ast.List (e.g. [x, y, z])\n"
246301
" - ast.Tuple (e.g. (x, y, z))\n"
247302
" - ast.Starred (e.g. *x)\n"
248-
" - ast.Subscript with slice of the above nodes (e.g. x[y])"
303+
" - ast.Subscript with slice of the above nodes (e.g. x[y])\n"
304+
" - ast.Subscript with ast.BinOp/ast.BoolOp/ast.Compare/ast.UnaryOp "
305+
"(e.g. x[y + 1], x[y and z])"
249306
)
250307

251308

0 commit comments

Comments
 (0)