aboutsummaryrefslogtreecommitdiff
path: root/java/dagger/internal/codegen/AssistedProcessingStep.java
blob: 3173987e13c4d9177de19feddee6345c40f190fd (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
/*
 * Copyright (C) 2020 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 static com.google.auto.common.MoreElements.isAnnotationPresent;
import static dagger.internal.codegen.langmodel.DaggerElements.closestEnclosingTypeElement;

import com.google.auto.common.MoreElements;
import com.google.common.collect.ImmutableSet;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedInject;
import dagger.internal.codegen.binding.AssistedInjectionAnnotations;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.internal.codegen.validation.TypeCheckingProcessingStep;
import dagger.internal.codegen.validation.ValidationReport;
import java.lang.annotation.Annotation;
import javax.annotation.processing.Messager;
import javax.inject.Inject;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;

/**
 * An annotation processor for {@link dagger.assisted.Assisted}-annotated types.
 *
 * <p>This processing step should run after {@link AssistedFactoryProcessingStep}.
 */
final class AssistedProcessingStep extends TypeCheckingProcessingStep<VariableElement> {
  private final KotlinMetadataUtil kotlinMetadataUtil;
  private final InjectionAnnotations injectionAnnotations;
  private final DaggerElements elements;
  private final DaggerTypes types;
  private final Messager messager;

  @Inject
  AssistedProcessingStep(
      KotlinMetadataUtil kotlinMetadataUtil,
      InjectionAnnotations injectionAnnotations,
      DaggerElements elements,
      DaggerTypes types,
      Messager messager) {
    super(MoreElements::asVariable);
    this.kotlinMetadataUtil = kotlinMetadataUtil;
    this.injectionAnnotations = injectionAnnotations;
    this.elements = elements;
    this.types = types;
    this.messager = messager;
  }

  @Override
  public ImmutableSet<Class<? extends Annotation>> annotations() {
    return ImmutableSet.of(Assisted.class);
  }

  @Override
  protected void process(
      VariableElement assisted, ImmutableSet<Class<? extends Annotation>> annotations) {
    new AssistedValidator().validate(assisted).printMessagesTo(messager);
  }

  private final class AssistedValidator {
    ValidationReport<VariableElement> validate(VariableElement assisted) {
      ValidationReport.Builder<VariableElement> report = ValidationReport.about(assisted);

      Element enclosingElement = assisted.getEnclosingElement();
      if (!isAssistedInjectConstructor(enclosingElement)
          && !isAssistedFactoryCreateMethod(enclosingElement)
          // The generated java stubs for kotlin data classes contain a "copy" method that has
          // the same parameters (and annotations) as the constructor, so just ignore it.
          && !isKotlinDataClassCopyMethod(enclosingElement)) {
        report.addError(
            "@Assisted parameters can only be used within an @AssistedInject-annotated "
                + "constructor.",
            assisted);
      }

      injectionAnnotations
          .getQualifiers(assisted)
          .forEach(
              qualifier ->
                  report.addError(
                      "Qualifiers cannot be used with @Assisted parameters.", assisted, qualifier));

      return report.build();
    }
  }

  private boolean isAssistedInjectConstructor(Element element) {
    return element.getKind() == ElementKind.CONSTRUCTOR
        && isAnnotationPresent(element, AssistedInject.class);
  }

  private boolean isAssistedFactoryCreateMethod(Element element) {
    if (element.getKind() == ElementKind.METHOD) {
      TypeElement enclosingElement = closestEnclosingTypeElement(element);
      return AssistedInjectionAnnotations.isAssistedFactoryType(enclosingElement)
          // This assumes we've already validated AssistedFactory and that a valid method exists.
          && AssistedInjectionAnnotations.assistedFactoryMethod(enclosingElement, elements, types)
              .equals(element);
    }
    return false;
  }

  private boolean isKotlinDataClassCopyMethod(Element element) {
    // Note: This is a best effort. Technically, we could check the return type and parameters of
    // the copy method to verify it's the one associated with the constructor, but I'd rather keep
    // this simple to avoid encoding too many details of kapt's stubs. At worst, we'll be allowing
    // an @Assisted annotation that has no affect, which is already true for many of Dagger's other
    // annotations.
    return element.getKind() == ElementKind.METHOD
        && element.getSimpleName().contentEquals("copy")
        && kotlinMetadataUtil.isDataClass(closestEnclosingTypeElement(element));
  }
}