Repository: yandex/yatagan (kotlin)

Issue:
Crash: Required value was null (at com.yandex.yatagan.codegen.impl.ComponentFactoryGenerator.fieldNameFor(ComponentFactoryGenerator.kt:85))
Yatagan version: `1.2.1`

Crash occures when the following situation arises:
Given the condition `A`; given three components `C0`, `C1`, `C2`, such as
`C0 (root) <- C1 <- C2` in terms of hierarchy.
And the condition `A` is used in `C0`, **not** used in `C1`, and again used in `C2`.

```
java.lang.IllegalStateException: Required value was null.
	at com.yandex.yatagan.codegen.impl.ComponentFactoryGenerator.fieldNameFor(ComponentFactoryGenerator.kt:82)
	at com.yandex.yatagan.codegen.impl.GeneratorUtilsKt.componentInstance(generatorUtils.kt:42)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator$EagerAccessStrategy.access(ConditionGenerator.kt:139)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.access(ConditionGenerator.kt:58)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.access(ConditionGenerator.kt:64)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.access(ConditionGenerator.kt:64)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.access(ConditionGenerator.kt:64)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.access$access(ConditionGenerator.kt:34)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.expression-wmBEi3A(ConditionGenerator.kt:91)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitAlternatives(generateCreation.kt:124)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitAlternatives(generateCreation.kt:45)
	at com.yandex.yatagan.core.graph.impl.bindings.AlternativesBindingImpl.accept(AlternativesBindingImpl.kt:52)
	at com.yandex.yatagan.codegen.impl.GenerateCreationKt.generateCreation(generateCreation.kt:231)
	at com.yandex.yatagan.codegen.impl.InlineCreationStrategy.generateAccess(accessStrategies.kt:170)
	at com.yandex.yatagan.codegen.impl.GeneratorUtilsKt.generateAccess(generatorUtils.kt:66)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor$visitProvision$1$1.genArgs(generateCreation.kt:61)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor$visitProvision$1$1.visitConstructor(generateCreation.kt:87)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor$visitProvision$1$1.visitConstructor(generateCreation.kt:58)
	at com.yandex.yatagan.lang.common.ConstructorBase.accept(ConstructorBase.kt:24)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitProvision(generateCreation.kt:58)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitProvision(generateCreation.kt:45)
	at com.yandex.yatagan.core.graph.impl.bindings.InjectConstructorProvisionBindingImpl.accept(InjectConstructorProvisionBindingImpl.kt:61)
	at com.yandex.yatagan.codegen.impl.GenerateCreationKt.generateCreation(generateCreation.kt:231)
	at com.yandex.yatagan.codegen.impl.CachingStrategySingleThread.generateInComponent(accessStrategies.kt:85)
	at com.yandex.yatagan.codegen.impl.CompositeStrategy.generateInComponent(accessStrategies.kt:232)
	at com.yandex.yatagan.codegen.impl.AccessStrategyManager.generate(AccessStrategyManager.kt:176)
	at com.yandex.yatagan.codegen.impl.ComponentGenerator.generate(ComponentGenerator.kt:233)
	at com.yandex.yatagan.codegen.impl.ComponentGenerator.generate(ComponentGenerator.kt:228)
	at com.yandex.yatagan.codegen.impl.ComponentGenerator.generate(ComponentGenerator.kt:228)
	at com.yandex.yatagan.codegen.impl.ComponentGenerator.generate(ComponentGenerator.kt:228)
	at com.yandex.yatagan.codegen.impl.ComponentGeneratorFacade.generateTo(ComponentGeneratorFacade.kt:38)
	at com.yandex.yatagan.processor.common.ProcessKt.process(process.kt:100)
	at com.yandex.yatagan.processor.ksp.KspYataganProcessor.process(KspYataganProcessor.kt:47)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$6$1.invoke(KotlinSymbolProcessingExtension.kt:291)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$6$1.invoke(KotlinSymbolProcessingExtension.kt:289)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:394)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:289)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:123)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:99)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:257)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:42)
	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:115)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:248)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:88)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:47)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:167)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:53)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:101)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:47)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1645)
	at jdk.internal.reflect.GeneratedMethodAccessor111.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

```

Pull request description:
Closes #85

Patch:
diff --git a/core/graph/impl/src/main/kotlin/BindingGraphImpl.kt b/core/graph/impl/src/main/kotlin/BindingGraphImpl.kt
index 2b920d2a..97d798d2 100644
--- a/core/graph/impl/src/main/kotlin/BindingGraphImpl.kt
+++ b/core/graph/impl/src/main/kotlin/BindingGraphImpl.kt
@@ -238,7 +238,12 @@ internal class BindingGraphImpl(
             val usesAssistedInjectFactories = child.localAssistedInjectFactories.removeAll(localAssistedInjectFactories)
             if (usesConditions || usesAssistedInjectFactories) {
                 // This will never be seen by materialization and that's okay, because no bindings are required here.
-                child.usedParents += this
+
+                // Walk up until `this` adding `this` to used parents.
+                for (graph in child.parentsSequence(includeThis = true)) {
+                    if (graph === this) break
+                    (graph as BindingGraphImpl).usedParents += this
+                }
             }
         }
     }


