Skip to content

Commit 36b80eb

Browse files
authored
Merge pull request square#459 from google/moe_sync_89_7
Moe sync 9/7
2 parents 7d87dde + aae8de1 commit 36b80eb

File tree

5 files changed

+130
-8
lines changed

5 files changed

+130
-8
lines changed

compiler/src/main/java/dagger/internal/codegen/BindingGraph.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -622,8 +622,19 @@ private ImmutableSet<ContributionBinding> createDelegateBindings(
622622
* delegate key.
623623
*/
624624
private ContributionBinding createDelegateBinding(DelegateDeclaration delegateDeclaration) {
625-
ResolvedBindings resolvedDelegate =
626-
lookUpBindings(delegateDeclaration.delegateRequest().bindingKey());
625+
BindingKey delegateBindingKey = delegateDeclaration.delegateRequest().bindingKey();
626+
627+
if (cycleStack.contains(delegateBindingKey)) {
628+
return provisionBindingFactory.missingDelegate(delegateDeclaration);
629+
}
630+
631+
ResolvedBindings resolvedDelegate;
632+
try {
633+
cycleStack.push(delegateBindingKey);
634+
resolvedDelegate = lookUpBindings(delegateBindingKey);
635+
} finally {
636+
cycleStack.pop();
637+
}
627638
if (resolvedDelegate.contributionBindings().isEmpty()) {
628639
// This is guaranteed to result in a missing binding error, so it doesn't matter if the
629640
// binding is a Provision or Production, except if it is a @IntoMap method, in which

compiler/src/test/java/dagger/internal/codegen/GraphValidationTest.java

+103
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,109 @@ public void falsePositiveCyclicDependencyIndirectionDetected() {
506506
.onLine(28);
507507
}
508508

509+
@Test
510+
public void circularBindsMethods() {
511+
JavaFileObject qualifier =
512+
JavaFileObjects.forSourceLines(
513+
"test.SomeQualifier",
514+
"package test;",
515+
"",
516+
"import javax.inject.Qualifier;",
517+
"",
518+
"@Qualifier @interface SomeQualifier {}");
519+
JavaFileObject module =
520+
JavaFileObjects.forSourceLines(
521+
"test.TestModule",
522+
"package test;",
523+
"",
524+
"import dagger.Binds;",
525+
"import dagger.Module;",
526+
"",
527+
"@Module",
528+
"abstract class TestModule {",
529+
" @Binds abstract Object bindUnqualified(@SomeQualifier Object qualified);",
530+
" @Binds @SomeQualifier abstract Object bindQualified(Object unqualified);",
531+
"}");
532+
JavaFileObject component =
533+
JavaFileObjects.forSourceLines(
534+
"test.TestComponent",
535+
"package test;",
536+
"",
537+
"import dagger.Component;",
538+
"",
539+
"@Component(modules = TestModule.class)",
540+
"interface TestComponent {",
541+
" Object unqualified();",
542+
" @SomeQualifier Object qualified();",
543+
"}");
544+
545+
assertThat(qualifier, module, component)
546+
.processedWith(new ComponentProcessor())
547+
.failsToCompile()
548+
.withErrorContaining(
549+
"test.TestComponent.unqualified() contains a dependency cycle:\n"
550+
+ " java.lang.Object is injected at\n"
551+
+ " test.TestModule.bindQualified(unqualified)\n"
552+
+ " @test.SomeQualifier java.lang.Object is injected at\n"
553+
+ " test.TestModule.bindUnqualified(qualified)\n"
554+
+ " java.lang.Object is provided at\n"
555+
+ " test.TestComponent.unqualified()")
556+
.in(component)
557+
.onLine(7)
558+
.and()
559+
.withErrorContaining(
560+
"test.TestComponent.qualified() contains a dependency cycle:\n"
561+
+ " @test.SomeQualifier java.lang.Object is injected at\n"
562+
+ " test.TestModule.bindUnqualified(qualified)\n"
563+
+ " java.lang.Object is injected at\n"
564+
+ " test.TestModule.bindQualified(unqualified)\n"
565+
+ " @test.SomeQualifier java.lang.Object is provided at\n"
566+
+ " test.TestComponent.qualified()")
567+
.in(component)
568+
.onLine(8);
569+
}
570+
571+
@Test
572+
public void selfReferentialBinds() {
573+
JavaFileObject module =
574+
JavaFileObjects.forSourceLines(
575+
"test.TestModule",
576+
"package test;",
577+
"",
578+
"import dagger.Binds;",
579+
"import dagger.Module;",
580+
"",
581+
"@Module",
582+
"abstract class TestModule {",
583+
" @Binds abstract Object bindToSelf(Object sameKey);",
584+
"}");
585+
JavaFileObject component =
586+
JavaFileObjects.forSourceLines(
587+
"test.TestComponent",
588+
"package test;",
589+
"",
590+
"import dagger.Component;",
591+
"",
592+
"@Component(modules = TestModule.class)",
593+
"interface TestComponent {",
594+
" Object selfReferential();",
595+
"}");
596+
597+
assertThat(module, component)
598+
.processedWith(new ComponentProcessor())
599+
.failsToCompile()
600+
.withErrorContaining(
601+
// TODO(gak): cl/126230644 produces a better error message in this case. Here it isn't
602+
// unclear what is going wrong.
603+
"test.TestComponent.selfReferential() contains a dependency cycle:\n"
604+
+ " java.lang.Object is injected at\n"
605+
+ " test.TestModule.bindToSelf(sameKey)\n"
606+
+ " java.lang.Object is provided at\n"
607+
+ " test.TestComponent.selfReferential()")
608+
.in(component)
609+
.onLine(7);
610+
}
611+
509612
@Test public void duplicateExplicitBindings_ProvidesAndComponentProvision() {
510613
JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer",
511614
"package test;",

core/src/main/java/dagger/Component.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,13 @@
171171
* inherit the <em>entire</em> binding graph from its parent when it is declared. For that reason,
172172
* a subcomponent isn't evaluated for completeness until it is associated with a parent.
173173
*
174-
* <p>Subcomponents are declared via a factory method on a parent component or subcomponent. The
175-
* method may have any name, but must return the subcomponent. The factory method's parameters may
176-
* be any number of the subcomponent's modules, but must at least include those without visible
174+
* <p>Subcomponents are declared by listing the class in the {@link Module#subcomponents()}
175+
* attribute of one of the parent component's modules. This binds the {@link Subcomponent.Builder}
176+
* within the parent component.
177+
*
178+
* <p>Subcomponents may also be declared via a factory method on a parent component or subcomponent.
179+
* The method may have any name, but must return the subcomponent. The factory method's parameters
180+
* may be any number of the subcomponent's modules, but must at least include those without visible
177181
* no-arg constructors. The following is an example of a factory method that creates a
178182
* request-scoped subcomponent from a singleton-scoped parent: <pre><code>
179183
* {@literal @}Singleton {@literal @}Component

core/src/main/java/dagger/Module.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
Class<?>[] includes() default {};
3838

3939
/**
40-
* Any {@link Subcomponent}- or {@link dagger.producers.ProductionSubcomponent}-annotated classes
41-
* which should be children of the component in which this module is installed. A subcomponent may
42-
* be listed in more than one module in a component.
40+
* Any {@link Subcomponent}- or {@code @ProductionSubcomponent}-annotated classes which should be
41+
* children of the component in which this module is installed. A subcomponent may be listed in
42+
* more than one module in a component.
43+
*
44+
* @since 2.7
4345
*/
4446
@Beta
4547
Class<?>[] subcomponents() default {};

producers/src/main/java/dagger/producers/ProducerModule.java

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
* Any {@link dagger.Subcomponent}- or {@link ProductionSubcomponent}-annotated classes which
4747
* should be children of the component in which this module is installed. A subcomponent may be
4848
* listed in more than one module in a component.
49+
*
50+
* @since 2.7
4951
*/
5052
Class<?>[] subcomponents() default {};
5153
}

0 commit comments

Comments
 (0)