1 /* 2 * Copyright (C) 2020 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.processingstep; 18 19 import static androidx.room.compiler.processing.XElementKt.isConstructor; 20 import static androidx.room.compiler.processing.XElementKt.isMethod; 21 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedFactoryMethod; 22 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType; 23 import static dagger.internal.codegen.xprocessing.XElements.asMethod; 24 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; 25 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 26 27 import androidx.room.compiler.processing.XExecutableElement; 28 import androidx.room.compiler.processing.XExecutableParameterElement; 29 import androidx.room.compiler.processing.XMessager; 30 import androidx.room.compiler.processing.XTypeElement; 31 import com.google.common.collect.ImmutableSet; 32 import com.squareup.javapoet.ClassName; 33 import dagger.internal.codegen.binding.InjectionAnnotations; 34 import dagger.internal.codegen.javapoet.TypeNames; 35 import dagger.internal.codegen.validation.ValidationReport; 36 import javax.inject.Inject; 37 38 /** 39 * An annotation processor for {@link dagger.assisted.Assisted}-annotated types. 40 * 41 * <p>This processing step should run after {@link AssistedFactoryProcessingStep}. 42 */ 43 final class AssistedProcessingStep extends TypeCheckingProcessingStep<XExecutableParameterElement> { 44 private final InjectionAnnotations injectionAnnotations; 45 private final XMessager messager; 46 47 @Inject AssistedProcessingStep(InjectionAnnotations injectionAnnotations, XMessager messager)48 AssistedProcessingStep(InjectionAnnotations injectionAnnotations, XMessager messager) { 49 this.injectionAnnotations = injectionAnnotations; 50 this.messager = messager; 51 } 52 53 @Override annotationClassNames()54 public ImmutableSet<ClassName> annotationClassNames() { 55 return ImmutableSet.of(TypeNames.ASSISTED); 56 } 57 58 @Override process( XExecutableParameterElement assisted, ImmutableSet<ClassName> annotations)59 protected void process( 60 XExecutableParameterElement assisted, ImmutableSet<ClassName> annotations) { 61 new AssistedValidator().validate(assisted).printMessagesTo(messager); 62 } 63 64 private final class AssistedValidator { validate(XExecutableParameterElement assisted)65 ValidationReport validate(XExecutableParameterElement assisted) { 66 ValidationReport.Builder report = ValidationReport.about(assisted); 67 68 XExecutableElement enclosingElement = assisted.getEnclosingElement(); 69 if (!isAssistedInjectConstructor(enclosingElement) 70 && !isAssistedFactoryCreateMethod(enclosingElement) 71 // The generated java stubs for kotlin data classes contain a "copy" method that has 72 // the same parameters (and annotations) as the constructor, so just ignore it. 73 && !isKotlinDataClassCopyMethod(enclosingElement)) { 74 report.addError( 75 "@Assisted parameters can only be used within an @AssistedInject-annotated " 76 + "constructor.", 77 assisted); 78 } 79 80 injectionAnnotations 81 .getQualifiers(assisted) 82 .forEach( 83 qualifier -> 84 report.addError( 85 "Qualifiers cannot be used with @Assisted parameters.", assisted, qualifier)); 86 87 return report.build(); 88 } 89 } 90 isAssistedInjectConstructor(XExecutableElement executableElement)91 private boolean isAssistedInjectConstructor(XExecutableElement executableElement) { 92 return isConstructor(executableElement) 93 && executableElement.hasAnnotation(TypeNames.ASSISTED_INJECT); 94 } 95 isAssistedFactoryCreateMethod(XExecutableElement executableElement)96 private boolean isAssistedFactoryCreateMethod(XExecutableElement executableElement) { 97 if (isMethod(executableElement)) { 98 XTypeElement enclosingElement = closestEnclosingTypeElement(executableElement); 99 return isAssistedFactoryType(enclosingElement) 100 // This assumes we've already validated AssistedFactory and that a valid method exists. 101 && assistedFactoryMethod(enclosingElement).equals(executableElement); 102 } 103 return false; 104 } 105 isKotlinDataClassCopyMethod(XExecutableElement executableElement)106 private boolean isKotlinDataClassCopyMethod(XExecutableElement executableElement) { 107 // Note: This is a best effort. Technically, we could check the return type and parameters of 108 // the copy method to verify it's the one associated with the constructor, but I'd rather keep 109 // this simple to avoid encoding too many details of kapt's stubs. At worst, we'll be allowing 110 // an @Assisted annotation that has no affect, which is already true for many of Dagger's other 111 // annotations. 112 return isMethod(executableElement) 113 && getSimpleName(asMethod(executableElement)).contentEquals("copy") 114 && closestEnclosingTypeElement(executableElement.getEnclosingElement()).isDataClass(); 115 } 116 } 117