• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen.validation;
18 
19 import static com.google.common.collect.Iterables.getOnlyElement;
20 import static dagger.internal.codegen.base.ComponentAnnotation.isComponentAnnotation;
21 import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation;
22 import static dagger.internal.codegen.base.ComponentCreatorAnnotation.getCreatorAnnotations;
23 import static dagger.internal.codegen.base.ModuleAnnotation.isModuleAnnotation;
24 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
25 import static dagger.internal.codegen.binding.ConfigurationAnnotations.getSubcomponentCreator;
26 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
27 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
28 import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName;
29 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
30 import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation;
31 import static dagger.internal.codegen.xprocessing.XTypeElements.hasTypeParameters;
32 import static dagger.internal.codegen.xprocessing.XTypeElements.isEffectivelyPrivate;
33 import static dagger.internal.codegen.xprocessing.XTypeElements.isEffectivelyPublic;
34 import static dagger.internal.codegen.xprocessing.XTypes.areEquivalentTypes;
35 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
36 import static java.util.stream.Collectors.joining;
37 import static kotlin.streams.jdk8.StreamsKt.asStream;
38 
39 import androidx.room.compiler.processing.XAnnotation;
40 import androidx.room.compiler.processing.XAnnotationValue;
41 import androidx.room.compiler.processing.XMethodElement;
42 import androidx.room.compiler.processing.XProcessingEnv;
43 import androidx.room.compiler.processing.XType;
44 import androidx.room.compiler.processing.XTypeElement;
45 import com.google.common.base.Joiner;
46 import com.google.common.collect.ImmutableList;
47 import com.google.common.collect.ImmutableListMultimap;
48 import com.google.common.collect.ImmutableSet;
49 import com.google.common.collect.ListMultimap;
50 import com.google.common.collect.MultimapBuilder;
51 import com.google.common.collect.Multimaps;
52 import com.google.common.collect.Sets;
53 import com.squareup.javapoet.ClassName;
54 import com.squareup.javapoet.TypeName;
55 import dagger.internal.codegen.base.ComponentCreatorAnnotation;
56 import dagger.internal.codegen.base.DaggerSuperficialValidation;
57 import dagger.internal.codegen.base.ModuleKind;
58 import dagger.internal.codegen.binding.BindingGraphFactory;
59 import dagger.internal.codegen.binding.ComponentDescriptorFactory;
60 import dagger.internal.codegen.binding.InjectionAnnotations;
61 import dagger.internal.codegen.binding.MethodSignatureFormatter;
62 import dagger.internal.codegen.javapoet.TypeNames;
63 import dagger.internal.codegen.xprocessing.XElements;
64 import dagger.spi.model.BindingGraph;
65 import dagger.spi.model.Scope;
66 import java.util.ArrayList;
67 import java.util.Collection;
68 import java.util.EnumSet;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.List;
72 import java.util.Map;
73 import java.util.Optional;
74 import java.util.Set;
75 import javax.inject.Inject;
76 import javax.inject.Singleton;
77 
78 /**
79  * A {@linkplain ValidationReport validator} for {@link dagger.Module}s or {@link
80  * dagger.producers.ProducerModule}s.
81  */
82 @Singleton
83 public final class ModuleValidator {
84   private static final ImmutableSet<ClassName> SUBCOMPONENT_TYPES =
85       ImmutableSet.of(TypeNames.SUBCOMPONENT, TypeNames.PRODUCTION_SUBCOMPONENT);
86   private static final ImmutableSet<ClassName> SUBCOMPONENT_CREATOR_TYPES =
87       ImmutableSet.of(
88           TypeNames.SUBCOMPONENT_BUILDER,
89           TypeNames.SUBCOMPONENT_FACTORY,
90           TypeNames.PRODUCTION_SUBCOMPONENT_BUILDER,
91           TypeNames.PRODUCTION_SUBCOMPONENT_FACTORY);
92   private static final Optional<Class<?>> ANDROID_PROCESSOR;
93   private static final String CONTRIBUTES_ANDROID_INJECTOR_NAME =
94       "dagger.android.ContributesAndroidInjector";
95   private static final String ANDROID_PROCESSOR_NAME = "dagger.android.processor.AndroidProcessor";
96 
97   static {
98     Class<?> clazz;
99     try {
100       clazz = Class.forName(ANDROID_PROCESSOR_NAME, false, ModuleValidator.class.getClassLoader());
101     } catch (ClassNotFoundException ignored) {
102       clazz = null;
103     }
104     ANDROID_PROCESSOR = Optional.ofNullable(clazz);
105   }
106 
107   private final AnyBindingMethodValidator anyBindingMethodValidator;
108   private final MethodSignatureFormatter methodSignatureFormatter;
109   private final ComponentDescriptorFactory componentDescriptorFactory;
110   private final BindingGraphFactory bindingGraphFactory;
111   private final BindingGraphValidator bindingGraphValidator;
112   private final InjectionAnnotations injectionAnnotations;
113   private final DaggerSuperficialValidation superficialValidation;
114   private final XProcessingEnv processingEnv;
115   private final Map<XTypeElement, ValidationReport> cache = new HashMap<>();
116   private final Set<XTypeElement> knownModules = new HashSet<>();
117 
118   @Inject
ModuleValidator( AnyBindingMethodValidator anyBindingMethodValidator, MethodSignatureFormatter methodSignatureFormatter, ComponentDescriptorFactory componentDescriptorFactory, BindingGraphFactory bindingGraphFactory, BindingGraphValidator bindingGraphValidator, InjectionAnnotations injectionAnnotations, DaggerSuperficialValidation superficialValidation, XProcessingEnv processingEnv)119   ModuleValidator(
120       AnyBindingMethodValidator anyBindingMethodValidator,
121       MethodSignatureFormatter methodSignatureFormatter,
122       ComponentDescriptorFactory componentDescriptorFactory,
123       BindingGraphFactory bindingGraphFactory,
124       BindingGraphValidator bindingGraphValidator,
125       InjectionAnnotations injectionAnnotations,
126       DaggerSuperficialValidation superficialValidation,
127       XProcessingEnv processingEnv) {
128     this.anyBindingMethodValidator = anyBindingMethodValidator;
129     this.methodSignatureFormatter = methodSignatureFormatter;
130     this.componentDescriptorFactory = componentDescriptorFactory;
131     this.bindingGraphFactory = bindingGraphFactory;
132     this.bindingGraphValidator = bindingGraphValidator;
133     this.injectionAnnotations = injectionAnnotations;
134     this.superficialValidation = superficialValidation;
135     this.processingEnv = processingEnv;
136   }
137 
138   /**
139    * Adds {@code modules} to the set of module types that will be validated during this compilation
140    * step. If a component or module includes a module that is not in this set, that included module
141    * is assumed to be valid because it was processed in a previous compilation step. If it were
142    * invalid, that previous compilation step would have failed and blocked this one.
143    *
144    * <p>This logic depends on this method being called before {@linkplain #validate(XTypeElement)
145    * validating} any module or {@linkplain #validateReferencedModules(XTypeElement, ModuleKind, Set,
146    * DiagnosticReporter.Builder) component}.
147    */
addKnownModules(Collection<XTypeElement> modules)148   public void addKnownModules(Collection<XTypeElement> modules) {
149     knownModules.addAll(modules);
150   }
151 
152   /** Returns a validation report for a module type. */
validate(XTypeElement module)153   public ValidationReport validate(XTypeElement module) {
154     return validate(module, new HashSet<>());
155   }
156 
validate(XTypeElement module, Set<XTypeElement> visitedModules)157   private ValidationReport validate(XTypeElement module, Set<XTypeElement> visitedModules) {
158     if (visitedModules.add(module)) {
159       return reentrantComputeIfAbsent(cache, module, m -> validateUncached(module, visitedModules));
160     }
161     return ValidationReport.about(module).build();
162   }
163 
validateUncached(XTypeElement module, Set<XTypeElement> visitedModules)164   private ValidationReport validateUncached(XTypeElement module, Set<XTypeElement> visitedModules) {
165     ValidationReport.Builder builder = ValidationReport.about(module);
166     ModuleKind moduleKind = ModuleKind.forAnnotatedElement(module).get();
167     Optional<XType> contributesAndroidInjector =
168         Optional.ofNullable(processingEnv.findTypeElement(CONTRIBUTES_ANDROID_INJECTOR_NAME))
169             .map(XTypeElement::getType);
170     List<XMethodElement> moduleMethods = module.getDeclaredMethods();
171     List<XMethodElement> bindingMethods = new ArrayList<>();
172     for (XMethodElement moduleMethod : moduleMethods) {
173       if (anyBindingMethodValidator.isBindingMethod(moduleMethod)) {
174         builder.addSubreport(anyBindingMethodValidator.validate(moduleMethod));
175         bindingMethods.add(moduleMethod);
176       }
177 
178       for (XAnnotation annotation : moduleMethod.getAllAnnotations()) {
179         if (!ANDROID_PROCESSOR.isPresent()
180             && contributesAndroidInjector.isPresent()
181             && areEquivalentTypes(contributesAndroidInjector.get(), annotation.getType())) {
182           builder.addSubreport(
183               ValidationReport.about(moduleMethod)
184                   .addError(
185                       String.format(
186                           "@%s was used, but %s was not found on the processor path",
187                           CONTRIBUTES_ANDROID_INJECTOR_NAME, ANDROID_PROCESSOR_NAME))
188                   .build());
189           break;
190         }
191       }
192     }
193 
194     if (bindingMethods.stream()
195         .map(ModuleMethodKind::ofMethod)
196         .collect(toImmutableSet())
197         .containsAll(
198             EnumSet.of(ModuleMethodKind.ABSTRACT_DECLARATION, ModuleMethodKind.INSTANCE_BINDING))) {
199       builder.addError(
200           String.format(
201               "A @%s may not contain both non-static and abstract binding methods",
202               moduleKind.annotation().simpleName()));
203     }
204 
205     validateModuleVisibility(module, moduleKind, builder);
206 
207     ImmutableListMultimap<String, XMethodElement> bindingMethodsByName =
208         Multimaps.index(bindingMethods, XElements::getSimpleName);
209 
210     validateMethodsWithSameName(builder, bindingMethodsByName);
211     if (!module.isInterface()) {
212       validateBindingMethodOverrides(
213           module,
214           builder,
215           Multimaps.index(moduleMethods, XElements::getSimpleName),
216           bindingMethodsByName);
217     }
218     validateModifiers(module, builder);
219     validateReferencedModules(module, moduleKind, visitedModules, builder);
220     validateReferencedSubcomponents(module, moduleKind, builder);
221     validateNoScopeAnnotationsOnModuleElement(module, moduleKind, builder);
222     validateSelfCycles(module, moduleKind, builder);
223     module.getEnclosedTypeElements().stream()
224         .filter(XTypeElement::isCompanionObject)
225         .collect(toOptional())
226         .ifPresent(companionModule -> validateCompanionModule(companionModule, builder));
227 
228     if (builder.build().isClean()
229         && bindingGraphValidator.shouldDoFullBindingGraphValidation(module)) {
230       validateModuleBindings(module, builder);
231     }
232 
233     return builder.build();
234   }
235 
validateReferencedSubcomponents( XTypeElement subject, ModuleKind moduleKind, ValidationReport.Builder builder)236   private void validateReferencedSubcomponents(
237       XTypeElement subject, ModuleKind moduleKind, ValidationReport.Builder builder) {
238     XAnnotation moduleAnnotation = moduleKind.getModuleAnnotation(subject);
239     for (XAnnotationValue subcomponentValue :
240         moduleAnnotation.getAsAnnotationValueList("subcomponents")) {
241       XType type = subcomponentValue.asType();
242       if (!isDeclared(type)) {
243         builder.addError(
244             type + " is not a valid subcomponent type",
245             subject,
246             moduleAnnotation,
247             subcomponentValue);
248         continue;
249       }
250 
251       XTypeElement subcomponentElement = type.getTypeElement();
252       if (hasAnyAnnotation(subcomponentElement, SUBCOMPONENT_TYPES)) {
253         validateSubcomponentHasBuilder(subject, subcomponentElement, moduleAnnotation, builder);
254       } else {
255         builder.addError(
256             hasAnyAnnotation(subcomponentElement, SUBCOMPONENT_CREATOR_TYPES)
257                 ? moduleSubcomponentsIncludesCreator(subcomponentElement)
258                 : moduleSubcomponentsIncludesNonSubcomponent(subcomponentElement),
259             subject,
260             moduleAnnotation,
261             subcomponentValue);
262       }
263     }
264   }
265 
moduleSubcomponentsIncludesNonSubcomponent(XTypeElement notSubcomponent)266   private static String moduleSubcomponentsIncludesNonSubcomponent(XTypeElement notSubcomponent) {
267     return notSubcomponent.getQualifiedName()
268         + " is not a @Subcomponent or @ProductionSubcomponent";
269   }
270 
moduleSubcomponentsIncludesCreator(XTypeElement moduleSubcomponentsAttribute)271   private String moduleSubcomponentsIncludesCreator(XTypeElement moduleSubcomponentsAttribute) {
272     XTypeElement subcomponentType = moduleSubcomponentsAttribute.getEnclosingTypeElement();
273     ComponentCreatorAnnotation creatorAnnotation =
274         getOnlyElement(getCreatorAnnotations(moduleSubcomponentsAttribute));
275     return String.format(
276         "%s is a @%s.%s. Did you mean to use %s?",
277         moduleSubcomponentsAttribute.getQualifiedName(),
278         subcomponentAnnotation(subcomponentType, superficialValidation).get().simpleName(),
279         creatorAnnotation.creatorKind().typeName(),
280         subcomponentType.getQualifiedName());
281   }
282 
validateSubcomponentHasBuilder( XTypeElement subject, XTypeElement subcomponentAttribute, XAnnotation moduleAnnotation, ValidationReport.Builder builder)283   private void validateSubcomponentHasBuilder(
284       XTypeElement subject,
285       XTypeElement subcomponentAttribute,
286       XAnnotation moduleAnnotation,
287       ValidationReport.Builder builder) {
288     if (getSubcomponentCreator(subcomponentAttribute).isPresent()) {
289       return;
290     }
291     builder.addError(
292         moduleSubcomponentsDoesntHaveCreator(subcomponentAttribute, moduleAnnotation),
293         subject,
294         moduleAnnotation);
295   }
296 
moduleSubcomponentsDoesntHaveCreator( XTypeElement subcomponent, XAnnotation moduleAnnotation)297   private String moduleSubcomponentsDoesntHaveCreator(
298       XTypeElement subcomponent, XAnnotation moduleAnnotation) {
299     return String.format(
300         "%1$s doesn't have a @%2$s.Builder or @%2$s.Factory, which is required when used with "
301             + "@%3$s.subcomponents",
302         subcomponent.getQualifiedName(),
303         subcomponentAnnotation(subcomponent, superficialValidation).get().simpleName(),
304         getClassName(moduleAnnotation).simpleName());
305   }
306 
307   enum ModuleMethodKind {
308     ABSTRACT_DECLARATION,
309     INSTANCE_BINDING,
310     STATIC_BINDING,
311     ;
312 
ofMethod(XMethodElement moduleMethod)313     static ModuleMethodKind ofMethod(XMethodElement moduleMethod) {
314       if (moduleMethod.isStatic()) {
315         return STATIC_BINDING;
316       } else if (moduleMethod.isAbstract()) {
317         return ABSTRACT_DECLARATION;
318       } else {
319         return INSTANCE_BINDING;
320       }
321     }
322   }
323 
validateModifiers(XTypeElement subject, ValidationReport.Builder builder)324   private void validateModifiers(XTypeElement subject, ValidationReport.Builder builder) {
325     // This coupled with the check for abstract modules in ComponentValidator guarantees that
326     // only modules without type parameters are referenced from @Component(modules={...}).
327     if (hasTypeParameters(subject) && !subject.isAbstract()) {
328       builder.addError("Modules with type parameters must be abstract", subject);
329     }
330   }
331 
validateMethodsWithSameName( ValidationReport.Builder builder, ListMultimap<String, XMethodElement> bindingMethodsByName)332   private void validateMethodsWithSameName(
333       ValidationReport.Builder builder, ListMultimap<String, XMethodElement> bindingMethodsByName) {
334     bindingMethodsByName.asMap().values().stream()
335         .filter(methods -> methods.size() > 1)
336         .flatMap(Collection::stream)
337         .forEach(
338             duplicateMethod -> {
339               builder.addError(
340                   "Cannot have more than one binding method with the same name in a single module",
341                   duplicateMethod);
342             });
343   }
344 
validateReferencedModules( XTypeElement subject, ModuleKind moduleKind, Set<XTypeElement> visitedModules, ValidationReport.Builder builder)345   private void validateReferencedModules(
346       XTypeElement subject,
347       ModuleKind moduleKind,
348       Set<XTypeElement> visitedModules,
349       ValidationReport.Builder builder) {
350     // Validate that all the modules we include are valid for inclusion.
351     XAnnotation mirror = moduleKind.getModuleAnnotation(subject);
352     builder.addSubreport(
353         validateReferencedModules(
354             subject, mirror, moduleKind.legalIncludedModuleKinds(), visitedModules));
355   }
356 
357   /**
358    * Validates modules included in a given module or installed in a given component.
359    *
360    * <p>Checks that the referenced modules are non-generic types annotated with {@code @Module} or
361    * {@code @ProducerModule}.
362    *
363    * <p>If the referenced module is in the {@linkplain #addKnownModules(Collection) known modules
364    * set} and has errors, reports an error at that module's inclusion.
365    *
366    * @param annotatedType the annotated module or component
367    * @param annotation the annotation specifying the referenced modules ({@code @Component},
368    *     {@code @ProductionComponent}, {@code @Subcomponent}, {@code @ProductionSubcomponent},
369    *     {@code @Module}, or {@code @ProducerModule})
370    * @param validModuleKinds the module kinds that the annotated type is permitted to include
371    */
validateReferencedModules( XTypeElement annotatedType, XAnnotation annotation, ImmutableSet<ModuleKind> validModuleKinds, Set<XTypeElement> visitedModules)372   ValidationReport validateReferencedModules(
373       XTypeElement annotatedType,
374       XAnnotation annotation,
375       ImmutableSet<ModuleKind> validModuleKinds,
376       Set<XTypeElement> visitedModules) {
377     superficialValidation.validateAnnotationOf(annotatedType, annotation);
378 
379     ValidationReport.Builder subreport = ValidationReport.about(annotatedType);
380     // TODO(bcorso): Consider creating a DiagnosticLocation object to encapsulate the location in a
381     // single object to avoid duplication across all reported errors
382     for (XAnnotationValue includedModule : getModules(annotation)) {
383       XType type = includedModule.asType();
384       if (!isDeclared(type)) {
385         subreport.addError(
386             String.format("%s is not a valid module type.", type),
387             annotatedType,
388             annotation,
389             includedModule);
390         continue;
391       }
392 
393       XTypeElement module = type.getTypeElement();
394       if (hasTypeParameters(module)) {
395         subreport.addError(
396             String.format(
397                 "%s is listed as a module, but has type parameters", module.getQualifiedName()),
398             annotatedType,
399             annotation,
400             includedModule);
401       }
402 
403       ImmutableSet<ClassName> validModuleAnnotations =
404           validModuleKinds.stream().map(ModuleKind::annotation).collect(toImmutableSet());
405       if (!hasAnyAnnotation(module, validModuleAnnotations)) {
406         subreport.addError(
407             String.format(
408                 "%s is listed as a module, but is not annotated with %s",
409                 module.getQualifiedName(),
410                 (validModuleAnnotations.size() > 1 ? "one of " : "")
411                     + validModuleAnnotations.stream()
412                         .map(otherClass -> "@" + otherClass.simpleName())
413                         .collect(joining(", "))),
414             annotatedType,
415             annotation,
416             includedModule);
417       } else if (knownModules.contains(module) && !validate(module, visitedModules).isClean()) {
418         subreport.addError(
419             String.format("%s has errors", module.getQualifiedName()),
420             annotatedType,
421             annotation,
422             includedModule);
423       }
424       if (module.isCompanionObject()) {
425         subreport.addError(
426             String.format(
427                 "%s is listed as a module, but it is a companion object class. "
428                     + "Add @Module to the enclosing class and reference that instead.",
429                 module.getQualifiedName()),
430             annotatedType,
431             annotation,
432             includedModule);
433       }
434     }
435     return subreport.build();
436   }
437 
getModules(XAnnotation annotation)438   private static ImmutableList<XAnnotationValue> getModules(XAnnotation annotation) {
439     if (isModuleAnnotation(annotation)) {
440       return ImmutableList.copyOf(annotation.getAsAnnotationValueList("includes"));
441     }
442     if (isComponentAnnotation(annotation)) {
443       return ImmutableList.copyOf(annotation.getAsAnnotationValueList("modules"));
444     }
445     throw new IllegalArgumentException(String.format("unsupported annotation: %s", annotation));
446   }
447 
validateBindingMethodOverrides( XTypeElement subject, ValidationReport.Builder builder, ImmutableListMultimap<String, XMethodElement> moduleMethodsByName, ImmutableListMultimap<String, XMethodElement> bindingMethodsByName)448   private void validateBindingMethodOverrides(
449       XTypeElement subject,
450       ValidationReport.Builder builder,
451       ImmutableListMultimap<String, XMethodElement> moduleMethodsByName,
452       ImmutableListMultimap<String, XMethodElement> bindingMethodsByName) {
453     // For every binding method, confirm it overrides nothing *and* nothing overrides it.
454     // Consider the following hierarchy:
455     // class Parent {
456     //    @Provides Foo a() {}
457     //    @Provides Foo b() {}
458     //    Foo c() {}
459     // }
460     // class Child extends Parent {
461     //    @Provides Foo a() {}
462     //    Foo b() {}
463     //    @Provides Foo c() {}
464     // }
465     // In each of those cases, we want to fail.  "a" is clear, "b" because Child is overriding
466     // a binding method in Parent, and "c" because Child is defining a binding method that overrides
467     // Parent.
468     XTypeElement currentClass = subject;
469     XType objectType = processingEnv.findType(TypeName.OBJECT);
470     // We keep track of visited methods so we don't spam with multiple failures.
471     Set<XMethodElement> visitedMethods = Sets.newHashSet();
472     ListMultimap<String, XMethodElement> allMethodsByName =
473         MultimapBuilder.hashKeys().arrayListValues().build(moduleMethodsByName);
474 
475     while (!currentClass.getSuperType().isSameType(objectType)) {
476       currentClass = currentClass.getSuperType().getTypeElement();
477       List<XMethodElement> superclassMethods = currentClass.getDeclaredMethods();
478       for (XMethodElement superclassMethod : superclassMethods) {
479         String name = getSimpleName(superclassMethod);
480         // For each method in the superclass, confirm our binding methods don't override it
481         for (XMethodElement bindingMethod : bindingMethodsByName.get(name)) {
482           if (visitedMethods.add(bindingMethod)
483               && bindingMethod.overrides(superclassMethod, subject)) {
484             builder.addError(
485                 String.format(
486                     "Binding methods may not override another method. Overrides: %s",
487                     methodSignatureFormatter.format(superclassMethod)),
488                 bindingMethod);
489           }
490         }
491         // For each binding method in superclass, confirm our methods don't override it.
492         if (anyBindingMethodValidator.isBindingMethod(superclassMethod)) {
493           for (XMethodElement method : allMethodsByName.get(name)) {
494             if (visitedMethods.add(method) && method.overrides(superclassMethod, subject)) {
495               builder.addError(
496                   String.format(
497                       "Binding methods may not be overridden in modules. Overrides: %s",
498                       methodSignatureFormatter.format(superclassMethod)),
499                   method);
500             }
501           }
502         }
503         // TODO(b/202521399): Add a test for cases that add to this map.
504         allMethodsByName.put(getSimpleName(superclassMethod), superclassMethod);
505       }
506     }
507   }
508 
validateModuleVisibility( XTypeElement moduleElement, ModuleKind moduleKind, ValidationReport.Builder reportBuilder)509   private void validateModuleVisibility(
510       XTypeElement moduleElement, ModuleKind moduleKind, ValidationReport.Builder reportBuilder) {
511     if (moduleElement.isPrivate()) {
512       reportBuilder.addError("Modules cannot be private.", moduleElement);
513     } else if (isEffectivelyPrivate(moduleElement)) {
514       reportBuilder.addError("Modules cannot be enclosed in private types.", moduleElement);
515     }
516     if (isEffectivelyPublic(moduleElement)) {
517       ImmutableSet<XTypeElement> invalidVisibilityIncludes =
518           getModuleIncludesWithInvalidVisibility(moduleKind.getModuleAnnotation(moduleElement));
519       if (!invalidVisibilityIncludes.isEmpty()) {
520         reportBuilder.addError(
521             String.format(
522                 "This module is public, but it includes non-public (or effectively non-public) "
523                     + "modules (%s) that have non-static, non-abstract binding methods. Either "
524                     + "reduce the visibility of this module, make the included modules "
525                     + "public, or make all of the binding methods on the included modules "
526                     + "abstract or static.",
527                 formatListForErrorMessage(invalidVisibilityIncludes.asList())),
528             moduleElement);
529       }
530     }
531   }
532 
getModuleIncludesWithInvalidVisibility( XAnnotation moduleAnnotation)533   private ImmutableSet<XTypeElement> getModuleIncludesWithInvalidVisibility(
534       XAnnotation moduleAnnotation) {
535     return moduleAnnotation.getAnnotationValue("includes").asTypeList().stream()
536         .map(XType::getTypeElement)
537         .filter(include -> !isEffectivelyPublic(include))
538         .filter(this::requiresModuleInstance)
539         .collect(toImmutableSet());
540   }
541 
542   /**
543    * Returns {@code true} if a module instance is needed for any of the binding methods on the given
544    * {@code module}. This is the case when the module has any binding methods that are neither
545    * {@code abstract} nor {@code static}. Alternatively, if the module is a Kotlin Object then the
546    * binding methods are considered {@code static}, requiring no module instance.
547    */
requiresModuleInstance(XTypeElement module)548   private boolean requiresModuleInstance(XTypeElement module) {
549     // Note: We use XTypeElement#getAllMethods() rather than XTypeElement#getDeclaredMethods() here
550     // because we need to include binding methods declared in supertypes because unlike most other
551     // validations being done in this class, which assume that supertype binding methods will be
552     // validated in a separate call to the validator since the supertype itself must be a @Module,
553     // we need to look at all the binding methods in the module's type hierarchy here.
554     return !(module.isKotlinObject() || module.isCompanionObject())
555         && !asStream(module.getAllMethods())
556             .filter(anyBindingMethodValidator::isBindingMethod)
557             .allMatch(method -> method.isAbstract() || method.isStatic());
558   }
559 
validateNoScopeAnnotationsOnModuleElement( XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder report)560   private void validateNoScopeAnnotationsOnModuleElement(
561       XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder report) {
562     for (Scope scope : injectionAnnotations.getScopes(module)) {
563       report.addError(
564           String.format(
565               "@%ss cannot be scoped. Did you mean to scope a method instead?",
566               moduleKind.annotation().simpleName()),
567           module,
568           scope.scopeAnnotation().xprocessing());
569     }
570   }
571 
validateSelfCycles( XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder builder)572   private void validateSelfCycles(
573       XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder builder) {
574     XAnnotation moduleAnnotation = moduleKind.getModuleAnnotation(module);
575     moduleAnnotation.getAsAnnotationValueList("includes").stream()
576         .filter(includedModule -> areEquivalentTypes(module.getType(), includedModule.asType()))
577         .forEach(
578             includedModule ->
579                 builder.addError(
580                     String.format(
581                         "@%s cannot include themselves.", moduleKind.annotation().simpleName()),
582                     module,
583                     moduleAnnotation,
584                     includedModule));
585   }
586 
validateCompanionModule( XTypeElement companionModule, ValidationReport.Builder builder)587   private void validateCompanionModule(
588       XTypeElement companionModule, ValidationReport.Builder builder) {
589     List<XMethodElement> companionBindingMethods = new ArrayList<>();
590     for (XMethodElement companionMethod : companionModule.getDeclaredMethods()) {
591       if (anyBindingMethodValidator.isBindingMethod(companionMethod)) {
592         builder.addSubreport(anyBindingMethodValidator.validate(companionMethod));
593         companionBindingMethods.add(companionMethod);
594       }
595 
596       // On normal modules only overriding other binding methods is disallowed, but for companion
597       // objects we are prohibiting any override. For this can rely on checking the @Override
598       // annotation since the Kotlin compiler will always produce them for overriding methods.
599       if (companionMethod.hasAnnotation(TypeNames.OVERRIDE)) {
600         builder.addError(
601             "Binding method in companion object may not override another method.", companionMethod);
602       }
603 
604       // TODO(danysantiago): Be strict about the usage of @JvmStatic, i.e. tell user to remove it.
605     }
606 
607     ImmutableListMultimap<String, XMethodElement> bindingMethodsByName =
608         Multimaps.index(companionBindingMethods, XElements::getSimpleName);
609     validateMethodsWithSameName(builder, bindingMethodsByName);
610 
611     // If there are provision methods, then check the visibility. Companion objects are composed by
612     // an inner class and a static field, it is not enough to check the visibility on the type
613     // element or the field, therefore we check the metadata.
614     if (!companionBindingMethods.isEmpty() && companionModule.isPrivate()) {
615       builder.addError(
616           "A Companion Module with binding methods cannot be private.", companionModule);
617     }
618   }
619 
validateModuleBindings(XTypeElement module, ValidationReport.Builder report)620   private void validateModuleBindings(XTypeElement module, ValidationReport.Builder report) {
621     BindingGraph bindingGraph =
622         bindingGraphFactory
623             .create(componentDescriptorFactory.moduleComponentDescriptor(module), true)
624             .topLevelBindingGraph();
625     if (!bindingGraphValidator.isValid(bindingGraph)) {
626       // Since the validator uses a DiagnosticReporter to report errors, the ValdiationReport won't
627       // have any Items for them. We have to tell the ValidationReport that some errors were
628       // reported for the subject.
629       report.markDirty();
630     }
631   }
632 
formatListForErrorMessage(List<?> things)633   private static String formatListForErrorMessage(List<?> things) {
634     switch (things.size()) {
635       case 0:
636         return "";
637       case 1:
638         return things.get(0).toString();
639       default:
640         StringBuilder output = new StringBuilder();
641         Joiner.on(", ").appendTo(output, things.subList(0, things.size() - 1));
642         output.append(" and ").append(things.get(things.size() - 1));
643         return output.toString();
644     }
645   }
646 }
647