private Executable matchingExecutable(
      TypeElement autoBuilderType,
      List<Executable> executables,
      ImmutableSet<ExecutableElement> methodsInAutoBuilderType,
      String description) {
    // There's more than one visible executable (constructor or method). We try to find the one that
    // corresponds to the methods in the @AutoBuilder interface. This is a bit approximate. We're
    // basically just looking for an executable where all the parameter names correspond to setters
    // or property builders in the interface. We might find out after choosing one that it is wrong
    // for whatever reason (types don't match, spurious methods, etc). But it is likely that if the
    // names are all accounted for in the methods, and if there's no other matching executable with
    // more parameters, then this is indeed the one we want. If we later get errors when we try to
    // analyze the interface in detail, those are probably legitimate errors and not because we
    // picked the wrong executable.
    ImmutableList<Executable> matches =
        executables.stream()
            .filter(x -> executableMatches(x, methodsInAutoBuilderType))
            .collect(toImmutableList());
    switch (matches.size()) {
      case 0:
        throw errorReporter()
            .abortWithError(
                autoBuilderType,
                "[AutoBuilderNoMatch] Property names do not correspond to the parameter names of"
                    + " any %s:\n%s",
                description,
                executableListString(executables));
      case 1:
        return matches.get(0);
      default:
        // More than one match, let's see if we can find the best one.
    }
    int max = matches.stream().mapToInt(e -> e.parameters().size()).max().getAsInt();
    ImmutableList<Executable> maxMatches =
        matches.stream().filter(c -> c.parameters().size() == max).collect(toImmutableList());
    if (maxMatches.size() > 1) {
      throw errorReporter()
          .abortWithError(
              autoBuilderType,
              "[AutoBuilderAmbiguous] Property names correspond to more than one %s:\n%s",
              description,
              executableListString(maxMatches));
    }
    return maxMatches.get(0);
  }
Returns a map from property names to the corresponding getters in the built type. The built
type is the return type of the given {@code executable}, and the property names are the names
of its parameters. If the return type is a {@link DeclaredType} {@code Foo} and if every
property name {@code bar} matches a method {@code bar()} or {@code getBar()} in {@code Foo},
then the method returns a map where {@code bar} maps to {@code bar} or {@code getBar}. If these
conditions are not met then the method returns an empty map.

<p>The method name match is case-insensitive, so we will also accept {@code baR()} or {@code
getbar()}. For a property of type {@code boolean}, we also accept {@code isBar()} (or {@code
isbar()} etc).

<p>The return type of each getter method must match the type of the corresponding parameter
exactly. This will always be true for our principal use cases, Java records and Kotlin data
classes. For other use cases, we may in the future accept getters where we know how to convert,
for example if the getter has type {@code ImmutableList<Baz>} and the parameter has type {@code
Baz[]}. We already have similar logic for the parameter types of builder setters.
value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java