Repository: yandex/yatagan (kotlin)

Issue:
Broken dependency loop validation
Dependency loops are not being correctly validated in some cases. One of the cases being the dependency loop is accessible only via `Provider`/`Lazy` edge. There may be other cases, need to investigate.

Pull request:
There was the same bug in procedural tests generation, thus `Tall & Thin` contained a loop. This fixes the bug, and it can be seen in the golden code.

Patch:
diff --git a/core/graph/impl/src/main/kotlin/loops.kt b/core/graph/impl/src/main/kotlin/loops.kt
index fd132600..5e3769a6 100644
--- a/core/graph/impl/src/main/kotlin/loops.kt
+++ b/core/graph/impl/src/main/kotlin/loops.kt
@@ -16,7 +16,6 @@
 
 package com.yandex.yatagan.core.graph.impl
 
-import com.yandex.yatagan.base.ifOrElseNull
 import com.yandex.yatagan.base.traverseDepthFirstWithPath
 import com.yandex.yatagan.core.graph.BindingGraph
 import com.yandex.yatagan.core.graph.bindings.AliasBinding
@@ -24,6 +23,7 @@ import com.yandex.yatagan.core.graph.bindings.BaseBinding
 import com.yandex.yatagan.core.graph.bindings.Binding
 import com.yandex.yatagan.core.graph.component1
 import com.yandex.yatagan.core.graph.component2
+import com.yandex.yatagan.core.model.DependencyKind
 import com.yandex.yatagan.core.model.NodeDependency
 import com.yandex.yatagan.core.model.component1
 import com.yandex.yatagan.core.model.component2
@@ -31,33 +31,35 @@ import com.yandex.yatagan.core.model.isEager
 import com.yandex.yatagan.validation.Validator
 import com.yandex.yatagan.validation.format.Strings
 import com.yandex.yatagan.validation.format.reportError
