# -*- Tcl -*-
#
# testing var resolution
#

package prefer latest

package require nx
package require nx::test

::nx::configure defaultMethodCallProtection false

::nsf::method::alias ::nx::Object objeval -frame object ::eval
::nsf::method::alias ::nx::Object array -frame object ::array
::nsf::method::alias ::nx::Object lappend -frame object ::lappend
::nsf::method::alias ::nx::Object incr -frame object ::incr
::nsf::method::alias ::nx::Object set -frame object ::set
::nsf::method::alias ::nx::Object unset -frame object ::unset

###########################################
# Basic tests for var resolution under
# per-object namespaces ...
###########################################
nx::test case globals
set ::globalVar 1
nx::Object create o
o require namespace
? {o info vars} ""
? {info exists ::globalVar} 1
? {set ::globalVar} 1
? {o eval {info exists :globalVar}} 0
? {o array exists globalVar} 0
o array set globalVar {1 2}
? {o eval {info exists :globalVar}} 1
? {o info vars} globalVar
? {o array exists globalVar} 1
? {set ::globalVar} 1
? {o set globalVar(1)} 2

o destroy
unset ::globalVar

###########################################
# scopes
###########################################
nx::test case scopes

nx::Object create o
nx::Object create o2 {set :i 1}
o objeval {
  # require a namespace within an objscoped frame; it is necessary to replace
  # vartables on the stack
  :require namespace
  global g
  ::nsf::var::import o2 i
  set x 1
  set :y 2
  set ::z 3
  set [current]::X 4
  set g 1
  set :a(:b) 1
  set :a(::c) 1
}
? {::nsf::var::import o2 j} \
    "importvar cannot import variable 'j' into method scope; not called from a method frame"

o object method foo {} {::nsf::var::import [current] :a}
? {o foo} "variable name \":a\" must not contain namespace separator or colon prefix"

o object method foo {} {::nsf::var::import [current] ::a}
? {o foo} "variable name \"::a\" must not contain namespace separator or colon prefix"

o object method foo {} {::nsf::var::import [current] a(:b)}
? {o foo} "can't make instance variable a(:b) on ::o: Variable cannot be an element in an array; use e.g. an alias."

o object method foo {} {::nsf::var::import [current] {a(:b) ab}}
? {o foo} ""

o object method foo {} {::nsf::var::exists [current] ::a}
? {o foo} "variable name \"::a\" must not contain namespace separator or colon prefix"

o object method foo {} {::nsf::var::exists [current] a(:b)}
? {o foo} 1

o object method foo {} {::nsf::var::exists [current] a(::c)}
? {o foo} 1

set ::o::Y 5
? {info vars ::x} ""

? {info exists ::z} 1
? {set ::z} 3
? {lsort [o info vars]} {X Y a g i x y}
? {o eval {info exists :x}} 1
? {o eval {info exists :y}} 1
? {o eval {info exists :z}} 0
? {o eval {info exists :X}} 1
? {o eval {info exists :Y}} 1
? {o set y} 2
? {set ::g} 1

o destroy
o2 destroy
unset ::z
unset ::g

# like the example above, but with the non-leaf initcmd

nx::Object create o2 {set :i 1}
nx::Object create o {
  :require namespace
  global g
  ::nsf::var::import o2 i
  set x 1
  set :y 2
  set ::z 3
  set [current]::X 4
  set g 1
}
set ::o::Y 5
? {info vars ::x} ""

? {info exists ::z} 1
? {set ::z} 3
? {lsort [o info vars]} {X Y y}
? {o eval {info exists :x}} 0
? {o eval {info exists :y}} 1
? {o eval {info exists :z}} 0
? {o eval {info exists :X}} 1
? {o eval {info exists :Y}} 1
? {o set y} 2
? {set ::g} 1

o destroy
o2 destroy
unset ::z
unset ::g
foreach v {::x ::z ::g} {unset -nocomplain $v}

###########################################
# var exists tests
###########################################
nx::test case exists {
    set y 1

    nx::Object create o {set :x 1}
    o object method foo {} {info exists :x}
    o object method bar {} {info exists :y}
    ? {o eval {info exists :x}} 1
    ? {o eval {info exists :y}} 0
    ? {o eval {info exists x}} 0
    ? {o foo} 1
    ? {o bar} 0
    ? {::nx::var exists o x} 1
    ? {::nx::var exists o y} 0
    ? {::nx::var exists o :x} {variable name ":x" must not contain namespace separator or colon prefix}
    ? {::nx::var exists o :y} {variable name ":y" must not contain namespace separator or colon prefix}
    ? {::nx::var set o y 2} 2
    ? {::nx::var exists o y} 1
    ? {::nx::var set o :y 2} {variable name ":y" must not contain namespace separator or colon prefix}
}

###########################################
# mix & match namespace and object interfaces
###########################################
nx::test case namespaces

