reported by vince darley in tclcore:
I notice that the latest cvs head has an
incompatibility in the whole
variable resolution process which, while it doesn't
(apparently) break any
of the test suite, does break a lot of code that I'm
exposed to. The
basic incompatible change is that this:
namespace eval foo {}
proc foo::bar {} {
global foo::name
set foo::name 1
}
foo::bar
->"can't set "foo::name": parent namespace doesn't exist"
throws the above error, which it never used to throw --
in fact the above
code worked fine (minus 'namespace') from Tcl 7.x
through 8.5a1. But now in 8.5a2 it throws an error.
Logged In: YES
user_id=148712
This was indeed broken by the fix to Bug #959052. There are
several possible fixes, they are the subject of the next post.
The code above is very non-standard, and does not work quite
as (I guess that) the author intended in tcl8.x (due to the
documentation bug #959831).
What happens in that code is:
(1) 'global' enters "name" in the variable hash-table for
foo, and creates a local variable called "name" linked to
it. ::foo::name is undefined, and
'info vars ::foo::name' returns {}
(2) when 'set foo::name 1' is called, the variable name
resolution proceeds as follows:
- "name" is looked up in the ::foo::foo namespace; not found
- "name" is looked up in the ::foo namespace; found (by
bug #959052)
That code works "by chance":
% info patch
8.3.4
% namespace eval foo {}
% namespace eval foo::foo {set name 2}
2
% proc foo::bar {} {
global foo::name
set foo::name 1
}
% foo::bar
1
% set foo::name
can't read "foo::name": no such variable
% set foo::foo::name
1
In tcl8.x the correct options would be:
variable name
set name 1
global foo::name
set name 1
set ::foo::name 1
The first two are the best ones, and offer the best
performance (variable access about 5x faster than the
original); I suppose that the second one is backwards
compatible to tcl7.x, but am not sure. The third option is
definitely backwards compatible to tcl7.x, but slower in
tcl8.x (about as fast as the original in general, 3x faster
in tcl8.5a2).
Logged In: YES
user_id=148712
The options to deal with this problem are:
(1) do nothing, break compat [even though I think it is
bug-compat, I'm not recomending this, just stating it is an
option]
(2) revert the fix for Bug #959052; restore backwards compat
completely. This could show a resurgence of Bug #736729
under some conditions - as the cached variable names can
keep the variable name in the hash table. To be completely
safe, the variable name caching could be disabled again (as
in 8.4), slowing the lookup of some variables by a factor of 3.
(3) Let 'global' and 'upvar' set the VAR_NAMESPACE_VAR flag,
so that a variable referred to by another variable is always
findable by
[namespace which -variables] and (new!) by [info vars]. Note
that at present [info vars] finds refernces by [variable],
but not by [global] or [upvar]. This restores consistency
between [ns which] and [info vars] differently, but might
break other scripts in a similar manner to this one?
Logged In: YES
user_id=148712
I am inclined to try option (3) while in alpha.
Logged In: YES
user_id=148712
Chose the prudent way out: (2)
Logged In: YES
user_id=148712
Vince's tclcore mail:
http://aspn.activestate.com/ASPN/Mail/Message/Tcl-core/2083396
Logged In: YES
user_id=148712
Note that it is "easy" to fix the code to keep the current
way it works: just replace 'foo::name' with '::foo::name'
everywhere. I do understand that "everywhere" could be lots
of places, not minimising the work involved.
Another (better) solution may be to do something like
if {[info command variable] == {}} {
proc nsvars {args} {
set ns [lindex $args 0]
set globals {}
set upvars {}
foreach var [lrange $args 1 end] {
set g $ns::$var
lappend globals $g
lappend upvars $g $var
}
uplevel 1 [list eval global $globals]
uplevel 1 [eval eval upvar 0 $upvars]
}
} else {
set ns [lindex $args 0]
set all {}
foreach var [lrange $args 1 end] {
lappend upvars $var $var
}
uplevel 1 [list namespace upvar $ns {expand}$upvars]
}
namespace eval foo {}
proc foo::bar {} {
nsvars foo name1 name2
set name1 1
set name2 2
}