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.