Skip to content

Implement TclObject support in tkinter varname_converter - #8465

Merged
youknowone merged 1 commit into
RustPython:mainfrom
lms0806:tkinter-checker
Aug 8, 2026
Merged

Implement TclObject support in tkinter varname_converter#8465
youknowone merged 1 commit into
RustPython:mainfrom
lms0806:tkinter-checker

Conversation

@lms0806

@lms0806 lms0806 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor
  • Closes #xxxx
  • [V] This PR follows our AI policy

Summary

I have addressed one of the current "Todo" items by modifying the PyTclObject checker in tkinter.

I implemented the logic found in CPython within RustPython.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of Tcl values converted to text.
    • Invalid text data now produces a clear Unicode decoding error instead of causing an incomplete-operation failure.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

varname_converter now converts TclObject values with Tcl’s Tcl_GetString API and UTF-8 decoding. Invalid UTF-8 produces a Python Unicode error instead of reaching todo!().

Changes

Tcl variable conversion

Layer / File(s) Summary
TclObject string conversion
crates/stdlib/src/tkinter.rs
varname_converter obtains the Tcl string, decodes it as UTF-8, propagates decoding errors, and returns the owned string.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: shaharnaveh

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding TclObject support to tkinter varname_converter.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/stdlib/src/tkinter.rs`:
- Around line 163-169: Update the code that constructs TclObject from the
Tcl_GetVar2Ex result to retain the returned Tcl_Obj before storing its pointer.
Add the matching single reference-count release in TclObject’s Drop
implementation, ensuring every retained object is decremented exactly once while
preserving the existing Tcl_GetString access in the shown conversion
path.</code>
- Around line 163-169: Update varname_converter() so TclObject variable names
use Tcl-aware modified UTF-8 decoding rather than CStr::to_str(), preserving
encoded U+0000 sequences before Tcl_GetVar2Ex consumes them. Retain correct
ownership and NUL termination for the Tcl API, and add regression coverage
covering encoded-null names and byte-array variables.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5467e31f-a5c8-4cee-8205-8e4eb62242d1

📥 Commits

Reviewing files that changed from the base of the PR and between bb75624 and 3e31764.

📒 Files selected for processing (1)
  • crates/stdlib/src/tkinter.rs

Comment on lines +163 to +169
if let Some(tcl_obj) = obj.downcast_ref::<TclObject>() {
let c_str = unsafe { tk_sys::Tcl_GetString(tcl_obj.value) };
let varname = unsafe { ffi::CStr::from_ptr(c_str as _) }
.to_str()
.map_err(|e| vm.new_unicode_decode_error(e.to_string()))?
.to_owned();
return Ok(varname);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 6 \
  'struct TclObject|TclObject\s*\{|impl Drop for TclObject|Tcl_IncrRefCount|Tcl_DecrRefCount|Tcl_GetVar2Ex' \
  crates/stdlib/src/tkinter.rs

Repository: RustPython/RustPython

Length of output: 4453


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== tkinter.rs relevant section =="
sed -n '55,95p;145,175p;300,335p' crates/stdlib/src/tkinter.rs

echo
echo "== pyclass macro impl for TclObject =="
rg -n -C 4 'impl TclObject|TclObject;' crates/stdlib/src/tkinter.rs

echo
echo "== pyobject refcount methods in RustPython source =="
rg -n 'pub fn.*dec[_ ]?ref|pub fn.*inc[_ ]?ref|fn.*decref|fn.*incref|PyPayload|PyRef' crates -g '*.rs' | head -n 80

Repository: RustPython/RustPython

Length of output: 11508


🌐 Web query:

Tcl_GetVar2Ex Tcl_Obj reference count caller owns returned Obj Tcl_DecrRefCount Tcl_IncrRefCount documentation

💡 Result:

When you call Tcl_GetVar2Ex, it returns a pointer to a Tcl_Obj that represents the current value of the variable [1][2]. Crucially, the reference count of the returned Tcl_Obj is not incremented by the function to account for this returned pointer; the caller does not own the object, and Tcl retains control over its lifetime [3][4]. The object returned by Tcl_GetVar2Ex is held by the variable itself [5][2]. Because the caller does not own this reference, the object is subject to being freed or modified by subsequent Tcl commands or variable operations [3][4]. To safely retain or use the returned Tcl_Obj beyond the immediate scope of the call: 1. You must explicitly call Tcl_IncrRefCount on the returned pointer to increment its reference count [6][4]. This marks the reference as owned by your code, ensuring Tcl does not free it prematurely [6]. 2. Once you are finished using the object, you must call Tcl_DecrRefCount to decrement the reference count [6]. If the reference count drops to zero, the storage for the object will be automatically reclaimed [6]. In summary, Tcl_GetVar2Ex returns a "borrowed" reference [3]. If you intend to keep a reference to the object, you are responsible for managing its reference count manually using Tcl_IncrRefCount and Tcl_DecrRefCount [6][3][4].

Citations:


Retain the Tcl_Obj before storing it in TclObject.

Tcl_GetVar2Ex returns a borrowed pointer, and TclObject stores that pointer for later Tcl_GetString access. Increment the returned object’s reference count when constructing TclObject, and decrement it exactly once in Drop to prevent use-after-free if Tcl replaces or unsets the variable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/stdlib/src/tkinter.rs` around lines 163 - 169, Update the code that
constructs TclObject from the Tcl_GetVar2Ex result to retain the returned
Tcl_Obj before storing its pointer. Add the matching single reference-count
release in TclObject’s Drop implementation, ensuring every retained object is
decremented exactly once while preserving the existing Tcl_GetString access in
the shown conversion path.</code>