nx::Object create o
o require namespace
o set x 1
? {namespace eval ::o {set x}} 1
? {::o set x} 1
? {namespace eval ::o {set x 3}} 3
? {::o set x} 3
? {namespace eval ::o {info exists x}} 1
? {::o unset x} ""
? {::nsf::var::exists o x} 0
? {o eval {info exists :x}} 0
? {info vars ::x} ""
? {namespace eval ::o {info exists x}} 0
o lappend y 3
? {namespace eval ::o {llength y}} 1
? {namespace eval ::o {unset y}} ""
? {o eval {info exists :y}} 0
o destroy

###########################################
# array-specific tests
###########################################
nx::test case namespaces-array

nx::Object create o
o require namespace

? {o array exists a} 0
? {namespace eval ::o array exists a} 0
o array set a {1 2 3 4 5 6}
? {o array exists a} 1
? {namespace eval ::o array exists a} 1
? {namespace eval ::o array names a} [::o array names a]
? {namespace eval ::o array size a} [::o array size a]
? {o set a(1) 7} 7
? {namespace eval ::o array get a 1} {1 7}
? {namespace eval ::o set a(1) 2} 2
? {o array get a 1} {1 2}
? {::o unset a} ""
? {::o array unset a} ""
? {o array exists a} 0
? {namespace eval ::o array exists a} 0

o destroy

###########################################
# tests on namespace-qualified var names
###########################################
nx::test case namespaced-var-names
nx::Object create o
o require namespace
nx::Object create o::oo
o::oo require namespace

? {::o set ::x 1} 1
? {info exists ::x} [set ::x]
? {catch {unset ::x}} 0

? {::o set ::o::x 1} 1
? {o eval {info exists :x}} [::o set ::o::x]
? {namespace eval ::o unset x} ""
? {o eval {info exists x}} 0

# Note, relatively qualified var names (not prefixed with ::*)
# are always resolved relative to the per-object namespace
? {catch {::o set o::x 1} msg} 1
? {::o set oo::x 1} 1
? {o::oo eval {info exists :x}} [::o set oo::x]
? {o unset oo::x} ""
? {o::oo eval {info exists :x}} 0

o destroy

###########################################
# tests on namespace-qualified on objects
# without namespaces
###########################################

# the tests below fail. We could consider
# to require namespaces on the fly in the future
#nx::Object create o
#? {::o set ::o::x 1} 1
#? {o exists x} [::o set ::o::x]
#? {namespace eval ::o unset x} ""
#? {o exists x} 0

#? {::o set o::x 1} 1
#? {o exists x} [::o set o::x]
#? {namespace eval ::o unset x} ""
#? {o exists x} 0

#o destroy

###############################################
# tests for the compiled var resolver on Object
###############################################
nx::test case var-resolver-object
nx::Object create o
o object method foo {x} {set :y 2; return ${:x},${:y}}
o object method bar {} {return ${:x},${:y}}
o set x 1
? {o foo 1} "1,2" "create var y and fetch var x"
? {o bar} "1,2" "fetch two instance variables"
? {o info vars} "x y"
# recreate object, check var caching;
# we have to recreate bar, so no problem
nx::Object create o
o set x 1
o object method bar {} {return ${:x},${:y}}
? {catch {o bar}} "1" "compiled var y should not exist"
o destroy

###############################################
# tests for the compiled var resolver on Class
###############################################
nx::test case var-resolver-class
nx::Class create C {:property {x 1}}
C create c1
C method foo {x} {set :y 2; return ${:x},${:y}}
C method bar {} {return ${:x},${:y}}
? {c1 info vars} "x"
? {c1 foo 1} "1,2" "create var y and fetch var x"
? {c1 bar} "1,2" "fetch two instance variables"
? {c1 info vars} "x y"
# recreate object, check var caching;
# we do not have to recreate bar, compiled var persists,
# change must be detected
C create c1
#puts stderr "after recreate"
? {catch {c1 bar}} "1" "compiled var y should not exist"
? {c1 info vars} "x"
c1 destroy
C destroy



###############################################
# tests for the compiled var resolver with eval
###############################################
nx::test case compiled-var-resolver
nx::Class create C {:property {x 1}}
C create c1
C method foo {x} {
  set :y 2;
  eval "set :z 3"
  return ${:x},${:y},${:z}
}
? {c1 info vars} "x"
? {c1 foo 1} "1,2,3"
? {c1 info vars} "x y z"
C create c1
? {c1 info vars} "x"
C method foo {x} {
  set cmd set
  lappend cmd :y
  lappend cmd 100
  eval $cmd
  return $x,${:y}
}
C method bar {} {return [info exists :x],[info exists :y]}
C method bar2 {} {if {[info exists :x]} {set :x 1000}; return [info exists :x],[info exists :y]}
? {c1 foo 1} "1,100"
? {c1 bar} "1,1"
? {c1 bar2} "1,1"
c1 unset x
? {c1 bar2} "0,1"
c1 destroy
C destroy

###############################################
# tests with array
###############################################

nx::Class create C
C create c1
C method foo {} {
  array set :a {a 1 b 2 c 3}
  set :z 100
}
? {c1 info vars} ""
c1 foo
? {lsort [c1 info vars]} {a z}

