Skip to content

Commit 8e21bb9

Browse files
committed
Fix implicit unused analysis
1 parent 317826a commit 8e21bb9

File tree

3 files changed

+101
-56
lines changed

3 files changed

+101
-56
lines changed

src/pdl/pdl_analysis.py

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
from dataclasses import dataclass
33
from typing import Sequence
44

5+
from .pdl_dumper import blocks_to_dict, dump_yaml
6+
57
from .pdl_ast import (
68
AdvancedBlockType,
79
ArrayBlock,
810
Block,
11+
BlocksType,
912
BlockType,
1013
CallBlock,
1114
CodeBlock,
@@ -44,7 +47,7 @@ def with_implicit_ignore(self, b):
4447

4548
def unused_warning(block: BlockType):
4649
global _DISPLAY_UNUSED_HINT # pylint: disable= global-statement
47-
print(f"Warning: the result of block `{block}` is not used.", file=sys.stderr)
50+
print(f"Warning: the result of block `{dump_yaml(blocks_to_dict(block, json_compatible=True))}` is not used.", file=sys.stderr)
4851
if _DISPLAY_UNUSED_HINT:
4952
_DISPLAY_UNUSED_HINT = False
5053
print(
@@ -55,15 +58,22 @@ def unused_warning(block: BlockType):
5558

5659
def unused_program(prog: Program) -> None:
5760
state = UnusedConfig(implicit_ignore=False)
58-
unused_advanced_block(state, LastOfBlock(lastOf=prog.root))
61+
unused_blocks(state, prog.root)
62+
63+
64+
def unused_blocks(state: UnusedConfig, blocks: BlocksType) -> None:
65+
if not isinstance(blocks, str) and isinstance(blocks, Sequence):
66+
unused_advanced_block(state, LastOfBlock(lastOf=blocks))
67+
else:
68+
unused_block(state, blocks)
5969

6070

61-
def unused_block(state, block: BlockType) -> None:
62-
if not isinstance(block, Block):
71+
def unused_block(state, blocks: BlockType) -> None:
72+
if isinstance(blocks, Block):
73+
unused_advanced_block(state, blocks)
74+
else:
6375
if state.implicit_ignore:
64-
unused_warning(block)
65-
return
66-
unused_advanced_block(state, block)
76+
unused_warning(blocks)
6777

6878

6979
def unused_advanced_block(state: UnusedConfig, block: AdvancedBlockType) -> None:
@@ -80,26 +90,46 @@ def unused_advanced_block(state: UnusedConfig, block: AdvancedBlockType) -> None
8090
unused_block(state, block.lastOf[-1])
8191
else:
8292
unused_block(state, block.lastOf)
83-
# Leaf blocks without side effects
84-
case DataBlock() | FunctionBlock() | GetBlock() | ModelBlock() | ReadBlock():
93+
case ArrayBlock() | ObjectBlock() | TextBlock():
8594
if state.implicit_ignore:
8695
unused_warning(block)
87-
return
88-
# Leaf blocks with side effects
89-
case CallBlock() | CodeBlock() | EmptyBlock() | ErrorBlock():
90-
return
91-
# Non-leaf blocks
96+
iter_block_children((lambda blocks: used_blocks(state, blocks)), block)
97+
# Leaf blocks
9298
case (
93-
ArrayBlock()
94-
| ForBlock()
95-
| IfBlock()
96-
| IncludeBlock()
99+
DataBlock()
100+
| FunctionBlock()
101+
| GetBlock()
97102
| MessageBlock()
98-
| ObjectBlock()
99-
| RepeatBlock()
100-
| RepeatUntilBlock()
101-
| TextBlock()
103+
| ModelBlock()
104+
| CallBlock()
105+
| CodeBlock()
106+
| EmptyBlock()
107+
| ReadBlock()
102108
):
103-
iter_block_children((lambda b: unused_block(state, b)), block)
109+
if state.implicit_ignore:
110+
unused_warning(block)
111+
iter_block_children(
112+
(
113+
lambda blocks: unused_blocks(
114+
state.with_implicit_ignore(False), blocks
115+
)
116+
),
117+
block,
118+
)
119+
case ErrorBlock():
120+
pass
121+
# Non-leaf blocks
122+
case (
123+
ForBlock() | IfBlock() | IncludeBlock() | RepeatBlock() | RepeatUntilBlock()
124+
):
125+
iter_block_children((lambda blocks: unused_blocks(state, blocks)), block)
104126
case _:
105127
assert False
128+
129+
130+
def used_blocks(state: UnusedConfig, blocks: BlocksType) -> None:
131+
if not isinstance(blocks, str) and isinstance(blocks, Sequence):
132+
for block in blocks:
133+
unused_block(state.with_implicit_ignore(False), block)
134+
else:
135+
unused_block(state.with_implicit_ignore(False), blocks)

src/pdl/pdl_ast_utils.py

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,69 +32,69 @@
3232
)
3333

3434

35-
def iter_block_children(f: Callable[[BlockType], None], block: BlockType) -> None:
35+
def iter_block_children(f: Callable[[BlocksType], None], block: BlockType) -> None:
3636
if not isinstance(block, Block):
3737
return
3838
for blocks in block.defs.values():
39-
iter_blocks(f, blocks)
39+
f(blocks)
4040
match block:
4141
case FunctionBlock():
4242
if block.returns is not None:
43-
iter_blocks(f, block.returns)
43+
f(block.returns)
4444
case CallBlock():
4545
if block.trace is not None:
46-
iter_blocks(f, block.trace)
46+
f(block.trace)
4747
case ModelBlock():
4848
if block.input is not None:
49-
iter_blocks(f, block.input)
49+
f(block.input)
5050
if block.trace is not None:
51-
iter_blocks(f, block.trace)
51+
f(block.trace)
5252
case CodeBlock():
53-
iter_blocks(f, block.code)
53+
f(block.code)
5454
case GetBlock():
5555
pass
5656
case DataBlock():
5757
pass
5858
case TextBlock():
59-
iter_blocks(f, block.text)
59+
f(block.text)
6060
case LastOfBlock():
61-
iter_blocks(f, block.lastOf)
61+
f(block.lastOf)
6262
case ArrayBlock():
63-
iter_blocks(f, block.array)
63+
f(block.array)
6464
case ObjectBlock():
6565
if isinstance(block.object, dict):
6666
for blocks in block.object.values():
67-
iter_blocks(f, blocks)
67+
f(blocks)
6868
else:
69-
iter_blocks(f, block.object)
69+
f(block.object)
7070
case MessageBlock():
71-
iter_blocks(f, block.content)
71+
f(block.content)
7272
case IfBlock():
73-
iter_blocks(f, block.then)
73+
f(block.then)
7474
if block.elses is not None:
75-
iter_blocks(f, block.elses)
75+
f(block.elses)
7676
case RepeatBlock():
77-
iter_blocks(f, block.repeat)
77+
f(block.repeat)
7878
if block.trace is not None:
7979
for trace in block.trace:
80-
iter_blocks(f, trace)
80+
f(trace)
8181
case RepeatUntilBlock():
82-
iter_blocks(f, block.repeat)
82+
f(block.repeat)
8383
if block.trace is not None:
8484
for trace in block.trace:
85-
iter_blocks(f, trace)
85+
f(trace)
8686
case ForBlock():
87-
iter_blocks(f, block.repeat)
87+
f(block.repeat)
8888
if block.trace is not None:
8989
for trace in block.trace:
90-
iter_blocks(f, trace)
90+
f(trace)
9191
case ErrorBlock():
92-
iter_blocks(f, block.program)
92+
f(block.program)
9393
case ReadBlock():
9494
pass
9595
case IncludeBlock():
9696
if block.trace is not None:
97-
iter_blocks(f, block.trace)
97+
f(block.trace)
9898
case EmptyBlock():
9999
pass
100100
case _:
@@ -105,17 +105,9 @@ def iter_block_children(f: Callable[[BlockType], None], block: BlockType) -> Non
105105
case "json" | "yaml" | RegexParser():
106106
pass
107107
case PdlParser():
108-
iter_blocks(f, block.parser.pdl)
108+
f(block.parser.pdl)
109109
if block.fallback is not None:
110-
iter_blocks(f, block.fallback)
111-
112-
113-
def iter_blocks(f: Callable[[BlockType], None], blocks: BlocksType) -> None:
114-
if not isinstance(blocks, str) and isinstance(blocks, Sequence):
115-
for block in blocks:
116-
f(block)
117-
else:
118-
f(blocks)
110+
f(block.fallback)
119111

120112

121113
class MappedFunctions:

tests/test_implicit_ignore.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,26 @@ def test_no_warning2(capsys):
5555
"warnings": [""],
5656
}
5757
do_test(capsys, test)
58+
59+
60+
def test_function(capsys):
61+
test = {
62+
"prog": """
63+
defs:
64+
f:
65+
function:
66+
return:
67+
- Hello
68+
- How are you?
69+
- Bye
70+
call: f
71+
args: {}
72+
""",
73+
"result": "Bye",
74+
"warnings": [
75+
"Warning: the result of block `Hello` is not used.",
76+
"Warning: the result of block `How are you?` is not used.",
77+
"",
78+
],
79+
}
80+
do_test(capsys, test)

0 commit comments

Comments
 (0)