Source: MCP tools


🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 6 \
  'varname_converter|Tcl_GetString|Tcl_GetUnicodeFromObj|to_str\(\)|CString::new' \
  crates/stdlib/src/tkinter.rs

fd -e rs . | xargs -r rg -n \
  'TclObject|varname_converter|U\+0000|C0 80|embedded null|NUL' || true

Repository: RustPython/RustPython

Length of output: 43692


🌐 Web query:

Tcl_GetString returns Tcl internal string representation NULL encoded as C0 80 Tcl object documentation

💡 Result:

Yes, it is accurate that Tcl represents the NUL character (\u0000) using the two-byte sequence C0 80 (hex) within its internal string representation [1][2][3]. In Tcl, strings are stored internally using a modified UTF-8 encoding [4]. In standard UTF-8, the NUL character is a single byte (0x00), which would terminate C strings and interfere with Tcl's string management routines. To avoid this, Tcl encodes the NUL character as the two-byte sequence 0xC0 0x80 [1][2][3]. This sequence is considered an "overlong" or "non-shortest form" encoding, which is technically illegal in standard UTF-8 as defined by the Unicode Consortium, but is required by Tcl to allow NUL bytes to be stored safely within string objects [5][3]. When you use Tcl_GetString or Tcl_GetStringFromObj to retrieve the string representation of a Tcl object, any embedded NUL characters will appear as this C0 80 sequence [1][4]. If your application requires handling raw binary data or truly literal NUL bytes without this conversion, you should avoid using Tcl_Obj string representation functions and instead use binary-safe types such as Tcl byte arrays (Tcl_NewByteArrayObj) or appropriate encoding/translation settings (e.g., fconfigure -translation binary) [1][5][4][6].

Citations:


🌐 Web query:

CPython tkinter cTkinter.py TkAppGetVar Tcl_GetStringVar Tcl_GetVar2Ex variable object bytes conversion

💡 Result:

In CPython's tkinter module, the interaction with Tcl variables and the conversion of data between Tcl objects (Tcl_Obj) and Python objects are handled primarily within the _tkinter C extension module [1][2]. The core mechanism for retrieving Tcl variables from C is Tcl_GetVar2Ex [3][4]. Unlike older functions such as Tcl_GetVar (which return a string representation), Tcl_GetVar2Ex is preferred because it returns a pointer to a Tcl_Obj, which is significantly more efficient and preserves the underlying Tcl object type information [3][5][4]. When CPython's tkinter operates in its default object-aware mode (enabled by the wantobjects flag), it attempts to automatically convert Tcl_Obj types to appropriate native Python types (such as str, int, float, bool, and bytes) using internal conversion logic, often centered around a function named FromObj in Modules/_tkinter.c [6][2][7]. Key points regarding variable and object conversion: 1. Efficiency and Type Integrity: Using Tcl_GetVar2Ex allows tkinter to avoid unnecessary string conversions and leverage Tcl's internal type-caching mechanisms [3][5][8]. 2. FromObj Conversion: When tkinter retrieves a Tcl_Obj, it inspects its type. If the type is recognized, it is converted to the corresponding Python equivalent. For example, bytearray objects in Tcl can be converted to Python bytes, and numeric types are mapped to Python int or float [6][7]. 3. Fallback: If a Tcl_Obj type is not explicitly handled by the conversion logic, it is typically wrapped in an opaque _tkinter.Tcl_Obj instance, which may require the user to explicitly call methods like str or get to access the underlying value [6][2][8]. 4. Evolution: The handling of these types is continuously updated to maintain compatibility with newer Tcl/Tk versions (e.g., Tcl 8.7 and 9.0), which may introduce new object types or change how older types are represented [6][7][9]. Python-level wrappers (such as StringVar, IntVar, etc., defined in tkinter.init.py) facilitate this by calling the underlying globalgetvar or globalsetvar methods, which internally interact with these C-level Tcl variable functions [10][11].

