TIP 689: "namespace unknown" independent on caller namespace

Login
Author:		Harald Oehlmann <[email protected]>
Author:		Jan Nijtmans <[email protected]>
Author:		Sergey G. Brester <[email protected]>
State:		Draft
Type:		Project
Vote:		Pending
Created:	13-03-2024
Tcl-Version:	8.7 and 9.0
Tcl-Branch:	tip-689
Vote-Summary:	
Votes-For:	
Votes-Against:	
Votes-Present:	

Abstract

TIP 181 introduced namespace unknown to call the unknown handler of the caller namespace. This TIP proposes to call the unknown handler of the namespace, where the command could not be resolved.

Rationale

A namespace assembles all commands of a package. The purpose of a procedure registered by namespace unknown is to to dynamically handle procs in a namespace which do not exist on initial setup. One application is auto loading on first use but other dynamic functions are possible. Currently, this only works, if called from the own namespace. If called from another namespace, the unknown handler of the other namespace is called.

This makes this feature unusable, as it is designed to be called from the outside.

Example:

    namespace eval ::t1 { namespace unknown ::u1 }
    proc ::u1 args {puts "u1 $args"}
    namespace eval ::t2 { namespace unknown ::u2 }
    proc ::u2 args {puts "u2 $args"}
    % namespace eval ::t1 {::t1::test}
    u1 ::t1::test
    % namespace eval ::t1 {::t2::test}
    u1 ::t2::test

As a demonstration, the TIP implementation also supplies a new ::tcl::clock unknown handler, replacing the old tclIndex method for the clock command.

Specification

namespace unknown should call the unknown function of the namespace where the proc was not found.

This changes the upper example to:

    % namespace eval ::t1 {::t2::test}
    u2 ::t2::test

Details

  1. namespace unknown will be invoked independently to the current namespace of current frame. In opposite to TIP 181 even for this call:
    ::A::B::C::D::cmd
    if unknown handler is set for any of that namespaces.

  2. The precedence of handler invocation: deepest NS with unknown-handler always wins (regardless from where exactly it was executed), thereby absolute namspace paths over relative paths. For the case of relative command name, firstly the current namespace and all parents will be inspected, hereafter if no one handler found, the affected namespaces relative global namespace, at end the global ::unknown handler.
    For instance, for this call:

        namespace eval ::A::B { C::D::cmd }
    

    the order of search for namespace unknown handler will be:

        ::A::B::C::D
        ::A::B::C
        ::A::B
        ::A
        ::C::D
        ::C
        ::
    
  3. The handler always get an originally supplied command name, which corresponds the command relative the caller frame (and therefore like by global ::unknown, relative to [uplevel {namespace current}]).

        % namespace eval ::A::B::C { cmd }
        = cmd
        % namespace eval ::A::B { C::cmd }
        = C::cmd
        % namespace eval ::A { B::C::cmd }
        = B::C::cmd
        % ::A::B::C::cmd
        = ::A::B::C::cmd
    

    To obtain real normalized command name inside the handler, one could use something like this (e. g. with auto_qualify like global ::unknown does):

        # fully-qualified command name:
        lindex [auto_qualify $cmd [uplevel {namespace current}]] 0
        # fully-qualified command name (without auto_qualify):
        regsub -all {(::){2}} [uplevel {namespace current}]::$cmd {::}
        # relative command name:
        regsub {^(::){1,2}foo::bar(::){1,2}} [uplevel {namespace current}]::$cmd {}
    

    Suggestion (not a part of this TIP, just as nice to have):
    For the last (to obtain normalized relative name) one could extend command namespace tail like this:

        % namespace tail
        namespace tail string ?relative?
        % namespace tail ::foo::bar::xxx::yyy ::foo::bar
        xxx::yyy
    
  4. Like in original implementation of TIP 181, only the first found unknown handler will be invoked. If logic of code expects to call unknown of near parent namespace instead, the handler must do that inside itself, for example using tailcall (to unfold recursion).

  5. There is a small compatibility issue possibly, because of the precedence matter.
    For instance:
    in case of 2 nested namespaces with handlers (::A::B and ::A::B::C), for the call:

        namespace eval ::A::B { C::cmd }
    

    it'd invoke handler of ::A::B::C now, where previously it'd rather invoke handler of ::A::B (since another was not implemented yet).
    Although a negative impact is hardly believable, because previously it was rather unexpected behaviour.

History

This TIP started as a bug ticket, not realizing that there was already a TIP describing the current behavior.

Implementation

Implementation is in TCL branch "tip-689".

Copyright

This document has been placed in the public domain.