diff --git a/hilt-compiler/main/java/dagger/hilt/processor/internal/MethodSignature.java b/hilt-compiler/main/java/dagger/hilt/processor/internal/MethodSignature.java index e397ec84308..b28e5fd6a58 100644 --- a/hilt-compiler/main/java/dagger/hilt/processor/internal/MethodSignature.java +++ b/hilt-compiler/main/java/dagger/hilt/processor/internal/MethodSignature.java @@ -21,16 +21,16 @@ import androidx.room3.compiler.processing.XExecutableElement; import androidx.room3.compiler.processing.XMethodElement; -import androidx.room3.compiler.processing.XMethodType; import androidx.room3.compiler.processing.XType; import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import dagger.internal.codegen.xprocessing.XElements; +import java.util.Objects; /** Represents the method signature needed to uniquely identify a method. */ -@AutoValue public abstract class MethodSignature { MethodSignature() {} @@ -40,22 +40,20 @@ public abstract class MethodSignature { /** Creates a {@link MethodSignature} from a method name and parameter {@link TypeName}s */ public static MethodSignature of(String methodName, TypeName... typeNames) { - return new AutoValue_MethodSignature(methodName, ImmutableList.copyOf(typeNames)); + return new AutoValue_MethodSignature_TypeNameMethodSignature( + methodName, ImmutableList.copyOf(typeNames)); } /** Creates a {@link MethodSignature} from a {@link MethodSpec} */ public static MethodSignature of(MethodSpec method) { - return new AutoValue_MethodSignature( + return new AutoValue_MethodSignature_TypeNameMethodSignature( method.name, method.parameters.stream().map(p -> p.type).collect(toImmutableList())); } /** Creates a {@link MethodSignature} from an {@link XExecutableElement} */ public static MethodSignature of(XExecutableElement executableElement) { - return new AutoValue_MethodSignature( - XElements.getSimpleName(executableElement), - executableElement.getParameters().stream() - .map(p -> p.getType().getTypeName()) - .collect(toImmutableList())); + return new AutoValue_MethodSignature_ElementMethodSignature( + executableElement, executableElement.getEnclosingElement().getType()); } /** @@ -64,12 +62,27 @@ public static MethodSignature of(XExecutableElement executableElement) { *

This version will resolve type parameters as declared by {@code enclosing}. */ static MethodSignature ofDeclaredType(XMethodElement method, XType enclosing) { - XMethodType executableType = method.asMemberOf(enclosing); - return new AutoValue_MethodSignature( - XElements.getSimpleName(method), - executableType.getParameterTypes().stream() - .map(XType::getTypeName) - .collect(toImmutableList())); + return new AutoValue_MethodSignature_ElementMethodSignature(method, enclosing); + } + + @Override + public final boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof MethodSignature)) { + return false; + } + MethodSignature that = (MethodSignature) other; + return Objects.equals(this.name(), that.name()) + && Objects.equals(this.parameters(), that.parameters()); + } + + @Override + public final int hashCode() { + // Only hash the name to avoid expensive parameter resolution. This allows Set and Map lookups + // to be efficient, only calling equals() when the name matches. + return Objects.hashCode(name()); } /** Returns a string in the format: METHOD_NAME(PARAM_TYPE1,PARAM_TYPE2,...) */ @@ -78,4 +91,33 @@ public final String toString() { return String.format( "%s(%s)", name(), parameters().stream().map(Object::toString).collect(joining(","))); } + + @AutoValue + abstract static class TypeNameMethodSignature extends MethodSignature { + @Override + public abstract String name(); + + @Override + public abstract ImmutableList parameters(); + } + + @AutoValue + abstract static class ElementMethodSignature extends MethodSignature { + abstract XExecutableElement executableElement(); + + abstract XType enclosingType(); + + @Override + public final String name() { + return XElements.getSimpleName(executableElement()); + } + + @Override + @Memoized + public ImmutableList parameters() { + return executableElement().asMemberOf(enclosingType()).getParameterTypes().stream() + .map(XType::getTypeName) + .collect(toImmutableList()); + } + } } diff --git a/javatests/dagger/hilt/processor/internal/BUILD b/javatests/dagger/hilt/processor/internal/BUILD index ed8d9d9433d..9d59fb395cd 100644 --- a/javatests/dagger/hilt/processor/internal/BUILD +++ b/javatests/dagger/hilt/processor/internal/BUILD @@ -31,6 +31,19 @@ java_test( ], ) +java_test( + name = "MethodSignatureTest", + size = "small", + srcs = ["MethodSignatureTest.java"], + deps = [ + "//hilt-compiler/main/java/dagger/hilt/processor/internal:method_signature", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", + "//third_party/java/junit", + "//third_party/java/truth", + ], +) + java_library( name = "generated_import", srcs = ["GeneratedImport.java"], diff --git a/javatests/dagger/hilt/processor/internal/MethodSignatureTest.java b/javatests/dagger/hilt/processor/internal/MethodSignatureTest.java new file mode 100644 index 00000000000..6a9f0ac581a --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/MethodSignatureTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2026 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.hilt.processor.internal; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class MethodSignatureTest { + + @Test + public void testBasicGetters() { + MethodSignature signature = MethodSignature.of("foo", TypeName.INT, TypeName.BOOLEAN); + assertThat(signature.name()).isEqualTo("foo"); + assertThat(signature.parameters()).containsExactly(TypeName.INT, TypeName.BOOLEAN).inOrder(); + } + + @Test + public void testOfMethodSpec() { + MethodSpec method = MethodSpec.methodBuilder("bar") + .addParameter(ParameterSpec.builder(TypeName.INT, "x").build()) + .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "y").build()) + .build(); + MethodSignature signature = MethodSignature.of(method); + assertThat(signature.name()).isEqualTo("bar"); + assertThat(signature.parameters()).containsExactly(TypeName.INT, TypeName.DOUBLE).inOrder(); + } + + @Test + public void testEqualsAndHashCode() { + MethodSignature sig1 = MethodSignature.of("foo", TypeName.INT); + MethodSignature sig2 = MethodSignature.of("foo", TypeName.INT); + MethodSignature sigDifferentParams = MethodSignature.of("foo", TypeName.BOOLEAN); + MethodSignature sigDifferentName = MethodSignature.of("bar", TypeName.INT); + + // Equality + assertThat(sig1).isEqualTo(sig2); + assertThat(sig1).isNotEqualTo(sigDifferentParams); + assertThat(sig1).isNotEqualTo(sigDifferentName); + + // HashCode contract (equal objects must have equal hashcodes) + assertThat(sig1.hashCode()).isEqualTo(sig2.hashCode()); + + // HashCode is name based to optimize initialization, so they can collide: + assertThat(sig1.hashCode()).isEqualTo(sigDifferentParams.hashCode()); + assertThat(sig1.hashCode()).isNotEqualTo(sigDifferentName.hashCode()); + } + + @Test + public void testEqualsShortCircuitsName() { + MethodSignature sigThrowsOnParams = new ThrowingMethodSignature("foo"); + MethodSignature sigDifferentName = MethodSignature.of("bar", TypeName.INT); + + // Since names differ, equals() must short-circuit on the name and never invoke parameters() + assertThat(sigThrowsOnParams.equals(sigDifferentName)).isFalse(); + + // A standard equality check with same name should invoke parameters() and throw + MethodSignature other = MethodSignature.of("foo", TypeName.INT); + assertThrows(AssertionError.class, () -> sigThrowsOnParams.equals(other)); + } + + @Test + public void testToString() { + MethodSignature signature = MethodSignature.of("foo", TypeName.INT, TypeName.BOOLEAN); + assertThat(signature.toString()).isEqualTo("foo(int,boolean)"); + } + + private static final class ThrowingMethodSignature extends MethodSignature { + private final String name; + + ThrowingMethodSignature(String name) { + this.name = name; + } + + @Override + public String name() { + return name; + } + + @Override + public ImmutableList parameters() { + throw new AssertionError("Parameters should not be resolved!"); + } + } +}