###############################################
# tests for the var resolver
###############################################
nx::test case var-resolver
nx::Class create C
C method bar0 {} {return ${:x}}
C method bar1 {} {set a ${:x}; return [info exists :x],[info exists :y]}
C method bar2 {} {return [info exists :x],[info exists :y]}
C method foo {} {
  array set :a {a 1 b 2 c 3}
  set :z 100
}
C create c1
c1 set x 100
? {c1 bar0} 100 "single compiled local"
? {c1 bar1} 1,0 "lookup one compiled var and one non-existing"
? {c1 bar2} 1,0 "lookup one non compiled var and one non-existing"
C create c2
? {c2 bar2} 0,0 "lookup two one non-existing, first access to varTable"
c1 foo
? {lsort [c1 info vars]} "a x z" "array variable set via resolver"
? {lsort [c1 array names a]} "a b c" "array looks ok"

###############################################
# first tests for the cmd resolver
###############################################
nx::Class create C
C method bar {args} {
  #puts stderr "[current] bar called with [list $args]"
  return $args
}
C forward test %self bar
C method foo {} {
  # this works
  lappend :r [:bar x 1]
  lappend :r [:test a b c]
  # these kind of works, but vars are nowhere....
  :set x 1
  :incr x 1
  :incr x 1
  return [lappend :r ${:x}]
}
C create c3
? {c3 foo} "{x 1} {a b c} 3"

###############################################
# refined tests for the var resolver under
# Tcl namespaces parallelling XOTcl objects
# (! not declared through require namespace !)
# e.g., "info has namespace" reports 0 rather
# than 1 as under "require namespace"
###############################################

set ::w 1
array set ::tmpArray {key value}

nx::Class create ::C
::nsf::method::alias ::C Set -frame object ::set
::nsf::method::alias ::C Unset -frame object ::unset

::C create ::c
namespace eval ::c {}
? {namespace exists ::c} 1
? {::nsf::object::exists ::c} 1
? {::c info has namespace} 0

? {::c Set w 2; expr {[::c Set w] == $::w}} 0
? {::c Unset w; info exists ::w} 1
? {::c Set tmpArray(key) value2; expr {[::c Set tmpArray(key)] == $::tmpArray(key)}} 0
? {::c Unset tmpArray(key); info exists ::tmpArray(key)} 1

::c destroy
::C destroy
unset ::w
unset ::tmpArray

##################################################
# Testing aliases for eval with and without
# -varscope flags and with a
# required namespace and without
##################################################
nx::test case eval-variants
::nsf::method::alias ::nx::Object objeval -frame object ::eval
::nsf::method::alias ::nx::Object softeval -frame method ::eval
::nsf::method::alias ::nx::Object softeval2 ::eval

set G 1

nx::Object create o {
  set xxx 1
  set :x 1
  ? {info exists G} 1
}
? {o eval {info exists :x}} 1
? {o eval {info exists :xxx}} 0

? {info exists ::xxx} 0
unset -nocomplain ::xxx

# eval does an objcope, all vars are instance variables; can access preexisting global vars
o objeval {
  set aaa 1
  set :a 1
  ? {info exists G} 1
}

? {o eval {info exists :a}} 1
? {o eval {info exists :aaa}} 1

? {info exists ::aaa} 0
unset -nocomplain ::aaa

# softeval (with -nonleaf) behaves like the initcmd and sets just
# instance variables via resolver.

o softeval {
  set bbb 1
  set :b 1
  ? {info exists G} 1
}
? {o eval {info exists :b}} 1
? {o eval {info exists :bbb}} 0

? {info vars ::bbb} ""
unset -nocomplain ::bbb

# softeval2 never sets instance variables
o softeval2 {
  set zzz 1
  set :z 1
  ? {info exists G} 1
}
? {o eval {info exists :z}} 0
? {o eval {info exists :zzz}} 0

? {info vars ::zzz} ::zzz
unset -nocomplain ::zzz

? {lsort [o info vars]} "a aaa b x"
o destroy

# now with an object namespace
nx::Object create o
o require namespace

# objeval does an objcope, all vars are instance variables
o objeval {
  set ccc 1
  set :c 1
}
? {o eval {info exists :c}} 1
? {o eval {info exists :ccc}} 1

# softeval behaves like the creation initcmd (just set dot vars)
o softeval {
  set ddd 1
  set :d 1
}
? {o eval {info exists :d}} 1
? {o eval {info exists :ddd}} 0

# softeval2 never sets variables
o softeval2 {
  set zzz 1
  set :z 1
}
? {o eval {info exists :z}} 0
? {o eval {info exists :zzz}} 0
? {lsort [o info vars]} "c ccc d"
o destroy

#################################################################
# The same as above, but with some global vars.  The global vars
# should not influence the behavior on instance variables
#################################################################
nx::test case with-global-vars
foreach var {.x x xxx :a a aaa :b b bbb :c c ccc :d d ddd :z z zzz} {set $var 1}

nx::Object create o {
  set xxx 1
  set :x 1
}
? {o eval {info exists :x}} 1
? {o eval {info exists :xxx}} 0

# objeval does an objcope, all vars are instance variables
o objeval {
  set aaa 1
  set :a 1
}
? {o eval {info exists :a}} 1
? {o eval {info exists :aaa}} 1

