Skip to content

Commit 0207097

Browse files
committed
Raise on unexpected operands in the formula operators
The `match` in each binary operator covers all four combinations of its two operands, plus the ones where an operand is `None`, and was followed by an unreachable `return None`. mypy can't narrow tuple items across `case` clauses (see python/mypy#12364), so it can't prove the `match` exhaustive no matter how many cases are written. Turn that trailing `return None` into the explicit last case, and raise instead of returning, so an operand of an unexpected type fails early rather than silently evaluating the whole formula to nothing: type hints are not enforced at runtime. `assert_never()` can't be used here, as mypy doesn't narrow the subject to `Never`. Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
1 parent 04cd32f commit 0207097

1 file changed

Lines changed: 20 additions & 4 deletions

File tree

  • src/frequenz/sdk/timeseries/formulas

‎src/frequenz/sdk/timeseries/formulas/_ast.py‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,11 @@ async def evaluate(self) -> Sample[QuantityT] | QuantityT | None:
177177
)
178178
case (None, _) | (_, None):
179179
return None
180-
return None
180+
case unexpected:
181+
# The cases above are exhaustive, but mypy can't narrow tuple patterns
182+
# (see python/mypy#12364), so it neither sees that nor accepts
183+
# assert_never() here.
184+
raise AssertionError(f"Unexpected operands: {unexpected!r}")
181185

182186
@override
183187
def format(self, wrap: bool = False) -> str:
@@ -254,7 +258,11 @@ async def evaluate(self) -> Sample[QuantityT] | QuantityT | None:
254258
)
255259
case (None, _) | (_, None):
256260
return None
257-
return None
261+
case unexpected:
262+
# The cases above are exhaustive, but mypy can't narrow tuple patterns
263+
# (see python/mypy#12364), so it neither sees that nor accepts
264+
# assert_never() here.
265+
raise AssertionError(f"Unexpected operands: {unexpected!r}")
258266

259267
@override
260268
def format(self, wrap: bool = False) -> str:
@@ -331,7 +339,11 @@ async def evaluate(self) -> Sample[QuantityT] | QuantityT | None:
331339
)
332340
case (None, _) | (_, None):
333341
return None
334-
return None
342+
case unexpected:
343+
# The cases above are exhaustive, but mypy can't narrow tuple patterns
344+
# (see python/mypy#12364), so it neither sees that nor accepts
345+
# assert_never() here.
346+
raise AssertionError(f"Unexpected operands: {unexpected!r}")
335347

336348
@override
337349
def format(self, wrap: bool = False) -> str:
@@ -406,7 +418,11 @@ async def evaluate(self) -> Sample[QuantityT] | QuantityT | None:
406418
)
407419
case (None, _) | (_, None):
408420
return None
409-
return None
421+
case unexpected:
422+
# The cases above are exhaustive, but mypy can't narrow tuple patterns
423+
# (see python/mypy#12364), so it neither sees that nor accepts
424+
# assert_never() here.
425+
raise AssertionError(f"Unexpected operands: {unexpected!r}")
410426

411427
@override
412428
def format(self, wrap: bool = False) -> str:

0 commit comments

Comments
 (0)