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