Index: generic/nsf.c =================================================================== diff -u -r08e157c4219837f40bae78c9ff5f04e9488ca8bd -ree323da025db17a708fcfbf7228b3011ce649d07 --- generic/nsf.c (.../nsf.c) (revision 08e157c4219837f40bae78c9ff5f04e9488ca8bd) +++ generic/nsf.c (.../nsf.c) (revision ee323da025db17a708fcfbf7228b3011ce649d07) @@ -8390,7 +8390,7 @@ } /*fprintf(stderr, "MethodDispatch method '%s' cmd %p cp=%p objc=%d cscPtr %p flags %.6x\n", - methodName, cmd, cp, objc, cscPtr, cscPtr->flags);*/ + methodName, cmd, cp, objc, cscPtr, cscPtr->flags);*/ assert(object->teardown); /* @@ -8548,11 +8548,15 @@ ObjectName(self),methodName, ObjectName(object), cscPtr, result, rst->unknown);*/ if (rst->unknown) { /* - * The appropriate unknown class is registered on the - * EnsembleObject class, from where it is currently not - * possible to determine the true calling object. Therefore, - * we pass the object as first argument of the unknown - * handler. + * Unknown handling: We trigger a dispatch to an unknown method. The + * appropriate unknown handler is either provided for the current + * object (at the class or the mixin level), or the default unknown + * handler takes it from there. The application-level unknown + * handler cannot determine the top-level calling object (referred + * to as the delegator). Therefore, we assemble all the neccesssary + * call data as the first argument passed to the unknown + * handler. Call data include the calling object (delegator), the + * method path, and the unknown final method. */ /*fprintf(stderr, "next calls DispatchUnknownMethod\n");*/ Tcl_Obj *callInfoObj = Tcl_NewListObj(0,NULL); @@ -11806,9 +11810,12 @@ * locate the appropriate callstack content and continue next on * that. */ + fprintf(stderr,">>> input framePtr %p input cscPtr %p\n",framePtr,cscPtr); cscPtr = CallStackFindEnsembleCsc(framePtr, &framePtr); assert(cscPtr); inEnsemble = 1; + fprintf(stderr,">>> framePtr %p cscPtr %p\n",framePtr,cscPtr); + fprintf(stderr,"cscPtr->objv[0] %s\n",cscPtr->objv[0]?ObjStr(cscPtr->objv[0]):NULL); *methodNamePtr = ObjStr(cscPtr->objv[0]); } else { inEnsemble = 0; @@ -11964,8 +11971,8 @@ result = NextSearchMethod(object, interp, cscPtr, &cl, &methodName, &cmd, &isMixinEntry, &isFilterEntry, &endOfFilterChain, ¤tCmd); - /*fprintf(stderr, "NEXT search on %s.%s cl %p cmd %p endOfFilterChain %d result %d\n", - ObjectName(object), methodName, cl, cmd, endOfFilterChain, result);*/ + /*fprintf(stderr, "NEXT search on %s.%s cl %p cmd %p endOfFilterChain %d result %d IS OK %d\n", + ObjectName(object), methodName, cl, cmd, endOfFilterChain, result, (result == TCL_OK));*/ if (result != TCL_OK) { goto next_search_and_invoke_cleanup; Index: generic/nsfStack.c =================================================================== diff -u -r08e157c4219837f40bae78c9ff5f04e9488ca8bd -ree323da025db17a708fcfbf7228b3011ce649d07 --- generic/nsfStack.c (.../nsfStack.c) (revision 08e157c4219837f40bae78c9ff5f04e9488ca8bd) +++ generic/nsfStack.c (.../nsfStack.c) (revision ee323da025db17a708fcfbf7228b3011ce649d07) @@ -119,7 +119,7 @@ *---------------------------------------------------------------------- * Nsf_PushFrameObj, Nsf_PopFrameObj -- * - * Push or pop a frame with a callstack content as a OBJECT + * Push or pop a frame with a callstack content as an OBJECT * frame. * * Results: @@ -550,20 +550,31 @@ NsfCallStackContent *cscPtr = NULL; assert(framePtr); - for (varFramePtr = Tcl_CallFrame_callerPtr(framePtr); - Tcl_CallFrame_isProcCallFrame(varFramePtr) & FRAME_IS_NSF_CMETHOD; + for (/* Skipping the starting frame, assumingly a "leaf" frame in an ensemle dispatch */ + varFramePtr = Tcl_CallFrame_callerPtr(framePtr); + Tcl_CallFrame_isProcCallFrame(varFramePtr) & FRAME_IS_NSF_CMETHOD; varFramePtr = Tcl_CallFrame_callerPtr(varFramePtr)) { cscPtr = (NsfCallStackContent *)Tcl_CallFrame_clientData(varFramePtr); assert(cscPtr); + + /*fprintf(stderr," --- frame %p cmdPtr %p NSF_CSC_TYPE_ENSEMBLE %d NSF_CSC_CALL_IS_ENSEMBLE %d \ + NSF_CSC_TYPE_INACTIVE %d\n", + varFramePtr, + cscPtr->cmdPtr, + (cscPtr->frameType & NSF_CSC_TYPE_ENSEMBLE) != 0, + (cscPtr->flags & NSF_CSC_CALL_IS_ENSEMBLE) != 0, + (cscPtr->frameType & NSF_CSC_TYPE_INACTIVE) != 0);*/ /* - * The test for CALL_IS_ENSEMBLE is just a saftey belt + * The "root" frame in a callstack branch resulting from an ensemble + * dispatch is not typed as an NSF_CSC_TYPE_ENSEMBLE frame, the call type + * /is/ NSF_CSC_CALL_IS_ENSEMBLE. */ - if ((cscPtr->flags & NSF_CSC_CALL_IS_ENSEMBLE) == 0) break; + if ((cscPtr->frameType & NSF_CSC_TYPE_ENSEMBLE) == 0 && + (cscPtr->flags & NSF_CSC_CALL_IS_ENSEMBLE)) break; } if (framePtrPtr) { *framePtrPtr = varFramePtr; } - return cscPtr; } @@ -590,26 +601,39 @@ /* * Append all ensemble names to the specified list obj */ - for (framePtr = Tcl_CallFrame_callerPtr(framePtr), elements = 0; + for (/* Skipping the starting frame, assumingly a "leaf" frame in an ensemle dispatch */ + framePtr = Tcl_CallFrame_callerPtr(framePtr), elements = 0; Tcl_CallFrame_isProcCallFrame(framePtr) & (FRAME_IS_NSF_CMETHOD|FRAME_IS_NSF_METHOD); framePtr = Tcl_CallFrame_callerPtr(framePtr)) { + NsfCallStackContent *cscPtr = (NsfCallStackContent *)Tcl_CallFrame_clientData(framePtr); assert(cscPtr); + + /*fprintf(stderr, "--- frame %p cmdPtr %p cmd %s NSF_CSC_TYPE_ENSEMBLE %d \ + NSF_CSC_CALL_IS_ENSEMBLE %d NSF_CSC_TYPE_INACTIVE %d\n", + framePtr, + cscPtr->cmdPtr, + Tcl_GetCommandName(interp, cscPtr->cmdPtr), + (cscPtr->frameType & NSF_CSC_TYPE_ENSEMBLE) != 0, + (cscPtr->flags & NSF_CSC_CALL_IS_ENSEMBLE) != 0, + (cscPtr->frameType & NSF_CSC_TYPE_INACTIVE) != 0);*/ - /* - * Beware configure transparency: NsfOConfigureMethod() pushes a CMETHOD - * frame with a NULL cmdPtr in its callstack content, especially for - * providing callstack transparency for alias parameters. If not bypassing - * this special-purpose frame, we end up with erroreneous method path - * introspection: Method paths would be reported with preceding empty - * string elements! + /* Do not record any INACTIVE frames in the method path */ + if ((cscPtr->frameType & NSF_CSC_TYPE_INACTIVE)) continue; + + Tcl_ListObjAppendElement(interp, methodPathObj, + Tcl_NewStringObj(Tcl_GetCommandName(interp, cscPtr->cmdPtr), -1)); + elements++; + + /* + * The "root" frame in a callstack branch resulting from an ensemble + * dispatch is not typed as an NSF_CSC_TYPE_ENSEMBLE frame, the call type + * /is/ NSF_CSC_CALL_IS_ENSEMBLE. */ - if (cscPtr->cmdPtr) { - Tcl_ListObjAppendElement(interp, methodPathObj, - Tcl_NewStringObj(Tcl_GetCommandName(interp, cscPtr->cmdPtr), -1)); - elements++; - } - if ((cscPtr->flags & NSF_CSC_TYPE_ENSEMBLE) == 0) break; + + if ((cscPtr->frameType & NSF_CSC_TYPE_ENSEMBLE) == 0 && + (cscPtr->flags & NSF_CSC_CALL_IS_ENSEMBLE)) break; + } /* * The resulting list has reveresed order. If there are multiple Index: tests/disposition.test =================================================================== diff -u -rece181a287bd6c4ac18c067a115dc099f47a029d -ree323da025db17a708fcfbf7228b3011ce649d07 --- tests/disposition.test (.../disposition.test) (revision ece181a287bd6c4ac18c067a115dc099f47a029d) +++ tests/disposition.test (.../disposition.test) (revision ee323da025db17a708fcfbf7228b3011ce649d07) @@ -780,6 +780,76 @@ ? {c1 eval {set :y}} 1 } +nx::Test case submethods-via-aliasparams { + # + # Could move to submethods.test? + # + Class create C { + :public class method setObjectParams {spec} { + set :objectparams $spec + ::nsf::invalidateobjectparameter [current] + } + :class method objectparameter {} { + if {[info exists :objectparams]} { + return ${:objectparams} + } + } + } + + # A depth-1 submethod ... + C public method "FOO foo" {} { + set :msg "[::nsf::current]--[::nsf::current methodpath]--[::nsf::current method]" + } + + # A depth-2 submethod ... + C public method "BAR BOO buu" {} { + set :msg "[::nsf::current]--[::nsf::current methodpath]--[::nsf::current method]" + } + + + # // Ordinary dispatch // + # The message send below expands into the following callstack + # structure (when viewed at from within foo(), N is the anonymous + # call site) + # + # N+3 |:CscFrame @Type(ENSEMBLE) | <-- foo (leaf) + # N+2 |:CscFrame @Call(ENSEMBLE) | <-- FOO (root) + # N+1 |:TclFrame| e.g. cmd, [namespace eval], [apply] + ? { + [C create c1] FOO foo; # N + c1 eval {set :msg} + } "::c1--FOO--foo" + + # + # Submethod levels greater than 1 turn into intermittent frames: + # N+4 |:CscFrame @Type(ENSEMBLE) | <-- buu (leaf) + # N+3 |:CscFrame @Type(ENSEMBLE) @Call(ENSEMBLE)| <-- BOO (intermittent) + # N+2 |:CscFrame @Call(ENSEMBLE) | <-- BAR (root) + # N+1 |:TclFrame| + # + ? { + [C create c3] BAR BOO buu; # N + c3 eval {set :msg} + } "::c3--BAR BOO--buu" + + + # // Parameter (alias) dispatch // + # + # In contrast to an ordinary dispatch, a parameter dispatch results + # in a different callstack structure, due to the interferring + # configure(): + # + # N+5 |:CscFrame @Type(ENSEMBLE)| <-- foo (leaf) + # N+4 |:CscFrame @Call(ENSEMBLE)| <-- FOO (root) + # N+3 |:CscFrame @INACTIVE| <-- (INNER configure() frame) + # N+2 |:ObjFrame| <-- ::c2 (OUTER configure() frame) + # N+1 |:TclFrame| + C setObjectParams [list FOO:alias] + ? { + [C create c2 foo] eval {set :msg}; # N + } "::c2--FOO--foo" +} + nx::Test case dispo-configure-transparency { Class create C { :public class method setObjectParams {spec} { @@ -810,6 +880,65 @@ # UNPATCHED: # ? {[C create c -show me] eval {set :msg}} "::c-{} show" ? {[C create c -show me] eval {set :msg}} "::c-show" + + # + # ... with mixin indirection + # + + # ... at the calling object level / configure() ... + Class create M { + :public method configure args { + next; + } + :public method foo args { + next; + } + } + + C setObjectParams [list [list FOO:alias,noarg ""]] + C mixin add M + ? {C create c} "::c-FOO" + + # ... at the called object level + + Object create ::callee { + :public method foo {} { + error [::nsf::current]-[::nsf::current methodpath] + } + } + + ::nsf::method::alias C FOO ::callee + + + + C setObjectParams [list [list FOO:alias,noarg ""]] + ? {C create c} "::c" "Defaultmethod of calle is invoked ..." + C setObjectParams [list [list FOO:alias "foo"]] + ? {C create c} "::c-FOO" "foo leaf method is selected ..." + ::callee mixin add M + ? {C create c} "::c-FOO" "With mixin ..." + + # + # ... at the calling object level / ensemble path + # + # This scenario effectively stacks additional call frames to be + # traversed by CallStackMethodPath(). However, these frames precede + # the first ensemble frame, that's why they are skipped by + # CallStackMethodPath(). + + M eval { + :public method FOO args { + puts stderr "!!!!! FOO MIXIN ...." + next; + } + } + + ? {C create c} "::c-FOO" "With mixin ..." + + # + # ... with filter indirection + # + } nx::Test case dispo-object-targets { @@ -914,14 +1043,14 @@ ? {T create tt YYY} "::obj: unable to dispatch method 'YYY'" "sending the msg: tt->z(::obj)->YYY()" ::obj mixin UnknownHandler ? {T create tt YYY} "CURRENT-::obj-DELEGATOR-::tt-UNKNOWNMETHOD-YYY-PATH-z" \ - "sending the msg: tt->z(::obj)->{}()" + "sending the msg: tt->z(::obj)->YYY()" ::obj mixin {} T setObjectParams [list -z:alias] ? {T create tt -z YYY} "::obj: unable to dispatch method 'YYY'" "sending the msg: tt->z(::obj)->YYY()" ::obj mixin UnknownHandler ? {T create tt -z YYY} "CURRENT-::obj-DELEGATOR-::tt-UNKNOWNMETHOD-YYY-PATH-z" \ - "sending the msg: tt->z(::obj)->{}()" + "sending the msg: tt->z(::obj)->YYY()" # # [current methodpath] & empty selector strings: @@ -975,7 +1104,7 @@ } -# exit +exit # # check xotcl with residual args