# softeval should behave like the creation initcmd (just set dot vars)
o softeval {
  set bbb 1
  set :b 1
}
? {o eval {info exists :b}} 1
? {o eval {info exists :bbb}} 0

# softeval2 never sets instance variables
o softeval2 {
  set zzz 1
  set :z 1
}
? {o eval {info exists :z}} 0
? {o eval {info exists :zzz}} 0

? {lsort [o info vars]} "a aaa b x"
o destroy

# now with namespace
nx::Object create o
o require namespace

# eval does an objcope, all vars are instance variables
o objeval {
  set ccc 1
  set :c 1
}
? {o eval {info exists :c}} 1
? {o eval {info exists :ccc}} 1

# softeval2 should behave like the creation initcmd (just set dot vars)
o softeval {
  set ddd 1
  set :d 1
}
? {o eval {info exists :d}} 1
? {o eval {info exists :ddd}} 0

# softeval2 never sets variables
o softeval2 {
  set zzz 1
  set :z 1
}
? {o eval {info exists :z}} 0
? {o eval {info exists :zzz}} 0
? {lsort [o info vars]} "c ccc d"
o destroy

##################################################
# Test with proc scopes
##################################################
nx::test case proc-scopes
::nsf::method::alias ::nx::Object objscoped-eval -frame object ::eval
::nsf::method::alias ::nx::Object nonleaf-eval -frame method ::eval
::nsf::method::alias ::nx::Object plain-eval ::eval

proc foo-via-initcmd {} {
  foreach v {x xxx} {unset -nocomplain ::$v}
  set p 1
  nx::Object create o {
    set xxx 1
    set :x 1
    set ::result G=[info exists G],p=[info exists p]
  }
    return [o eval {info exists :x}]-[o eval {info exists :xxx}]-[info exists x]-[info exists xxx]-[info exists ::x]-[info exists ::xxx]-$::result
}

proc foo {type} {
  foreach v {x xxx} {unset -nocomplain ::$v}
  set p 1
  nx::Object create o
  o $type {
    set xxx 1
    set :x 1
    set ::result G=[info exists G],p=[info exists p]
  }
  return [o eval {info exists :x}]-[o eval {info exists :xxx}]-[info exists x]-[info exists xxx]-[info exists ::x]-[info exists ::xxx]-$::result
}

proc foo-tcl {what} {
  foreach v {x xxx} {unset -nocomplain ::$v}
  set p 1
  set body {
    set xxx 1
    set :x 1
    set ::result G=[info exists G],p=[info exists p]
  }
  switch $what {
    eval {eval $body}
    ns-eval {namespace eval [namespace current] $body}
  }
  return [o eval {info exists :x}]-[o eval {info exists :xxx}]-[info exists x]-[info exists xxx]-[info exists ::x]-[info exists ::xxx]-$::result
}

set G 1


 ? {foo-via-initcmd}    1-0-0-0-0-0-G=0,p=0
 ? {foo nonleaf-eval}   1-0-0-0-0-0-G=0,p=0
 ? {foo objscoped-eval} 1-1-0-0-0-0-G=0,p=0
 ? {foo plain-eval}     0-0-0-1-0-0-G=0,p=1
 ? {foo-tcl eval}       0-0-0-1-0-0-G=0,p=1
 ? {foo-tcl ns-eval}    0-0-0-0-0-1-G=1,p=0

##################################################
# dotCmd tests
##################################################
nx::test case dotcmd
set C 0
proc bar {} {incr ::C}
nx::Class create Foo {
  :method init {} {set :c 0}
  :method callDot1 {} {:bar}
  :method callDot2 {} {:bar}
  :method callDot3 {} {:bar; ::bar; :bar}
  :method bar {} {incr :c}
}

Foo create f1
f1 callDot1
? {set ::C} 0
? {f1 eval {set :c}} 1

# call via callback
after 1 {f1 callDot2}
after 10 {set ::X 1}
vwait X

#
# Test vwait with colon variable and vwait method
#
nx::Object create o {
  set :x 0
  :public object method foo {} {incr :x}
  :public object method vwait {varName} {
    if {[regexp {:[^:]*} $varName]} {
      error "invalid varName '$varName'; only plain or fully qualified variable names allowed"
    }
    if {[string match ::* $varName]} {
      ::vwait $varName
    } else {
      ::vwait :$varName
    }
  }
  #
  # Tcl vwait command with instance variable
  #
  after 10 {o foo}
  #puts stderr ===waiting
  vwait :x
  #puts stderr ===waiting-DONE
  #
  # vwait method
  #
  after 10 {o foo}
  #puts stderr ===waiting
  :vwait x
  #puts stderr ===waiting-DONE
  ? {o vwait :x} {invalid varName ':x'; only plain or fully qualified variable names allowed}
}
o destroy

? {set ::C} 0
? {f1 eval {set :c}} 2

# call via callback, call :bar via .. from method
after 1 {f1 callDot3}
after 10 {set ::X 2}
vwait X

? {set ::C} 1
? {f1 eval {set :c}} 4


