Skip to content

Commit 004a9c1

Browse files
authored
Fix implicit unused analysis (#158)
1 parent 5c3302b commit 004a9c1

16 files changed

+189
-95
lines changed

examples/hello/hello-type.pdl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ text:
99
language: str
1010
spec: int
1111
return:
12-
- "\nTranslate the sentence '${ sentence }' to ${ language }\n"
12+
lastOf:
13+
- "\nTranslate the sentence '${ sentence }' to ${ language }.\n"
1314
- model: replicate/ibm-granite/granite-3.0-8b-instruct
1415
parameters:
1516
stop_sequences: "\n"

examples/talk/4-function.pdl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ text:
55
sentence: str
66
language: str
77
return:
8-
- "\nTranslate the sentence '${ sentence }' to ${ language }.\n"
9-
- model: replicate/ibm-granite/granite-3.0-8b-instruct
10-
parameters:
11-
stop_sequences: "\n"
12-
temperature: 0
8+
lastOf:
9+
- "\nTranslate the sentence '${ sentence }' to ${ language }.\n"
10+
- model: replicate/ibm-granite/granite-3.0-8b-instruct
11+
parameters:
12+
stop_sequences: "\n"
13+
temperature: 0
1314
- call: translate
1415
args:
1516
sentence: I love Paris!

examples/talk/8-tools.pdl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ text:
3131
- "\n"
3232
- if: ${ action.name == "Calc" }
3333
then:
34-
- "Obs: "
35-
- lang: python
36-
code: result = ${ action.arguments.expr }
34+
text:
35+
- "Obs: "
36+
- lang: python
37+
code: result = ${ action.arguments.expr }

examples/tools/calc.pdl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ text:
3131
- "\n"
3232
- if: ${ action.name == "Calc" }
3333
then:
34-
- "Obs: "
35-
- lang: python
36-
code: result = ${ action.arguments.expr }
34+
text:
35+
- "Obs: "
36+
- lang: python
37+
code: result = ${ action.arguments.expr }

examples/tutorial/function_definition.pdl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ text:
55
sentence: str
66
language: str
77
return:
8-
- "\nTranslate the sentence '${ sentence }' to ${ language }.\n"
8+
- text: "\nTranslate the sentence '${ sentence }' to ${ language }.\n"
9+
contribute: [context]
910
- model: replicate/ibm-granite/granite-3.0-8b-instruct
1011
parameters:
1112
stop_sequences: "\n"

examples/tutorial/grouping_definitions.pdl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ defs:
55
sentence: str
66
language: str
77
return:
8-
- "\nTranslate the sentence '${ sentence }' to ${ language }.\n"
8+
- text: "\nTranslate the sentence '${ sentence }' to ${ language }.\n"
9+
contribute: [context]
910
- model: replicate/ibm-granite/granite-3.0-8b-instruct
1011
parameters:
1112
stop_sequences: "\n"

examples/tutorial/muting_block_output.pdl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ defs:
55
sentence: str
66
language: str
77
return:
8-
- "\nTranslate the sentence '${ sentence }' to ${ language }.\n"
8+
- text: "\nTranslate the sentence '${ sentence }' to ${ language }.\n"
9+
contribute: [context]
910
- model: replicate/ibm-granite/granite-3.0-8b-instruct
1011
parameters:
1112
stop_sequences: "\n"

src/pdl/pdl_analysis.py

Lines changed: 81 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
AdvancedBlockType,
77
ArrayBlock,
88
Block,
9+
BlocksType,
910
BlockType,
1011
CallBlock,
1112
CodeBlock,
@@ -29,41 +30,66 @@
2930
TextBlock,
3031
)
3132
from .pdl_ast_utils import iter_block_children
33+
from .pdl_dumper import blocks_to_dict, dump_yaml
3234

3335

3436
@dataclass
3537
class UnusedConfig:
3638
implicit_ignore: bool
39+
implicit_lastOf: bool # pylint: disable=invalid-name
3740

3841
def with_implicit_ignore(self, b):
39-
return UnusedConfig(implicit_ignore=b)
42+
return UnusedConfig(implicit_ignore=b, implicit_lastOf=self.implicit_lastOf)
43+
44+
def with_implicit_lastOf(self, b): # pylint: disable=invalid-name
45+
return UnusedConfig(implicit_ignore=self.implicit_ignore, implicit_lastOf=b)
4046

4147

4248
_DISPLAY_UNUSED_HINT = True
4349

4450

4551
def unused_warning(block: BlockType):
46-
global _DISPLAY_UNUSED_HINT # pylint: disable= global-statement
47-
print(f"Warning: the result of block `{block}` is not used.", file=sys.stderr)
52+
global _DISPLAY_UNUSED_HINT # pylint: disable=global-statement
53+
print(
54+
f"Warning: the result of block `{dump_yaml(blocks_to_dict(block, json_compatible=True))}` is not used.",
55+
file=sys.stderr,
56+
)
4857
if _DISPLAY_UNUSED_HINT:
4958
_DISPLAY_UNUSED_HINT = False
5059
print(
51-
" You might want to use a `text` block around the list or explicitly ignore the result with `contribute: [context]`.",
60+
" You might want to use a `text` block around the list or explicitly ignore the result with a `lastOf` block or `contribute: [context]`.",
5261
file=sys.stderr,
5362
)
5463

