Tcl Source Code

View Ticket
Login
Ticket UUID: 58fa7cc7f88d70c467ccf57fac9dbc532509277
Title: Callback for unset trace on namespace variable has inexplicable effect on its visibility
Type: Bug Version: 9.0.1
Submitter: erikleunissen Created on: 2025-02-20 10:04:51
Subsystem: 21. [namespace] Assigned To: nobody
Priority: 5 Medium Severity: Important
Status: Closed Last Modified: 2025-02-20 13:56:01
Resolution: Invalid Closed By: sebres
    Closed on: 2025-02-20 13:56:01
Description:
The script below exercises two different inexplicable issues:

1. (Cases A1+B1 below)
   The variable x is unset, the trace callback redefines x, but x does not
   become visible. Only if the callback command is called another time
   (directly), the namespace variable becomes visible again.

2. (Case A2)
   The namespace variable (with the same initial visibility as case A1) is
   unset but this does not trigger the callback command.

--

namespace eval ::child {
    variable x
    proc callback {args} {
	if {[llength $args] > 0} {
	    puts "Callback triggered by unset trace"
	} else {
	    puts "Callback invoked directly (not as a trace callback)"
	}
	variable x; # restore namespace variable
    }
    trace add variable x unset [list ::child::callback]
}

# Experiment with different invocations of the callback proc

# Case 0: Initial visibility of the namespace variable
puts "O. Initial situation\n|[namespace inscope ::child [list info vars x]]|"

# Case A1. let "unset" trigger the callback
puts -nonewline "\nA1. "
namespace inscope ::child {unset -nocomplain x}
puts "|[namespace inscope ::child [list info vars x]]|"

# Case B1. invoke "callback" directly, i.e. not as a callback command
puts -nonewline "\nB1. "
::child::callback
puts "|[namespace inscope ::child [list info vars x]]|"

# Repeat A. and B. and see the different results

# Case A2: should trigger the callback as in A1, but it doesn't
puts -nonewline "\nA2. "
namespace inscope ::child {unset -nocomplain x}
puts "|[namespace inscope ::child [list info vars x]]|"

# Case B2: as B1
puts -nonewline "\nB2. "
::child::callback
puts "|[namespace inscope ::child [list info vars x]]|"

--

Results:
-------

O. Initial situation
|x|

A1. Callback triggered by unset trace
||

B1. Callback invoked directly (not as a trace callback)
|x|

A2. ||

B2. Callback invoked directly (not as a trace callback)
|x|

-- end of message --
User Comments: sebres added on 2025-02-20 13:56:01:

This is fully expected behaviour and can be simply explained:

Invocation of variable x without value in a proc-frame doesn't restore the variable, so after all the unset-trace would not retain it and variable gets deleted hereafter (by the way together with the trace). Basically variable without value only creates a link in local proc-frame (scope), which then points to non-existing variable. And if trace leaves the proc callback, this local link gets removed together with the proc-frame.

So one has to set variable in trace-callkback (e. g. with variable x some-val) then it'd be retained. Otherwise it will be always removed by unset.

Even in a NS or global level, variable x would not really create a variable, but rather a link to var (which still doesn't exist):

% variable x
% list [info vars x] [info exists x]
x 0

As for the case by repeat, it is also pretty obvious: the unset of variable also removes its trace, so one has to register it again if needed.

Here is a code that hopefully explains it:

% trace add variable x unset {apply {args {puts "** $args"; variable x}}}
% unset -nocomplain x
** x {} unset
% list [info vars x] [info exists x]
{} 0
% trace add variable x unset {apply {args {puts "** $args"; variable x "restored"}}}
% list [info vars x] [info exists x]
{} 0
% unset -nocomplain x
** x {} unset
% list [info vars x] [info exists x]
x 1

Thus closed as invalid.