##################################################
# test for namespace resolver
##################################################
nx::test case nsresolver
namespace eval module {
  nx::Class create C
  nx::Class create M1
  nx::Class create M2

  C mixins set M1
  ? {::nsf::relation::get C class-mixin} "::module::M1"

  C mixins add M2
  ? {::nsf::relation::get C class-mixin} "::module::M2 ::module::M1"
}


##################################################
# test setting of instance variables for
# objects with namespaces in and outside
# of an eval (one case uses compiler)
##################################################

nx::test case alias-dot-resolver-interp
# outside of eval scope (interpreted)
nx::Class create V {
  set :Z 1
  set ZZZ 1
  :method bar {z} { return $z }
  :object method bar {z} { return $z }
  :create v {
    set zzz 2
    set :z 2
  }
}
? {lsort [V info vars]} {Z}
? {lsort [v info vars]} {z}

# dot-resolver/ dot-dispatcher used in aliased proc

nx::test case alias-dot-resolver {

  nx::Class create V {
    set :Z 1
    set ZZZ 1
    :method bar {z} { return $z }
    :object method bar {z} { return $z }
    :create v {
      set :z 2
      set zzz 2
    }
  }
  ? {lsort [V info vars]} {Z}
  ? {lsort [v info vars]} {z}
}

#
# test [info vars] in eval method
#

nx::test case info-vars-in-eval {

  nx::Object create o
  ? {o eval {
    set x 1
    expr {[info vars "x"] eq "x"}
  }} 1
}

#
# test for former crash when variable is used in connection with
# prefixed variables
#
nx::test case tcl-variable-cmd {
  nx::Object create o {
    :public object method ? {varname} {info exists :$varname}
    :public object method bar args {
      variable :a
      set a 3
      variable b
      set b 3
      variable c 1
      variable :d 1
      :info vars
    }
  }

  ? {o bar} ""
  ? {o ? a} 0
  ? {o ? b} 0
  ? {o ? c} 0
  ? {o ? d} 0
  ? {lsort [o info vars]} ""
  o eval {set :a 1}
  ? {o ? a} 1
  ? {lsort [o info vars]} a
}

