From d11d109f7b3ed936eee880e9741781f7f9115aaa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 09:52:26 +0000 Subject: [PATCH] Fix "Odd length text property list" error when property value is nil The bug occurred because tp--parse-args was incorrectly omitting nil values from the property list, producing (face) instead of (face nil). Fixed the same bug pattern in: - tp--parse-args (used by tp-set, tp-reset, tp-add) - tp-add-to-layers - tp-add-to-all-layers Added 6 new regression tests for nil property values. Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com> --- tp-tests.el | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tp.el | 17 ++++++----------- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/tp-tests.el b/tp-tests.el index e22a375..ec59853 100644 --- a/tp-tests.el +++ b/tp-tests.el @@ -3766,5 +3766,59 @@ internally, and the final result should have face properties, not tp-palette." (should (listp mouse-face-prop)) (should (memq 'highlight mouse-face-prop)))))) +;;; ============================================================ +;;; Nil Value Property Tests (Issue: "Odd length text property list") +;;; ============================================================ + +(ert-deftest tp-test-set-with-nil-value () + "Test tp-set with nil value produces valid property list. +Regression test for: (tp-set \"emacs\" 'face nil) erroring with +\"Odd length text property list\"." + (tp-test-with-temp-buffer + (let ((result (tp-set "emacs" 'face nil))) + ;; Result should be #("emacs" 0 5 (face nil)) + (should (stringp result)) + (should (eq (get-text-property 0 'face result) nil)) + ;; Verify the property list is valid (has even length) + (let ((props (text-properties-at 0 result))) + (should (= (% (length props) 2) 0)))))) + +(ert-deftest tp-test-set-with-nil-value-in-middle () + "Test tp-set with nil value in middle of property list." + (tp-test-with-temp-buffer + (let ((result (tp-set "emacs" 'face 'bold 'help-echo nil 'display "test"))) + ;; Result should have face=bold, help-echo=nil, display="test" + (should (eq (get-text-property 0 'face result) 'bold)) + (should (eq (get-text-property 0 'help-echo result) nil)) + (should (equal (get-text-property 0 'display result) "test"))))) + +(ert-deftest tp-test-set-with-multiple-nil-values () + "Test tp-set with multiple nil values." + (tp-test-with-temp-buffer + (let ((result (tp-set "emacs" 'face nil 'help-echo nil))) + (should (eq (get-text-property 0 'face result) nil)) + (should (eq (get-text-property 0 'help-echo result) nil))))) + +(ert-deftest tp-test-reset-with-nil-value () + "Test tp-reset with nil value works correctly." + (tp-test-with-temp-buffer + (let ((result (tp-reset "emacs" 'face nil))) + ;; Result should have face=nil + (should (eq (get-text-property 0 'face result) nil))))) + +(ert-deftest tp-test-add-with-nil-value () + "Test tp-add with nil value works correctly." + (tp-test-with-temp-buffer + (let ((result (tp-add "emacs" 'face nil))) + ;; Result should have face=nil + (should (eq (get-text-property 0 'face result) nil))))) + +(ert-deftest tp-test-set-nil-value-in-buffer () + "Test tp-set with nil value in buffer region." + (tp-test-with-temp-buffer + (insert "emacs") + (tp-set 1 6 '(face nil)) + (should (eq (tp-at 1 'face) nil)))) + (provide 'tp-ert-tests) ;;; tp-ert-tests.el ends here diff --git a/tp.el b/tp.el index 4df620f..ca4242b 100644 --- a/tp.el +++ b/tp.el @@ -1055,11 +1055,10 @@ Supports multiple calling conventions: (null rest)) (setq props (list end-or-prop))) ;; Standard flat plist: (tp-set "str" 'prop1 val1 'prop2 val2 ...) + ;; Always include props-or-val even if it's nil, to handle (tp-set "str" 'prop nil) (t (setq props (if end-or-prop - (if props-or-val - (cons end-or-prop (cons props-or-val rest)) - (list end-or-prop)) + (cons end-or-prop (cons props-or-val rest)) nil))))) ;; First arg is a number - region convention ((numberp start-or-string) @@ -3924,11 +3923,9 @@ Returns the modified object (string) or nil for buffer operations." start 0 end (length start-or-string)) ;; Construct plist from end-or-plist, plist-or-object, and rest + ;; Always include plist-or-object even if nil, to handle (... 'prop nil) (when end-or-plist - (setq plist (cons end-or-plist - (if plist-or-object - (cons plist-or-object rest) - rest))))) + (setq plist (cons end-or-plist (cons plist-or-object rest))))) ;; Region form: (tp-add-to-layers ids start end plist object) ((numberp start-or-string) (setq start start-or-string @@ -3996,11 +3993,9 @@ Returns the modified object (string) or nil for buffer operations." start 0 end (length start-or-string)) ;; Construct plist from end-or-plist, plist-or-object, and rest + ;; Always include plist-or-object even if nil, to handle (... 'prop nil) (when end-or-plist - (setq plist (cons end-or-plist - (if plist-or-object - (cons plist-or-object rest) - rest))))) + (setq plist (cons end-or-plist (cons plist-or-object rest))))) ;; Region form: (tp-add-to-all-layers start end plist object) ((numberp start-or-string) (setq start start-or-string