Tcl Source Code

Artifact [7efccd0870]
Login

Artifact 7efccd0870a56b5af10fef0e72c7718f0e30e934:

Attachment "nsPath.diff" to ticket [1159942fff] added by dkf 2005-05-20 22:35:09.
Index: generic/tclBasic.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclBasic.c,v
retrieving revision 1.155
diff -u -r1.155 tclBasic.c
--- generic/tclBasic.c	19 May 2005 15:18:02 -0000	1.155
+++ generic/tclBasic.c	20 May 2005 15:25:03 -0000
@@ -1576,6 +1576,7 @@
 	 */
 
 	TclInvalidateNsCmdLookup(nsPtr);
+	TclInvalidateNsPath(nsPtr);
     }
     cmdPtr = (Command *) ckalloc(sizeof(Command));
     Tcl_SetHashValue(hPtr, cmdPtr);
@@ -1700,6 +1701,7 @@
     }
 
     hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &new);
+	TclInvalidateNsPath(nsPtr);
     if (!new) {
 	cmdPtr = (Command *) Tcl_GetHashValue(hPtr);
 
@@ -1746,6 +1748,7 @@
 	 */
 
 	TclInvalidateNsCmdLookup(nsPtr);
+	TclInvalidateNsPath(nsPtr);
     }
     cmdPtr = (Command *) ckalloc(sizeof(Command));
     Tcl_SetHashValue(hPtr, cmdPtr);
Index: generic/tclCmdIL.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclCmdIL.c,v
retrieving revision 1.74
diff -u -r1.74 tclCmdIL.c
--- generic/tclCmdIL.c	10 May 2005 18:34:08 -0000	1.74
+++ generic/tclCmdIL.c	20 May 2005 15:25:03 -0000
@@ -712,6 +712,7 @@
     Tcl_Obj *listPtr, *elemObjPtr;
     int specificNsInPattern = 0;  /* Init. to avoid compiler warning. */
     Tcl_Command cmd;
+    int i;
 
     /*
      * Get the pattern and find the "effective namespace" in which to
@@ -779,16 +780,43 @@
 		elemObjPtr = Tcl_NewStringObj(cmdName, -1);
 	    }
 	    Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr);
-	} else if ((nsPtr != globalNsPtr) && !specificNsInPattern) {
-	    entryPtr = Tcl_FindHashEntry(&globalNsPtr->cmdTable,
-		    simplePattern);
+	    Tcl_SetObjResult(interp, listPtr);
+	    return TCL_OK;
+	}
+	if ((nsPtr != globalNsPtr) && !specificNsInPattern) {
+	    Tcl_HashTable *tablePtr;
+
+	    for (i=0 ; i<nsPtr->commandPathLength ; i++) {
+		Namespace *pathNsPtr = nsPtr->commandPathArray[i].nsPtr;
+
+		if (pathNsPtr == NULL) {
+		    continue;
+		}
+		tablePtr = &pathNsPtr->cmdTable;
+		entryPtr = Tcl_FindHashEntry(tablePtr, simplePattern);
+		if (entryPtr != NULL) {
+		    break;
+		}
+	    }
+	    if (entryPtr == NULL) {
+		tablePtr = &globalNsPtr->cmdTable;
+		entryPtr = Tcl_FindHashEntry(tablePtr, simplePattern);
+	    }
 	    if (entryPtr != NULL) {
-		cmdName = Tcl_GetHashKey(&globalNsPtr->cmdTable, entryPtr);
+		cmdName = Tcl_GetHashKey(tablePtr, entryPtr);
 		Tcl_ListObjAppendElement(interp, listPtr,
 			Tcl_NewStringObj(cmdName, -1));
+		Tcl_SetObjResult(interp, listPtr);
+		return TCL_OK;
 	    }
 	}
-    } else {
+    } else if (nsPtr->commandPathLength == 0 || specificNsInPattern) {
+	/*
+	 * The pattern is non-trivial, but either there is no explicit
+	 * path or there is an explicit namespace in the pattern. In
+	 * both cases, the old matching scheme is perfect.
+	 */
+
 	entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search);
 	while (entryPtr != NULL) {
 	    cmdName = Tcl_GetHashKey(&nsPtr->cmdTable, entryPtr);
@@ -828,6 +856,95 @@
 		entryPtr = Tcl_NextHashEntry(&search);
 	    }
 	}
+    } else {
+	/*
+	 * The pattern is non-trivial (can match more than one command
+	 * name), there is an explicit path, and there is no explicit
+	 * namespace in the pattern. This means that we have to
+	 * traverse the path to discover all the commands defined.
+	 */
+
+	Tcl_HashTable addedCommandsTable;
+	int isNew;
+	int foundGlobal = (nsPtr == globalNsPtr);
+
+	/*
+	 * We keep a hash of the objects already added to the result
+	 * list.
+	 */
+	Tcl_InitObjHashTable(&addedCommandsTable);
+
+	entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search);
+	while (entryPtr != NULL) {
+	    cmdName = Tcl_GetHashKey(&nsPtr->cmdTable, entryPtr);
+	    if ((simplePattern == NULL)
+		    || Tcl_StringMatch(cmdName, simplePattern)) {
+		elemObjPtr = Tcl_NewStringObj(cmdName, -1);
+		Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr);
+		(void) Tcl_CreateHashEntry(&addedCommandsTable,
+			(char *)elemObjPtr, &isNew);
+	    }
+	    entryPtr = Tcl_NextHashEntry(&search);
+	}
+
+	/*
+	 * Search the path next.
+	 */
+
+	for (i=0 ; i<nsPtr->commandPathLength ; i++) {
+	    Namespace *pathNsPtr = nsPtr->commandPathArray[i].nsPtr;
+
+	    if (pathNsPtr == NULL) {
+		continue;
+	    }
+	    if (pathNsPtr == globalNsPtr) {
+		foundGlobal = 1;
+	    }
+	    entryPtr = Tcl_FirstHashEntry(&pathNsPtr->cmdTable, &search);
+	    while (entryPtr != NULL) {
+		cmdName = Tcl_GetHashKey(&pathNsPtr->cmdTable, entryPtr);
+		if ((simplePattern == NULL)
+			|| Tcl_StringMatch(cmdName, simplePattern)) {
+		    elemObjPtr = Tcl_NewStringObj(cmdName, -1);
+		    (void) Tcl_CreateHashEntry(&addedCommandsTable,
+			    (char *) elemObjPtr, &isNew);
+		    if (isNew) {
+			Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr);
+		    } else {
+			TclDecrRefCount(elemObjPtr);
+		    }
+		}
+		entryPtr = Tcl_NextHashEntry(&search);
+	    }
+	}
+
+	/*
+	 * If the effective namespace isn't the global :: namespace, and a
+	 * specific namespace wasn't requested in the pattern, then add in
+	 * all global :: commands that match the simple pattern. Of course,
+	 * we add in only those commands that aren't hidden by a command in
+	 * the effective namespace.
+	 */
+
+	if (!foundGlobal) {
+	    entryPtr = Tcl_FirstHashEntry(&globalNsPtr->cmdTable, &search);
+	    while (entryPtr != NULL) {
+		cmdName = Tcl_GetHashKey(&globalNsPtr->cmdTable, entryPtr);
+		if ((simplePattern == NULL)
+			|| Tcl_StringMatch(cmdName, simplePattern)) {
+		    elemObjPtr = Tcl_NewStringObj(cmdName, -1);
+		    if (Tcl_FindHashEntry(&addedCommandsTable,
+			    (char *) elemObjPtr) == NULL) {
+			Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr);
+		    } else {
+			TclDecrRefCount(elemObjPtr);
+		    }
+		}
+		entryPtr = Tcl_NextHashEntry(&search);
+	    }
+	}
+
+	Tcl_DeleteHashTable(&addedCommandsTable);
     }
 
     Tcl_SetObjResult(interp, listPtr);
Index: generic/tclInt.h
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclInt.h,v
retrieving revision 1.230
diff -u -r1.230 tclInt.h
--- generic/tclInt.h	18 May 2005 15:43:36 -0000	1.230
+++ generic/tclInt.h	20 May 2005 15:25:03 -0000
@@ -131,6 +131,7 @@
  */
 
 typedef struct Tcl_Ensemble Tcl_Ensemble;
+typedef struct NamespacePathEntry NamespacePathEntry;
 
 /*
  * The structure below defines a namespace.
@@ -233,9 +234,35 @@
     Tcl_Ensemble *ensembles;	/* List of structures that contain the details
 				 * of the ensembles that are implemented on
 				 * top of this namespace. */
