TIP 644: Make Tcl_ObjType extensible

Login
Author:		Ashok P. Nadkarni <[email protected]>
State:		Final
Type:		Project
Vote:		Done
Created:	10-Oct-2022
Tcl-Version:	9.0
Tcl-Branch:	tip-644
Vote-Summary:	Accepted 3/0/0
Votes-For:	BG JN SL
Votes-Against:	none
Votes-Present:	none

Abstract

Add a version field to the Tcl_ObjType field of the Tcl_ObjType structure to permit future expansion.

Rationale

In Tcl 8.x, the Tcl_ObjType structure enables extension writers to implement new Tcl_Obj value types by defining appropriate dispatch functions. However, because the structure is of a fixed size and with no versioning information, new dispatch functions cannot be added except when accompanied by a Tcl major version change.

The Tcl_ChannelType structure includes a version field which has allowed the channel subsystem to be enhanced with additional dispatch functions without requiring a Tcl major version change. This TIP proposes adding similar version information to the Tcl_ObjType field.

An example where versioning would be beneficial is TIP 636, which implements abstract list functionality for 8.7. Because the Tcl_ObjType structure could not be modified, this has been implemented through essentially an additional level of indirection with an attendant cost in performance and complexity. An ability to version Tcl_ObjType would have permitted the list dispatch function to be directly added to that structure even for 8.7.

This TIP only proposes the versioning capability be made possible, not for any specific additional functions to be defined. TIP 636 would however be a candidate to be adapted.

Specification

The Tcl_ObjType structure will be defined as follows:

typedef struct Tcl_ObjType {
    const char *name;
    Tcl_FreeInternalRepProc *freeIntRepProc;
    Tcl_DupInternalRepProc *dupIntRepProc;
    Tcl_UpdateStringProc *updateStringProc;
    Tcl_SetFromAnyProc *setFromAnyProc;
    size_t version;
} Tcl_ObjType;
#define TCL_OBJTYPE_V0 0
#define TCL_OBJTYPE_CURRENT TCL_OBJTYPE_V0

The first five fields are in common with Tcl 8.6. Keeping the version information at the end means existing Tcl_ObjType definitions that are statically defined (as is usually the case) do not have to modified as the compiler will auto-initialize the field to 0. Allocated Tcl_ObjType structures that are explicitly initialized at runtime will have to be modified to initialize the field to TCL_OBJTYPE_V0. This is rare. (In fact, the comments in the code indicate the structure must be statically allocated though the manpages do not mention this.**

Note: Gcc produces a missing initializer for field warning when the version field is not explicitly initialized. This is due to the use of -Wextra in the compile flags. This is completely bogus in the author's opinion since implicit initialization for struct fields is well defined C. The compiler might as well complain about uninitialized static variables. In any case, the warning can be fixed by either explicitly initializing or adding the -Wno-missing-field-initializers switch. Extensions that compile against both Tcl 8 and 9 will have to either turn off the warning or use ifdefs.

The structure can be extended for new functionality by adding fields at the end of the structure with a corresponding bump in version. TCL_OBJTYPE_CURRENT should be updated correspndingly. Access to the new fields must be protected by a check for the appropriate version. Note that the new fields need not be restricted to function pointers. See example below.

The Tcl_RegisterObjType function will be unchanged and depend on the caller to have correctly initialized the passed structure as is the case today.

Examples

TIP 636 is written under the assumption that the Tcl_ObjType is unchanged from Tcl8. Abstract lists are implemented as an instance of Tcl_ObjType whose intrep contains an additional function dispatch table for list related methods. Thus there is an additional indirection and level of indirection required.

With TIP 644, abstract lists could instead be implemented by extending the Tcl_ObjType structure as follows

typedef struct Tcl_ObjType {
    const char *name;
    Tcl_FreeInternalRepProc *freeIntRepProc;
    Tcl_DupInternalRepProc *dupIntRepProc;
    Tcl_UpdateStringProc *updateStringProc;
    Tcl_SetFromAnyProc *setFromAnyProc;
    size_t version;
    int (*listLengthProc)(Tcl_Interp *interp, Tcl_Obj *objPtr);
    ...other list functions...
} Tcl_ObjType;
#define TCL_OBJTYPE_V0 0
#define TCL_OBJTYPE_V1 1
#define TCL_OBJTYPE_CURRENT TCL_OBJTYPE_V1

The Tcl_ListObjLength function would then look like

if (objPtr->typePtr &&
    objPtr->typePtr->version >= TCL_OBJTYPE_V1 &&
    objPtr->typePtr->listLengthProc != NULL
    ) {
    return objPtr->typePtr->listLengthProc(...);
} else {
    ...essentially existing code shimmering to a list...
}

The ArithSeries implementation would then define its Tcl_ObjType definition as

const Tcl_ObjType tclArithSeriesType = {
    "arithseries",
    FreeArithSeriesInternalRep, /* freeIntRepProc */
    DupArithSeriesInternalRep,  /* dupIntRepProc */
    UpdateStringOfArithSeries,  /* updateStringProc */
    SetArithSeriesFromAny,      /* setFromAnyProc */
    TCL_OBJTYPE_V1,             /* version */
    ArithSeriesLen              /* listLengthProc */
    ...other list methods...
};

Further extensions in the future would proceed similarly by bumping the version, adding new fields with appropriate checks when accessing.

The new fields added to the structure need not be restricted to function pointers. Even when used purely for that purpose, it can make sense to encapsulate the addition functions within another structure. This can make initialization easier when those fields are not used. For example, if the structure included both abstract lists and abstract dictionaries, it might look like

typedef struct Tcl_AbstractListType {
    Tcl_ALLengthProc *lengthProc;
    Tcl_ALIndexProc *indexProc;
    ...additional functions...
} Tcl_AbstractListType;
typedef struct Tcl_AbstractDictType {
    Tcl_ADSizeProc *sizeProc;
    Tcl_ADLookupProc *lookupProc;
    ...additional functions...
} Tcl_AbstractDictType;
typedef struct Tcl_ObjType {
    const char *name;
    Tcl_FreeInternalRepProc *freeIntRepProc;
    Tcl_DupInternalRepProc *dupIntRepProc;
    Tcl_UpdateStringProc *updateStringProc;
    Tcl_SetFromAnyProc *setFromAnyProc;
    size_t version;
    Tcl_AbstractListType abstractListType;
    Tcl_AbstractDictType abstractDictType;
} Tcl_ObjType;
#define TCL_OBJTYPE_V0 0
#define TCL_OBJTYPE_V1 1
#define TCL_OBJTYPE_CURRENT TCL_OBJTYPE_V1

Initialization of an abstract dictionary concrete type is then a little easier since the fields of abstractListType do not need to be individually initialized.

Copyright

This document has been placed in the public domain.