Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
* <li>Validates scope hierarchy of component dependencies and subcomponents.
* <li>Reports errors if there are component dependency cycles.
* <li>Reports errors if any abstract modules have non-abstract instance binding methods.
* <li>Reports errors if modules from dependencies are missing generated Dagger factories.
* <li>Validates component creator types.
* </ul>
*/
Expand All @@ -99,19 +100,22 @@ public final class ComponentDescriptorValidator {
private final ComponentHierarchyValidator componentHierarchyValidator;
private final InjectionAnnotations injectionAnnotations;
private final DaggerSuperficialValidation superficialValidation;
private final ModuleValidator moduleValidator;

@Inject
ComponentDescriptorValidator(
CompilerOptions compilerOptions,
MethodSignatureFormatter methodSignatureFormatter,
ComponentHierarchyValidator componentHierarchyValidator,
InjectionAnnotations injectionAnnotations,
DaggerSuperficialValidation superficialValidation) {
DaggerSuperficialValidation superficialValidation,
ModuleValidator moduleValidator) {
this.compilerOptions = compilerOptions;
this.methodSignatureFormatter = methodSignatureFormatter;
this.componentHierarchyValidator = componentHierarchyValidator;
this.injectionAnnotations = injectionAnnotations;
this.superficialValidation = superficialValidation;
this.moduleValidator = moduleValidator;
}

public ValidationReport validate(ComponentDescriptor component) {
Expand Down Expand Up @@ -260,6 +264,9 @@ private void validateModules(ComponentDescriptor component) {
}
}
}
// Detect modules from dependencies that were not processed by the Dagger compiler
// (missing *Factory types). See https://github.com/google/dagger/issues/5146.
moduleValidator.checkGeneratedFactoriesAvailable(module, report(component));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
import static dagger.internal.codegen.base.ModuleAnnotation.isModuleAnnotation;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.getSubcomponentCreator;
import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding;
import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.model.BindingKind.PRODUCTION;
import static dagger.internal.codegen.model.BindingKind.PROVISION;
import static dagger.internal.codegen.validation.ModuleValidator.ModuleMethodKind.ABSTRACT_DECLARATION;
import static dagger.internal.codegen.validation.ModuleValidator.ModuleMethodKind.INSTANCE_BINDING;
import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName;
Expand Down Expand Up @@ -62,8 +65,10 @@
import dagger.internal.codegen.binding.BindingGraphFactory;
import dagger.internal.codegen.binding.ComponentDescriptor;
import dagger.internal.codegen.binding.ComponentRequirement;
import dagger.internal.codegen.binding.ContributionBinding;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.binding.MethodSignatureFormatter;
import dagger.internal.codegen.binding.ModuleDescriptor;
import dagger.internal.codegen.model.BindingGraph;
import dagger.internal.codegen.model.Scope;
import dagger.internal.codegen.xprocessing.XElements;
Expand Down Expand Up @@ -119,6 +124,8 @@ public final class ModuleValidator {
private final XProcessingEnv processingEnv;
private final Map<XTypeElement, ValidationReport> cache = new HashMap<>();
private final Set<XTypeElement> knownModules = new HashSet<>();
/** Qualified names of modules being compiled in this compilation (see {@link #knownModules}). */
private final Set<String> knownModuleNames = new HashSet<>();

@Inject
ModuleValidator(
Expand Down Expand Up @@ -152,6 +159,58 @@ public final class ModuleValidator {
*/
public void addKnownModules(Collection<XTypeElement> modules) {
knownModules.addAll(modules);
for (XTypeElement module : modules) {
knownModuleNames.add(module.getQualifiedName());
}
}

/**
* Returns {@code true} if {@code module} is being compiled in this compilation (i.e. Dagger will
* generate factories for its binding methods), as opposed to coming from a dependency on the
* classpath.
*/
public boolean isModuleFromCurrentCompilation(XTypeElement module) {
return knownModules.contains(module) || knownModuleNames.contains(module.getQualifiedName());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to check both knownModules and knowModuleNames?


/**
* Reports an error if {@code module} is from a dependency and is missing generated factories for
* its {@code @Provides} / {@code @Produces} methods.
*
* <p>This typically means the Dagger compiler was not applied to the library that defines the
* module. Factories for modules in the current compilation are generated by {@code
* ModuleProcessingStep} and may not yet be visible via {@link XProcessingEnv#findTypeElement}, so
* those modules are skipped.
*
* @see <a href="https://github.com/google/dagger/issues/5146">dagger#5146</a>
*/
public void checkGeneratedFactoriesAvailable(
ModuleDescriptor module, ValidationReport.Builder report) {
XTypeElement moduleElement = module.moduleElement();
if (isModuleFromCurrentCompilation(moduleElement)) {
return;
}
for (ContributionBinding binding : module.bindings()) {
if (binding.kind() != PROVISION && binding.kind() != PRODUCTION) {
continue;
}
if (!binding.bindingElement().isPresent()) {
continue;
}
XClassName factoryName = generatedClassNameForBinding(binding);
if (processingEnv.findTypeElement(factoryName) == null) {
report.addError(
String.format(
"The Dagger factory type %s is missing. This usually means the Dagger compiler was"
+ " not applied to the module %s where the binding is defined. Ensure that the"
+ " dependency that contains that module runs the Dagger annotation processor"
+ " (or KSP plugin).",
factoryName.getCanonicalName(), moduleElement.getQualifiedName()),
moduleElement);
// One diagnostic per module is enough to point users at the root cause.
return;
}
}
}

/** Returns a validation report for a module type. */
Expand Down
135 changes: 135 additions & 0 deletions javatests/dagger/internal/codegen/ModuleMissingFactoryErrorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (C) 2026 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dagger.internal.codegen;

import androidx.room3.compiler.processing.util.Source;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import dagger.testing.compile.CompilerTests;
import java.io.File;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
* Tests that using a {@code @Module} from a library that was not processed by Dagger yields a
* clear error (https://github.com/google/dagger/issues/5146).
*/
@RunWith(Parameterized.class)
public class ModuleMissingFactoryErrorTest {
@Parameters(name = "{0}")
public static ImmutableCollection<Object[]> parameters() {
return CompilerMode.TEST_PARAMETERS;
}

private final CompilerMode compilerMode;

public ModuleMissingFactoryErrorTest(CompilerMode compilerMode) {
this.compilerMode = compilerMode;
}

@Test
public void moduleFromLibraryWithoutDaggerCompiler_reportsHelpfulError() {
Source libraryModule =
CompilerTests.javaSource(
"lib.LibraryModule",
"package lib;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"public final class LibraryModule {",
" @Provides",
" static String provideString() {",
" return \"hello\";",
" }",
"}");

// Compile the library without Dagger's annotation processor so no *Factory is generated.
ImmutableList<File> libraryClasspath = CompilerTests.libraryCompiler(libraryModule).compile();

Source component =
CompilerTests.javaSource(
"test.TestComponent",
"package test;",
"",
"import dagger.Component;",
"import lib.LibraryModule;",
"",
"@Component(modules = LibraryModule.class)",
"interface TestComponent {",
" String string();",
"}");

CompilerTests.daggerCompiler(component)
.withAdditionalClasspath(libraryClasspath)
.withProcessingOptions(compilerMode.processorOptions())
.compile(
subject -> {
subject.hasErrorContaining("LibraryModule_ProvideStringFactory is missing");
subject.hasErrorContaining("Dagger compiler was not applied");
subject.hasErrorContaining("lib.LibraryModule");
});
}

@Test
public void moduleFromLibraryWithOnlyBinds_doesNotRequireFactory() {
Source libraryModule =
CompilerTests.javaSource(
"lib.LibraryBindsModule",
"package lib;",
"",
"import dagger.Binds;",
"import dagger.Module;",
"import javax.inject.Inject;",
"",
"@Module",
"public abstract class LibraryBindsModule {",
" @Binds",
" abstract Object bindObject(Foo foo);",
"",
" public static final class Foo {",
" @Inject",
" Foo() {}",
" }",
"}");

ImmutableList<File> libraryClasspath = CompilerTests.libraryCompiler(libraryModule).compile();

Source component =
CompilerTests.javaSource(
"test.TestComponent",
"package test;",
"",
"import dagger.Component;",
"import lib.LibraryBindsModule;",
"",
"@Component(modules = LibraryBindsModule.class)",
"interface TestComponent {",
" Object object();",
"}");

// @Binds does not generate a module method factory; @Inject on Foo is generated by the
// consuming compilation via InjectBindingRegistry.
CompilerTests.daggerCompiler(component)
.withAdditionalClasspath(libraryClasspath)
.withProcessingOptions(compilerMode.processorOptions())
.compile(subject -> subject.hasErrorCount(0));
}
}