-import kotlin.collections.component1
-import kotlin.collections.component2
 
 internal fun validateNoLoops(graph: BindingGraph, validator: Validator) {
-    traverseDepthFirstWithPath(
-        roots = buildList<BaseBinding> {
+    traverseDepthFirstWithPath<Pair<DependencyKind, BaseBinding>>(
+        roots = buildList {
             graph.entryPoints.forEach { (_, dependency) ->
-                add(graph.resolveBindingRaw(dependency.node))
+                add(dependency.kind to graph.resolveBindingRaw(dependency.node))
             }
             graph.memberInjectors.forEach {
                 it.membersToInject.forEach { (_, dependency) ->
-                    add(graph.resolveBindingRaw(dependency.node))
+                    add(dependency.kind to graph.resolveBindingRaw(dependency.node))
                 }
             }
         },
-        childrenOf = { binding ->
+        childrenOf = { (_, binding) ->
             class DependenciesVisitor : BaseBinding.Visitor<List<NodeDependency>> {
                 override fun visitAlias(alias: AliasBinding) = listOf(alias.source)
                 override fun visitBinding(binding: Binding) =
                     binding.dependencies + binding.nonStaticConditionProviders
             }
-            binding.accept(DependenciesVisitor()).mapNotNull { (node, kind) ->
-                ifOrElseNull(kind.isEager) { binding.owner.resolveBindingRaw(node) }
-            }.asIterable()
+            binding.accept(DependenciesVisitor()).map { (node, kind) ->
+                kind to binding.owner.resolveBindingRaw(node)
+            }
         },
         onLoop = { bindingLoop ->
-            validator.reportError(Strings.Errors.dependencyLoop(chain = bindingLoop.toList()))
+            // Check for lazy edges - if they are present, this is a "benign" loop, don't report it.
+            if (bindingLoop.all { (kind, _) -> kind.isEager }) {
+                val chain = bindingLoop.mapTo(arrayListOf()) { (_, binding) -> binding }
+                validator.reportError(Strings.Errors.dependencyLoop(chain = chain))
+            }
         }
     )
 }
\ No newline at end of file


Test patch:
diff --git a/testing/procedural/src/main/kotlin/generate.kt b/testing/procedural/src/main/kotlin/generate.kt
index 4e6d14c5..2863eb6a 100644
--- a/testing/procedural/src/main/kotlin/generate.kt
+++ b/testing/procedural/src/main/kotlin/generate.kt
@@ -199,6 +199,7 @@ fun generate(
             }
         }
         run {
+            // Break the loops
             fun Binding.withoutDependencies(nodes: Collection<LogicalNode>): Binding {
                 fun replaceWithLazy(): Map<LogicalNode, DependencyKind> {
                     return buildMap {
@@ -225,41 +226,55 @@ fun generate(
                 }
             }
 
-            // Break the loops
+            // Nodes currently being visited (entered)
             val markedGray = hashSetOf<LogicalNode>()
+            // Already visited nodes (entered and exited)
             val markedBlack = hashSetOf<LogicalNode>()
+            // Local eager-edge connectivity sub-graph
             val stack = arrayListOf<LogicalNode>()
+            // Pending nodes behind non-eager edges.
+            val pendingQueue = arrayListOf<LogicalNode>()
 
-            stack += component.entryPoints.keys
+            pendingQueue += component.entryPoints.keys
             for (memberInjector in component.memberInjectors) {
-                stack += memberInjector.members
+                pendingQueue += memberInjector.members
             }
 
-            while (stack.isNotEmpty()) {
-                when (val node = stack.last()) {
-                    in markedBlack -> {
-                        stack.removeLast()
-                    }
-                    in markedGray -> {
-                        stack.removeLast()
-                        markedBlack += node
-                        markedGray -= node
-                    }
-                    else -> {
-                        markedGray += node
-                        var binding = componentTree.resolveBinding(node)
-                        // Check if this binding introduces a loop into the graph
-                        val formsLoopBy = binding.dependencies.mapNotNull { (depNode, depKind) ->
-                            depNode.takeIf { depKind == DependencyKind.Direct && depNode in markedGray }
+            while (pendingQueue.isNotEmpty()) {
+                check(markedGray.isEmpty())
+
+                stack += pendingQueue
+                pendingQueue.clear()
+
+                while (stack.isNotEmpty()) {
+                    when (val node = stack.last()) {
+                        in markedBlack -> {
+                            stack.removeLast()
                         }
-                        if (formsLoopBy.isNotEmpty()) {
-                            // Need to break the loop
-                            binding = binding.withoutDependencies(formsLoopBy)
-                            componentTree.updateBinding(node, binding)
+
+                        in markedGray -> {
+                            stack.removeLast()
+                            markedBlack += node
+                            markedGray -= node
                         }
-                        for ((depNode, depKind) in binding.dependencies) {
-                            if (depKind == DependencyKind.Direct) {
-                                stack += depNode
+
+                        else -> {
+                            markedGray += node
+                            var binding = componentTree.resolveBinding(node)
+                            // Check if this binding introduces a loop into the graph
+                            val formsLoopBy = binding.dependencies.mapNotNull { (depNode, depKind) ->
+                                depNode.takeIf { depKind == DependencyKind.Direct && depNode in markedGray }
+                            }
+                            if (formsLoopBy.isNotEmpty()) {
+                                // Need to break the loop
+                                binding = binding.withoutDependencies(formsLoopBy)
+                                componentTree.updateBinding(node, binding)
+                            }
+                            for ((depNode, depKind) in binding.dependencies) {
+                                when (depKind) {
+                                    DependencyKind.Direct -> stack += depNode
+                                    else -> pendingQueue += depNode
+                                }
                             }
                         }
                     }
diff --git a/testing/tests/src/test/kotlin/GraphLoopsFailureTest.kt b/testing/tests/src/test/kotlin/GraphLoopsFailureTest.kt
index f68cd0a1..e5b27fdd 100644
--- a/testing/tests/src/test/kotlin/GraphLoopsFailureTest.kt
+++ b/testing/tests/src/test/kotlin/GraphLoopsFailureTest.kt
@@ -72,6 +72,45 @@ class GraphLoopsFailureTest(
         compileRunAndValidate()
     }
 
+    @Test
+    fun `dependency loop behind Provider`() {
+        givenKotlinSource("test.TestCase", """
+            import com.yandex.yatagan.*
+            import javax.inject.*
+            
+            class ClassA @Inject constructor(b: ClassB)
+            class ClassB @Inject constructor(a: ClassC)
+            class ClassC @Inject constructor(a: Optional<ClassD>)
+            class ClassD @Inject constructor(a: ClassA)
+            class Entry @Inject constructor(a: Provider<ClassA>)
+
+            class MyApi
+            @Module class MyModule {
+                @Provides @Named("X") fun provideX(@Named("Y") d: MyApi): MyApi = MyApi()
+                @Provides @Named("Y") fun provideY(@Named("Z") d: MyApi): MyApi = MyApi()
+                @Provides @Named("Z") fun provideZ(@Named("X") d1: MyApi, 
+                                                   @Named("Y") d2: MyApi): MyApi = MyApi()
+
+                @Provides @Named("E") fun provideE(@Named("Y") d1: Lazy<MyApi>,
+                                                   @Named("X") d2: Lazy<MyApi>): MyApi = MyApi()
+            }
+
+            @Component
+            interface RootComponent {
+                val c: Entry
+                fun sub(): SubComponent
+            }
+
+            @Component(isRoot = false, modules = [MyModule::class])
+            interface SubComponent {
+                val c: Entry
+                @get:Named("E") val z: MyApi
+            }
+        """.trimIndent())
+
+        compileRunAndValidate()
+    }
+
     @Test
     fun `dependency loop with alias`() {
         givenKotlinSource("test.TestCase", """
diff --git a/testing/tests/src/test/resources/golden/GraphLoopsFailureTest/dependency-loop-behind-Provider.golden.txt b/testing/tests/src/test/resources/golden/GraphLoopsFailureTest/dependency-loop-behind-Provider.golden.txt
new file mode 100644
index 00000000..350d77b4
--- /dev/null
+++ b/testing/tests/src/test/resources/golden/GraphLoopsFailureTest/dependency-loop-behind-Provider.golden.txt
@@ -0,0 +1,32 @@
+error: Binding dependency loop detected:
+(*) `@javax.inject.Named(value="Y") test.MyApi` provided by `provision test.MyModule::provideY(.. 1 dependency)` depends on <-
+    `@javax.inject.Named(value="Z") test.MyApi` provided by `provision test.MyModule::provideZ(.. 2 dependencies)` depends on <-
+    `@javax.inject.Named(value="X") test.MyApi` provided by `provision test.MyModule::provideX(.. 1 dependency)` depends on <- (*)
+Encountered:
+  in graph for root-component test.RootComponent
+  here: graph for component test.SubComponent
+        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: Binding dependency loop detected:
+(*) `@javax.inject.Named(value="Y") test.MyApi` provided by `provision test.MyModule::provideY(.. 1 dependency)` depends on <-
+    `@javax.inject.Named(value="Z") test.MyApi` provided by `provision test.MyModule::provideZ(.. 2 dependencies)` depends on <- (*)
+Encountered:
+  in graph for root-component test.RootComponent
+  here: graph for component test.SubComponent
+        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: Binding dependency loop detected:
+(*) `test.ClassB` provided by `inject-constructor test.ClassB(.. 1 dependency)` depends on <-
+    `test.ClassC` provided by `inject-constructor test.ClassC(.. 1 dependency)` depends on <-
+    `test.ClassD` provided by `inject-constructor test.ClassD(.. 1 dependency)` depends on <-
+    `test.ClassA` provided by `inject-constructor test.ClassA(.. 1 dependency)` depends on <- (*)
+Encountered:
+  here: graph for root-component test.RootComponent
+        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Encountered:
+  in graph for root-component test.RootComponent
+  here: graph for component test.SubComponent
+        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\ No newline at end of file
diff --git a/testing/tests/src/test/resources/golden/HeavyTest/procedural-Tall-Thin.golden-code.txt b/testing/tests/src/test/resources/golden/HeavyTest/procedural-Tall-Thin.golden-code.txt
index 31b6af9c..0e7ab360 100644
--- a/testing/tests/src/test/resources/golden/HeavyTest/procedural-Tall-Thin.golden-code.txt
+++ b/testing/tests/src/test/resources/golden/HeavyTest/procedural-Tall-Thin.golden-code.txt
@@ -3989,7 +3989,7 @@ public final class Yatagan$Component_323 implements Component_323 {
           Object local = this.mNode_104Instance;
           if (local == null) {
             ThreadAssertions.assertThreadAccess();
-            local = new Node_104(Checks.checkProvisionNotNull(Module_401.Companion.provides_550()), Checks.checkProvisionNotNull(Module_406.Companion.provides_640(this.accessNode_132(), this.accessNode_223(), new ProviderImpl(this, 8), new Component_328Impl.ProviderImpl(this.mComponent_328, 9), new ProviderImpl(this, 15), new Component_328Impl.ProviderImpl(this.mComponent_328, 12), new Yatagan$Component_323.ProviderImpl(this.mComponent_323, 1), this.cacheNode_240(), new Component_328Impl.ProviderImpl(this.mComponent_328, 5), this.mComponent_323.cacheNode_102(), this.mComponent_327.cacheNode_241(), Checks.checkProvisionNotNull(Module_406.Companion.provides_641(this.accessNode_274(), this.mComponent_323.cacheNode_257())), this.mComponent_327.mSet_0, this.mComponent_328.cacheNode_206(), this.cacheNode_136(), this.mComponent_323.cacheNode_257(), this.mComponent_327.cacheNode_293(), this.accessNode_197(), new Yatagan$Component_323.ProviderImpl(this.mComponent_323, 2), this.mComponent_323.mDependency_310.ep_0(), this.mComponent_323.mDependency_310.ep_1(), this.mComponent_323.cacheNode_257(), this.cacheNode_104())), this.accessNode_161(), new Component_328Impl.CachingProviderImpl(this.mComponent_328, 1), this.mComponent_327.accessNode_185(), this.accessNode_62(), this.mComponent_328.mDependency_315.ep_0(), this.accessNode_132(), this.mComponent_323.cacheNode_17(), this.mComponent_323.mDependency_310.ep_3());
+            local = new Node_104(Checks.checkProvisionNotNull(Module_401.Companion.provides_550()), Checks.checkProvisionNotNull(Module_406.Companion.provides_640(this.accessNode_132(), this.accessNode_223(), new ProviderImpl(this, 8), new Component_328Impl.ProviderImpl(this.mComponent_328, 9), new ProviderImpl(this, 15), new Component_328Impl.ProviderImpl(this.mComponent_328, 12), new Yatagan$Component_323.ProviderImpl(this.mComponent_323, 1), this.cacheNode_240(), new Component_328Impl.ProviderImpl(this.mComponent_328, 5), this.mComponent_323.cacheNode_102(), this.mComponent_327.cacheNode_241(), Checks.checkProvisionNotNull(Module_406.Companion.provides_641(this.accessNode_274(), this.mComponent_323.cacheNode_257())), this.mComponent_327.mSet_0, this.mComponent_328.cacheNode_206(), this.cacheNode_136(), this.mComponent_323.cacheNode_257(), this.mComponent_327.cacheNode_293(), this.accessNode_197(), new Yatagan$Component_323.ProviderImpl(this.mComponent_323, 2), this.mComponent_323.mDependency_310.ep_0(), this.mComponent_323.mDependency_310.ep_1(), this.mComponent_323.cacheNode_257(), new ProviderImpl(this, 16))), this.accessNode_161(), new Component_328Impl.CachingProviderImpl(this.mComponent_328, 1), this.mComponent_327.accessNode_185(), this.accessNode_62(), this.mComponent_328.mDependency_315.ep_0(), this.accessNode_132(), this.mComponent_323.cacheNode_17(), this.mComponent_323.mDependency_310.ep_3());
             this.mNode_104Instance = local;
           }
           return (Node_104) local;
@@ -7310,7 +7310,7 @@ public final class Yatagan$Component_323 implements Component_323 {
         }
 
         Node_117 accessNode_117() {
-          return Checks.checkProvisionNotNull(Module_415.Companion.provides_727(this.accessNode_280(), new Component_330Impl.ProviderImpl(this.mComponent_330, 6), this.mComponent_323.accessNode_210(), this.mComponent_327.mDependency_311.ep_3(), this.mComponent_323.accessNode_15(), this.cacheNode_194(), new Yatagan$Component_323.ProviderImpl(this.mComponent_323, 13), this.mComponent_330.accessNode_308(), Checks.checkProvisionNotNull(Module_415.Companion.provides_731(this.mComponent_323.cacheNode_281(), this.mComponent_327.cacheNode_260(), this.mComponent_330.mDependency_314.ep_0(), this.mComponent_327.accessNode_185(), this.cacheNode_258(), new CachingProviderImpl(this, 1), new ProviderImpl(this, 5), new ProviderImpl(this, 9), new Component_327Impl.ProviderImpl(this.mComponent_327, 0), new Component_330Impl.CachingProviderImpl(this.mComponent_330, 2), this.mComponent_323.cacheNode_30(), this.mComponent_323.mDependency_310.ep_0(), new ProviderImpl(this, 12), this.mComponent_323.accessNode_210(), new ProviderImpl(this, 3), this.mComponent_330.cacheNode_183(), this.accessNode_269(), this.mComponent_330.cacheNode_191(), this.mComponent_323.cacheNode_107(), this.mComponent_323.cacheNode_257(), this.cacheNode_80(), this.mComponent_323.mDependency_310.ep_1(), this.mComponent_323.mSet_2, this.mComponent_327.cacheNode_241(), new Component_327Impl.ProviderImpl(this.mComponent_327, 15), this.accessNode_253(), this.mComponent_323.cacheNode_262(), Checks.checkProvisionNotNull(Module_401.Companion.provides_547()))), new Yatagan$Component_323.CachingProviderImpl(this.mComponent_323, 16), this.cacheNode_186(), this.mComponent_323.cacheNode_107(), new Component_330Impl.ProviderImpl(this.mComponent_330, 10), this.mComponent_323.cacheNode_257(), this.mComponent_327.cacheNode_260(), Checks.checkProvisionNotNull(Module_401.Companion.provides_550()), this.mComponent_323.cacheNode_262(), this.mComponent_323.accessNode_15(), this.mComponent_330.cacheNode_244(), this.accessNode_53(), this.cacheNode_222()));
+          return Checks.checkProvisionNotNull(Module_415.Companion.provides_727(this.accessNode_280(), new Component_330Impl.ProviderImpl(this.mComponent_330, 6), this.mComponent_323.accessNode_210(), this.mComponent_327.mDependency_311.ep_3(), this.mComponent_323.accessNode_15(), this.cacheNode_194(), new Yatagan$Component_323.ProviderImpl(this.mComponent_323, 13), this.mComponent_330.accessNode_308(), Checks.checkProvisionNotNull(Module_415.Companion.provides_731(this.mComponent_323.cacheNode_281(), this.mComponent_327.cacheNode_260(), this.mComponent_330.mDependency_314.ep_0(), this.mComponent_327.accessNode_185(), new ProviderImpl(this, 13), new CachingProviderImpl(this, 1), new ProviderImpl(this, 5), new ProviderImpl(this, 9), new Component_327Impl.ProviderImpl(this.mComponent_327, 0), new Component_330Impl.CachingProviderImpl(this.mComponent_330, 2), this.mComponent_323.cacheNode_30(), this.mComponent_323.mDependency_310.ep_0(), new ProviderImpl(this, 12), this.mComponent_323.accessNode_210(), new ProviderImpl(this, 3), this.mComponent_330.cacheNode_183(), this.accessNode_269(), this.mComponent_330.cacheNode_191(), this.mComponent_323.cacheNode_107(), this.mComponent_323.cacheNode_257(), this.cacheNode_80(), this.mComponent_323.mDependency_310.ep_1(), this.mComponent_323.mSet_2, this.mComponent_327.cacheNode_241(), new Component_327Impl.ProviderImpl(this.mComponent_327, 15), this.accessNode_253(), this.mComponent_323.cacheNode_262(), Checks.checkProvisionNotNull(Module_401.Companion.provides_547()))), new Yatagan$Component_323.CachingProviderImpl(this.mComponent_323, 16), this.cacheNode_186(), this.mComponent_323.cacheNode_107(), new Component_330Impl.ProviderImpl(this.mComponent_330, 10), this.mComponent_323.cacheNode_257(), this.mComponent_327.cacheNode_260(), Checks.checkProvisionNotNull(Module_401.Companion.provides_550()), this.mComponent_323.cacheNode_262(), this.mComponent_323.accessNode_15(), this.mComponent_330.cacheNode_244(), this.accessNode_53(), this.cacheNode_222()));
         }
 
         Node_194 cacheNode_194() {
@@ -8142,7 +8142,7 @@ public final class Yatagan$Component_323 implements Component_323 {
           Object local = this.mNode_104Instance;
           if (local == null) {
             ThreadAssertions.assertThreadAccess();
-            local = new Node_104(Checks.checkProvisionNotNull(Module_401.Companion.provides_550()), Checks.checkProvisionNotNull(Module_416.Companion.provides_745(this.accessNode_132(), this.accessNode_223(), new ProviderImpl(this, 1), new Component_331Impl.ProviderImpl(this.mComponent_331, 1), new ProviderImpl(this, 5), new Component_331Impl.CachingProviderImpl(this.mComponent_331, 4), new Yatagan$Component_323.ProviderImpl(this.mComponent_323, 1), this.accessNode_240(), new Component_331Impl.ProviderImpl(this.mComponent_331, 17), this.mComponent_323.cacheNode_102(), this.mComponent_327.cacheNode_241(), Checks.checkProvisionNotNull(Module_416.Companion.provides_748(this.accessNode_274(), this.mComponent_323.cacheNode_257())), this.mComponent_327.mSet_0, this.mComponent_331.cacheNode_206(), this.cacheNode_136(), this.mComponent_323.cacheNode_257(), this.mComponent_327.cacheNode_293(), this.accessNode_197(), new Yatagan$Component_323.ProviderImpl(this.mComponent_323, 2), this.mComponent_323.mDependency_310.ep_0(), this.mComponent_323.mDependency_310.ep_1(), this.mComponent_323.cacheNode_257(), this.cacheNode_104())), this.accessNode_161(), new Component_331Impl.CachingProviderImpl(this.mComponent_331, 7), this.mComponent_327.accessNode_185(), this.accessNode_62(), this.mComponent_331.mDependency_315.ep_0(), this.accessNode_132(), this.mComponent_323.cacheNode_17(), this.mComponent_323.mDependency_310.ep_3());
+            local = new Node_104(Checks.checkProvisionNotNull(Module_401.Companion.provides_550()), Checks.checkProvisionNotNull(Module_416.Companion.provides_745(this.accessNode_132(), this.accessNode_223(), new ProviderImpl(this, 1), new Component_331Impl.ProviderImpl(this.mComponent_331, 1), new ProviderImpl(this, 5), new Component_331Impl.CachingProviderImpl(this.mComponent_331, 4), new Yatagan$Component_323.ProviderImpl(this.mComponent_323, 1), this.accessNode_240(), new Component_331Impl.ProviderImpl(this.mComponent_331, 17), this.mComponent_323.cacheNode_102(), this.mComponent_327.cacheNode_241(), Checks.checkProvisionNotNull(Module_416.Companion.provides_748(this.accessNode_274(), this.mComponent_323.cacheNode_257())), this.mComponent_327.mSet_0, this.mComponent_331.cacheNode_206(), this.cacheNode_136(), this.mComponent_323.cacheNode_257(), this.mComponent_327.cacheNode_293(), this.accessNode_197(), new Yatagan$Component_323.ProviderImpl(this.mComponent_323, 2), this.mComponent_323.mDependency_310.ep_0(), this.mComponent_323.mDependency_310.ep_1(), this.mComponent_323.cacheNode_257(), new ProviderImpl(this, 11))), this.accessNode_161(), new Component_331Impl.CachingProviderImpl(this.mComponent_331, 7), this.mComponent_327.accessNode_185(), this.accessNode_62(), this.mComponent_331.mDependency_315.ep_0(), this.accessNode_132(), this.mComponent_323.cacheNode_17(), this.mComponent_323.mDependency_310.ep_3());
             this.mNode_104Instance = local;
           }
           return (Node_104) local;