struct Var proposals

FM This page is to discuss here about the interest to have a typing for Tcl variables.

Well, as a script programmer, I don't need to know what is the object type of the TclOO struct internaly. But very often I need some extra information about the variable I use. Sometimes a name convention is sufficient, but sometimes it's not.

I've tried an experiment about introducing a "type" on a Tcl variable. This is based on the 8.5.8 tcl source, here it is :

# file tclInt.h

typedef struct Var {
    int flags;                        /* Miscellaneous bits of information about
                                 * variable. See below for definitions. */
    Tcl_Obj *userTypeObjPtr;   /* additional user type to variable */
    union {
        Tcl_Obj *objPtr;        /* The variable's object value. Used for
                                 * scalar variables and array elements. */
        TclVarHashTable *tablePtr;/* For array variables, this points to
                                 * information about the hash table used to
                                 * implement the associative array. Points to
                                 * ckalloc-ed data. */
        struct Var *linkPtr;        /* If this is a global variable being referred
                                 * to in a procedure, or a variable created by
                                 * "upvar", this field points to the
                                 * referenced variable's Var struct. */
    } value;
} Var;

MODULE_SCOPE int        Tcl_SetUserTypeObjCmd(ClientData clientData,
                            Tcl_Interp *interp, int objc,
                            Tcl_Obj *const objv[]);


# file tclVar.c

int
Tcl_SetUserTypeObjCmd(
   ClientData dummy,                /* Not used. */
    register Tcl_Interp *interp,/* Current interpreter. */
    int objc,                        /* Number of arguments. */
    Tcl_Obj *const objv[])        /* Argument objects. */
{
    Tcl_Obj *userTypeObj;

    if (objc == 2) {
        userTypeObj = Tcl_ObjGetUserType2(interp, objv[1], NULL,TCL_LEAVE_ERR_MSG);
        if (userTypeObj == NULL) {
            return TCL_ERROR;
        } else {
            Tcl_SetObjResult(interp, userTypeObj);
        }
    } else if (objc == 3) { 
        userTypeObj = Tcl_ObjSetUserType2(interp, objv[1], NULL, objv[2],
                                          TCL_LEAVE_ERR_MSG);
        if (userTypeObj == NULL) {
            return TCL_ERROR;
        }
        Tcl_SetObjResult(interp, objv[1]);
        return TCL_OK;
    } else {
        Tcl_WrongNumArgs(interp, 1, objv, "varName ?newType?");
        return TCL_ERROR;
             
    } 
}

Tcl_Obj *
Tcl_ObjGetUserType2 (
    Tcl_Interp *interp,                /* Command interpreter in which variable is to
                                 * be found. */
    register Tcl_Obj *part1Ptr,        /* Points to an object holding the name of an
                                 * array (if part2 is non-NULL) or the name of
                                 * a variable. */
    register Tcl_Obj *part2Ptr,        /* If non-NULL, points to an object holding
                                 * the name of an element in the array
                                 * part1Ptr. */
    int flags)        
{
    Var *varPtr, *arrayPtr;
    Interp *iPtr = (Interp *) interp;

    /*
     * Filter to pass through only the flags this interface supports.
     */

    flags &= (TCL_GLOBAL_ONLY|TCL_NAMESPACE_ONLY|TCL_LEAVE_ERR_MSG
            |TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
    varPtr = TclObjLookupVarEx(interp, part1Ptr, part2Ptr, flags, "set the type of",
            /*createPart1*/ 0, /*createPart2*/ 1, &arrayPtr);
    if (varPtr == NULL) {
        return NULL;
    }
    return varPtr->userTypeObjPtr;
}

Tcl_Obj *
Tcl_ObjSetUserType2 (
    Tcl_Interp *interp,                /* Command interpreter in which variable is to
                                 * be found. */
    register Tcl_Obj *part1Ptr,        /* Points to an object holding the name of an
                                 * array (if part2 is non-NULL) or the name of
                                 * a variable. */
    register Tcl_Obj *part2Ptr,        /* If non-NULL, points to an object holding
                                 * the name of an element in the array
                                 * part1Ptr. */
    Tcl_Obj *TypePtr,             /* Type for variable. */
    int flags)        
{
    Var *varPtr, *arrayPtr;

    /*
     * Filter to pass through only the flags this interface supports.
     */

    flags &= (TCL_GLOBAL_ONLY|TCL_NAMESPACE_ONLY|TCL_LEAVE_ERR_MSG
            |TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
    varPtr = TclObjLookupVarEx(interp, part1Ptr, part2Ptr, flags, "set",
            /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr);
    if (varPtr == NULL) {
        if (TypePtr->refCount == 0) {
            Tcl_DecrRefCount(TypePtr);
        }
        return NULL;
    }
    Tcl_IncrRefCount(TypePtr);
    varPtr->userTypeObjPtr = TypePtr;
    return varPtr->userTypeObjPtr;
}

# file tclDecls.h

#ifndef Tcl_ObjSetUserType2_TCL_DECLARED
#define Tcl_ObjSetUserType2_TCL_DECLARED
EXTERN Tcl_Obj *        Tcl_ObjSetUserType2 (Tcl_Interp * interp, 
                                Tcl_Obj * part1Ptr, Tcl_Obj * part2Ptr, 
                                Tcl_Obj * TypePtr, int flags);
#endif
#ifndef Tcl_ObjGetUserType2_TCL_DECLARED
#define Tcl_ObjGetUserType2_TCL_DECLARED
EXTERN Tcl_Obj *        Tcl_ObjGetUserType2 (Tcl_Interp * interp, 
                                Tcl_Obj * part1Ptr, Tcl_Obj * part2Ptr, 
                                int flags);
#endif

# file tclBasic.c
# added to : static const CmdInfo builtInCmds

    {"type",            Tcl_SetUserTypeObjCmd,  NULL,                   1},

Test

type A; #can't set the type of "A": no such variable
type A [list this is the type]; # A
set A [list this is the A value]; # this is the A value
type A ;# this is the type
set A ; this is the A value
set [type A [list this is the type]] [list this is the A value]; # this is the A value
set B [list this is the B value]
type B [type A]; # B
type [type B [type A]]; # this is the type
type [type D [type [type C [type [type B [type A]]]]]]; # this is the type
type D; #this is the type
type C; #this is the type
type [type D [type [type C [type [type B [type A]]]]]]; # this is the type
set D;# can't read "D": no such variable (bug)
set C [list 1 2 3];# 1 2 3
type C; #this is the type (no bug ???)

type A [list I'am A]
type B [list I'am B]
type C [list I'am C]

proc tester {var} {
    upvar $var v
    if {[type v] eq "I'am B"} {
        puts "$v"
    } elseif {[type v] eq "I'am A"} {
        puts $v
    } else {
        puts stderr "unexpected type '[type v]'"
    }
}
tester A; #this is the A value
tester B; #this is the B value
tester C; #unexpected type 'I'am C'

There is still some bugs... Comments are welcome.