nx::test case interactions {

  # SS: Adding an exemplary test destilled from the behavior observed
  # for AOLserver vs. NaviServer when introspecting object variables
  # by means of the colon-resolver interface. It exemplifies the (by now
  # resolved for good) interactions between: (a) the compiling and
  # non-compiling var resolvers and (b) compiled and non-compiled
  # script execution

  nx::Object create ::o {
    :public object method bar {} {
      # 1. creates a proc-local, compiled var "type"
      set type 1
      # 2. at compile time: create a proc-local, compiled link-var ":type"
      info exists :type
      # 3. at (unoptimized) interpretation time: bypasses compiled link-var
      # ":type" (invokeStr instruction; a simple eval), does a var
      # lookup with ":type", handled by InterpColonVarResolver();
      # CompiledLocalsLookup() receives the var name (i.e., ":type")
      # and finds the proc-local compiled var ":type" (actually a link
      # variable to the actual/real object variable).
      eval {info exists :type};
      # Note! A [info exists :type] would have been optimized on the
      # bytecode fastpath (i.e., existsScalar instruction) and would
      # use the compiled-local link-var ":type" directly (without
      # visiting InterpColonVarResolver()!)
    }
  }

  ? {o bar} 0

  #
  # document compile-time var resolver side effects: link variables
  #
  # At compile time, the compile-time var resolver looks up (and
  # creates) object variables for the colon-prefixed vars processed:
  # ":u" -> "u", ":v" -> "v"; hence, the resolver always returns a
  # Var structure! As a consequence, the compiler emits
  # colon-prefixed *link* variables (either in state "undefined" or
  # "defined", depending on providing a value or not) into the
  # compiled local array (e.g., ":u"), as proxies pointing to the
  # actual object variables (e.g., "u").
  #
  # Consequences: These link vars are visible through introspection
  # sensible to created vars (rather than defined/undefined var
  # states) in compiled scripts ([info vars] vs. [info locals]). This
  # resembles [upvar]-created local link vars, yet it does not
  # intuitively compare with the [set]/[unset] behavior on
  # non-prefixed, ordinary variables from the angle of
  # introspection. Also, this constitutes an observable behavioral
  # difference between compiled and non-compiled scripts ...

  set script {
    # early probing: reflects the compiled-only, unexecuted state
    set _ [join [list {*}[lsort [info vars :*]] [info locals :*] \
                     [info exists :u] [::nsf::var::exists [::nsf::current] u] \
                     [info exists :v] [::nsf::var::exists [::nsf::current] v] \
                     [info exists :x] [::nsf::var::exists [::nsf::current] x]] "-"]
    catch {set :u}
    set :v 1
    unset :x
    # late probing: reflects the (ideally) compiled, *executed* state
    append _ | [join [list {*}[lsort [info vars :*]] [info locals :*] \
                        [info exists :u] [::nsf::var::exists [::nsf::current] u] \
                        [info exists :v] [::nsf::var::exists [::nsf::current] v] \
                        [info exists :x] [::nsf::var::exists [::nsf::current] x]] "-"]
    return $_
  }

  # compiled execution
  o public object method baz {} $script
  o eval {set :x 1; unset -nocomplain :v}
  ? {o baz} :u-:v-:x--0-0-0-0-1-1|:u-:v-:x--0-0-1-1-0-0 ; #:u-:v-:x--1-1-0-0-0-1-0-:u-:v-:x

  # non-compiled execution
  o eval {set :x 1; unset -nocomplain :v}
  ? [list o eval $script] -0-0-0-0-1-1|-0-0-1-1-0-0

  #
  # testing interactions between the compile-time var resolver and ...
  #

  # ... [variable]
  #
  # background: the [variable] statement is compiled. During
  # compilation, our compile-time resolver is contacted, finds (and
  # eventually creates) an object variable "x". The compiler machinery
  # then creates a link-variable ":x" which is stored as a compiled
  # local, as usual. at the time of writing/testing, there are two
  # issues with this:
  #
  # ISSUE 1: In its non-compiled execution, [variable] sets the
  # AVOID_RESOLVERS flags, so our resolvers are not touched ... in its
  # compiled execution, AVOID_RESOLVERS is missing though (although
  # [variable] is compiled into a slow path execution, i.e., involves
  # a Tcl var lookup). Therefore, we get a link variable in the
  # compiled locals (and an undefined obj var).

  # this has some implications ...

  namespace eval ::ns1 {
    nx::Object create o {
      :public object method foo {} {
        set _ [join [list {*}[lsort [info vars :*]] [info locals :*] \
                         [info exists w] [::nsf::var::exists [::nsf::current] w] \
                         [info exists :x] [::nsf::var::exists [::nsf::current] x]] "-"]
        variable w; # -> intention: a variable "w" in the effective namespace (e.g., "::ns1::w")
        variable :x; # -> intention: a variable ":x" in the effective namespace (e.g., "::ns1:::x"!).
        append _ | [join [list {*}[lsort [info vars :*]] [info locals :*] \
                              [info exists w] [::nsf::var::exists [::nsf::current] w] \
                              [info exists :x] [::nsf::var::exists [::nsf::current] x]] "-"]
        return $_
      }
    }

    ? {::ns1::o foo} ":x--0-0-0-0|:x--0-0-0-0"

    o eval {
      :public object method faz {} {
        set _ [join [list {*}[lsort [info vars :*]] [info locals :*] \
                         [namespace which -variable [namespace current]::w] \
                         [info exists [namespace current]::w] \
                         [info exists w] [::nsf::var::exists [::nsf::current] w] \
                         [namespace which -variable [namespace current]:::x] \
                         [info exists [namespace current]:::x] \
                         [info exists :x] [::nsf::var::exists [::nsf::current] x]] "-"]
        variable w 1; # -> intention: a variable "w" in the effective namespace (e.g., "::ns1::w")
        variable :x 2; # -> intention: a variable ":x" in the effective namespace (e.g., "::ns1:::x"!).
        append _ | [join [list {*}[lsort [info vars :*]] [info locals :*] \
                         [namespace which -variable [namespace current]::w] \
                         [info exists [namespace current]::w] \
                         [info exists w] [::nsf::var::exists [::nsf::current] w] \
                         [namespace which -variable [namespace current]:::x] \
                              [info exists [namespace current]:::x] [namespace eval  [namespace current] {info exists :x}] \
                              [namespace eval  [namespace current] {variable :x; info exists :x}] \
                              [info exists :x] [::nsf::var::exists [::nsf::current] x]] "-"]

        append _ | [join [list [expr {$w eq [namespace eval [namespace current] {variable w; set w}]}] \
                              [expr {${:x} eq [namespace eval [namespace current] {variable w; set :x}]}]] -]
        return $_
      }
    }

    ? {::ns1::o faz} ":x--::ns1::w-0-0-0--0-0-0|:x--::ns1::w-1-1-0--0-1-1-1-0|1-1"

    #
    # ISSUE 2: Colon-prefixed variables become represented by linked
    # variables in the compiled local arrays during
    # compilation. However, linked variables are mutable (in contrast
    # to proc-local variables), that is, they can be changed to point
    # to another target variable. This target switch currently happens
    # between object variables and [variable] links which (due to
    # executing the compile-time var resolver because of lacking
    # AVOID_RESOLVERS) emits a "replacing" link var
    #
    # In the example below, there won't be an error exception
    # 'variable ":aaa" already exists', because ":aaa" is resolved on
    # the fly to "::ns1::o1.aaa" in a non-compiled execution and in a
    # compiled situation, the compiled-local link variable ":aaa" is
    # simply cleared and recreated to proxy a namespace variable.

    o eval {
      set :aaa 1
      :public object method caz {} {
        set _ "[info exists :aaa]-${:aaa}-[set :aaa]"
        variable :aaa
        append _ "-[info exists :aaa]"
        set :aaa 2
        append _ "-${:aaa}-[set :aaa]-[namespace eval [namespace current] {variable :aaa; set :aaa}]"
        unset :aaa
        append _ "-[info exists :aaa]-[namespace which -variable [namespace current]:::aaa]-[::nsf::var::exists [current] aaa]-[[current] eval {set :aaa}]"
        return $_
      }
    }

    ? {::ns1::o caz} "1-1-1-0-2-2-2-0--1-1"

    #
    # In non-compiled executions, there is another form of interaction
    # between our var resolvers and [variable] in the sense of
    # switching references. A [variable] statement is then handled by
    # Tcl_VariableObjCmd() directly, our compile-time resolver is
    # never called, hence, no link variables are created. The
    # non-compiling resolver InterpColonVarResolver() is called to
    # duty from within Tcl_VariableObjCmd(), however, it fast-forwards
    # by signalling TCL_CONTINUE as [variable] requests
    # TCL_NAMESPACE_ONLY explicitly.
    #
    # While [variable] creates a local link var ":aaa", any later
    # referencing of :aaa is intercepted by InterpColonVarResolver()
    # and resolved to the obj var "aaa".  The effects of this
    # interaction are probably counter-intuitive to standard
    # [variable] behavior.
    #
    # 1. There will not be a 'variable ":aaa" already exists' to
    # signal a naming conflict in the local naming scope, because the
    # symbolic name ":aaa" in a [set :aaa 1] and in a [variable :aaa
    # 1] is resolved differently (see above).
    #
    # 2. There is no way to refer to the local link var ":aaa" created
    # by [variable] in subsequent calls because the name will resolve
    # to an obj var "aaa". By calling [variable] in its setting mode,
    # you can still set namespace var values.
    ? {::ns1::o eval {
      set _ "[info exists :aaa]-${:aaa}-[set :aaa]"
      variable :aaa
      append _ "-[info exists :aaa]"
      set :aaa 2
      append _ "-${:aaa}-[set :aaa]-[[current] eval {set :aaa}]-[namespace eval [namespace current] {variable :aaa; info exists :aaa}]"
      variable :aaa 5
      unset :aaa
      append _ "-[info exists :aaa]-[namespace which -variable [namespace current]:::aaa]-[::nsf::var::exists [current] aaa]-[namespace eval [namespace current] {variable :aaa; info exists :aaa}]-[namespace eval [namespace current] {variable :aaa; set :aaa}]"
      return $_
    }} "1-1-1-1-2-2-2-0-0--0-1-5"


    # ... [upvar]
    #
    # Exhibits the same interactions as [variable] due to creating
    # link variables by the compiling var resolver, namely the context
    # switching and effective disabling of the colon-prefixed
    # accessing of object state ...
    #

    nx::Object create p {
      :public object method foo {var} {
        set :x XXX
        set _ ${:x}
        upvar $var :x
        append _ -[join [list ${:x} [set :x] {*}[info vars :*] {*}[:info vars] \
                             [info exists :x] \
                             [[current] eval {info exists :x}]] "-"]
        unset :x
        append _ -[join [list {*}[info vars :*] {*}[:info vars] \
                            [info exists :x] [[current] eval {info exists :x}] \
                            [[current] eval {set :x}]] "-"]
      }

      :object method bar {var1 var2 var3 var4 var5 var6} {
        upvar $var1 xx $var2 :yy $var3 :zz $var4 q $var5 :el1 $var6 :el2
        set _ [join [list {*}[lsort [:info vars]] {*}[lsort [info vars :*]] \
                          [info exists xx] $xx \
                          [info exists :yy] ${:yy} \
                          [info exists :zz] ${:zz} \
                          [info exists q] [[current] eval {info exists :q}]] -]
        incr :yy
        incr xx
        incr :zz
        incr q
        incr :el1
        incr :el2
        return $_
      }

      :public object method baz {} {
        set :x 10
        set y 20
        set :z 30
        unset -nocomplain :q
        set :arr(a) 40
        set _ [:bar :x y :z :q :arr(a) :arr(b)]
        append _ -[join [list ${:x} $y ${:z} ${:q} [set :arr(a)] [set :arr(b)] [:info vars q]] -]
      }
    }

    ? {set y 1; p foo y} "XXX-1-1-:x-x-1-1-:x-x-0-1-XXX"
    ? {p baz} "arr-x-z-:el1-:el2-:yy-:zz-1-10-1-20-1-30-0-0-11-21-31-1-41-1-q"

    #
    # ... [namespace which]
    #
    # Similar to the compiled, slow-path [variable] instructions,
    # [namespace which] as implemented by NamespaceWhichCmd() in
    # tclNamesp.c lacks AVOID_RESOLVERS. Therefore, we end up in our
    # var resolver which resolves colon-prefixed vars to object
    # variables. Also, NamespaceWhichCmd() does not set any other
    # var-resolution flags (TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY) as
    # this would defeat its purpose. Anywyays, our resolver is
    # therefore completely blind when handling calls from [namespace
    # which].
    #
    # This leads to the unexpected behavior in the test below:
    # [namespace which -variable :XXX] != [namespace which -variable
    # [namespace current]:::XXX]

    o eval {
      :public object method bar {} {
        set :XXX 1
        return [join [list ${:XXX} [set :XXX] [namespace which -variable :XXX] \
                          [namespace which -variable [namespace current]:::XXX]] -]
      }
    }

    ? {::ns1::o bar} "1-1-:XXX-"
  }
}

