From eb76edaa5b5d7a6cf0c24a420630708fa366cb8a Mon Sep 17 00:00:00 2001 From: fei <204683769+feiiiiii5@users.noreply.github.com> Date: Sun, 25 Oct 2026 11:25:16 +0800 Subject: [PATCH 1/3] Copy trivia in _new so results cannot mutate the original item Integer/Float/String/DateTime/Date/Time _new passed the original item's Trivia object to the result, so editing the result's comment or indentation rewrote the line still stored in the document. Table and InlineTable __copy__ already copy trivia; do the same on the six arithmetic paths. --- CHANGELOG.md | 4 ++++ tests/test_items.py | 22 ++++++++++++++++++++++ tomlkit/items.py | 12 ++++++------ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f414f212..d36550d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Arithmetic and `replace()` results for `Integer`, `Float`, `String`, `DateTime`, `Date` and `Time` no longer share the original item's `Trivia`, so changing the result's comment or indentation no longer rewrites the item still stored in the document. + ## [0.15.2] - 2026-10-16 ### Changed diff --git a/tests/test_items.py b/tests/test_items.py index 27a1c6e2..432213a6 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -701,6 +701,28 @@ def test_item_array_of_dicts_converted_to_aot() -> None: ) +@pytest.mark.parametrize( + ("content", "op"), + [ + ("my_int = 1 # keep\n", lambda item: item + 1), + ("my_float = 1.5 # keep\n", lambda item: item + 1), + ('my_str = "a" # keep\n', lambda item: item + "b"), + ("my_dt = 1979-05-27T07:32:00Z # keep\n", lambda item: item + timedelta(days=1)), + ("my_date = 1979-05-27 # keep\n", lambda item: item + timedelta(days=1)), + ("my_time = 07:32:00 # keep\n", lambda item: item.replace(second=1)), + ], +) +def test_arithmetic_result_does_not_share_trivia(content: str, op: Callable) -> None: + doc = parse(content) + original = doc[list(doc)[0]] + + result = op(original) + assert result.trivia is not original.trivia + + result.trivia.comment = "# MUTATED" + assert doc.as_string() == content + + def test_add_float_to_int() -> None: content = "[table]\nmy_int = 2043" doc = parse(content) diff --git a/tomlkit/items.py b/tomlkit/items.py index 30d8a4f2..f8f0d1dd 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -671,7 +671,7 @@ def _new(self, result: int) -> Integer: if self._sign and result >= 0: raw = f"+{raw}" - return Integer(result, self._trivia, raw) + return Integer(result, self._trivia.copy(), raw) def _getstate(self, protocol: int = 3) -> tuple[int, Trivia, str]: return int(self), self._trivia, self._raw @@ -896,7 +896,7 @@ def _new(self, result: float) -> Float: if self._sign and result >= 0 and not raw.startswith("-"): raw = f"+{raw}" - return Float(result, self._trivia, raw) + return Float(result, self._trivia.copy(), raw) def _getstate(self, protocol: int = 3) -> tuple[float, Trivia, str]: return float(self), self._trivia, self._raw @@ -1202,7 +1202,7 @@ def _new(self, result: datetime) -> DateTime: result.second, result.microsecond, result.tzinfo, - self._trivia, + self._trivia.copy(), raw, fold=result.fold, ) @@ -1297,7 +1297,7 @@ def replace(self, *args: object, **kwargs: object) -> Date: def _new(self, result: date) -> Date: raw = result.isoformat() - return Date(result.year, result.month, result.day, self._trivia, raw) + return Date(result.year, result.month, result.day, self._trivia.copy(), raw) def _getstate(self, protocol: int = 3) -> tuple[int, int, int, Trivia, str]: return (self.year, self.month, self.day, self._trivia, self._raw) @@ -1363,7 +1363,7 @@ def _new(self, result: time) -> Time: result.second, result.microsecond, result.tzinfo, - self._trivia, + self._trivia.copy(), raw, fold=result.fold, ) @@ -2291,7 +2291,7 @@ def __add__(self, other: str) -> String: return self._new(result, original) def _new(self, result: str, original: str) -> String: - return String(self._t, result, original, self._trivia) + return String(self._t, result, original, self._trivia.copy()) def _getstate(self, protocol: int = 3) -> tuple[StringType, str, str, Trivia]: return self._t, str(self), self._original, self._trivia From ee19ceebda34913ecebfc61074505103fe5f786c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 03:27:13 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_items.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_items.py b/tests/test_items.py index 432213a6..c0908b93 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -707,7 +707,10 @@ def test_item_array_of_dicts_converted_to_aot() -> None: ("my_int = 1 # keep\n", lambda item: item + 1), ("my_float = 1.5 # keep\n", lambda item: item + 1), ('my_str = "a" # keep\n', lambda item: item + "b"), - ("my_dt = 1979-05-27T07:32:00Z # keep\n", lambda item: item + timedelta(days=1)), + ( + "my_dt = 1979-05-27T07:32:00Z # keep\n", + lambda item: item + timedelta(days=1), + ), ("my_date = 1979-05-27 # keep\n", lambda item: item + timedelta(days=1)), ("my_time = 07:32:00 # keep\n", lambda item: item.replace(second=1)), ], From b95d40e5de7d18b2b14f08bb3dc6915de3c672b8 Mon Sep 17 00:00:00 2001 From: fei <204683769+feiiiiii5@users.noreply.github.com> Date: Mon, 26 Oct 2026 10:34:19 +0800 Subject: [PATCH 3/3] test: use next(iter(doc)) instead of list(doc)[0] pre-commit's ruff hook flags list(doc)[0] as SIM118. The test only wants the first key, so next(iter(doc)) is the same without building a list. --- tests/test_items.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_items.py b/tests/test_items.py index c0908b93..36f4afd2 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -717,7 +717,7 @@ def test_item_array_of_dicts_converted_to_aot() -> None: ) def test_arithmetic_result_does_not_share_trivia(content: str, op: Callable) -> None: doc = parse(content) - original = doc[list(doc)[0]] + original = doc[next(iter(doc))] result = op(original) assert result.trivia is not original.trivia