Skip to content

Commit 757d709

Browse files
bcorsoDagger Team
authored andcommitted
Add better error message for @Module-annotated Kotlin object with invalid instance methods inherited from superclass.
See details in b/264618194. RELNOTES=N/A PiperOrigin-RevId: 506327206
1 parent ebf8e9e commit 757d709

3 files changed

Lines changed: 140 additions & 1 deletion

File tree

java/dagger/internal/codegen/validation/ModuleValidator.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ private ValidationReport validateUncached(XTypeElement module, Set<XTypeElement>
176176
}
177177
}
178178

179+
validateKotlinObjectDoesNotInheritInstanceBindingMethods(module, moduleKind, builder);
179180
validateDaggerAndroidProcessorRequirements(module, builder);
180181

181182
if (bindingMethods.stream()
@@ -239,6 +240,29 @@ private void validateDaggerAndroidProcessorRequirements(
239240
.build()));
240241
}
241242

243+
private void validateKotlinObjectDoesNotInheritInstanceBindingMethods(
244+
XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder builder) {
245+
if (!module.isKotlinObject()) {
246+
return;
247+
}
248+
XTypeElement currentClass = module;
249+
while (!currentClass.getSuperType().getTypeName().equals(TypeName.OBJECT)) {
250+
currentClass = currentClass.getSuperType().getTypeElement();
251+
currentClass.getDeclaredMethods().stream()
252+
.filter(anyBindingMethodValidator::isBindingMethod)
253+
.filter(method -> ModuleMethodKind.ofMethod(method) == INSTANCE_BINDING)
254+
.forEach(
255+
method ->
256+
// TODO(b/264618194): Consider allowing this use case.
257+
builder.addError(
258+
String.format(
259+
"@%s-annotated Kotlin object cannot inherit instance (i.e. non-abstract, "
260+
+ "non-JVM static) binding method: %s",
261+
moduleKind.annotation().simpleName(),
262+
methodSignatureFormatter.format(method))));
263+
}
264+
}
265+
242266
private void validateReferencedSubcomponents(
243267
XTypeElement subject, ModuleKind moduleKind, ValidationReport.Builder builder) {
244268
XAnnotation moduleAnnotation = moduleKind.getModuleAnnotation(subject);

java/dagger/testing/compile/CompilerTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ public static XProcessingEnv.Backend backend(CompilationResultSubject subject) {
7070
String output = subject.getCompilationResult().toString();
7171
if (output.startsWith("CompilationResult (with ksp)")) {
7272
return XProcessingEnv.Backend.KSP;
73-
} else if (output.startsWith("CompilationResult (with javac)")) {
73+
} else if (output.startsWith("CompilationResult (with javac)")
74+
|| output.startsWith("CompilationResult (with kapt)")) {
7475
return XProcessingEnv.Backend.JAVAC;
7576
}
7677
throw new AssertionError("Unexpected backend for subject.");

javatests/dagger/internal/codegen/ModuleValidationTest.java

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatModuleMethod;
2020

21+
import androidx.room.compiler.processing.XProcessingEnv;
2122
import androidx.room.compiler.processing.util.Source;
2223
import dagger.Module;
2324
import dagger.producers.ProducerModule;
@@ -463,4 +464,117 @@ public void moduleIncludesSelfCycle() {
463464
}
464465
});
465466
}
467+
468+
// Regression test for b/264618194.
469+
@Test
470+
public void objectModuleInheritsInstanceBindingFails() {
471+
Source objectModule =
472+
CompilerTests.kotlinSource(
473+
"test.ObjectModule.kt",
474+
"package test",
475+
"",
476+
"import dagger.Module",
477+
"import dagger.Provides",
478+
"",
479+
"@Module",
480+
"object ObjectModule : ClassModule() {",
481+
" @Provides fun provideString(): String = \"\"",
482+
"}");
483+
Source classModule =
484+
CompilerTests.kotlinSource(
485+
"test.ClassModule.kt",
486+
"package test",
487+
"",
488+
"import dagger.Module",
489+
"import dagger.Provides",
490+
"",
491+
"@Module",
492+
"abstract class ClassModule {",
493+
" @Provides fun provideInt(): Int = 1",
494+
"}");
495+
Source component =
496+
CompilerTests.kotlinSource(
497+
"test.TestComponent.kt",
498+
"package test",
499+
"",
500+
"import dagger.Component",
501+
"",
502+
"@Component(modules = [ObjectModule::class])",
503+
"interface TestComponent {",
504+
" fun getInt(): Int",
505+
" fun getString(): String",
506+
"}");
507+
508+
CompilerTests.daggerCompiler(component, objectModule, classModule)
509+
.compile(
510+
subject -> {
511+
subject.hasErrorCount(2);
512+
subject.hasErrorContaining("test.ObjectModule has errors")
513+
.onSource(component)
514+
.onLineContaining("ObjectModule::class");
515+
subject.hasErrorContaining(
516+
"@Module-annotated Kotlin object cannot inherit instance "
517+
+ "(i.e. non-abstract, non-JVM static) binding method: "
518+
+ "@Provides int test.ClassModule.provideInt()")
519+
.onSource(objectModule)
520+
.onLineContaining(
521+
// TODO(b/267223703): KAPT incorrectly reports the error on the annotation.
522+
CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC
523+
? "@Module"
524+
: "object ObjectModule");
525+
});
526+
}
527+
528+
// Regression test for b/264618194.
529+
@Test
530+
public void objectModuleInheritsNonInstanceBindingSucceeds() {
531+
Source objectModule =
532+
CompilerTests.kotlinSource(
533+
"test.ObjectModule.kt",
534+
"package test",
535+
"",
536+
"import dagger.Module",
537+
"import dagger.Provides",
538+
"",
539+
"@Module",
540+
"object ObjectModule : ClassModule() {",
541+
" @Provides fun provideString(): String = \"\"",
542+
"}");
543+
Source classModule =
544+
CompilerTests.javaSource(
545+
"test.ClassModule",
546+
"package test;",
547+
"",
548+
"import dagger.Binds;",
549+
"import dagger.Module;",
550+
"import dagger.Provides;",
551+
"",
552+
"@Module",
553+
"public abstract class ClassModule {",
554+
" // A non-binding instance method is okay.",
555+
" public int nonBindingMethod() {",
556+
" return 1;",
557+
" }",
558+
"",
559+
" // A static binding method is also okay.",
560+
" @Provides",
561+
" public static int provideInt() {",
562+
" return 1;",
563+
" }",
564+
"}");
565+
Source component =
566+
CompilerTests.kotlinSource(
567+
"test.TestComponent.kt",
568+
"package test",
569+
"",
570+
"import dagger.Component",
571+
"",
572+
"@Component(modules = [ObjectModule::class])",
573+
"interface TestComponent {",
574+
" fun getInt(): Int",
575+
" fun getString(): String",
576+
"}");
577+
CompilerTests.daggerCompiler(component, objectModule, classModule)
578+
.compile(subject -> subject.hasErrorCount(0));
579+
}
466580
}

0 commit comments

Comments
 (0)