Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import dagger.internal.codegen.kotlin.KotlinMetadataFactory;
import dagger.internal.codegen.processingstep.ProcessingStepsModule;
import dagger.internal.codegen.validation.AnyBindingMethodValidator;
import dagger.internal.codegen.validation.AssistedValidator;
import dagger.internal.codegen.validation.BindingMethodValidatorsModule;
import dagger.internal.codegen.validation.ComponentCreatorValidator;
import dagger.internal.codegen.validation.ComponentValidator;
Expand Down Expand Up @@ -186,6 +187,10 @@ interface ProcessingRoundCacheModule {
@IntoSet
ClearableCache componentCreatorValidator(ComponentCreatorValidator cache);

@Binds
@IntoSet
ClearableCache assistedValidator(AssistedValidator cache);

@Binds
@IntoSet
ClearableCache kotlinMetadata(KotlinMetadataFactory cache);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,10 @@

package dagger.internal.codegen.processingstep;

import static androidx.room.compiler.processing.XElementKt.isConstructor;
import static androidx.room.compiler.processing.XElementKt.isMethod;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedFactoryMethod;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType;
import static dagger.internal.codegen.xprocessing.XElements.asMethod;
import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;

import androidx.room.compiler.codegen.XClassName;
import androidx.room.compiler.processing.XExecutableElement;
import androidx.room.compiler.processing.XExecutableParameterElement;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.collect.ImmutableSet;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.validation.ValidationReport;
import dagger.internal.codegen.validation.AssistedValidator;
import dagger.internal.codegen.xprocessing.XTypeNames;
import javax.inject.Inject;

Expand All @@ -40,11 +29,11 @@
* <p>This processing step should run after {@link AssistedFactoryProcessingStep}.
*/
final class AssistedProcessingStep extends TypeCheckingProcessingStep<XExecutableParameterElement> {
private final InjectionAnnotations injectionAnnotations;
private final AssistedValidator assistedValidator;

@Inject
AssistedProcessingStep(InjectionAnnotations injectionAnnotations) {
this.injectionAnnotations = injectionAnnotations;
AssistedProcessingStep(AssistedValidator assistedValidator) {
this.assistedValidator = assistedValidator;
}

@Override
Expand All @@ -55,59 +44,11 @@ public ImmutableSet<XClassName> annotationClassNames() {
@Override
protected void process(
XExecutableParameterElement assisted, ImmutableSet<XClassName> annotations) {
new AssistedValidator().validate(assisted).printMessagesTo(messager);
}

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

XExecutableElement 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();
// If the AssistedValidator already validated this element as part of InjectValidator, then we
// don't need to report the errors again.
if (assistedValidator.containsCache(assisted)) {
return;
}
}

private boolean isAssistedInjectConstructor(XExecutableElement executableElement) {
return isConstructor(executableElement)
&& executableElement.hasAnnotation(XTypeNames.ASSISTED_INJECT);
}

private boolean isAssistedFactoryCreateMethod(XExecutableElement executableElement) {
if (isMethod(executableElement)) {
XTypeElement enclosingElement = closestEnclosingTypeElement(executableElement);
return isAssistedFactoryType(enclosingElement)
// This assumes we've already validated AssistedFactory and that a valid method exists.
&& assistedFactoryMethod(enclosingElement).equals(executableElement);
}
return false;
}

private boolean isKotlinDataClassCopyMethod(XExecutableElement executableElement) {
// 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 isMethod(executableElement)
&& getSimpleName(asMethod(executableElement)).contentEquals("copy")
&& closestEnclosingTypeElement(executableElement.getEnclosingElement()).isDataClass();
assistedValidator.validate(assisted).printMessagesTo(messager);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright (C) 2025 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 androidx.room.compiler.processing.XElementKt.isConstructor;
import static androidx.room.compiler.processing.XElementKt.isMethod;
import static com.google.common.base.Preconditions.checkArgument;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedFactoryMethod;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType;
import static dagger.internal.codegen.xprocessing.XElements.asMethod;
import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;

import androidx.room.compiler.processing.XExecutableElement;
import androidx.room.compiler.processing.XExecutableParameterElement;
import androidx.room.compiler.processing.XTypeElement;
import dagger.internal.codegen.base.ClearableCache;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.xprocessing.XTypeNames;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;

/** Validates an {@link dagger.assisted.Assisted}-annotated parameter. */
@Singleton
public final class AssistedValidator implements ClearableCache {
private final InjectionAnnotations injectionAnnotations;
private final Map<XExecutableParameterElement, ValidationReport> cache = new HashMap<>();

@Inject
AssistedValidator(InjectionAnnotations injectionAnnotations) {
this.injectionAnnotations = injectionAnnotations;
}

@Override
public void clearCache() {
cache.clear();
}

public boolean containsCache(XExecutableParameterElement assisted) {
return cache.containsKey(assisted);
}

public ValidationReport validate(XExecutableParameterElement assisted) {
checkArgument(assisted.hasAnnotation(XTypeNames.ASSISTED));
return cache.computeIfAbsent(assisted, this::validateUncached);
}


private ValidationReport validateUncached(XExecutableParameterElement assisted) {
ValidationReport.Builder report = ValidationReport.about(assisted);

XExecutableElement 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(XExecutableElement executableElement) {
return isConstructor(executableElement)
&& executableElement.hasAnnotation(XTypeNames.ASSISTED_INJECT);
}

private boolean isAssistedFactoryCreateMethod(XExecutableElement executableElement) {
if (isMethod(executableElement)) {
XTypeElement enclosingElement = closestEnclosingTypeElement(executableElement);
return isAssistedFactoryType(enclosingElement)
// This assumes we've already validated AssistedFactory and that a valid method exists.
&& assistedFactoryMethod(enclosingElement).equals(executableElement);
}
return false;
}

private boolean isKotlinDataClassCopyMethod(XExecutableElement executableElement) {
// 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 isMethod(executableElement)
&& getSimpleName(asMethod(executableElement)).contentEquals("copy")
&& closestEnclosingTypeElement(executableElement.getEnclosingElement()).isDataClass();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,15 @@
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedParameter;
import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors;
import static dagger.internal.codegen.binding.SourceFiles.factoryNameForElement;
import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
import static dagger.internal.codegen.xprocessing.XElements.getAnyAnnotation;
import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters;
import static dagger.internal.codegen.xprocessing.XTypeNames.injectTypeNames;
import static dagger.internal.codegen.xprocessing.XTypes.isSubtype;

import androidx.room.compiler.codegen.XClassName;
import androidx.room.compiler.codegen.XTypeName;
import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XConstructorElement;
Expand All @@ -49,7 +47,6 @@
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.langmodel.Accessibility;
import dagger.internal.codegen.model.Scope;
import dagger.internal.codegen.xprocessing.XAnnotations;
import dagger.internal.codegen.xprocessing.XTypeNames;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -72,6 +69,7 @@ public final class InjectValidator implements ClearableCache {
private final MethodSignatureFormatter methodSignatureFormatter;
private final InternalValidator validator;
private final InternalValidator validatorWhenGeneratingCode;
private final AssistedValidator assistedValidator;

@Inject
InjectValidator(
Expand All @@ -80,12 +78,14 @@ public final class InjectValidator implements ClearableCache {
CompilerOptions compilerOptions,
InjectionAnnotations injectionAnnotations,
DaggerSuperficialValidation superficialValidation,
MethodSignatureFormatter methodSignatureFormatter) {
MethodSignatureFormatter methodSignatureFormatter,
AssistedValidator assistedValidator) {
this.processingEnv = processingEnv;
this.dependencyRequestValidator = dependencyRequestValidator;
this.injectionAnnotations = injectionAnnotations;
this.superficialValidation = superficialValidation;
this.methodSignatureFormatter = methodSignatureFormatter;
this.assistedValidator = assistedValidator;

// When validating types that require a generated factory class we need to error on private and
// static inject members even if the compiler options are set to not error.
Expand Down Expand Up @@ -191,21 +191,24 @@ private ValidationReport validateConstructor(XConstructorElement constructorElem
ValidationReport.Builder builder =
ValidationReport.about(constructorElement.getEnclosingElement());

if (InjectionAnnotations.hasInjectAnnotation(constructorElement)
&& constructorElement.hasAnnotation(XTypeNames.ASSISTED_INJECT)) {
boolean isInjectConstructor = InjectionAnnotations.hasInjectAnnotation(constructorElement);
boolean isAssistedInjectConstructor =
InjectionAnnotations.hasAssistedInjectAnnotation(constructorElement);
final String injectAnnotationName;
if (isInjectConstructor && isAssistedInjectConstructor) {
builder.addError("Constructors cannot be annotated with both @Inject and @AssistedInject");
// The rest of the validation assumes that only one of the annotations is present so return
// early if there are both.
return builder.build();
} else if (isInjectConstructor) {
injectAnnotationName = "Inject";
} else if (isAssistedInjectConstructor) {
injectAnnotationName = "AssistedInject";
} else {
throw new AssertionError(
"No @Inject or @AssistedInject annotation found: " + constructorElement);
}

XClassName injectAnnotation =
getAnyAnnotation(
constructorElement,
ImmutableSet.<XClassName>builder()
.addAll(injectTypeNames())
.add(XTypeNames.ASSISTED_INJECT)
.build())
.map(XAnnotations::asClassName)
.get();

if (constructorElement.isPrivate()) {
builder.addError(
"Dagger does not support injection into private constructors", constructorElement);
Expand All @@ -221,15 +224,15 @@ private ValidationReport validateConstructor(XConstructorElement constructorElem
builder.addError(
String.format(
"@Qualifier annotations are not allowed on @%s constructors",
injectAnnotation.getSimpleName()),
injectAnnotationName),
constructorElement,
qualifier);
}

String scopeErrorMsg =
String.format(
"@Scope annotations are not allowed on @%s constructors",
injectAnnotation.getSimpleName());
injectAnnotationName);

if (InjectionAnnotations.hasInjectAnnotation(constructorElement)) {
scopeErrorMsg += "; annotate the class instead";
Expand All @@ -243,14 +246,19 @@ private ValidationReport validateConstructor(XConstructorElement constructorElem

for (XExecutableParameterElement parameter : constructorElement.getParameters()) {
superficialValidation.validateTypeOf(parameter);
validateDependencyRequest(builder, parameter);
if (isAssistedParameter(parameter)) {
builder.addSubreport(assistedValidator.validate(parameter));
} else {
// Only validate dependency requests for non-assisted parameters.
validateDependencyRequest(builder, parameter);
}
}

if (throwsCheckedExceptions(constructorElement)) {
builder.addItem(
String.format(
"Dagger does not support checked exceptions on @%s constructors",
injectAnnotation.getSimpleName()),
injectAnnotationName),
privateMemberDiagnosticKind,
constructorElement);
}
Expand All @@ -262,7 +270,7 @@ private ValidationReport validateConstructor(XConstructorElement constructorElem
builder.addError(
String.format(
"@%s is nonsense on the constructor of an abstract class",
injectAnnotation.getSimpleName()),
injectAnnotationName),
constructorElement);
}

Expand All @@ -271,7 +279,7 @@ private ValidationReport validateConstructor(XConstructorElement constructorElem
String.format(
"@%s constructors are invalid on inner classes. "
+ "Did you mean to make the class static?",
injectAnnotation.getSimpleName()),
injectAnnotationName),
constructorElement);
}

Expand Down
Loading
Loading