Test patch:
diff --git a/testing/tests/src/test/kotlin/ConditionsTest.kt b/testing/tests/src/test/kotlin/ConditionsTest.kt
index dc6985e4..c9d9c3ed 100644
--- a/testing/tests/src/test/kotlin/ConditionsTest.kt
+++ b/testing/tests/src/test/kotlin/ConditionsTest.kt
@@ -449,6 +449,33 @@ class ConditionsTest(
         compileRunAndValidate()
     }
 
+    @Test
+    fun `issue #85 - conditions in component hierarchy with usage gaps`() {
+        includeFromSourceSet(features)
+        givenKotlinSource("test.TestCase", """
+            import com.yandex.yatagan.*
+            import javax.inject.*
+
+            @Conditional(Conditions.FeatureA::class) class ClassA @Inject constructor()
+            @Conditional(Conditions.FeatureA::class) class ClassB @Inject constructor()
+
+            @Component interface RootComponent {
+                val dummy: Optional<ClassA>
+                fun createSub(): SubComponent
+            }
+
+            @Component(isRoot = false) interface SubComponent {
+                fun createSub2(): Sub2Component
+            }
+
+            @Component(isRoot = false) interface Sub2Component {
+                val dummy: Optional<ClassB>
+            }
+        """.trimIndent())
+
+        compileRunAndValidate()
+    }
+
     @Test
     fun `conditional provide - basic case`() {
         includeFromSourceSet(features)
diff --git a/testing/tests/src/test/resources/golden/ConditionsTest/issue-85-conditions-in-component-hierarchy-with-usage-gaps.golden-code.txt b/testing/tests/src/test/resources/golden/ConditionsTest/issue-85-conditions-in-component-hierarchy-with-usage-gaps.golden-code.txt
new file mode 100644
index 00000000..e9a2a610
--- /dev/null
+++ b/testing/tests/src/test/resources/golden/ConditionsTest/issue-85-conditions-in-component-hierarchy-with-usage-gaps.golden-code.txt
@@ -0,0 +1,84 @@
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Name: test/Yatagan$RootComponent.java
+package test;
+
+import com.yandex.yatagan.AutoBuilder;
+import com.yandex.yatagan.Optional;
+import com.yandex.yatagan.internal.Checks;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.SuppressWarnings;
+import java.util.Collections;
+
+@SuppressWarnings({"unchecked", "rawtypes", "NullableProblems"})
+public final class Yatagan$RootComponent implements RootComponent {
+  final boolean mFeaturesFooBar = Features.fooBar();
+
+  private Yatagan$RootComponent() {
+  }
+
+  @Override
+  public SubComponent createSub() {
+    return new SubComponentImpl(this);
+  }
+
+  @Override
+  public Optional<ClassA> getDummy() {
+    return this.optOfClassA();
+  }
+
+  Optional optOfClassA() {
+    return this.mFeaturesFooBar ? Optional.of(new ClassA()) : Optional.empty();
+  }
+
+  public static AutoBuilder<Yatagan$RootComponent> autoBuilder() {
+    return new AutoBuilderImpl();
+  }
+
+  @SuppressWarnings({"unchecked", "rawtypes", "NullableProblems"})
+  static final class SubComponentImpl implements SubComponent {
+    final Yatagan$RootComponent mRootComponent;
+
+    private SubComponentImpl(Yatagan$RootComponent pRootComponent) {
+      this.mRootComponent = pRootComponent;
+    }
+
+    @Override
+    public Sub2Component createSub2() {
+      return new Sub2ComponentImpl(this.mRootComponent);
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes", "NullableProblems"})
+    static final class Sub2ComponentImpl implements Sub2Component {
+      final Yatagan$RootComponent mRootComponent;
+
+      private Sub2ComponentImpl(Yatagan$RootComponent pRootComponent) {
+        this.mRootComponent = pRootComponent;
+      }
+
+      @Override
+      public Optional<ClassB> getDummy() {
+        return this.optOfClassB();
+      }
+
+      Optional optOfClassB() {
+        return this.mRootComponent.mFeaturesFooBar ? Optional.of(new ClassB()) : Optional.empty();
+      }
+    }
+  }
+
+  private static final class AutoBuilderImpl implements AutoBuilder<Yatagan$RootComponent> {
+    @Override
+    public final <I> AutoBuilder<Yatagan$RootComponent> provideInput(I input, Class<I> inputClass) {
+      Checks.reportUnexpectedAutoBuilderInput(input.getClass(), Collections.emptyList());
+      return this;
+    }
+
+    @Override
+    public final Yatagan$RootComponent create() {
+      return new Yatagan$RootComponent();
+    }
+  }
+}
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~