5564

5665
def unused_program(prog: Program) -> None:
57-
state = UnusedConfig(implicit_ignore=False)
58-
unused_advanced_block(state, LastOfBlock(lastOf=prog.root))
66+
try:
67+
state = UnusedConfig(implicit_ignore=False, implicit_lastOf=True)
68+
unused_blocks(state, prog.root)
69+
except Exception as exc:
70+
print(f"Unexpected error in implicit ignored analysis: {exc}")
71+
72+
73+
def unused_blocks(state: UnusedConfig, blocks: BlocksType) -> None:
74+
if not isinstance(blocks, str) and isinstance(blocks, Sequence):
75+
if state.implicit_lastOf:
76+
state_with_ignore = state.with_implicit_ignore(True)
77+
for b in blocks[:-1]:
78+
unused_block(state_with_ignore, b)
79+
unused_block(state, blocks[-1])
80+
else:
81+
for b in blocks:
82+
unused_block(state, b)
83+
else:
84+
unused_block(state, blocks)
5985

6086

61-
def unused_block(state, block: BlockType) -> None:
62-
if not isinstance(block, Block):
87+
def unused_block(state, blocks: BlockType) -> None:
88+
if isinstance(blocks, Block):
89+
unused_advanced_block(state, blocks)
90+
else:
6391
if state.implicit_ignore:
64-
unused_warning(block)
65-
return
66-
unused_advanced_block(state, block)
92+
unused_warning(blocks)
6793

6894

6995
def unused_advanced_block(state: UnusedConfig, block: AdvancedBlockType) -> None:
@@ -72,34 +98,54 @@ def unused_advanced_block(state: UnusedConfig, block: AdvancedBlockType) -> None
7298
if ContributeTarget.RESULT not in block.contribute:
7399
state = state.with_implicit_ignore(False)
74100
match block:
75-
case LastOfBlock():
76-
if not isinstance(block.lastOf, str) and isinstance(block.lastOf, Sequence):
77-
state_with_ignore = state.with_implicit_ignore(True)
78-
for b in block.lastOf[:-1]:
79-
unused_block(state_with_ignore, b)
80-
unused_block(state, block.lastOf[-1])
81-
else:
82-
unused_block(state, block.lastOf)
83-
# Leaf blocks without side effects
84-
case DataBlock() | FunctionBlock() | GetBlock() | ModelBlock() | ReadBlock():
101+
case ArrayBlock() | LastOfBlock() | ObjectBlock() | TextBlock():
85102
if state.implicit_ignore:
86103
unused_warning(block)
87-
return
88-
# Leaf blocks with side effects
89-
case CallBlock() | CodeBlock() | EmptyBlock() | ErrorBlock():
90-
return
91-
# Non-leaf blocks
104+
state = state.with_implicit_lastOf(False)
105+
iter_block_children(
106+
(lambda blocks: used_blocks(state, blocks)),
107+
block,
108+
)
109+
# Leaf blocks
92110
case (
93-
ArrayBlock()
94-
| ForBlock()
95-
| IfBlock()
96-
| IncludeBlock()
111+
DataBlock()
112+
| FunctionBlock()
113+
| GetBlock()
97114
| MessageBlock()
98-
| ObjectBlock()
99-
| RepeatBlock()
100-
| RepeatUntilBlock()
101-
| TextBlock()
115+
| ModelBlock()
116+
| CallBlock()
117+
| CodeBlock()
118+
| ReadBlock()
102119
):
103-
iter_block_children((lambda b: unused_block(state, b)), block)
120+
if state.implicit_ignore:
121+
unused_warning(block)
122+
state = state.with_implicit_ignore(False).with_implicit_lastOf(True)
123+
iter_block_children(
124+
(lambda blocks: unused_blocks(state, blocks)),
125+
block,
126+
)
127+
case EmptyBlock():
128+
state = state.with_implicit_ignore(False).with_implicit_lastOf(True)
129+
iter_block_children(
130+
(lambda blocks: unused_blocks(state, blocks)),
131+
block,
132+
)
133+
# Non-leaf blocks
134+
case IfBlock() | IncludeBlock():
135+
state = state.with_implicit_lastOf(True)
136+
iter_block_children((lambda blocks: unused_blocks(state, blocks)), block)
137+
# Loops blocks
138+
case ForBlock() | RepeatBlock() | RepeatUntilBlock():
139+
iter_block_children((lambda blocks: unused_blocks(state, blocks)), block)
140+
case ErrorBlock():
141+
pass
104142
case _:
105143
assert False
144+
145+
146+
def used_blocks(state: UnusedConfig, blocks: BlocksType) -> None:
147+
if not isinstance(blocks, str) and isinstance(blocks, Sequence):
148+
for block in blocks:
149+
unused_block(state.with_implicit_ignore(False), block)
150+
else:
151+
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:

0 commit comments

Comments
 (0)