fix(selector): round() should not crash on non-numeric values (#6776)

FormatTransformer.round() called Decimal() directly on the input value,
which raises decimal.InvalidOperation when the value is a non-numeric
string (a text property, or a value carrying a unit suffix like "12.5 m").
In a spreadsheet export this crashed the entire operation as soon as one
element carried such a value.

Now round() catches InvalidOperation and returns the value unchanged, the
same graceful-fallback convention used by add(). Numeric rounding is
unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Petru Conduraru
2026-07-09 13:24:28 +03:00
committed by Dion Moult
parent 6b3cc54afc
commit 2eea7728d2
2 changed files with 11 additions and 2 deletions
@@ -18,7 +18,7 @@
import re import re
from collections.abc import Iterable from collections.abc import Iterable
from decimal import Decimal from decimal import Decimal, InvalidOperation
from types import EllipsisType from types import EllipsisType
from typing import Any, Optional, Union from typing import Any, Optional, Union
@@ -316,7 +316,13 @@ class FormatTransformer(lark.Transformer):
return value in ("true", "1", "yes") return value in ("true", "1", "yes")
def round(self, args): def round(self, args):
value = Decimal(0.0 if args[0] == "None" else args[0] or 0.0) try:
value = Decimal(0.0 if args[0] == "None" else args[0] or 0.0)
except InvalidOperation:
# The value is not numeric (e.g. a text property, or a value with
# a unit suffix like "12.5 m"). Rounding is meaningless here, so
# return it unchanged instead of crashing the whole expression.
return args[0]
nearest = Decimal(args[1]) nearest = Decimal(args[1])
result = round(value / nearest) * nearest result = round(value / nearest) * nearest
if nearest % 1 == 0: if nearest % 1 == 0:
@@ -55,6 +55,9 @@ class TestFormat(test.bootstrap.IFC4):
assert subject.format("round(123, 5)") == "125" assert subject.format("round(123, 5)") == "125"
assert subject.format('round("123", 5)') == "125" assert subject.format('round("123", 5)') == "125"
assert subject.format("round(-123, 5)") == "-125" assert subject.format("round(-123, 5)") == "-125"
# Non-numeric values must pass through unchanged instead of crashing (#6776).
assert subject.format('round("Level 1", 0.01)') == "Level 1"
assert subject.format('round("12.5 m", 0.01)') == "12.5 m"
assert subject.format("int(123.123)") == "123" assert subject.format("int(123.123)") == "123"
assert subject.format("int(123)") == "123" assert subject.format("int(123)") == "123"
assert subject.format("number(123)") == "123" assert subject.format("number(123)") == "123"