Citations:


🌐 Web query:

site:github.com/python/cpython Modules/_tkinter.c FromObj Tcl_GetString to_pystringbytes

💡 Result:

In the Python CPython source file Modules/_tkinter.c, FromObj is a central function responsible for converting Tcl objects (Tcl_Obj) into native Python objects [1][2][3]. It inspects the typePtr of a Tcl_Obj to determine the appropriate conversion strategy (e.g., boolean, integer, float, list, or string) [1][2][4]. Regarding the specific identifiers and functions mentioned: Tcl_GetString is a standard Tcl C API function used to retrieve the string representation of a Tcl object [5]. In Modules/_tkinter.c, variants like Tcl_GetStringFromObj are frequently used to obtain both the string pointer and its length, which is safer than accessing the internal bytes member directly, especially to avoid issues with embedded null characters or invalid states [6][7]. to_pystringbytes does not exist as a standard function name within Modules/_tkinter.c. It is likely a misremembered or hypothetical name for one of the internal conversion helpers such as: PyBytes_FromStringAndSize: Used within FromObj when encountering a Tcl ByteArrayType to convert the Tcl byte array into a Python bytes object [1][2][3]. unicodeFromTclObj / unicodeFromTclStringAndSize: Internal functions used to convert Tcl string representations into Python unicode/string objects [1][2][4]. The FromObj function is critical to Python's tkinter "wantobjects" mode [8][9]. When wantobjects is enabled (the default), FromObj attempts to map Tcl types to natural Python types (int, float, str, bytes, tuple) [8][9][10]. For types it cannot resolve to a native Python type, it typically wraps the Tcl_Obj in an opaque Python object, preserving the Tcl-side object for later use [11][9]. This design recognizes that while Tcl often treats all values as strings internally, preserving their specific Tcl type can be necessary for performance and correct round-trip behavior in Tkinter applications [5][9].

Citations:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

sed -n '130,210p' crates/stdlib/src/tkinter.rs
printf '\n=== lines 260-360 ===\n'
sed -n '260,360p' crates/stdlib/src/tkinter.rs
printf '\n=== TkInterpreter/tcl object conversion context ===\n'
sed -n '320,430p' crates/stdlib/src/tkinter.rs

python3 - <<'PY'
bytes_c080 = b'\xC0\x80'
try:
    print("C080 as standard UTF-8:", bytes_c080.decode("utf-8"))
except UnicodeDecodeError as e:
    print("C080 decoding error:", type(e).__name__, e)

bytes_null_utf8 = b"\x00"
print("U+0000 UTF-8 byte length:", len(bytearray(bytes_null_utf8)))
PY

Repository: RustPython/RustPython

Length of output: 11035


Preserve Tcl variable names through Tcl’s modified UTF-8 representation.

varname_converter() converts TclObject with Tcl_GetString, then rejects 0xC0 0x80 via CStr::to_str() before Tcl_GetVar2Ex. Tcl encodes U+0000 as 0xC0 0x80, so these valid Tcl variable names are rejected here. Handle TclObject with Tcl-aware encoding/ownership instead of standard UTF-8 validation, and add regression coverage for encoded nulls and byte-array variables.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/stdlib/src/tkinter.rs` around lines 163 - 169, Update
varname_converter() so TclObject variable names use Tcl-aware modified UTF-8
decoding rather than CStr::to_str(), preserving encoded U+0000 sequences before
Tcl_GetVar2Ex consumes them. Retain correct ownership and NUL termination for
the Tcl API, and add regression coverage covering encoded-null names and
byte-array variables.

Source: MCP tools

@moreal moreal added the z-ca-2026 Tag to track Contribution Academy 2026 label Aug 8, 2026

@youknowone youknowone left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@youknowone
youknowone merged commit cbaa589 into RustPython:main Aug 8, 2026
27 checks passed
kyokuping pushed a commit to kyokuping/RustPython that referenced this pull request Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

z-ca-2026 Tag to track Contribution Academy 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants