diff --git a/src/Analysis/Core/Impl/DependencyResolution/PathResolver.cs b/src/Analysis/Core/Impl/DependencyResolution/PathResolver.cs index 3a1b59bfa..7c189c6f7 100644 --- a/src/Analysis/Core/Impl/DependencyResolution/PathResolver.cs +++ b/src/Analysis/Core/Impl/DependencyResolution/PathResolver.cs @@ -20,6 +20,9 @@ namespace Microsoft.Python.Analysis.Core.DependencyResolution { public sealed class PathResolver { public PathResolver(in PythonLanguageVersion pythonLanguageVersion, in string root, in ImmutableArray interpreterSearchPaths, in ImmutableArray userSearchPaths) { + // need to audit whether CurrentSnapshot ever get updated concurrently + // thread safety is probably fine but serialization is another issue where if 2 callers do TryAddModulePath or other calls concurrently, + // they will use same CurrentSnapshot to update which ends up with 2 different snapshots with same version. CurrentSnapshot = new PathResolverSnapshot(pythonLanguageVersion, root, interpreterSearchPaths, userSearchPaths); } diff --git a/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.Edge.cs b/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.Edge.cs index 10847d27d..b76dbf127 100644 --- a/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.Edge.cs +++ b/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.Edge.cs @@ -35,55 +35,69 @@ private readonly struct Edge { // ┌─╚═╗ // [x] [y] // - // will be stored as list of pairs: (2, a), (0, i), (1, y) + // will be stored as list of pairs: (2, c), (0, i), (1, y) // // To provide immutability, it must be changed only by calling List.Add - private readonly ImmutableArray<(int index, Node node)> _vertices; - private readonly int _index; + private readonly ImmutableArray<(int nodeIndexInParent, Node node)> _pathFromRoot; - public Node Start => IsFirst ? default : _vertices[_index - 1].node; - public int EndIndex => _vertices[_index].index; - public Node End => _vertices[_index].node; - public Edge Previous => IsFirst ? default : new Edge(_vertices, _index - 1); - public Edge Next => IsLast ? default : new Edge(_vertices, _index + 1); - public int PathLength => _vertices.Count; - public bool IsFirst => _index == 0; - public bool IsNonRooted => _vertices.Count > 0 && _vertices[0].node.Name == "*"; - public bool IsEmpty => _vertices.Count == 0; - private bool IsLast => _index == _vertices.Count - 1; + // indicates which entry in the _pathFromRoot correspond to this edge. + private readonly int _indexInPath; + + public Node ParentNode => IsFirst ? default : _pathFromRoot[_indexInPath - 1].node; + public int NodeIndexInParent => _pathFromRoot[_indexInPath].nodeIndexInParent; + public Node Node => _pathFromRoot[_indexInPath].node; + public Edge Previous => IsFirst ? default : new Edge(_pathFromRoot, _indexInPath - 1); + public Edge Next => IsLast ? default : new Edge(_pathFromRoot, _indexInPath + 1); + public int PathLength => _pathFromRoot.Count; + public bool IsFirst => _indexInPath == 0; + public bool IsNonRooted => _pathFromRoot.Count > 0 && _pathFromRoot[0].node.Name == "*"; + public bool IsEmpty => _pathFromRoot.Count == 0; + private bool IsLast => _indexInPath == _pathFromRoot.Count - 1; public Edge(int startIndex, Node start) { - _vertices = ImmutableArray<(int, Node)>.Empty.Add((startIndex, start)); - _index = 0; + _pathFromRoot = ImmutableArray<(int, Node)>.Empty.Add((startIndex, start)); + _indexInPath = 0; } - public Edge FirstEdge => new Edge(_vertices, 0); - public Edge GetPrevious(int count) => count > _index ? default : new Edge(_vertices, _index - count); - public Edge GetNext(int count) => _index + count > _vertices.Count - 1 ? default : new Edge(_vertices, _index + count); + public Edge FirstEdge => new Edge(_pathFromRoot, indexInVertices: 0); + public Edge GetPrevious(int count) => count > _indexInPath ? default : new Edge(_pathFromRoot, _indexInPath - count); + public Edge GetNext(int count) => _indexInPath + count > _pathFromRoot.Count - 1 ? default : new Edge(_pathFromRoot, _indexInPath + count); + + private Edge(ImmutableArray<(int index, Node node)> vertices, int indexInVertices) { + // Node has only down pointers for the immutable tree so that when tree needs to be updated + // it can update only spine of the tree and reuse all other tree nodes. basically making + // the tree incrementally updatable. + // but not having Parent pointer makes using the tree data structure hard. so + // Edge tracks the spine (provide Back/Parent pointer) but only created on demand for + // a specific node requested. + // + // concept is similar to green-red tree. + // see https://blog.yaakov.online/red-green-trees/ + // https://blogs.msdn.microsoft.com/ericlippert/2012/06/08/persistence-facades-and-roslyns-red-green-trees/ + // https://github.com/KirillOsenkov/Bliki/wiki/Roslyn-Immutable-Trees - private Edge(ImmutableArray<(int index, Node node)> vertices, int index) { - _vertices = vertices; - _index = index; + _pathFromRoot = vertices; + _indexInPath = indexInVertices; } /// /// Appends new arc to the end vertex of current arc /// - /// + /// /// New last arc - public Edge Append(int nextVertexIndex) { - var nextVertex = End.Children[nextVertexIndex]; - var trimLength = _vertices.Count - _index - 1; - var vertices = _vertices.ReplaceAt(_index + 1, trimLength, (nextVertexIndex, nextVertex)); - return new Edge(vertices, _index + 1); + public Edge Append(int childVertexIndexToAppened) { + var nextVertex = Node.Children[childVertexIndexToAppened]; + var trimLength = _pathFromRoot.Count - _indexInPath - 1; + var vertices = _pathFromRoot.ReplaceAt(_indexInPath + 1, trimLength, (childVertexIndexToAppened, nextVertex)); + return new Edge(vertices, _indexInPath + 1); } public override bool Equals(object obj) => obj is Edge other && Equals(other); - public bool Equals(Edge other) => Equals(_vertices, other._vertices) && _index == other._index; + public bool Equals(Edge other) => Equals(_pathFromRoot, other._pathFromRoot) && _indexInPath == other._indexInPath; public override int GetHashCode() { unchecked { - return (_vertices.GetHashCode() * 397) ^ _index; + return (_pathFromRoot.GetHashCode() * 397) ^ _indexInPath; } } @@ -92,9 +106,9 @@ public override int GetHashCode() { private string DebuggerDisplay { get { - var start = _index > 0 ? _vertices[_index - 1].node.Name : "null"; - var endIndex = _vertices[_index].index.ToString(); - var end = _vertices[_index].node.Name; + var start = _indexInPath > 0 ? _pathFromRoot[_indexInPath - 1].node.Name : "null"; + var endIndex = _pathFromRoot[_indexInPath].nodeIndexInParent.ToString(); + var end = _pathFromRoot[_indexInPath].node.Name; return $"[{start}]-{endIndex}-[{end}]"; } } diff --git a/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.Node.cs b/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.Node.cs index 339d9e494..3b99e0307 100644 --- a/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.Node.cs +++ b/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.Node.cs @@ -110,7 +110,7 @@ public ImmutableArray GetChildrenNames() { var results = ImmutableArray.Empty; foreach (var edge in _edges) { - var children = edge.End.Children; + var children = edge.Node.Children; foreach (var child in children) { if (IsNotInitPy(child)) { results = results.Add(child.Name); @@ -123,7 +123,7 @@ public ImmutableArray GetChildrenNames() { public bool TryGetChildImport(string name, out IImportSearchResult child) { foreach (var edge in _edges) { - var index = edge.End.GetChildIndex(name); + var index = edge.Node.GetChildIndex(name); if (index == -1) { continue; } @@ -132,7 +132,7 @@ public bool TryGetChildImport(string name, out IImportSearchResult child) { if (_snapshot.TryCreateModuleImport(childEdge, out var moduleImport)) { child = moduleImport; } else { - child = new ImplicitPackageImport(new ChildrenSource(_snapshot, childEdge), childEdge.End.Name, childEdge.End.FullModuleName); + child = new ImplicitPackageImport(new ChildrenSource(_snapshot, childEdge), childEdge.Node.Name, childEdge.Node.FullModuleName); } return true; diff --git a/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs b/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs index 68aa0f8c7..576b8ecb8 100644 --- a/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs +++ b/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs @@ -48,25 +48,25 @@ public partial class PathResolverSnapshot { private readonly ImmutableArray _userSearchPaths; private readonly ImmutableArray _roots; private readonly int _userRootsCount; - public int Version { get; } - public PathResolverSnapshot(PythonLanguageVersion pythonLanguageVersion, string workDirectory, ImmutableArray interpreterSearchPaths, ImmutableArray userSearchPaths) - : this(pythonLanguageVersion, workDirectory, userSearchPaths, interpreterSearchPaths, ImmutableArray.Empty, 0, Node.CreateDefaultRoot(), Node.CreateBuiltinRoot(), default) { + public readonly int Version; - _pythonLanguageVersion = pythonLanguageVersion; - _workDirectory = workDirectory; - _userSearchPaths = userSearchPaths; - _interpreterSearchPaths = interpreterSearchPaths; + public PathResolverSnapshot(PythonLanguageVersion pythonLanguageVersion, string workDirectory, ImmutableArray interpreterSearchPaths, ImmutableArray userSearchPaths) + : this(pythonLanguageVersion, + workDirectory, + userSearchPaths, + interpreterSearchPaths, + roots: ImmutableArray.Empty, + userRootsCount: 0, + nonRooted: Node.CreateDefaultRoot(), + builtins: Node.CreateBuiltinRoot(), + version: default) { if (workDirectory != string.Empty) { CreateRootsWithDefault(workDirectory, userSearchPaths, interpreterSearchPaths, out _roots, out _userRootsCount); } else { CreateRootsWithoutDefault(userSearchPaths, interpreterSearchPaths, out _roots, out _userRootsCount); } - - _nonRooted = Node.CreateDefaultRoot(); - _builtins = Node.CreateDefaultRoot(); - Version = default; } private PathResolverSnapshot(PythonLanguageVersion pythonLanguageVersion, string workDirectory, ImmutableArray userSearchPaths, ImmutableArray interpreterSearchPaths, ImmutableArray roots, int userRootsCount, Node nonRooted, Node builtins, int version) { @@ -137,7 +137,7 @@ public IImportChildrenSource GetModuleParentFromModuleName(in string fullModuleN } if (parentEdge.IsFirst) { - return new ImportRoot(new ChildrenSource(this, parentEdge), parentEdge.End.Name); + return new ImportRoot(new ChildrenSource(this, parentEdge), parentEdge.Node.Name); } } } @@ -159,7 +159,7 @@ public IImportChildrenSource GetModuleParentFromModuleName(in string fullModuleN private bool TryFindModuleByName(in int rootIndex, in string fullModuleName, out Edge lastEdge) { lastEdge = new Edge(rootIndex, _roots[rootIndex]); foreach (var nameSpan in fullModuleName.SplitIntoSpans('.')) { - var moduleNode = lastEdge.End; + var moduleNode = lastEdge.Node; var childIndex = moduleNode.GetChildIndex(nameSpan); if (childIndex == -1 || moduleNode.IsModule && !IsInitPyModule(moduleNode, out _)) { return false; @@ -235,12 +235,12 @@ public IImportSearchResult GetImportsFromAbsoluteName(in string modulePath, in I // sys.modules['module2.mod'] = None // VALUE = 42 - if (shortestPath.PathLength > 0 && shortestPath.End.IsModule) { + if (shortestPath.PathLength > 0 && shortestPath.Node.IsModule) { var possibleFullName = string.Join(".", fullNameList); - var rootPath = shortestPath.FirstEdge.End.Name; - var existingModuleName = shortestPath.End.Name; - var existingModuleFullName = shortestPath.End.FullModuleName; - var existingModulePath = shortestPath.End.ModulePath; + var rootPath = shortestPath.FirstEdge.Node.Name; + var existingModuleName = shortestPath.Node.Name; + var existingModuleFullName = shortestPath.Node.FullModuleName; + var existingModulePath = shortestPath.Node.ModulePath; var remainingNameParts = fullNameList.Skip(shortestPath.PathLength - 1).ToList(); return new PossibleModuleImport(possibleFullName, rootPath, existingModuleName, existingModuleFullName, existingModulePath, remainingNameParts); } @@ -266,11 +266,11 @@ public IImportSearchResult GetImportsFromRelativePath(in string modulePath, in i return default; } - if (parentCount == 1 && fullNameList.Count == 1 && lastEdge.Start.TryGetChild(fullNameList[0], out var nameNode)) { - return new ModuleImport(ChildrenSource.Empty, fullNameList[0], fullNameList[0], lastEdge.Start.Name, nameNode.ModulePath, nameNode.ModuleFileSize, IsPythonCompiled(nameNode.ModulePath), false); + if (parentCount == 1 && fullNameList.Count == 1 && lastEdge.ParentNode.TryGetChild(fullNameList[0], out var nameNode)) { + return new ModuleImport(ChildrenSource.Empty, fullNameList[0], fullNameList[0], lastEdge.ParentNode.Name, nameNode.ModulePath, nameNode.ModuleFileSize, IsPythonCompiled(nameNode.ModulePath), false); } - return new ImportNotFound(new StringBuilder(lastEdge.Start.Name) + return new ImportNotFound(new StringBuilder(lastEdge.ParentNode.Name) .Append(".") .Append(".", fullNameList) .ToString()); @@ -302,7 +302,7 @@ public IImportSearchResult GetImportsFromRelativePath(in string modulePath, in i } public bool IsLibraryFile(string filePath) - => TryFindModule(filePath, out var edge, out _) && IsLibraryPath(edge.FirstEdge.End.Name); + => TryFindModule(filePath, out var edge, out _) && IsLibraryPath(edge.FirstEdge.Node.Name); private bool TryGetSearchResults(in Edge matchedEdge, out IImportChildrenSource searchResult) => TryGetSearchResults(ImmutableArray.Create(matchedEdge), out searchResult); @@ -324,8 +324,8 @@ private bool TryGetSearchResults(in ImmutableArray matchedEdges, out IImpo } private bool TryCreateModuleImport(Edge lastEdge, out ModuleImport moduleImport) { - var moduleNode = lastEdge.End; - var rootNode = lastEdge.FirstEdge.End; + var moduleNode = lastEdge.Node; + var rootNode = lastEdge.FirstEdge.Node; if (IsInitPyModule(moduleNode, out var initPyNode)) { moduleImport = new ModuleImport( @@ -377,7 +377,7 @@ private bool TryCreateNamespacePackageImports(in ImmutableArray matchedEdg continue; } - var importNode = edge.End; + var importNode = edge.Node; searchResult = new ImplicitPackageImport(new ChildrenSource(this, matchedEdges), importNode.Name, importNode.FullModuleName); return true; } @@ -459,7 +459,7 @@ public PathResolverSnapshot AddModulePath(in string modulePath, long moduleFileS if (isFound) { // Module is already added - fullModuleName = lastEdge.End.FullModuleName; + fullModuleName = lastEdge.Node.FullModuleName; return this; } @@ -468,8 +468,8 @@ public PathResolverSnapshot AddModulePath(in string modulePath, long moduleFileS } var newEnd = CreateNewNodes(lastEdge, unmatchedPathSpan, moduleFileSize, out fullModuleName); - var newRoot = UpdateNodesFromEnd(lastEdge, newEnd); - return ImmutableReplaceRoot(newRoot, lastEdge.FirstEdge.EndIndex); + var newRoot = RewriteTreeSpine(lastEdge, newEnd); + return ImmutableReplaceRoot(newRoot, lastEdge.FirstEdge.NodeIndexInParent); } public PathResolverSnapshot RemoveModulePath(in string modulePath) { @@ -478,22 +478,22 @@ public PathResolverSnapshot RemoveModulePath(in string modulePath) { return this; } - var moduleNode = lastEdge.End; - var moduleParent = lastEdge.Start; - var moduleIndex = lastEdge.EndIndex; + var moduleNode = lastEdge.Node; + var moduleParent = lastEdge.ParentNode; + var moduleIndex = lastEdge.NodeIndexInParent; var newParent = moduleNode.Children.Count > 0 ? moduleParent.ReplaceChildAt(moduleNode.ToPackage(), moduleIndex) // preserve node as package : moduleParent.RemoveChildAt(moduleIndex); if (lastEdge.IsNonRooted) { - var directoryIndex = lastEdge.Previous.EndIndex; + var directoryIndex = lastEdge.Previous.NodeIndexInParent; return ReplaceNonRooted(_nonRooted.ReplaceChildAt(newParent, directoryIndex)); } lastEdge = lastEdge.Previous; - var newRoot = UpdateNodesFromEnd(lastEdge, newParent); - return ImmutableReplaceRoot(newRoot, lastEdge.FirstEdge.EndIndex); + var newRoot = RewriteTreeSpine(lastEdge, newParent); + return ImmutableReplaceRoot(newRoot, lastEdge.FirstEdge.NodeIndexInParent); } private bool MatchNodePathInNonRooted(string normalizedPath, out Edge lastEdge, out StringSpan unmatchedPathSpan) { @@ -511,7 +511,7 @@ private bool MatchNodePathInNonRooted(string normalizedPath, out Edge lastEdge, } lastEdge = lastEdge.Append(directoryNodeIndex); - var nameNodeIndex = lastEdge.End.GetChildIndex(nameSpan); + var nameNodeIndex = lastEdge.Node.GetChildIndex(nameSpan); if (nameNodeIndex == -1) { unmatchedPathSpan = nameSpan; return false; @@ -529,7 +529,7 @@ private bool MatchNodePath(in string normalizedModulePath, in int rootIndex, out unmatchedPathSpan = new StringSpan(normalizedModulePath, -1, 0); foreach (var nameSpan in normalizedModulePath.SplitIntoSpans(Path.DirectorySeparatorChar, root.Name.Length, modulePathLength - root.Name.Length)) { - var childIndex = lastEdge.End.GetChildIndex(nameSpan); + var childIndex = lastEdge.Node.GetChildIndex(nameSpan); if (childIndex == -1) { unmatchedPathSpan = normalizedModulePath.Slice(nameSpan.Start, modulePathLength - nameSpan.Start); break; @@ -537,7 +537,7 @@ private bool MatchNodePath(in string normalizedModulePath, in int rootIndex, out lastEdge = lastEdge.Append(childIndex); } - return unmatchedPathSpan.Length == 0 && lastEdge.End.IsModule; + return unmatchedPathSpan.Length == 0 && lastEdge.Node.IsModule; } private static bool TryFindImport(IEnumerable rootEdges, List fullNameList, out ImmutableArray matchedEdges, out Edge shortestPath) { @@ -549,7 +549,7 @@ private static bool TryFindImport(IEnumerable rootEdges, List full matchedEdges = matchedEdges.Add(lastEdge); } - if (lastEdge.End.IsModule && (shortestPath.IsEmpty || lastEdge.PathLength < shortestPath.PathLength)) { + if (lastEdge.Node.IsModule && (shortestPath.IsEmpty || lastEdge.PathLength < shortestPath.PathLength)) { shortestPath = lastEdge; } } @@ -560,10 +560,10 @@ private static bool TryFindImport(IEnumerable rootEdges, List full private static bool TryFindName(in Edge edge, in List nameParts, out Edge lastEdge) { lastEdge = edge; foreach (var name in nameParts) { - if (lastEdge.End.IsModule && !IsInitPyModule(lastEdge.End, out _)) { + if (lastEdge.Node.IsModule && !IsInitPyModule(lastEdge.Node, out _)) { return false; } - var index = lastEdge.End.GetChildIndex(name); + var index = lastEdge.Node.GetChildIndex(name); if (index == -1) { return false; } @@ -579,9 +579,9 @@ private bool RootContains(in int rootIndex, in Edge lastEdge, out Edge rootEdge) var targetEdge = new Edge(rootIndex, root); while (sourceEdge != lastEdge) { - var targetNode = targetEdge.End; + var targetNode = targetEdge.Node; - var nextSourceNode = sourceEdge.Next.End; + var nextSourceNode = sourceEdge.Next.Node; var childIndex = targetNode.GetChildIndex(nextSourceNode.Name); if (childIndex == -1) { rootEdge = default; @@ -612,8 +612,8 @@ private Node AddToNonRooted(in Edge lastEdge, in StringSpan unmatchedPathSpan, i return _nonRooted.AddChild(Node.CreatePackage(directory, directory, moduleNode)); } - var directoryIndex = lastEdge.EndIndex; - var directoryNode = lastEdge.End.AddChild(moduleNode); + var directoryIndex = lastEdge.NodeIndexInParent; + var directoryNode = lastEdge.Node.AddChild(moduleNode); return _nonRooted.ReplaceChildAt(directoryNode, directoryIndex); } @@ -622,7 +622,7 @@ private static Node CreateNewNodes(in Edge lastEdge, in StringSpan unmatchedPath if (unmatchedPathLength == 0) { // Module name matches name of existing package fullModuleName = GetFullModuleName(lastEdge); - return lastEdge.End.ToModuleNode(modulePath, moduleFileSize, fullModuleName); + return lastEdge.Node.ToModuleNode(modulePath, moduleFileSize, fullModuleName); } if (unmatchedPathStart == GetModuleNameStart(modulePath)) { @@ -630,7 +630,7 @@ private static Node CreateNewNodes(in Edge lastEdge, in StringSpan unmatchedPath var name = modulePath.Substring(unmatchedPathStart, unmatchedPathLength); fullModuleName = GetFullModuleName(lastEdge, name); var newChildNode = Node.CreateModule(name, modulePath, moduleFileSize, fullModuleName); - return lastEdge.End.AddChild(newChildNode); + return lastEdge.Node.AddChild(newChildNode); } var names = modulePath.Split(Path.DirectorySeparatorChar, unmatchedPathStart, unmatchedPathLength); @@ -641,7 +641,7 @@ private static Node CreateNewNodes(in Edge lastEdge, in StringSpan unmatchedPath newNode = Node.CreatePackage(names[i], GetFullModuleName(lastEdge, names, i), newNode); } - return lastEdge.End.AddChild(newNode); + return lastEdge.Node.AddChild(newNode); } private static string GetFullModuleName(in Edge lastEdge) { @@ -650,10 +650,10 @@ private static string GetFullModuleName(in Edge lastEdge) { } var sb = GetFullModuleNameBuilder(lastEdge); - if (lastEdge.End.IsModule) { - AppendNameIfNotInitPy(sb, lastEdge.End.Name); + if (lastEdge.Node.IsModule) { + AppendNameIfNotInitPy(sb, lastEdge.Node.Name); } else { - AppendName(sb, lastEdge.End.Name); + AppendName(sb, lastEdge.Node.Name); } return sb.ToString(); @@ -665,7 +665,7 @@ private static string GetFullModuleName(in Edge lastEdge, string name) { } var sb = GetFullModuleNameBuilder(lastEdge); - AppendName(sb, lastEdge.End.Name); + AppendName(sb, lastEdge.Node.Name); AppendNameIfNotInitPy(sb, name); return sb.ToString(); } @@ -673,7 +673,7 @@ private static string GetFullModuleName(in Edge lastEdge, string name) { private static string GetFullModuleName(in Edge lastEdge, string[] names, int lastNameIndex) { var sb = GetFullModuleNameBuilder(lastEdge); if (!lastEdge.IsFirst) { - AppendName(sb, lastEdge.End.Name); + AppendName(sb, lastEdge.Node.Name); sb.Append('.'); } @@ -695,7 +695,7 @@ private static StringBuilder GetFullModuleNameBuilder(in Edge lastEdge) { edge = edge.Next; while (edge != lastEdge) { - AppendName(sb, edge.End.Name); + AppendName(sb, edge.Node.Name); edge = edge.Next; } @@ -711,14 +711,14 @@ private static void AppendNameIfNotInitPy(StringBuilder builder, string name) { private static void AppendName(StringBuilder builder, string name) => builder.AppendIf(builder.Length > 0, ".").Append(name); - private static Node UpdateNodesFromEnd(Edge lastEdge, Node newEnd) { - while (lastEdge.Start != null) { - var newStart = lastEdge.Start.ReplaceChildAt(newEnd, lastEdge.EndIndex); + private static Node RewriteTreeSpine(Edge lastEdge, Node newNode) { + while (lastEdge.ParentNode != null) { + var newParentNode = lastEdge.ParentNode.ReplaceChildAt(newNode, lastEdge.NodeIndexInParent); lastEdge = lastEdge.Previous; - newEnd = newStart; + newNode = newParentNode; } - return newEnd; + return newNode; } private bool TryFindModule(string modulePath, out Edge lastEdge, out StringSpan unmatchedPathSpan) {