yuho 5.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yuho/__init__.py +16 -0
- yuho/ast/__init__.py +196 -0
- yuho/ast/builder.py +926 -0
- yuho/ast/constant_folder.py +280 -0
- yuho/ast/dead_code.py +199 -0
- yuho/ast/exhaustiveness.py +503 -0
- yuho/ast/nodes.py +907 -0
- yuho/ast/overlap.py +291 -0
- yuho/ast/reachability.py +293 -0
- yuho/ast/scope_analysis.py +490 -0
- yuho/ast/transformer.py +490 -0
- yuho/ast/type_check.py +471 -0
- yuho/ast/type_inference.py +425 -0
- yuho/ast/visitor.py +239 -0
- yuho/cli/__init__.py +14 -0
- yuho/cli/commands/__init__.py +1 -0
- yuho/cli/commands/api.py +431 -0
- yuho/cli/commands/ast_viz.py +334 -0
- yuho/cli/commands/check.py +218 -0
- yuho/cli/commands/config.py +311 -0
- yuho/cli/commands/contribute.py +122 -0
- yuho/cli/commands/diff.py +487 -0
- yuho/cli/commands/explain.py +240 -0
- yuho/cli/commands/fmt.py +253 -0
- yuho/cli/commands/generate.py +316 -0
- yuho/cli/commands/graph.py +410 -0
- yuho/cli/commands/init.py +120 -0
- yuho/cli/commands/library.py +656 -0
- yuho/cli/commands/lint.py +503 -0
- yuho/cli/commands/lsp.py +36 -0
- yuho/cli/commands/preview.py +377 -0
- yuho/cli/commands/repl.py +444 -0
- yuho/cli/commands/serve.py +44 -0
- yuho/cli/commands/test.py +528 -0
- yuho/cli/commands/transpile.py +121 -0
- yuho/cli/commands/wizard.py +370 -0
- yuho/cli/completions.py +182 -0
- yuho/cli/error_formatter.py +193 -0
- yuho/cli/main.py +1064 -0
- yuho/config/__init__.py +46 -0
- yuho/config/loader.py +235 -0
- yuho/config/mask.py +194 -0
- yuho/config/schema.py +147 -0
- yuho/library/__init__.py +84 -0
- yuho/library/index.py +328 -0
- yuho/library/install.py +699 -0
- yuho/library/lockfile.py +330 -0
- yuho/library/package.py +421 -0
- yuho/library/resolver.py +791 -0
- yuho/library/signature.py +335 -0
- yuho/llm/__init__.py +45 -0
- yuho/llm/config.py +75 -0
- yuho/llm/factory.py +123 -0
- yuho/llm/prompts.py +146 -0
- yuho/llm/providers.py +383 -0
- yuho/llm/utils.py +470 -0
- yuho/lsp/__init__.py +14 -0
- yuho/lsp/code_action_handler.py +518 -0
- yuho/lsp/completion_handler.py +85 -0
- yuho/lsp/diagnostics.py +100 -0
- yuho/lsp/hover_handler.py +130 -0
- yuho/lsp/server.py +1425 -0
- yuho/mcp/__init__.py +10 -0
- yuho/mcp/server.py +1452 -0
- yuho/parser/__init__.py +8 -0
- yuho/parser/source_location.py +108 -0
- yuho/parser/wrapper.py +311 -0
- yuho/testing/__init__.py +48 -0
- yuho/testing/coverage.py +274 -0
- yuho/testing/fixtures.py +263 -0
- yuho/transpile/__init__.py +52 -0
- yuho/transpile/alloy_transpiler.py +546 -0
- yuho/transpile/base.py +100 -0
- yuho/transpile/blocks_transpiler.py +338 -0
- yuho/transpile/english_transpiler.py +470 -0
- yuho/transpile/graphql_transpiler.py +404 -0
- yuho/transpile/json_transpiler.py +217 -0
- yuho/transpile/jsonld_transpiler.py +250 -0
- yuho/transpile/latex_preamble.py +161 -0
- yuho/transpile/latex_transpiler.py +406 -0
- yuho/transpile/latex_utils.py +206 -0
- yuho/transpile/mermaid_transpiler.py +357 -0
- yuho/transpile/registry.py +275 -0
- yuho/verify/__init__.py +43 -0
- yuho/verify/alloy.py +352 -0
- yuho/verify/combined.py +218 -0
- yuho/verify/z3_solver.py +1155 -0
- yuho-5.0.0.dist-info/METADATA +186 -0
- yuho-5.0.0.dist-info/RECORD +91 -0
- yuho-5.0.0.dist-info/WHEEL +4 -0
- yuho-5.0.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constant folding optimization for Yuho AST.
|
|
3
|
+
|
|
4
|
+
Evaluates compile-time constant expressions and replaces them with
|
|
5
|
+
their literal values. This includes:
|
|
6
|
+
- Arithmetic operations on numeric literals (+, -, *, /)
|
|
7
|
+
- Boolean operations on boolean literals (&&, ||, !)
|
|
8
|
+
- Comparison operations on numeric literals (==, !=, <, >, <=, >=)
|
|
9
|
+
- String concatenation on string literals (+)
|
|
10
|
+
- Negation of numeric literals (-)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Optional, Union
|
|
14
|
+
|
|
15
|
+
from yuho.ast import nodes
|
|
16
|
+
from yuho.ast.transformer import Transformer
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ConstantFoldingError(Exception):
|
|
20
|
+
"""Error during constant folding (e.g., division by zero)."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConstantFolder(Transformer):
|
|
25
|
+
"""
|
|
26
|
+
AST transformer that evaluates compile-time constant expressions.
|
|
27
|
+
|
|
28
|
+
Traverses the AST and replaces constant expressions with their
|
|
29
|
+
computed literal values. Preserves source location information
|
|
30
|
+
for error reporting.
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
folder = ConstantFolder()
|
|
34
|
+
optimized_ast = folder.transform(ast)
|
|
35
|
+
|
|
36
|
+
Handles:
|
|
37
|
+
- Integer arithmetic: 1 + 2 -> 3
|
|
38
|
+
- Float arithmetic: 1.5 * 2.0 -> 3.0
|
|
39
|
+
- Boolean logic: TRUE && FALSE -> FALSE
|
|
40
|
+
- Comparisons: 5 > 3 -> TRUE
|
|
41
|
+
- String concatenation: "a" + "b" -> "ab"
|
|
42
|
+
- Unary negation: -5 -> IntLit(-5)
|
|
43
|
+
- Unary not: !TRUE -> FALSE
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, *, fold_division_by_zero: bool = False):
|
|
47
|
+
"""
|
|
48
|
+
Initialize the constant folder.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
fold_division_by_zero: If True, division by zero will raise
|
|
52
|
+
ConstantFoldingError. If False (default), the expression
|
|
53
|
+
is left unchanged for runtime error handling.
|
|
54
|
+
"""
|
|
55
|
+
self.fold_division_by_zero = fold_division_by_zero
|
|
56
|
+
|
|
57
|
+
def transform_binary_expr(self, node: nodes.BinaryExprNode) -> nodes.ASTNode:
|
|
58
|
+
"""
|
|
59
|
+
Transform binary expressions, folding constants where possible.
|
|
60
|
+
|
|
61
|
+
First transforms children (bottom-up folding), then attempts
|
|
62
|
+
to evaluate if both operands are literals.
|
|
63
|
+
"""
|
|
64
|
+
# First, transform children to fold nested expressions
|
|
65
|
+
node = super().transform_binary_expr(node)
|
|
66
|
+
|
|
67
|
+
# Check if both operands are now literals
|
|
68
|
+
left = node.left
|
|
69
|
+
right = node.right
|
|
70
|
+
op = node.operator
|
|
71
|
+
|
|
72
|
+
# Integer operations
|
|
73
|
+
if isinstance(left, nodes.IntLit) and isinstance(right, nodes.IntLit):
|
|
74
|
+
result = self._fold_int_binary(left.value, op, right.value, node)
|
|
75
|
+
if result is not None:
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
# Float operations (including mixed int/float)
|
|
79
|
+
if self._is_numeric_literal(left) and self._is_numeric_literal(right):
|
|
80
|
+
left_val = self._get_numeric_value(left)
|
|
81
|
+
right_val = self._get_numeric_value(right)
|
|
82
|
+
result = self._fold_float_binary(left_val, op, right_val, node)
|
|
83
|
+
if result is not None:
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
# Boolean operations
|
|
87
|
+
if isinstance(left, nodes.BoolLit) and isinstance(right, nodes.BoolLit):
|
|
88
|
+
result = self._fold_bool_binary(left.value, op, right.value, node)
|
|
89
|
+
if result is not None:
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
# String concatenation
|
|
93
|
+
if isinstance(left, nodes.StringLit) and isinstance(right, nodes.StringLit):
|
|
94
|
+
if op == "+":
|
|
95
|
+
return nodes.StringLit(
|
|
96
|
+
value=left.value + right.value,
|
|
97
|
+
source_location=node.source_location,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Cannot fold, return transformed node
|
|
101
|
+
return node
|
|
102
|
+
|
|
103
|
+
def transform_unary_expr(self, node: nodes.UnaryExprNode) -> nodes.ASTNode:
|
|
104
|
+
"""
|
|
105
|
+
Transform unary expressions, folding constants where possible.
|
|
106
|
+
|
|
107
|
+
Handles:
|
|
108
|
+
- Negation of numeric literals: -5 -> IntLit(-5)
|
|
109
|
+
- Logical not of booleans: !TRUE -> FALSE
|
|
110
|
+
"""
|
|
111
|
+
# First, transform the operand
|
|
112
|
+
node = super().transform_unary_expr(node)
|
|
113
|
+
|
|
114
|
+
operand = node.operand
|
|
115
|
+
op = node.operator
|
|
116
|
+
|
|
117
|
+
# Numeric negation
|
|
118
|
+
if op == "-":
|
|
119
|
+
if isinstance(operand, nodes.IntLit):
|
|
120
|
+
return nodes.IntLit(
|
|
121
|
+
value=-operand.value,
|
|
122
|
+
source_location=node.source_location,
|
|
123
|
+
)
|
|
124
|
+
if isinstance(operand, nodes.FloatLit):
|
|
125
|
+
return nodes.FloatLit(
|
|
126
|
+
value=-operand.value,
|
|
127
|
+
source_location=node.source_location,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Logical not
|
|
131
|
+
if op == "!":
|
|
132
|
+
if isinstance(operand, nodes.BoolLit):
|
|
133
|
+
return nodes.BoolLit(
|
|
134
|
+
value=not operand.value,
|
|
135
|
+
source_location=node.source_location,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Cannot fold, return transformed node
|
|
139
|
+
return node
|
|
140
|
+
|
|
141
|
+
def _is_numeric_literal(self, node: nodes.ASTNode) -> bool:
|
|
142
|
+
"""Check if a node is a numeric literal (int or float)."""
|
|
143
|
+
return isinstance(node, (nodes.IntLit, nodes.FloatLit))
|
|
144
|
+
|
|
145
|
+
def _get_numeric_value(self, node: nodes.ASTNode) -> Union[int, float]:
|
|
146
|
+
"""Extract numeric value from an IntLit or FloatLit."""
|
|
147
|
+
if isinstance(node, nodes.IntLit):
|
|
148
|
+
return node.value
|
|
149
|
+
if isinstance(node, nodes.FloatLit):
|
|
150
|
+
return node.value
|
|
151
|
+
raise TypeError(f"Expected numeric literal, got {type(node).__name__}")
|
|
152
|
+
|
|
153
|
+
def _fold_int_binary(
|
|
154
|
+
self,
|
|
155
|
+
left: int,
|
|
156
|
+
op: str,
|
|
157
|
+
right: int,
|
|
158
|
+
node: nodes.BinaryExprNode,
|
|
159
|
+
) -> Optional[nodes.ASTNode]:
|
|
160
|
+
"""Fold binary operations on two integers."""
|
|
161
|
+
loc = node.source_location
|
|
162
|
+
|
|
163
|
+
# Arithmetic operations
|
|
164
|
+
if op == "+":
|
|
165
|
+
return nodes.IntLit(value=left + right, source_location=loc)
|
|
166
|
+
if op == "-":
|
|
167
|
+
return nodes.IntLit(value=left - right, source_location=loc)
|
|
168
|
+
if op == "*":
|
|
169
|
+
return nodes.IntLit(value=left * right, source_location=loc)
|
|
170
|
+
if op == "/":
|
|
171
|
+
if right == 0:
|
|
172
|
+
if self.fold_division_by_zero:
|
|
173
|
+
raise ConstantFoldingError(
|
|
174
|
+
f"Division by zero at {loc}" if loc else "Division by zero"
|
|
175
|
+
)
|
|
176
|
+
return None # Leave for runtime
|
|
177
|
+
# Integer division
|
|
178
|
+
return nodes.IntLit(value=left // right, source_location=loc)
|
|
179
|
+
|
|
180
|
+
# Comparison operations
|
|
181
|
+
if op == "==":
|
|
182
|
+
return nodes.BoolLit(value=left == right, source_location=loc)
|
|
183
|
+
if op == "!=":
|
|
184
|
+
return nodes.BoolLit(value=left != right, source_location=loc)
|
|
185
|
+
if op == "<":
|
|
186
|
+
return nodes.BoolLit(value=left < right, source_location=loc)
|
|
187
|
+
if op == ">":
|
|
188
|
+
return nodes.BoolLit(value=left > right, source_location=loc)
|
|
189
|
+
if op == "<=":
|
|
190
|
+
return nodes.BoolLit(value=left <= right, source_location=loc)
|
|
191
|
+
if op == ">=":
|
|
192
|
+
return nodes.BoolLit(value=left >= right, source_location=loc)
|
|
193
|
+
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
def _fold_float_binary(
|
|
197
|
+
self,
|
|
198
|
+
left: Union[int, float],
|
|
199
|
+
op: str,
|
|
200
|
+
right: Union[int, float],
|
|
201
|
+
node: nodes.BinaryExprNode,
|
|
202
|
+
) -> Optional[nodes.ASTNode]:
|
|
203
|
+
"""Fold binary operations involving at least one float."""
|
|
204
|
+
loc = node.source_location
|
|
205
|
+
|
|
206
|
+
# If both are actually ints, let _fold_int_binary handle it
|
|
207
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
# Arithmetic operations (result is float)
|
|
211
|
+
if op == "+":
|
|
212
|
+
return nodes.FloatLit(value=float(left + right), source_location=loc)
|
|
213
|
+
if op == "-":
|
|
214
|
+
return nodes.FloatLit(value=float(left - right), source_location=loc)
|
|
215
|
+
if op == "*":
|
|
216
|
+
return nodes.FloatLit(value=float(left * right), source_location=loc)
|
|
217
|
+
if op == "/":
|
|
218
|
+
if right == 0:
|
|
219
|
+
if self.fold_division_by_zero:
|
|
220
|
+
raise ConstantFoldingError(
|
|
221
|
+
f"Division by zero at {loc}" if loc else "Division by zero"
|
|
222
|
+
)
|
|
223
|
+
return None # Leave for runtime
|
|
224
|
+
return nodes.FloatLit(value=float(left) / float(right), source_location=loc)
|
|
225
|
+
|
|
226
|
+
# Comparison operations (result is bool)
|
|
227
|
+
if op == "==":
|
|
228
|
+
return nodes.BoolLit(value=left == right, source_location=loc)
|
|
229
|
+
if op == "!=":
|
|
230
|
+
return nodes.BoolLit(value=left != right, source_location=loc)
|
|
231
|
+
if op == "<":
|
|
232
|
+
return nodes.BoolLit(value=left < right, source_location=loc)
|
|
233
|
+
if op == ">":
|
|
234
|
+
return nodes.BoolLit(value=left > right, source_location=loc)
|
|
235
|
+
if op == "<=":
|
|
236
|
+
return nodes.BoolLit(value=left <= right, source_location=loc)
|
|
237
|
+
if op == ">=":
|
|
238
|
+
return nodes.BoolLit(value=left >= right, source_location=loc)
|
|
239
|
+
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
def _fold_bool_binary(
|
|
243
|
+
self,
|
|
244
|
+
left: bool,
|
|
245
|
+
op: str,
|
|
246
|
+
right: bool,
|
|
247
|
+
node: nodes.BinaryExprNode,
|
|
248
|
+
) -> Optional[nodes.ASTNode]:
|
|
249
|
+
"""Fold binary operations on two booleans."""
|
|
250
|
+
loc = node.source_location
|
|
251
|
+
|
|
252
|
+
if op == "&&":
|
|
253
|
+
return nodes.BoolLit(value=left and right, source_location=loc)
|
|
254
|
+
if op == "||":
|
|
255
|
+
return nodes.BoolLit(value=left or right, source_location=loc)
|
|
256
|
+
if op == "==":
|
|
257
|
+
return nodes.BoolLit(value=left == right, source_location=loc)
|
|
258
|
+
if op == "!=":
|
|
259
|
+
return nodes.BoolLit(value=left != right, source_location=loc)
|
|
260
|
+
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def fold_constants(ast: nodes.ASTNode, **kwargs) -> nodes.ASTNode:
|
|
265
|
+
"""
|
|
266
|
+
Convenience function to fold constants in an AST.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
ast: The AST to transform.
|
|
270
|
+
**kwargs: Options passed to ConstantFolder constructor.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
A new AST with constant expressions folded.
|
|
274
|
+
|
|
275
|
+
Example:
|
|
276
|
+
from yuho.ast.constant_folder import fold_constants
|
|
277
|
+
optimized = fold_constants(parsed_ast)
|
|
278
|
+
"""
|
|
279
|
+
folder = ConstantFolder(**kwargs)
|
|
280
|
+
return folder.transform(ast)
|
yuho/ast/dead_code.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dead code elimination for Yuho AST.
|
|
3
|
+
|
|
4
|
+
Removes provably unreachable code branches:
|
|
5
|
+
- Match arms that can never be reached (covered by earlier patterns)
|
|
6
|
+
- Branches guarded by constant FALSE conditions
|
|
7
|
+
- Entire match expressions that reduce to a single arm
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import List, Optional, Set
|
|
12
|
+
|
|
13
|
+
from yuho.ast import nodes
|
|
14
|
+
from yuho.ast.transformer import Transformer
|
|
15
|
+
from yuho.ast.constant_folder import ConstantFolder
|
|
16
|
+
from yuho.ast.reachability import ReachabilityChecker, ReachabilityResult
|
|
17
|
+
from yuho.ast.type_inference import TypeInferenceResult
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class EliminationStats:
|
|
22
|
+
"""Statistics about eliminated dead code."""
|
|
23
|
+
|
|
24
|
+
removed_match_arms: int = 0
|
|
25
|
+
removed_true_guards: int = 0
|
|
26
|
+
simplified_matches: int = 0
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def total_eliminations(self) -> int:
|
|
30
|
+
return self.removed_match_arms + self.removed_true_guards + self.simplified_matches
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DeadCodeEliminator(Transformer):
|
|
34
|
+
"""
|
|
35
|
+
AST transformer that removes provably unreachable branches.
|
|
36
|
+
|
|
37
|
+
Performs the following optimizations:
|
|
38
|
+
1. Removes match arms that are unreachable (covered by earlier patterns)
|
|
39
|
+
2. Removes guards that are constant TRUE (simplifies to unguarded arm)
|
|
40
|
+
3. Removes entire match expressions that have only one reachable arm
|
|
41
|
+
4. Removes branches guarded by FALSE conditions (not yet if-exprs exist)
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
eliminator = DeadCodeEliminator()
|
|
45
|
+
optimized_ast = eliminator.transform(ast)
|
|
46
|
+
print(f"Removed {eliminator.stats.total_eliminations} dead code items")
|
|
47
|
+
|
|
48
|
+
Note:
|
|
49
|
+
This transformer should typically be run after constant folding
|
|
50
|
+
to maximize dead code detection.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
type_info: Optional[TypeInferenceResult] = None,
|
|
56
|
+
*,
|
|
57
|
+
fold_constants_first: bool = True,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Initialize the dead code eliminator.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
type_info: Type inference result for better reachability analysis.
|
|
64
|
+
fold_constants_first: If True, fold constants before elimination
|
|
65
|
+
to maximize dead code detection.
|
|
66
|
+
"""
|
|
67
|
+
self.type_info = type_info
|
|
68
|
+
self.fold_constants_first = fold_constants_first
|
|
69
|
+
self.stats = EliminationStats()
|
|
70
|
+
self._constant_folder = ConstantFolder() if fold_constants_first else None
|
|
71
|
+
|
|
72
|
+
def transform(self, node: nodes.ASTNode) -> nodes.ASTNode:
|
|
73
|
+
"""
|
|
74
|
+
Transform the AST, eliminating dead code.
|
|
75
|
+
|
|
76
|
+
If fold_constants_first is True, runs constant folding pass first.
|
|
77
|
+
"""
|
|
78
|
+
if self._constant_folder:
|
|
79
|
+
node = self._constant_folder.transform(node)
|
|
80
|
+
return super().transform(node)
|
|
81
|
+
|
|
82
|
+
def transform_match_expr(self, node: nodes.MatchExprNode) -> nodes.ASTNode:
|
|
83
|
+
"""
|
|
84
|
+
Transform match expression, removing unreachable arms.
|
|
85
|
+
|
|
86
|
+
1. Transform children first (recurse into arm bodies)
|
|
87
|
+
2. Run reachability analysis
|
|
88
|
+
3. Filter out unreachable arms
|
|
89
|
+
4. If only one arm remains and it's a wildcard, simplify
|
|
90
|
+
"""
|
|
91
|
+
# First, transform children (arm bodies, guards, etc.)
|
|
92
|
+
node = super().transform_match_expr(node)
|
|
93
|
+
|
|
94
|
+
# Run reachability check on this match
|
|
95
|
+
checker = ReachabilityChecker(self.type_info)
|
|
96
|
+
result = checker._check_match_reachability(node)
|
|
97
|
+
|
|
98
|
+
# Get indices of unreachable arms
|
|
99
|
+
unreachable_indices: Set[int] = {
|
|
100
|
+
arm.arm_index for arm in result.unreachable_arms
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Filter and transform arms
|
|
104
|
+
new_arms = []
|
|
105
|
+
for i, arm in enumerate(node.arms):
|
|
106
|
+
if i in unreachable_indices:
|
|
107
|
+
self.stats.removed_match_arms += 1
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
# Check if guard is constant TRUE - can remove guard
|
|
111
|
+
new_arm = self._simplify_arm_guard(arm)
|
|
112
|
+
new_arms.append(new_arm)
|
|
113
|
+
|
|
114
|
+
# If no arms remain (shouldn't happen in valid code), return unchanged
|
|
115
|
+
if not new_arms:
|
|
116
|
+
return node
|
|
117
|
+
|
|
118
|
+
# If only one arm remains and it's a wildcard, simplify to just the body
|
|
119
|
+
if len(new_arms) == 1:
|
|
120
|
+
single_arm = new_arms[0]
|
|
121
|
+
if self._is_catch_all_arm(single_arm):
|
|
122
|
+
self.stats.simplified_matches += 1
|
|
123
|
+
# Return the body directly
|
|
124
|
+
return single_arm.body
|
|
125
|
+
|
|
126
|
+
# Check if arms changed
|
|
127
|
+
if len(new_arms) != len(node.arms):
|
|
128
|
+
return nodes.MatchExprNode(
|
|
129
|
+
scrutinee=node.scrutinee,
|
|
130
|
+
arms=tuple(new_arms),
|
|
131
|
+
ensure_exhaustiveness=node.ensure_exhaustiveness,
|
|
132
|
+
source_location=node.source_location,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return node
|
|
136
|
+
|
|
137
|
+
def _simplify_arm_guard(self, arm: nodes.MatchArm) -> nodes.MatchArm:
|
|
138
|
+
"""
|
|
139
|
+
Simplify arm guard if it's a constant TRUE.
|
|
140
|
+
|
|
141
|
+
A guard that's always TRUE can be removed entirely.
|
|
142
|
+
"""
|
|
143
|
+
if arm.guard is None:
|
|
144
|
+
return arm
|
|
145
|
+
|
|
146
|
+
# Check if guard is constant TRUE
|
|
147
|
+
guard = arm.guard
|
|
148
|
+
if self._constant_folder:
|
|
149
|
+
guard = self._constant_folder.transform(guard)
|
|
150
|
+
|
|
151
|
+
if isinstance(guard, nodes.BoolLit) and guard.value is True:
|
|
152
|
+
self.stats.removed_true_guards += 1
|
|
153
|
+
return nodes.MatchArm(
|
|
154
|
+
pattern=arm.pattern,
|
|
155
|
+
guard=None, # Remove the always-true guard
|
|
156
|
+
body=arm.body,
|
|
157
|
+
source_location=arm.source_location,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Guard is constant FALSE - this arm is dead
|
|
161
|
+
# (but ReachabilityChecker should have caught this)
|
|
162
|
+
|
|
163
|
+
return arm
|
|
164
|
+
|
|
165
|
+
def _is_catch_all_arm(self, arm: nodes.MatchArm) -> bool:
|
|
166
|
+
"""
|
|
167
|
+
Check if an arm is a catch-all (wildcard with no guard).
|
|
168
|
+
|
|
169
|
+
A catch-all arm matches any value unconditionally.
|
|
170
|
+
"""
|
|
171
|
+
if arm.guard is not None:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
pattern = arm.pattern
|
|
175
|
+
return isinstance(pattern, (nodes.WildcardPattern, nodes.BindingPattern))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def eliminate_dead_code(
|
|
179
|
+
ast: nodes.ASTNode,
|
|
180
|
+
type_info: Optional[TypeInferenceResult] = None,
|
|
181
|
+
**kwargs,
|
|
182
|
+
) -> nodes.ASTNode:
|
|
183
|
+
"""
|
|
184
|
+
Convenience function to eliminate dead code from an AST.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
ast: The AST to transform.
|
|
188
|
+
type_info: Optional type inference result for better analysis.
|
|
189
|
+
**kwargs: Options passed to DeadCodeEliminator constructor.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
A new AST with dead code eliminated.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
from yuho.ast.dead_code import eliminate_dead_code
|
|
196
|
+
optimized = eliminate_dead_code(parsed_ast)
|
|
197
|
+
"""
|
|
198
|
+
eliminator = DeadCodeEliminator(type_info, **kwargs)
|
|
199
|
+
return eliminator.transform(ast)
|