+    int commandPathLength;	/* The length of the explicit path. */
+    NamespacePathEntry *commandPathArray;
+				/* The explicit path of the namespace as an
+				 * array. */
+    NamespacePathEntry *commandPathSourceList;
+				/* Linked list of path entries that point to
+				 * this namespace. */
 } Namespace;
 
 /*
+ * An entry on a namespace's command resolution path.
+ */
+
+struct NamespacePathEntry {
+    Namespace *nsPtr;		/* What does this path entry point to? If it
+				 *is NULL, this path entry points is redundant
+				 * and should be skipped. */
+    Namespace *creatorNsPtr;	/* Where does this path entry point from? This
+				 * allows for efficient invalidation of
+				 * references when the path entry's target
+				 * updates its current list of defined
+				 * commands. */
+    NamespacePathEntry *prevPtr, *nextPtr;
+				/* Linked list pointers or NULL at either end
+				 * of the list that hangs off Namespaces'
+				 * commandPathSourceList field. */
+};
+
+/*
  * Flags used to represent the status of a namespace:
  *
  * NS_DYING -	1 means Tcl_DeleteNamespace has been called to delete the
@@ -2434,6 +2461,7 @@
 			    Var *varPtr, Var *arrayPtr, CONST char *part1,
 			    CONST char *part2, CONST Tcl_WideInt i,
 			    CONST int flags));
+MODULE_SCOPE void	TclInvalidateNsPath _ANSI_ARGS_((Namespace *nsPtr));
 
 /*
  *----------------------------------------------------------------
Index: generic/tclNamesp.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclNamesp.c,v
retrieving revision 1.75
diff -u -r1.75 tclNamesp.c
--- generic/tclNamesp.c	19 May 2005 22:49:01 -0000	1.75
+++ generic/tclNamesp.c	20 May 2005 15:25:04 -0000
@@ -242,6 +242,9 @@
 static int		NamespaceParentCmd _ANSI_ARGS_((
 			    ClientData dummy, Tcl_Interp *interp,
 			    int objc, Tcl_Obj *CONST objv[]));
+static int		NamespacePathCmd _ANSI_ARGS_((
+			    ClientData dummy, Tcl_Interp *interp,
+			    int objc, Tcl_Obj *CONST objv[]));
 static int		NamespaceQualifiersCmd _ANSI_ARGS_((
 			    ClientData dummy, Tcl_Interp *interp,
 			    int objc, Tcl_Obj *CONST objv[]));
@@ -270,6 +273,9 @@
 static void		DupEnsembleCmdRep _ANSI_ARGS_((Tcl_Obj *objPtr,
 			    Tcl_Obj *copyPtr));
 static void		StringOfEnsembleCmdRep _ANSI_ARGS_((Tcl_Obj *objPtr));
+static void		UnlinkNsPath _ANSI_ARGS_((Namespace *nsPtr));
+static void		SetNsPath _ANSI_ARGS_((Namespace *nsPtr,
+			    int pathLength, Tcl_Namespace *pathAry[]));
 
 /*
  * This structure defines a Tcl object type that contains a
@@ -844,6 +850,9 @@
     nsPtr->compiledVarResProc = NULL;
     nsPtr->exportLookupEpoch = 0;
     nsPtr->ensembles = NULL;
+    nsPtr->commandPathLength = 0;
+    nsPtr->commandPathArray = NULL;
+    nsPtr->commandPathSourceList = NULL;
 
     if (parentPtr != NULL) {
 	entryPtr = Tcl_CreateHashEntry(&parentPtr->childTable, simpleName,
@@ -1065,6 +1074,22 @@
     nsPtr->parentPtr = NULL;
 
     /*
+     * Delete the namespace path if one is installed.
+     */
+
+    if (nsPtr->commandPathLength != 0) {
+	UnlinkNsPath(nsPtr);
+	nsPtr->commandPathLength = 0;
+    }
+    if (nsPtr->commandPathSourceList != NULL) {
+	NamespacePathEntry *nsPathPtr = nsPtr->commandPathSourceList;
+	do {
+	    nsPathPtr->nsPtr = NULL;
+	    nsPathPtr = nsPathPtr->nextPtr;
+	} while (nsPathPtr != NULL);
+    }
+
+    /*
      * Delete all the child namespaces.
      *
      * BE CAREFUL: When each child is deleted, it will divorce
@@ -2309,15 +2334,11 @@
 				  * ignored. */
 {
     Interp *iPtr = (Interp*)interp;
-
-    ResolverScheme *resPtr;
-    Namespace *nsPtr[2], *cxtNsPtr;
-    CONST char *simpleName;
+    Namespace *cxtNsPtr;
     register Tcl_HashEntry *entryPtr;
     register Command *cmdPtr;
-    register int search;
+    CONST char *simpleName;
     int result;
-    Tcl_Command cmd;
 
     /*
      * If this namespace has a command resolver, then give it first
@@ -2326,7 +2347,7 @@
      * procedures may return a Tcl_Command value, they may signal
      * to continue onward, or they may signal an error.
      */
-    if ((flags & TCL_GLOBAL_ONLY) != 0) {
+    if (flags & TCL_GLOBAL_ONLY) {
 	cxtNsPtr = (Namespace *) Tcl_GetGlobalNamespace(interp);
     } else if (contextNsPtr != NULL) {
 	cxtNsPtr = (Namespace *) contextNsPtr;
@@ -2335,7 +2356,8 @@
     }
 
     if (cxtNsPtr->cmdResProc != NULL || iPtr->resolverPtr != NULL) {
-	resPtr = iPtr->resolverPtr;
+	ResolverScheme *resPtr = iPtr->resolverPtr;
+	Tcl_Command cmd;
 
 	if (cxtNsPtr->cmdResProc) {
 	    result = (*cxtNsPtr->cmdResProc)(interp, name,
@@ -2363,33 +2385,90 @@
      * Find the namespace(s) that contain the command.
      */
 
-    TclGetNamespaceForQualName(interp, name, (Namespace *) contextNsPtr,
-	    flags, &nsPtr[0], &nsPtr[1], &cxtNsPtr, &simpleName);
-
-    /*
-     * Look for the command in the command table of its namespace.
-     * Be sure to check both possible search paths: from the specified
-     * namespace context and from the global namespace.
-     */
-
     cmdPtr = NULL;
-    for (search = 0;  (search < 2) && (cmdPtr == NULL);  search++) {
-	if ((nsPtr[search] != NULL) && (simpleName != NULL)) {
-	    entryPtr = Tcl_FindHashEntry(&nsPtr[search]->cmdTable,
-		    simpleName);
+    if (cxtNsPtr->commandPathLength!=0 && strncmp(name, "::", 2)) {
+	int i;
+	Namespace *pathNsPtr, *realNsPtr, *dummyNsPtr;
+
+	(void) TclGetNamespaceForQualName(interp, name, cxtNsPtr,
+		TCL_NAMESPACE_ONLY, &realNsPtr, &dummyNsPtr, &dummyNsPtr,
+		&simpleName);
+	if (realNsPtr != NULL && simpleName != NULL) {
+	    entryPtr = Tcl_FindHashEntry(&realNsPtr->cmdTable, simpleName);
 	    if (entryPtr != NULL) {
 		cmdPtr = (Command *) Tcl_GetHashValue(entryPtr);
 	    }
 	}
+
+	/*
+	 * Next, check along the path.
+	 */
+
+	for (i=0 ; i<cxtNsPtr->commandPathLength && cmdPtr==NULL ; i++) {
+	    pathNsPtr = cxtNsPtr->commandPathArray[i].nsPtr;
+	    if (pathNsPtr == NULL) {
+		continue;
+	    }
+	    (void) TclGetNamespaceForQualName(interp, name, pathNsPtr,
+		    TCL_NAMESPACE_ONLY, &realNsPtr, &dummyNsPtr, &dummyNsPtr,
+		    &simpleName);
+	    if (realNsPtr != NULL && simpleName != NULL) {
+		entryPtr = Tcl_FindHashEntry(&realNsPtr->cmdTable, simpleName);
+		if (entryPtr != NULL) {
+		    cmdPtr = (Command *) Tcl_GetHashValue(entryPtr);
+		}
+	    }
+	}
+
+	/*
+	 * If we've still not found the command, look in the global
+	 * namespace as a last resort.
+	 */
+
+	if (cmdPtr == NULL) {
+	    (void) TclGetNamespaceForQualName(interp, name, NULL,
+		    TCL_GLOBAL_ONLY, &realNsPtr, &dummyNsPtr, &dummyNsPtr,
+		    &simpleName);
+	    if (realNsPtr != NULL && simpleName != NULL) {
+		entryPtr = Tcl_FindHashEntry(&realNsPtr->cmdTable, simpleName);
+		if (entryPtr != NULL) {
+		    cmdPtr = (Command *) Tcl_GetHashValue(entryPtr);
+		}
+	    }
+	}
+    } else {
+	Namespace *nsPtr[2];
+	register int search;
+
+	TclGetNamespaceForQualName(interp, name, (Namespace *) contextNsPtr,
+		flags, &nsPtr[0], &nsPtr[1], &cxtNsPtr, &simpleName);
+
+	/*
+	 * Look for the command in the command table of its namespace.
+	 * Be sure to check both possible search paths: from the
+	 * specified namespace context and from the global namespace.
+	 */
+
+	for (search = 0;  (search < 2) && (cmdPtr == NULL);  search++) {
+	    if ((nsPtr[search] != NULL) && (simpleName != NULL)) {
+		entryPtr = Tcl_FindHashEntry(&nsPtr[search]->cmdTable,
+			simpleName);
+		if (entryPtr != NULL) {
+		    cmdPtr = (Command *) Tcl_GetHashValue(entryPtr);
+		}
+	    }
+	}
     }
+
     if (cmdPtr != NULL) {
 	return (Tcl_Command) cmdPtr;
-    } else if (flags & TCL_LEAVE_ERR_MSG) {
+    }
+
+    if (flags & TCL_LEAVE_ERR_MSG) {
 	Tcl_ResetResult(interp);
 	Tcl_AppendResult(interp, "unknown command \"", name,
 		"\"", (char *) NULL);
     }
-
     return (Tcl_Command) NULL;
 }
 
@@ -2628,6 +2707,7 @@
 	    hPtr = Tcl_FindHashEntry(&shadowNsPtr->cmdTable, cmdName);
 	    if (hPtr != NULL) {
 		nsPtr->cmdRefEpoch++;
+		TclInvalidateNsPath(nsPtr);
 
 		/* 
 		 * If the shadowed command was compiled to bytecodes, we
@@ -2831,13 +2911,13 @@
     static CONST char *subCmds[] = {
 	"children", "code", "current", "delete", "ensemble",
 	"eval", "exists", "export", "forget", "import",
-	"inscope", "origin", "parent", "qualifiers",
+	"inscope", "origin", "parent", "path", "qualifiers",
 	"tail", "which", (char *) NULL
     };
     enum NSSubCmdIdx {
 	NSChildrenIdx, NSCodeIdx, NSCurrentIdx, NSDeleteIdx, NSEnsembleIdx,
 	NSEvalIdx, NSExistsIdx, NSExportIdx, NSForgetIdx, NSImportIdx,
-	NSInscopeIdx, NSOriginIdx, NSParentIdx, NSQualifiersIdx,
+	NSInscopeIdx, NSOriginIdx, NSParentIdx, NSPathIdx, NSQualifiersIdx,
 	NSTailIdx, NSWhichIdx
     };
     int index, result;
@@ -2897,6 +2977,9 @@
     case NSParentIdx:
 	result = NamespaceParentCmd(clientData, interp, objc, objv);
 	break;
+    case NSPathIdx:
+	result = NamespacePathCmd(clientData, interp, objc, objv);
+	break;
     case NSQualifiersIdx:
 	result = NamespaceQualifiersCmd(clientData, interp, objc, objv);
 	break;
@@ -3906,6 +3989,225 @@
 /*
  *----------------------------------------------------------------------
  *
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+NamespacePathCmd(dummy, interp, objc, objv)
+    ClientData dummy;		/* Not used. */
+    Tcl_Interp *interp;		/* Current interpreter. */
+    int objc;			/* Number of arguments. */
+    Tcl_Obj *CONST objv[];	/* Argument objects. */
+{
+    Namespace *nsPtr = (Namespace *) Tcl_GetCurrentNamespace(interp);
+    int i, nsObjc, result = TCL_ERROR;
+    Tcl_Obj **nsObjv;
+    Tcl_Namespace **namespaceList = NULL;
+    Tcl_Namespace *staticNs[4];
+
+    if (objc > 3) {
+	Tcl_WrongNumArgs(interp, 2, objv, "?pathList?");
+	return TCL_ERROR;
+    }
+
+    /*
+     * If no path is given, return the current path.
+     */
+
+    if (objc == 2) {
+	/*
+	 * Not a very fast way to compute this, but easy to get right.
+	 */
+	for (i=0 ; i<nsPtr->commandPathLength ; i++) {
+	    if (nsPtr->commandPathArray[i].nsPtr != NULL) {
+		Tcl_AppendElement(interp,
+			nsPtr->commandPathArray[i].nsPtr->fullName);
+	    }
+	}
+	return TCL_OK;
+    }
+
+    /*
+     * There is a path given, so parse it into an array of namespace
+     * pointers.
+     */
+
+    if (Tcl_ListObjGetElements(interp, objv[2], &nsObjc, &nsObjv) != TCL_OK) {
+	goto badNamespace;
+    }
+    if (nsObjc != 0) {
+	if (nsObjc > 4) {
+	    namespaceList = (Tcl_Namespace **)
+		    ckalloc(sizeof(Tcl_Namespace *) * nsObjc);
+	} else {
+	    namespaceList = staticNs;
+	}
+
+	for (i=0 ; i<nsObjc ; i++) {
+	    if (TclGetNamespaceFromObj(interp, nsObjv[i],
+		    &namespaceList[i]) != TCL_OK) {
+		goto badNamespace;
+	    }
+	    if (namespaceList[i] == NULL) {
+		Tcl_AppendResult(interp, "unknown namespace \"",
+			TclGetString(nsObjv[i]), "\"", NULL);
+		goto badNamespace;
+	    }
+	}
+    }
+
+    /*
+     * Now we have the list of valid namespaces, install it as the
+     * path.
+     */
+
+    SetNsPath(nsPtr, nsObjc, namespaceList);
+
+    result = TCL_OK;
+  badNamespace:
+    if (namespaceList != NULL && namespaceList != staticNs) {
+	ckfree((char *) namespaceList);
+    }
+    return result;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SetNsPath --
+ *
+ *	Sets the namespace command name resolution path to the given
+ *	list of namespaces. If the list is empty (of zero length) the
+ *	path is set to empty and the default old-style behaviour of
+ *	command name resolution is used.
+ *
+ * Results:
+ *	nothing
+ *
+ * Side effects:
+ *	Invalidates the command name resolution caches for any command
+ *	resolved in the given namespace.
+ *
+ *----------------------------------------------------------------------
+ */
+
+/* EXPOSE ME? */
+static void
+SetNsPath(nsPtr, pathLength, pathAry)
+    Namespace *nsPtr;		/* Namespace whose path is to be set. */
+    int pathLength;		/* Length of pathAry */
+    Tcl_Namespace *pathAry[];	/* Array of namespaces that are the path. */
+{
+    NamespacePathEntry *tmpPathArray;
+    int i;
+
+    if (pathLength != 0) {
+	tmpPathArray = (NamespacePathEntry *)
+		ckalloc(sizeof(NamespacePathEntry) * pathLength);
+	for (i=0 ; i<pathLength ; i++) {
+	    tmpPathArray[i].nsPtr = (Namespace *) pathAry[i];
+	    tmpPathArray[i].creatorNsPtr = nsPtr;
+	    tmpPathArray[i].prevPtr = NULL;
+	    tmpPathArray[i].nextPtr =
+		    tmpPathArray[i].nsPtr->commandPathSourceList;
+	    if (tmpPathArray[i].nextPtr != NULL) {
+		tmpPathArray[i].nextPtr->prevPtr = &tmpPathArray[i];
+	    }
+	    tmpPathArray[i].nsPtr->commandPathSourceList = &tmpPathArray[i];
+	}
+	if (nsPtr->commandPathLength != 0) {
+	    UnlinkNsPath(nsPtr);
+	}
+	nsPtr->commandPathArray = tmpPathArray;
+    } else {
+	if (nsPtr->commandPathLength != 0) {
+	    UnlinkNsPath(nsPtr);
+	}
+    }
+
+    nsPtr->commandPathLength = pathLength;
+    nsPtr->cmdRefEpoch++;
+    nsPtr->resolverEpoch++;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UnlinkNsPath --
+ *
+ *	Delete the given namespace's command name resolution path. Only
+ *	call if the path is non-empty. Caller must reset the counter
+ *	containing the path size.
+ *
+ * Results:
+ *	nothing
+ *
+ * Side effects:
+ *	Deletes the array of path entries and unlinks those path entries
+ *	from the target namespace's list of interested namespaces.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+UnlinkNsPath(nsPtr)
+    Namespace *nsPtr;
+{
+    int i;
+    for (i=0 ; i<nsPtr->commandPathLength ; i++) {
+	NamespacePathEntry *nsPathPtr = &nsPtr->commandPathArray[i];
+	if (nsPathPtr->prevPtr != NULL) {
+	    nsPathPtr->prevPtr->nextPtr = nsPathPtr->nextPtr;
+	}
+	if (nsPathPtr->nextPtr != NULL) {
+	    nsPathPtr->nextPtr->prevPtr = nsPathPtr->prevPtr;
+	}
+	if (nsPathPtr->nsPtr != NULL) {
+	    if (nsPathPtr->nsPtr->commandPathSourceList == nsPathPtr) {
+		nsPathPtr->nsPtr->commandPathSourceList = nsPathPtr->nextPtr;
+	    }
+	}
+    }
+    ckfree((char *) nsPtr->commandPathArray);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclInvalidateNsPath --
+ *
+ *	Invalidate the name resolution caches for all names looked up
+ *	in namespaces whose name path includes the given namespace.
+ *
+ * Results:
+ *	nothing
+ *
+ * Side effects:
+ *	Increments the command reference epoch in each namespace whose
+ *	path includes the given namespace. This causes any cached
+ *	resolved names whose root cacheing context starts at that
+ *	namespace to be recomputed the next time they are used.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclInvalidateNsPath(nsPtr)
+    Namespace *nsPtr;
+{
+    NamespacePathEntry *nsPathPtr = nsPtr->commandPathSourceList;
+    while (nsPathPtr != NULL) {
+	if (nsPathPtr->nsPtr != NULL) {
+	    nsPathPtr->creatorNsPtr->cmdRefEpoch++;
+	}
+	nsPathPtr = nsPathPtr->nextPtr;
+    }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
  * NamespaceQualifiersCmd --
  *
  *	Invoked to implement the "namespace qualifiers" command that returns
Index: generic/tclResolve.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclResolve.c,v
retrieving revision 1.4
diff -u -r1.4 tclResolve.c
--- generic/tclResolve.c	25 Jan 2002 22:01:32 -0000	1.4
+++ generic/tclResolve.c	20 May 2005 15:25:04 -0000
@@ -273,6 +273,7 @@
         childNsPtr = (Namespace *) Tcl_GetHashValue(entry);
         BumpCmdRefEpochs(childNsPtr);
     }
+    TclInvalidateNsPath(nsPtr);
 }
 
 
@@ -369,6 +370,7 @@
 
     nsPtr->cmdRefEpoch++;
     nsPtr->resolverEpoch++;
+    TclInvalidateNsPath(nsPtr);
 }
 
 /*
Index: tests/namespace.test
===================================================================
RCS file: /cvsroot/tcl/tcl/tests/namespace.test,v
retrieving revision 1.43
diff -u -r1.43 namespace.test
--- tests/namespace.test	29 Oct 2004 15:39:10 -0000	1.43
+++ tests/namespace.test	20 May 2005 15:25:07 -0000
@@ -838,7 +838,7 @@
 } {1 {wrong # args: should be "namespace subcommand ?arg ...?"}}
 test namespace-20.2 {Tcl_NamespaceObjCmd, bad subcommand} {
     list [catch {namespace wombat {}} msg] $msg
-} {1 {bad option "wombat": must be children, code, current, delete, ensemble, eval, exists, export, forget, import, inscope, origin, parent, qualifiers, tail, or which}}
+} {1 {bad option "wombat": must be children, code, current, delete, ensemble, eval, exists, export, forget, import, inscope, origin, parent, path, qualifiers, tail, or which}}
 test namespace-20.3 {Tcl_NamespaceObjCmd, abbreviations are okay} {
     namespace ch :: test_ns_*
 } {}
@@ -945,7 +945,7 @@
 } {1 {wrong # args: should be "namespace eval name arg ?arg...?"}}
 test namespace-25.2 {NamespaceEvalCmd, bad args} {
     list [catch {namespace test_ns_1} msg] $msg
-} {1 {bad option "test_ns_1": must be children, code, current, delete, ensemble, eval, exists, export, forget, import, inscope, origin, parent, qualifiers, tail, or which}}
+} {1 {bad option "test_ns_1": must be children, code, current, delete, ensemble, eval, exists, export, forget, import, inscope, origin, parent, path, qualifiers, tail, or which}}
 catch {unset v}
 test namespace-25.3 {NamespaceEvalCmd, new namespace} {
     set v 123
@@ -1956,6 +1956,389 @@
     rename a {}
 }
 
+test namespace-51.1 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+	namespace eval test_ns_2 {
+	    proc pathtestA {} {
+		::return [pathtestB],[pathtestC],[pathtestD],[namespace path]
+	    }
+	    proc pathtestC {} {
+		::return 2
+	    }
+	}
+	proc pathtestB {} {
+	    return 1
+	}
+	proc pathtestC {} {
+	    return 1
+	}
+	namespace path ::test_ns_1
+    }
+    proc ::pathtestB {} {
+	return global
+    }
+    proc ::pathtestD {} {
+	return global
+    }
+    test_ns_1::test_ns_2::pathtestA
+} -result "global,2,global," -cleanup {
+    namespace delete ::test_ns_1
+    catch {rename ::pathtestB {}}
+    catch {rename ::pathtestD {}}
+}
+test namespace-51.2 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+	namespace eval test_ns_2 {
+	    namespace path ::test_ns_1
+	    proc pathtestA {} {
+		::return [pathtestB],[pathtestC],[pathtestD],[namespace path]
+	    }
+	    proc pathtestC {} {
+		::return 2
+	    }
+	}
+	proc pathtestB {} {
+	    return 1
+	}
+	proc pathtestC {} {
+	    return 1
+	}
+    }
+    proc ::pathtestB {} {
+	return global
+    }
+    proc ::pathtestD {} {
+	return global
+    }
+    ::test_ns_1::test_ns_2::pathtestA
+} -result "1,2,global,::test_ns_1" -cleanup {
+    namespace delete ::test_ns_1
+    catch {rename ::pathtestB {}}
+    catch {rename ::pathtestD {}}
+}
+test namespace-51.3 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+	namespace eval test_ns_2 {
+	    proc pathtestA {} {
+		::return [pathtestB],[pathtestC],[pathtestD],[namespace path]
+	    }
+	    proc pathtestC {} {
+		::return 2
+	    }
+	}
+	proc pathtestB {} {
+	    return 1
+	}
+	proc pathtestC {} {
+	    return 1
+	}
+    }
+    proc ::pathtestB {} {
+	return global
+    }
+    proc ::pathtestD {} {
+	return global
+    }
+    set result [::test_ns_1::test_ns_2::pathtestA]
+    namespace eval ::test_ns_1::test_ns_2 {
+	namespace path ::test_ns_1
+    }
+    lappend result [::test_ns_1::test_ns_2::pathtestA]
+    rename ::test_ns_1::pathtestB {}
+    lappend result [::test_ns_1::test_ns_2::pathtestA]
+} -result "global,2,global, 1,2,global,::test_ns_1 global,2,global,::test_ns_1" -cleanup {
+    namespace delete ::test_ns_1
+    catch {rename ::pathtestB {}}
+    catch {rename ::pathtestD {}}
+}
+test namespace-51.4 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+	namespace eval test_ns_2 {
+	    proc pathtestA {} {
+		::return [pathtestB],[pathtestC],[pathtestD],[namespace path]
+	    }
+	    proc pathtestC {} {
+		::return 2
+	    }
+	}
+	proc pathtestB {} {
+	    return 1
+	}
+	proc pathtestC {} {
+	    return 1
+	}
+    }
+    proc ::pathtestB {} {
+	return global
+    }
+    proc ::pathtestD {} {
+	return global
+    }
+    set result [::test_ns_1::test_ns_2::pathtestA]
+    namespace eval ::test_ns_1::test_ns_2 {
+	namespace path ::test_ns_1
+    }
+    lappend result [::test_ns_1::test_ns_2::pathtestA]
+    namespace eval ::test_ns_1::test_ns_2 {
+	namespace path {}
+    }
+    lappend result [::test_ns_1::test_ns_2::pathtestA]
+} -result "global,2,global, 1,2,global,::test_ns_1 global,2,global," -cleanup {
+    namespace delete ::test_ns_1
+    catch {rename ::pathtestB {}}
+    catch {rename ::pathtestD {}}
+}
+test namespace-51.5 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+	namespace eval test_ns_2 {
+	    proc pathtestA {} {
+		::return [pathtestB],[pathtestC],[pathtestD],[namespace path]
+	    }
+	    proc pathtestC {} {
+		::return 2
+	    }
+	    namespace path ::test_ns_1
+	}
+	proc pathtestB {} {
+	    return 1
+	}
+	proc pathtestC {} {
+	    return 1
+	}
+	proc pathtestD {} {
+	    return 1
+	}
+    }
+    proc ::pathtestB {} {
+	return global
+    }
+    proc ::pathtestD {} {
+	return global
+    }
+    set result [::test_ns_1::test_ns_2::pathtestA]
+    namespace eval ::test_ns_1::test_ns_2 {
+	namespace path {:: ::test_ns_1}
+    }
+    lappend result [::test_ns_1::test_ns_2::pathtestA]
+    rename ::test_ns_1::test_ns_2::pathtestC {}
+    lappend result [::test_ns_1::test_ns_2::pathtestA]
+} -result "1,2,1,::test_ns_1 {global,2,global,:: ::test_ns_1} {global,1,global,:: ::test_ns_1}" -cleanup {
+    namespace delete ::test_ns_1
+    catch {rename ::pathtestB {}}
+    catch {rename ::pathtestD {}}
+}
+test namespace-51.6 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+	namespace eval test_ns_2 {
+	    proc pathtestA {} {
+		::return [pathtestB],[pathtestC],[pathtestD],[namespace path]
+	    }
+	    proc pathtestC {} {
+		::return 2
+	    }
+	    namespace path ::test_ns_1
+	}
+	proc pathtestB {} {
+	    return 1
+	}
+	proc pathtestC {} {
+	    return 1
+	}
+	proc pathtestD {} {
+	    return 1
+	}
+    }
+    proc ::pathtestB {} {
+	return global
+    }
+    proc ::pathtestD {} {
+	return global
+    }
+    set result [::test_ns_1::test_ns_2::pathtestA]
+    namespace eval ::test_ns_1::test_ns_2 {
+	namespace path {:: ::test_ns_1}
+    }
+    lappend result [::test_ns_1::test_ns_2::pathtestA]
+    rename ::test_ns_1::test_ns_2::pathtestC {}
+    lappend result [::test_ns_1::test_ns_2::pathtestA]
+    proc ::pathtestC {} {
+	return global
+    }
+    lappend result [::test_ns_1::test_ns_2::pathtestA]
+} -result "1,2,1,::test_ns_1 {global,2,global,:: ::test_ns_1} {global,1,global,:: ::test_ns_1} {global,global,global,:: ::test_ns_1}" -cleanup {
+    namespace delete ::test_ns_1
+    catch {rename ::pathtestB {}}
+    catch {rename ::pathtestD {}}
+}
+test namespace-51.7 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+    }
+    namespace eval ::test_ns_2 {
+	namespace path ::test_ns_1
+	proc getpath {} {namespace path}
+    }
+    list [::test_ns_2::getpath] [namespace delete ::test_ns_1] [::test_ns_2::getpath]
+} -result {::test_ns_1 {} {}} -cleanup {
+    catch {namespace delete ::test_ns_1}
+    namespace delete ::test_ns_2
+}
+test namespace-51.8 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+    }
+    namespace eval ::test_ns_2 {
+    }
+    namespace eval ::test_ns_3 {
+    }
+    namespace eval ::test_ns_4 {
+	namespace path {::test_ns_1 ::test_ns_2 ::test_ns_3}
+	proc getpath {} {namespace path}
+    }
+    list [::test_ns_4::getpath] [namespace delete ::test_ns_2] [::test_ns_4::getpath]
+} -result {{::test_ns_1 ::test_ns_2 ::test_ns_3} {} {::test_ns_1 ::test_ns_3}} -cleanup {
+    catch {namespace delete ::test_ns_1}
+    catch {namespace delete ::test_ns_2}
+    catch {namespace delete ::test_ns_3}
+    catch {namespace delete ::test_ns_4}
+}
+test namespace-51.9 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+    }
+    namespace eval ::test_ns_2 {
+    }
+    namespace eval ::test_ns_3 {
+    }
+    namespace eval ::test_ns_4 {
+	namespace path {::test_ns_1 ::test_ns_2 ::test_ns_3}
+	proc getpath {} {namespace path}
+    }
+    list [::test_ns_4::getpath] [namespace delete ::test_ns_2] [namespace eval ::test_ns_2 {}] [::test_ns_4::getpath]
+} -result {{::test_ns_1 ::test_ns_2 ::test_ns_3} {} {} {::test_ns_1 ::test_ns_3}} -cleanup {
+    catch {namespace delete ::test_ns_1}
+    catch {namespace delete ::test_ns_2}
+    catch {namespace delete ::test_ns_3}
+    catch {namespace delete ::test_ns_4}
+}
+test namespace-51.10 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+	namespace path does::not::exist
+    }
+} -returnCodes error -result {unknown namespace "does::not::exist"} -cleanup {
+    catch {namespace delete ::test_ns_1}
+}
+test namespace-51.11 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+	proc foo {} {return 1}
+    }
+    namespace eval ::test_ns_2 {
+	proc foo {} {return 2}
+    }
+    namespace eval ::test_ns_3 {
+	namespace path ::test_ns_1
+    }
+    namespace eval ::test_ns_4 {
+	namespace path {::test_ns_3 ::test_ns_2}
+	foo
+    }
+} -result 2 -cleanup {
+    catch {namespace delete ::test_ns_1}
+    catch {namespace delete ::test_ns_2}
+    catch {namespace delete ::test_ns_3}
+    catch {namespace delete ::test_ns_4}
+}
+test namespace-51.12 {name resolution path control} -body {
+    namespace eval ::test_ns_1 {
+	proc foo {} {return 1}
+    }
+    namespace eval ::test_ns_2 {
+	proc foo {} {return 2}
+    }
+    namespace eval ::test_ns_3 {
+	namespace path ::test_ns_1
+    }
+    namespace eval ::test_ns_4 {
+	namespace path {::test_ns_3 ::test_ns_2}
+	list [foo] [namespace delete ::test_ns_3] [foo]
+    }
+} -result {2 {} 2} -cleanup {
+    catch {namespace delete ::test_ns_1}
+    catch {namespace delete ::test_ns_2}
+    catch {namespace delete ::test_ns_3}
+    catch {namespace delete ::test_ns_4}
+}
+# Fails right now due to unrelated bug...
+test namespace-51.13 {name resolution path control} -constraints knownBug -body {
+    set ::result {}
+    namespace eval ::test_ns_1 {
+	proc foo {} {lappend ::result 1}
+    }
+    namespace eval ::test_ns_2 {
+	proc foo {} {lappend ::result 2}
+	trace add command foo delete {namespace eval ::test_ns_3 foo;#}
+    }
+    namespace eval ::test_ns_3 {
+	proc foo {} {
+	    lappend ::result 3
+	    namespace delete [namespace current]
+	    ::test_ns_4::bar
+	}
+    }
+    namespace eval ::test_ns_4 {
+	namespace path {::test_ns_2 ::test_ns_3 ::test_ns_1}
+	proc bar {} {
+	    list [foo] [namespace delete ::test_ns_2] [foo]
+	}
+	bar
+    }
+    # Should the result be "2 {} {2 3 1 1}" instead?
+} -result {2 {} {2 3 2 1}} -cleanup {
+    catch {namespace delete ::test_ns_1}
+    catch {namespace delete ::test_ns_2}
+    catch {namespace delete ::test_ns_3}
+    catch {namespace delete ::test_ns_4}
+}
+test namespace-51.14 {name resolution path control} -body {
+    proc foo0 {} {}
+    namespace eval ::test_ns_1 {
+	proc foo1 {} {}
+    }
+    namespace eval ::test_ns_2 {
+	proc foo2 {} {}
+    }
+    namespace eval ::test_ns_3 {
+	variable result {}
+	lappend result [info commands foo*]
+	namespace path {::test_ns_1 ::test_ns_2}
+	lappend result [info commands foo*]
+	proc foo2 {} {}
+	lappend result [info commands foo*]
+	rename foo2 {}
+	lappend result [info commands foo*]
+	namespace delete ::test_ns_1
+	lappend result [info commands foo*]
+    }
+} -result {foo0 {foo1 foo2 foo0} {foo2 foo1 foo0} {foo1 foo2 foo0} {foo2 foo0}} -cleanup {
+    catch {namespace delete ::test_ns_1}
+    catch {namespace delete ::test_ns_2}
+    catch {namespace delete ::test_ns_3}
+}
+test namespace-51.15 {namespace resolution path control} -body {
+    namespace eval ::test_ns_2 {
+	proc foo {} {return 2}
+    }
+    namespace eval ::test_ns_1 {
+	namespace eval test_ns_2 {
+	    proc foo {} {return 1_2}
+	}
+	namespace eval test_ns_3 {
+	    namespace path ::test_ns_1
+	    test_ns_2::foo
+	}
+    }
+} -result 1_2 -cleanup {
+    namespace delete ::test_ns_1
+    namespace delete ::test_ns_2
+}
+
 # cleanup
 catch {rename cmd1 {}}
 catch {unset l}