aboutsummaryrefslogtreecommitdiff
path: root/java/dagger/internal/codegen/validation/DependencyRequestValidator.java
blob: f280497536d1801d93dd64560608effe04708f90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*
 * Copyright (C) 2018 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.validation;

import static com.google.auto.common.MoreElements.asType;
import static com.google.auto.common.MoreElements.asVariable;
import static com.google.auto.common.MoreTypes.asTypeElement;
import static dagger.internal.codegen.base.RequestKinds.extractKeyType;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedInjectionType;
import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType;
import static javax.lang.model.element.Modifier.STATIC;
import static javax.lang.model.type.TypeKind.WILDCARD;

import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableCollection;
import dagger.MembersInjector;
import dagger.assisted.Assisted;
import dagger.internal.codegen.base.FrameworkTypes;
import dagger.internal.codegen.base.RequestKinds;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.model.RequestKind;
import java.util.Optional;
import javax.inject.Inject;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

/** Validation for dependency requests. */
final class DependencyRequestValidator {
  private final MembersInjectionValidator membersInjectionValidator;
  private final InjectionAnnotations injectionAnnotations;
  private final KotlinMetadataUtil metadataUtil;
  private final DaggerElements elements;

  @Inject
  DependencyRequestValidator(
      MembersInjectionValidator membersInjectionValidator,
      InjectionAnnotations injectionAnnotations,
      KotlinMetadataUtil metadataUtil,
      DaggerElements elements) {
    this.membersInjectionValidator = membersInjectionValidator;
    this.injectionAnnotations = injectionAnnotations;
    this.metadataUtil = metadataUtil;
    this.elements = elements;
  }

  /**
   * Adds an error if the given dependency request has more than one qualifier annotation or is a
   * non-instance request with a wildcard type.
   */
  void validateDependencyRequest(
      ValidationReport.Builder<?> report, Element requestElement, TypeMirror requestType) {
    if (MoreElements.isAnnotationPresent(requestElement, Assisted.class)) {
      // Don't validate assisted parameters. These are not dependency requests.
      return;
    }
    if (missingQualifierMetadata(requestElement)) {
      report.addError(
          "Unable to read annotations on an injected Kotlin property. The Dagger compiler must"
              + " also be applied to any project containing @Inject properties.",
          requestElement);

      // Skip any further validation if we don't have valid metadata for a type that needs it.
      return;
    }

    new Validator(report, requestElement, requestType).validate();
  }

  /** Returns {@code true} if a kotlin inject field is missing metadata about its qualifiers. */
  private boolean missingQualifierMetadata(Element requestElement) {
    if (requestElement.getKind() == ElementKind.FIELD
        // static injected fields are not supported, no need to get qualifier from kotlin metadata
        && !requestElement.getModifiers().contains(STATIC)
        && metadataUtil.hasMetadata(requestElement)
        && metadataUtil.isMissingSyntheticPropertyForAnnotations(asVariable(requestElement))) {
      Optional<TypeElement> membersInjector =
          Optional.ofNullable(
              elements.getTypeElement(
                  membersInjectorNameForType(asType(requestElement.getEnclosingElement()))));
      return !membersInjector.isPresent();
    }
    return false;
  }

  private final class Validator {
    private final ValidationReport.Builder<?> report;
    private final Element requestElement;
    private final TypeMirror requestType;
    private final TypeMirror keyType;
    private final RequestKind requestKind;
    private final ImmutableCollection<? extends AnnotationMirror> qualifiers;


    Validator(ValidationReport.Builder<?> report, Element requestElement, TypeMirror requestType) {
      this.report = report;
      this.requestElement = requestElement;
      this.requestType = requestType;
      this.keyType = extractKeyType(requestType);
      this.requestKind = RequestKinds.getRequestKind(requestType);
      this.qualifiers = injectionAnnotations.getQualifiers(requestElement);
    }

    void validate() {
      checkQualifiers();
      checkType();
    }

    private void checkQualifiers() {
      if (qualifiers.size() > 1) {
        for (AnnotationMirror qualifier : qualifiers) {
          report.addError(
              "A single dependency request may not use more than one @Qualifier",
              requestElement,
              qualifier);
        }
      }
    }

    private void checkType() {
      if (qualifiers.isEmpty() && keyType.getKind() == TypeKind.DECLARED) {
        TypeElement typeElement = asTypeElement(keyType);
        if (isAssistedInjectionType(typeElement)) {
          report.addError(
              "Dagger does not support injecting @AssistedInject type, "
                  + requestType
                  + ". Did you mean to inject its assisted factory type instead?",
              requestElement);
        }
        if (requestKind != RequestKind.INSTANCE && isAssistedFactoryType(typeElement)) {
          report.addError(
              "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, "
                  + "or Produced<T> when T is an @AssistedFactory-annotated type such as "
                  + keyType,
              requestElement);
        }
      }
      if (keyType.getKind().equals(WILDCARD)) {
        // TODO(ronshapiro): Explore creating this message using RequestKinds.
        report.addError(
            "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, "
                + "or Produced<T> when T is a wildcard type such as "
                + keyType,
            requestElement);
      }
      if (MoreTypes.isType(keyType) && MoreTypes.isTypeOf(MembersInjector.class, keyType)) {
        DeclaredType membersInjectorType = MoreTypes.asDeclared(keyType);
        if (membersInjectorType.getTypeArguments().isEmpty()) {
          report.addError("Cannot inject a raw MembersInjector", requestElement);
        } else {
          report.addSubreport(
              membersInjectionValidator.validateMembersInjectionRequest(
                  requestElement, membersInjectorType.getTypeArguments().get(0)));
        }
      }
    }
  }

  /**
   * Adds an error if the given dependency request is for a {@link dagger.producers.Producer} or
   * {@link dagger.producers.Produced}.
   *
   * <p>Only call this when processing a provision binding.
   */
  // TODO(dpb): Should we disallow Producer entry points in non-production components?
  void checkNotProducer(ValidationReport.Builder<?> report, VariableElement requestElement) {
    TypeMirror requestType = requestElement.asType();
    if (FrameworkTypes.isProducerType(requestType)) {
      report.addError(
          String.format(
              "%s may only be injected in @Produces methods",
              MoreTypes.asTypeElement(requestType).getSimpleName()),
          requestElement);
    }
  }
}