Menu

#2739 bug in varName resolution

obsolete: 8.5a2
closed-fixed
7
2004-05-25
2004-05-24
No

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.

Discussion

  • miguel sofer

    miguel sofer - 2004-05-25

    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).

     
  • miguel sofer

    miguel sofer - 2004-05-25

    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?

     
  • miguel sofer

    miguel sofer - 2004-05-25

    Logged In: YES
    user_id=148712

    I am inclined to try option (3) while in alpha.

     
  • miguel sofer

    miguel sofer - 2004-05-25
    • status: open --> closed-fixed
     
  • miguel sofer

    miguel sofer - 2004-05-25

    Logged In: YES
    user_id=148712

    Chose the prudent way out: (2)

     
  • miguel sofer

    miguel sofer - 2006-10-02

    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
    }