Index: TODO =================================================================== diff -u -r1617c4f080e984ae2725d8337f994d5e5fa3d4cc -rb8c0176cfeae7f18490e9d6887ece97b713f0fe0 --- TODO (.../TODO) (revision 1617c4f080e984ae2725d8337f994d5e5fa3d4cc) +++ TODO (.../TODO) (revision b8c0176cfeae7f18490e9d6887ece97b713f0fe0) @@ -3311,7 +3311,12 @@ * added ./apps/utils/source-doc-beautifier.tcl * fixed the file-handle output/formatting in rosetta-serialization.tcl; using proc "!" +- nsf.c: + * fixed next path computation in cases where command handles + are used to refer to methods in the current mixin order. + * extended regression test + TODO: - nx: * maybe provide a replacement for -attributes, but without the magic variable. Index: generic/nsf.c =================================================================== diff -u -rfd3666742ed34091dbb93a872fe1d34d6b66ad9d -rb8c0176cfeae7f18490e9d6887ece97b713f0fe0 --- generic/nsf.c (.../nsf.c) (revision fd3666742ed34091dbb93a872fe1d34d6b66ad9d) +++ generic/nsf.c (.../nsf.c) (revision b8c0176cfeae7f18490e9d6887ece97b713f0fe0) @@ -6411,96 +6411,232 @@ } /* - * Walk through the command list until the current command is reached. - * return the next entry. + *---------------------------------------------------------------------- + * SeekCurrent -- * + * Walk through the command list until the provided command is reached. + * return the next entry. If the provided cmd is NULL, then return the + * first entry. + * + * Results: + * Command list pointer or NULL + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- */ static NsfCmdList * -SeekCurrent(Tcl_Command currentCmd, register NsfCmdList *cmdl) { - if (currentCmd) { - /* go forward to current class */ - for (; cmdl; cmdl = cmdl->nextPtr) { - if (cmdl->cmdPtr == currentCmd) { - return cmdl->nextPtr; +SeekCurrent(Tcl_Command cmd, register NsfCmdList *cmdListPtr) { + + if (cmd) { + for (; cmdListPtr; cmdListPtr = cmdListPtr->nextPtr) { + if (cmdListPtr->cmdPtr == cmd) { + return cmdListPtr->nextPtr; } } + return NULL; + } + return cmdListPtr; +} + +/* + *---------------------------------------------------------------------- + * CanInvokeMixinMethod -- + * + * Check, whether the provided cmd is allowed to be dispatch in a mixin. + * + * Results: + * Tcl result code or NSF_CHECK_FAILED in case, search should continue + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +CanInvokeMixinMethod(Tcl_Interp *interp, NsfObject *object, Tcl_Command cmd, NsfCmdList *cmdList) { + int result = TCL_OK; + + if (Tcl_Command_flags(cmd) & NSF_CMD_CLASS_ONLY_METHOD && !NsfObjectIsClass(object)) { + /* + * The command is not applicable for objects (i.e. might crash, + * since it expects a class record); therefore skip it + */ + return NSF_CHECK_FAILED; } - return cmdl; + + if (cmdList->clientData && !RUNTIME_STATE(interp)->guardCount) { + /*fprintf(stderr, "guardcall\n");*/ + result = GuardCall(object, interp, (Tcl_Obj *)cmdList->clientData, NULL); + } + return result; } + /* + *---------------------------------------------------------------------- + * MixinSearchProc -- + * + * Search for a methodname in the mixin list of the provided + * object. According to the state of the mixin stack it start the search + * from the beginning of from the last dispatched method shadowed method on + * the mixin path. + * + * Results: + * Tcl result code. + * Returns as well always cmd (maybe NULL) in cmdPtr. + * Returns on success as well the class and the currentCmdPointer + * for continuation in next. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +/* * before we can perform a mixin dispatch, MixinSearchProc seeks the * current mixin and the relevant calling information */ static int -MixinSearchProc(Tcl_Interp *interp, NsfObject *object, CONST char *methodName, +MixinSearchProc(Tcl_Interp *interp, NsfObject *object, + CONST char *methodName, Tcl_Obj *methodObj, NsfClass **clPtr, Tcl_Command *currentCmdPtr, Tcl_Command *cmdPtr) { Tcl_Command cmd = NULL; NsfCmdList *cmdList; - NsfClass *cl; + NsfClass *cl = NULL; int result = TCL_OK; assert(object); assert(object->mixinStack); + assert(methodName); /* ensure that the mixin order is not invalid, otherwise compute order */ assert(object->flags & NSF_MIXIN_ORDER_VALID); /*MixinComputeDefined(interp, object);*/ cmdList = SeekCurrent(object->mixinStack->currentCmdPtr, object->mixinOrder); RUNTIME_STATE(interp)->currentMixinCmdPtr = cmdList ? cmdList->cmdPtr : NULL; - /*fprintf(stderr, "MixinSearch searching for '%s' %p\n", methodName, cmdList);*/ - /*CmdListPrint(interp, "MixinSearch CL = \n", cmdList);*/ + /*fprintf(stderr, "MixinSearch searching for '%s' %p\n", methodName, cmdList); + CmdListPrint(interp, "MixinSearch CL = \n", cmdList);*/ - for (; cmdList; cmdList = cmdList->nextPtr) { + if (*methodName == ':') { + Tcl_DString ds, *dsPtr = &ds; + Tcl_Command cmd1, lastCmdPtr = NULL; + NsfClass *cl1; + NsfObject *regObject, *defObject; + CONST char *methodName1; + int fromClassNS = 0; - if (Tcl_Command_cmdEpoch(cmdList->cmdPtr)) { - continue; - } - cl = NsfGetClassFromCmdPtr(cmdList->cmdPtr); - assert(cl); /* - fprintf(stderr, "+++ MixinSearch %s->%s in %p cmdPtr %p clientData %p\n", - ObjectName(object), methodName, cmdList, - cmdList->cmdPtr, cmdList->clientData); - */ - cmd = FindMethod(cl->nsPtr, methodName); - if (cmd == NULL) { - continue; + * We have an abolute name provided. We have to check, whether the + * absolute refers of a class in the mixin list of the current object. If + * so, we have to advance the current cmd pointer to this cmd to allow + * next to start from this point. + */ + + if (methodObj == NULL) { + methodObj = Tcl_NewStringObj(methodName, -1); } - if (Tcl_Command_flags(cmd) & NSF_CMD_CLASS_ONLY_METHOD) { - /*fprintf(stderr, "we found class specific method %s on class %s object %s, isclass %d\n", - methodName, ClassName(cl), ObjectName(object), NsfObjectIsClass(object));*/ - if (!NsfObjectIsClass(object)) { - /* the command is not for us; skip it */ - cmd = NULL; - continue; + INCR_REF_COUNT(methodObj); + Tcl_DStringInit(dsPtr); + cmd1 = ResolveMethodName(interp, NULL, methodObj, + dsPtr, ®Object, &defObject, &methodName1, &fromClassNS); + if (cmd1 && regObject) { + /* + * The absolute name was found and refers to a name in a class. We + * iterate over the mixin list and search for the class. + */ + for (; cmdList; cmdList = cmdList->nextPtr) { + if (Tcl_Command_cmdEpoch(cmdList->cmdPtr)) { continue; } + cl1 = NsfGetClassFromCmdPtr(cmdList->cmdPtr); + assert(cl1); + lastCmdPtr = cmdList->cmdPtr; + + if ((NsfObject *)cl1 == regObject) { + /* + * The class was found. + */ + result = CanInvokeMixinMethod(interp, object, cmd1, cmdList); + /* + * No matter, what the result is, stop the search through the mixin + * classes here. + */ + if (result == NSF_CHECK_FAILED) { + result = TCL_OK; + cmd1 = NULL; + } + + cmd = cmd1; + cl = cl1; + break; + } } + } + Tcl_DStringFree(dsPtr); + DECR_REF_COUNT(methodObj); + + if (cl) { + assert(cmdList); + /* + * on success: return class and cmdList->cmdPtr; + */ + *clPtr = cl; + *currentCmdPtr = cmdList->cmdPtr; + /*fprintf(stderr, "mixinsearch returns %p (cl %s)\n", cmd, ClassName(cl));*/ + + } else { + /* + * We did not find the absolute entry in the mixins. Set the + * currentCmdPtr (on the mixin stack) to the last entry to flag, that + * the mixin list should not started again on a next. + */ + *currentCmdPtr = lastCmdPtr; } + + *cmdPtr = cmd; + return result; - if (cmdList->clientData) { - if (!RUNTIME_STATE(interp)->guardCount) { - /*fprintf(stderr, "guardcall\n");*/ - result = GuardCall(object, interp, (Tcl_Obj *)cmdList->clientData, NULL); + } else { + + for (; cmdList; cmdList = cmdList->nextPtr) { + + if (Tcl_Command_cmdEpoch(cmdList->cmdPtr)) { + continue; } - } - if (result == TCL_OK) { + cl = NsfGetClassFromCmdPtr(cmdList->cmdPtr); + assert(cl); /* - * on success: compute mixin call data + fprintf(stderr, "+++ MixinSearch %s->%s in %p cmdPtr %p clientData %p\n", + ObjectName(object), methodName, cmdList, + cmdList->cmdPtr, cmdList->clientData); + */ + cmd = FindMethod(cl->nsPtr, methodName); + if (cmd == NULL) { + continue; + } + + result = CanInvokeMixinMethod(interp, object, cmd, cmdList); + if (unlikely(result == TCL_ERROR)) { + return result; + } else if (result == NSF_CHECK_FAILED) { + result = TCL_OK; + cmd = NULL; + continue; + } + + /* + * cmd was found and is applicable. We return class and cmdPtr. */ *clPtr = cl; *currentCmdPtr = cmdList->cmdPtr; /*fprintf(stderr, "mixinsearch returns %p (cl %s)\n", cmd, ClassName(cl));*/ break; - } else if (result == TCL_ERROR) { - break; - } else { - if (result == NSF_CHECK_FAILED) result = TCL_OK; - cmd = NULL; } + } - *cmdPtr = cmd; return result; } @@ -7842,6 +7978,8 @@ return paramDefs; } + + /* *---------------------------------------------------------------------- * ParamDefsFree -- @@ -9170,9 +9308,9 @@ /* * The entry is just searched and pushed on the stack when we * have no filter; in the filter case, the search happens in - * next + * next. */ - result = MixinSearchProc(interp, object, methodName, &cl, + result = MixinSearchProc(interp, object, methodName, methodObj, &cl, &object->mixinStack->currentCmdPtr, &cmd); if (result != TCL_OK) { /*fprintf(stderr, "mixinsearch returned an error for %p %s.%s\n", @@ -9187,12 +9325,16 @@ } } - /* check if an absolute method name was provided */ - if (unlikely(*methodName == ':')) { + /* + * Check if an absolute method name was provided + */ + if (unlikely(cmd == NULL && *methodName == ':')) { + cmd = Tcl_GetCommandFromObj(interp, methodObj); if (cmd) { Tcl_ObjCmdProc *procPtr = Tcl_Command_objProc(cmd); + /*fprintf(stderr, "absolute lookup of %s returned %p\n", ObjStr(methodObj), cmd);*/ if (procPtr == NsfObjDispatch) { /* * Don't allow to call objects as methods (for the time being) @@ -12023,8 +12165,10 @@ /*fprintf(stderr, "... mixinstack %p => %p\n", object, object->mixinStack);*/ if (object->mixinStack) { - int result = MixinSearchProc(interp, object, *methodNamePtr, clPtr, currentCmdPtr, cmdPtr); - if (result != TCL_OK) { + int result = MixinSearchProc(interp, object, *methodNamePtr, NULL, + clPtr, currentCmdPtr, cmdPtr); + + if (unlikely(result != TCL_OK)) { return result; } @@ -17429,17 +17573,16 @@ Tcl_DStringInit(dsPtr); cmd = ResolveMethodName(interp, cl ? cl->nsPtr : object->nsPtr, methodObj, dsPtr, ®Object, &defObject, &methodName1, &fromClassNS); - + Tcl_DStringFree(dsPtr); /*fprintf(stderr, "methodProperty for method '%s' prop %d value %s => cl %p cmd %p\n", methodName, methodproperty, valueObj ? ObjStr(valueObj) : "NULL", cl, cmd);*/ + if (!cmd) { - Tcl_DStringFree(dsPtr); return NsfPrintError(interp, "Cannot lookup %smethod '%s' for %s", cl == 0 ? "object " : "", methodName, ObjectName(object)); } - Tcl_DStringFree(dsPtr); switch (methodproperty) { case MethodpropertyClass_onlyIdx: /* fall through */ Index: tests/interceptor-slot.test =================================================================== diff -u -r67591822465e64d9051583c9aa71f8d3a4ef5c96 -rb8c0176cfeae7f18490e9d6887ece97b713f0fe0 --- tests/interceptor-slot.test (.../interceptor-slot.test) (revision 67591822465e64d9051583c9aa71f8d3a4ef5c96) +++ tests/interceptor-slot.test (.../interceptor-slot.test) (revision b8c0176cfeae7f18490e9d6887ece97b713f0fe0) @@ -205,8 +205,62 @@ ? {ob2 baz} {} } -puts stderr ======EXIT +# +# Test the next-path with just intrinsic classes in cases where a +# method handle is used for method dispatch +# +nx::Test case intrinsic+method-handles { + Class create A {:public method foo {} {return "A [next]"}} + Class create B -superclass A {:public method foo {} {return "B [next]"}} + Class create C -superclass B {:public method foo {} {return "C [next]"}} + + C create c1 + ? {c1 foo} "C B A " + ? {c1 [C info method origin foo]} "C B A " + ? {c1 [B info method origin foo]} "B A " + ? {c1 [A info method origin foo]} "A " + +} +# +# Test the next-path with mixin classes in cases where a +# method handle is used for method dispatch +# +nx::Test case mixins+method-handles { + # + # Just mixin classes + # + Class create A {:public method foo {} {return "A [next]"}} + Class create B {:public method foo {} {return "B [next]"}} + Class create C {:public method foo {} {return "C [next]"}} + + Class create X -mixin {C B A} + X create c1 + ? {c1 foo} "C B A " + ? {c1 [C info method origin foo]} "C B A " + ? {c1 [B info method origin foo]} "B A " + ? {c1 [A info method origin foo]} "A " + + # + # Intrinsic classes and mixin classes + # + + Class create Y {:public method foo {} {return "Y [next]"}} + Class create Z -superclass Y {:public method foo {} {return "Z [next]"}} + + Z create c1 -mixin {C B A} + ? {c1 foo} "C B A Z Y " + ? {c1 [C info method origin foo]} "C B A Z Y " + ? {c1 [B info method origin foo]} "B A Z Y " + ? {c1 [A info method origin foo]} "A Z Y " + ? {c1 [Z info method origin foo]} "Z Y " + ? {c1 [Y info method origin foo]} "Y " + +} + + + +