nx::test case after-next {
  #
  # This test case tests, whether we can use e..g an instvar in an
  # mixin method after the next. In such cases, the frame flags have
  # to be altered from inactive mixin to active mixin (otherwise the
  # actual frame would be skipped in useActiveFrames below in :instvar).
  #
  nx::Class create FormPage {
    :property package_id
    :public method initialize_loaded_object {} {;}
  }

  nx::Class create WorkflowPage {
    :alias instvar :::nsf::methods::object::instvar
    :public method initialize_loaded_object {} {
      next
      :instvar package_id
      return $package_id
    }

    FormPage mixins add WorkflowPage
    FormPage create p1 -package_id 123

    ? {p1 initialize_loaded_object} 123
  }
}

nx::test case unconfigured-varresolver {
  #
  # Test robustness of varresolver for unconfigured objects
  #
  nx::Class create O {
    :public method configure args {;}
    :create ateh
  }
  ? {ateh eval {info exists :x}} 0
}

#
# Test variable resolver in respect to uplevel and apply
# (lambda frames)
#

::nx::test case var-resolver-uplevel-apply {

  nx::Object create o1 {
    set :a o1.a
    :public object method foo {} {return ${:a}}
    :public object method bar {} {o2 foo}
    :public object method bar2 {} {o2 foo2}

  }

  nx::Object create o2 {
    set :a o2.a
    set :cmd {set :a}
    :public object method foo {} {eval ${:cmd}}
    :public object method foo2 {} {uplevel ${:cmd}}
    :public object method foo3 {} {uplevel 2 ${:cmd}}
    :public object method bar {} {:foo}
    :public object method bar2 {} {:foo2}
    :public object method bar3a {} {:foo3}
    :public object method bar3 {} {:bar3a}
  }

  #
  # test cases for uplevel over multiple levels in the same object
  #
  ? {o2 foo} o2.a
  ? {o2 bar} o2.a
  ? {o2 bar2} o2.a
  ? {o2 bar3} o2.a

  #
  # test cases for uplevel over multiple levels in different objects
  #
  ? {o1 foo} o1.a
  ? {o1 bar} o2.a
  ? {o1 bar2} o1.a

  #
  # test cases for uplevel over apply
  #
  proc x {cmd} {uplevel $cmd}

  nx::Object create o3 {
    set :a o3.a
    :public object method set {var} {set :$var}
    :public object method foo-m {} {:set a}
    :public object method foo-r {} {::set :a}
    :public object method foo-m-u {} {x {:set a}}
    :public object method foo-r-u {} {x {::set :a}}
    :public object method foo-a-m {} {::apply [list {} {:set a} [self]]}
    :public object method foo-a-r {} {::apply [list {} {::set :a} [self]]}
    :public object method foo-a-m-u {} {::apply [list {} {x {:set a}} [self]]}
    :public object method foo-a-r-u {} {::apply [list {} {x {::set :a}} [self]]}
  }

  #
  # resolver and method should behave the same
  #
  ? {o3 foo-m} "o3.a"
  ? {o3 foo-r} "o3.a"

  #
  # resolver and method should behave the same, also when uplevel is used
  #
  ? {o3 foo-m-u} "o3.a"
  ? {o3 foo-r-u} "o3.a"

  #
  # resolver and method should behave the same, also when apply is used
  #
  ? {o3 foo-a-m} "o3.a"
  ? {o3 foo-a-r} "o3.a"

  #
  # resolver and method should behave the same, also when apply and
  # uplevel are used
  #
  ? {o3 foo-a-m-u} "o3.a"
  ? {o3 foo-a-r-u} "o3.a"
}


nx::test case compiled_colon_lookup {

    nx::Object create p {

        :object method bar {var3 var4 var5 var6} {
            upvar $var3 :zz $var4 q $var5 :el1
            return ${:zz}
        }

        :public object method baz {} {
            set :z 30
            unset -nocomplain :q
            set :arr(a) 40
            return [:bar :z :q :arr(a) :arr(b)]
        }
    }
    #
    # the upvar construct causes a "slow" access path via the colon
    # var resolver. The first call will cause the creation of the
    # sorted lookup cache.
    #
    ? {p baz} 30
    #
    # later calls use this cache
    #
    ? {p baz} 30

    #
    # Now redefine the method containing the compiled locals with
    # an additional variable on the first position. In case the cache
    # would not be refreshed appropriately, the index would point to a
    # different variable and we would see wrong results here.
    #
    #puts stderr "=====redefine with an additional variable on the first position"
    p public object method baz {} {
        set :a 123
        set :z 30
        unset -nocomplain :q
        set :arr(a) 40
        return [:bar :z :q :arr(a) :arr(b)]
    }
    ? {p baz} 30
    ? {p baz} 30

}

#
# Local variables:
#    mode: tcl
#    tcl-indent-level: 2
#    indent-tabs-mode: nil
# End:
