diff options
Diffstat (limited to 'value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java')
-rw-r--r-- | value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java | 444 |
1 files changed, 263 insertions, 181 deletions
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java index 81751e30..51773e6f 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java @@ -15,8 +15,7 @@ */ package com.google.auto.value.processor; -import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.nullableAnnotationFor; -import static com.google.common.collect.Sets.difference; +import static com.google.auto.value.processor.AutoValueishProcessor.nullableAnnotationFor; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; @@ -38,7 +37,9 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.stream.Stream; import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -54,19 +55,35 @@ import javax.lang.model.util.Types; /** * Classifies methods inside builder types, based on their names and parameter and return types. * + * @param <E> the kind of {@link Element} that the corresponding properties are defined by. This is + * {@link ExecutableElement} for AutoValue, where properties are defined by abstract methods, + * and {@link VariableElement} for AutoBuilder, where they are defined by constructor or method + * parameters. * @author Éamonn McManus */ -class BuilderMethodClassifier { +abstract class BuilderMethodClassifier<E extends Element> { private static final Equivalence<TypeMirror> TYPE_EQUIVALENCE = MoreTypes.equivalence(); private final ErrorReporter errorReporter; private final Types typeUtils; private final Elements elementUtils; - private final TypeElement autoValueClass; + private final TypeMirror builtType; private final TypeElement builderType; - private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName; - private final ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType; - private final ImmutableMap<String, ExecutableElement> getterNameToGetter; + + /** + * Property types, rewritten to refer to type variables in the builder. For example, suppose you + * have {@code @AutoValue abstract class Foo<T>} with a getter {@code abstract T bar()} and a + * builder {@code @AutoValue.Builder interface Builder<T>} with a setter {@code abstract + * Builder<T> setBar(T t)}. Then the {@code T} of {@code Foo<T>} and the {@code T} of {@code + * Foo.Builder<T>} are two separate variables. Originally {@code bar()} returned the {@code T} of + * {@code Foo<T>}, but in this map we have rewritten it to be the {@code T} of {@code + * Foo.Builder<T>}. + * + * <p>Importantly, this rewrite <b>loses type annotations</b>, so when those are important we must + * be careful to look at the original type as reported by the {@link #originalPropertyType} + * method. + */ + private final ImmutableMap<String, TypeMirror> rewrittenPropertyTypes; private final Set<ExecutableElement> buildMethods = new LinkedHashSet<>(); private final Map<String, BuilderSpec.PropertyGetter> builderGetters = new LinkedHashMap<>(); @@ -79,76 +96,27 @@ class BuilderMethodClassifier { private boolean settersPrefixed; - private BuilderMethodClassifier( + BuilderMethodClassifier( ErrorReporter errorReporter, ProcessingEnvironment processingEnv, - TypeElement autoValueClass, + TypeMirror builtType, TypeElement builderType, - ImmutableBiMap<ExecutableElement, String> getterToPropertyName, - ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType) { + ImmutableMap<String, TypeMirror> rewrittenPropertyTypes) { this.errorReporter = errorReporter; this.typeUtils = processingEnv.getTypeUtils(); this.elementUtils = processingEnv.getElementUtils(); - this.autoValueClass = autoValueClass; + this.builtType = builtType; this.builderType = builderType; - this.getterToPropertyName = getterToPropertyName; - this.getterToPropertyType = getterToPropertyType; - ImmutableMap.Builder<String, ExecutableElement> getterToPropertyNameBuilder = - ImmutableMap.builder(); - for (ExecutableElement getter : getterToPropertyName.keySet()) { - getterToPropertyNameBuilder.put(getter.getSimpleName().toString(), getter); - } - this.getterNameToGetter = getterToPropertyNameBuilder.build(); + this.rewrittenPropertyTypes = rewrittenPropertyTypes; this.eclipseHack = new EclipseHack(processingEnv); } /** - * Classifies the given methods from a builder type and its ancestors. - * - * @param methods the abstract methods in {@code builderType} and its ancestors. - * @param errorReporter where to report errors. - * @param processingEnv the ProcessingEnvironment for annotation processing. - * @param autoValueClass the {@code AutoValue} class containing the builder. - * @param builderType the builder class or interface within {@code autoValueClass}. - * @param getterToPropertyName a map from getter methods to the properties they get. - * @param getterToPropertyType a map from getter methods to their return types. The return types - * here use type parameters from the builder class (if any) rather than from the {@code - * AutoValue} class, even though the getter methods are in the latter. - * @param autoValueHasToBuilder true if the containing {@code @AutoValue} class has a {@code - * toBuilder()} method. - * @return an {@code Optional} that contains the results of the classification if it was - * successful or nothing if it was not. - */ - static Optional<BuilderMethodClassifier> classify( - Iterable<ExecutableElement> methods, - ErrorReporter errorReporter, - ProcessingEnvironment processingEnv, - TypeElement autoValueClass, - TypeElement builderType, - ImmutableBiMap<ExecutableElement, String> getterToPropertyName, - ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType, - boolean autoValueHasToBuilder) { - BuilderMethodClassifier classifier = - new BuilderMethodClassifier( - errorReporter, - processingEnv, - autoValueClass, - builderType, - getterToPropertyName, - getterToPropertyType); - if (classifier.classifyMethods(methods, autoValueHasToBuilder)) { - return Optional.of(classifier); - } else { - return Optional.empty(); - } - } - - /** * Returns a multimap from the name of a property to the methods that set it. If the property is * defined by an abstract method in the {@code @AutoValue} class called {@code foo()} or {@code * getFoo()} then the name of the property is {@code foo} and there will be an entry in the map - * where the key is {@code "foo"} and the value describes a method in the builder called - * {@code foo} or {@code setFoo}. + * where the key is {@code "foo"} and the value describes a method in the builder called {@code + * foo} or {@code setFoo}. */ ImmutableMultimap<String, PropertySetter> propertyNameToSetters() { return ImmutableMultimap.copyOf( @@ -179,8 +147,7 @@ class BuilderMethodClassifier { } /** Classifies the given methods and sets the state of this object based on what is found. */ - private boolean classifyMethods( - Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder) { + boolean classifyMethods(Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder) { int startErrorCount = errorReporter.errorCount(); for (ExecutableElement method : methods) { classifyMethod(method); @@ -198,49 +165,48 @@ class BuilderMethodClassifier { } else { errorReporter.reportError( propertyNameToUnprefixedSetters.values().iterator().next().getSetter(), - "[AutoValueSetNotSet] If any setter methods use the setFoo convention then all must"); + "[%sSetNotSet] If any setter methods use the setFoo convention then all must", + autoWhat()); return false; } - getterToPropertyName.forEach( - (getter, property) -> { - TypeMirror propertyType = getterToPropertyType.get(getter); - boolean hasSetter = propertyNameToSetter.containsKey(property); - PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property); - boolean hasBuilder = propertyBuilder != null; - if (hasBuilder) { - // If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must - // be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a - // toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is - // possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll - // method that accepts a Bar argument. - boolean canMakeBarBuilder = - (propertyBuilder.getBuiltToBuilder() != null - || propertyBuilder.getCopyAll() != null); - boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter); - if (needToMakeBarBuilder && !canMakeBarBuilder) { - errorReporter.reportError( - propertyBuilder.getPropertyBuilderMethod(), - "[AutoValueCantMakeBuilder] Property builder method returns %1$s but there is no" - + " way to make that type from %2$s: %2$s does not have a non-static" - + " toBuilder() method that returns %1$s, and %1$s does not have a method" - + " addAll or putAll that accepts an argument of type %2$s", - propertyBuilder.getBuilderTypeMirror(), - propertyType); - } - } else if (!hasSetter) { - // We have neither barBuilder() nor setBar(Bar), so we should complain. - String setterName = settersPrefixed ? prefixWithSet(property) : property; - errorReporter.reportError( - builderType, - "[AutoValueBuilderMissingMethod] Expected a method with this signature: %s%s" - + " %s(%s), or a %sBuilder() method", - builderType, - typeParamsString(), - setterName, - propertyType, - property); - } - }); + for (String property : rewrittenPropertyTypes.keySet()) { + TypeMirror propertyType = rewrittenPropertyTypes.get(property); + boolean hasSetter = propertyNameToSetter.containsKey(property); + PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property); + boolean hasBuilder = propertyBuilder != null; + if (hasBuilder) { + // If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must + // be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a + // toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is + // possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll + // method that accepts a Bar argument. + boolean canMakeBarBuilder = + (propertyBuilder.getBuiltToBuilder() != null || propertyBuilder.getCopyAll() != null); + boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter); + if (needToMakeBarBuilder && !canMakeBarBuilder) { + errorReporter.reportError( + propertyBuilder.getPropertyBuilderMethod(), + "[AutoValueCantMakeBuilder] Property builder method returns %1$s but there is no" + + " way to make that type from %2$s: %2$s does not have a non-static" + + " toBuilder() method that returns %1$s, and %1$s does not have a method" + + " addAll or putAll that accepts an argument of type %2$s", + propertyBuilder.getBuilderTypeMirror(), + propertyType); + } + } else if (!hasSetter) { + // We have neither barBuilder() nor setBar(Bar), so we should complain. + String setterName = settersPrefixed ? prefixWithSet(property) : property; + errorReporter.reportError( + builderType, + "[%sBuilderMissingMethod] Expected a method with this signature: %s" + + " %s(%s), or a %sBuilder() method", + autoWhat(), + builderType.asType(), + setterName, + propertyType, + property); + } + } return errorReporter.errorCount() == startErrorCount; } @@ -255,7 +221,7 @@ class BuilderMethodClassifier { break; default: errorReporter.reportError( - method, "[AutoValueBuilderArgs] Builder methods must have 0 or 1 parameters"); + method, "[%sBuilderArgs] Builder methods must have 0 or 1 parameters", autoWhat()); } } @@ -268,26 +234,26 @@ class BuilderMethodClassifier { * ImmutableList<String> foos()} or {@code getFoos()}. */ private void classifyMethodNoArgs(ExecutableElement method) { - String methodName = method.getSimpleName().toString(); - TypeMirror returnType = builderMethodReturnType(method); - - ExecutableElement getter = getterNameToGetter.get(methodName); - if (getter != null) { - classifyGetter(method, getter); + Optional<String> getterProperty = propertyForBuilderGetter(method); + if (getterProperty.isPresent()) { + classifyGetter(method, getterProperty.get()); return; } + String methodName = method.getSimpleName().toString(); + TypeMirror returnType = builderMethodReturnType(method); + if (methodName.endsWith("Builder")) { String property = methodName.substring(0, methodName.length() - "Builder".length()); - if (getterToPropertyName.containsValue(property)) { + if (rewrittenPropertyTypes.containsKey(property)) { PropertyBuilderClassifier propertyBuilderClassifier = new PropertyBuilderClassifier( errorReporter, typeUtils, elementUtils, this, - getterToPropertyName, - getterToPropertyType, + this::propertyIsNullable, + rewrittenPropertyTypes, eclipseHack); Optional<PropertyBuilder> propertyBuilder = propertyBuilderClassifier.makePropertyBuilder(method, property); @@ -298,22 +264,24 @@ class BuilderMethodClassifier { } } - if (TYPE_EQUIVALENCE.equivalent(returnType, autoValueClass.asType())) { + if (TYPE_EQUIVALENCE.equivalent(returnType, builtType)) { buildMethods.add(method); } else { errorReporter.reportError( method, - "[AutoValueBuilderNoArg] Method without arguments should be a build method returning" - + " %1$s%2$s, or a getter method with the same name and type as a getter method of" - + " %1$s, or fooBuilder() where foo() or getFoo() is a getter method of %1$s", - autoValueClass, - typeParamsString()); + "[%1$sBuilderNoArg] Method without arguments should be a build method returning" + + " %2$s, or a getter method with the same name and type as %3$s," + + " or fooBuilder() where %4$s is %3$s", + // "where foo() or getFoo() is a method in..." or "where foo is a parameter of..." + autoWhat(), + builtType, + getterMustMatch(), + fooBuilderMustMatch()); } } - private void classifyGetter(ExecutableElement builderGetter, ExecutableElement originalGetter) { - String propertyName = getterToPropertyName.get(originalGetter); - TypeMirror originalGetterType = getterToPropertyType.get(originalGetter); + private void classifyGetter(ExecutableElement builderGetter, String propertyName) { + TypeMirror originalGetterType = rewrittenPropertyTypes.get(propertyName); TypeMirror builderGetterType = builderMethodReturnType(builderGetter); String builderGetterTypeString = TypeEncoder.encodeWithAnnotations(builderGetterType); if (TYPE_EQUIVALENCE.equivalent(builderGetterType, originalGetterType)) { @@ -344,51 +312,76 @@ class BuilderMethodClassifier { builderGetter, "[AutoValueBuilderReturnType] Method matches a property of %1$s but has return type %2$s" + " instead of %3$s or an Optional wrapping of %3$s", - autoValueClass, + builtType, builderGetterType, originalGetterType); } /** - * Classifies a method given that it has one argument. Currently, a method with one argument can - * only be a setter, meaning that it must look like {@code foo(T)} or {@code setFoo(T)}, where the - * {@code AutoValue} class has a property called {@code foo} of type {@code T}. + * Classifies a method given that it has one argument. A method with one argument can be: + * + * <ul> + * <li>a setter, meaning that it looks like {@code foo(T)} or {@code setFoo(T)}, where the + * {@code AutoValue} class has a property called {@code foo} of type {@code T}; + * <li>a property builder with one argument, meaning it looks like {@code + * ImmutableSortedSet.Builder<V> foosBuilder(Comparator<V>)}, where the {@code AutoValue} + * class has a property called {@code foos} with a type whose builder can be made with an + * argument of the given type. + * </ul> */ private void classifyMethodOneArg(ExecutableElement method) { + if (classifyPropertyBuilderOneArg(method)) { + return; + } String methodName = method.getSimpleName().toString(); - Map<String, ExecutableElement> propertyNameToGetter = getterToPropertyName.inverse(); + ImmutableMap<String, E> propertyElements = propertyElements(); String propertyName = null; - ExecutableElement valueGetter = propertyNameToGetter.get(methodName); + E propertyElement = propertyElements.get(methodName); Multimap<String, PropertySetter> propertyNameToSetters = null; - if (valueGetter != null) { + if (propertyElement != null) { propertyNameToSetters = propertyNameToUnprefixedSetters; propertyName = methodName; - } else if (valueGetter == null && methodName.startsWith("set") && methodName.length() > 3) { + } else if (methodName.startsWith("set") && methodName.length() > 3) { propertyNameToSetters = propertyNameToPrefixedSetters; propertyName = PropertyNames.decapitalizeLikeJavaBeans(methodName.substring(3)); - valueGetter = propertyNameToGetter.get(propertyName); - if (valueGetter == null) { + propertyElement = propertyElements.get(propertyName); + if (propertyElement == null) { // If our property is defined by a getter called getOAuth() then it is called "OAuth" - // because of Introspector.decapitalize. Therefore we want Introspector.decapitalize to - // be used for the setter too, so that you can write setOAuth(x). Meanwhile if the property - // is defined by a getter called oAuth() then it is called "oAuth", but you would still - // expect to be able to set it using setOAuth(x). Hence the second try using a decapitalize - // method without the quirky two-leading-capitals rule. + // because of JavaBeans rules. Therefore we want JavaBeans rules to be used for the setter + // too, so that you can write setOAuth(x). Meanwhile if the property is defined by a getter + // called oAuth() then it is called "oAuth", but you would still expect to be able to set it + // using setOAuth(x). Hence the second try using a decapitalize method without the quirky + // two-leading-capitals rule. propertyName = PropertyNames.decapitalizeNormally(methodName.substring(3)); - valueGetter = propertyNameToGetter.get(propertyName); + propertyElement = propertyElements.get(propertyName); + } + } else { + // We might also have an unprefixed setter, so the getter is called OAuth() or getOAuth() and + // the setter is called oAuth(x), where again JavaBeans rules imply that it should be called + // OAuth(x). Iterating over the properties here is a bit clunky but this case should be + // unusual. + propertyNameToSetters = propertyNameToUnprefixedSetters; + for (Map.Entry<String, E> entry : propertyElements.entrySet()) { + if (methodName.equals(PropertyNames.decapitalizeNormally(entry.getKey()))) { + propertyName = entry.getKey(); + propertyElement = entry.getValue(); + break; + } } } - if (valueGetter == null || propertyNameToSetters == null) { + if (propertyElement == null || propertyNameToSetters == null) { // The second disjunct isn't needed but convinces control-flow checkers that // propertyNameToSetters can't be null when we call put on it below. errorReporter.reportError( method, - "[AutoValueBuilderWhatProp] Method does not correspond to a property of %s", - autoValueClass); + "[%sBuilderWhatProp] Method %s does not correspond to %s", + autoWhat(), + methodName, + getterMustMatch()); checkForFailedJavaBean(method); return; } - Optional<Copier> function = getSetterFunction(valueGetter, method); + Optional<Copier> function = getSetterFunction(propertyElement, method); if (function.isPresent()) { DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType()); ExecutableType methodMirror = @@ -399,28 +392,44 @@ class BuilderMethodClassifier { propertyName, new PropertySetter(method, parameterType, function.get())); } else { errorReporter.reportError( - method, "Setter methods must return %s%s", builderType, typeParamsString()); + method, + "[%sBuilderRet] Setter methods must return %s", + autoWhat(), + builderType.asType()); } } } - // A frequent source of problems is where the JavaBeans conventions have been followed for - // most but not all getters. Then AutoValue considers that they haven't been followed at all, - // so you might have a property called getFoo where you thought it was called just foo, and - // you might not understand why your setter called setFoo is rejected (it would have to be called - // setGetFoo). - private void checkForFailedJavaBean(ExecutableElement rejectedSetter) { - ImmutableSet<ExecutableElement> allGetters = getterToPropertyName.keySet(); - ImmutableSet<ExecutableElement> prefixedGetters = - AutoValueProcessor.prefixedGettersIn(allGetters); - if (prefixedGetters.size() < allGetters.size() - && prefixedGetters.size() >= allGetters.size() / 2) { - errorReporter.reportNote( - rejectedSetter, - "This might be because you are using the getFoo() convention" - + " for some but not all methods. These methods don't follow the convention: %s", - difference(allGetters, prefixedGetters)); + /** + * Classifies a method given that it has one argument and is a property builder with a parameter, + * like {@code ImmutableSortedSet.Builder<String> foosBuilder(Comparator<String>)}. + * + * @param method A method to classify + * @return true if method has been classified successfully + */ + private boolean classifyPropertyBuilderOneArg(ExecutableElement method) { + String methodName = method.getSimpleName().toString(); + if (!methodName.endsWith("Builder")) { + return false; } + String property = methodName.substring(0, methodName.length() - "Builder".length()); + if (!rewrittenPropertyTypes.containsKey(property)) { + return false; + } + PropertyBuilderClassifier propertyBuilderClassifier = + new PropertyBuilderClassifier( + errorReporter, + typeUtils, + elementUtils, + this, + this::propertyIsNullable, + rewrittenPropertyTypes, + eclipseHack); + Optional<PropertyBuilder> maybePropertyBuilder = + propertyBuilderClassifier.makePropertyBuilder(method, property); + maybePropertyBuilder.ifPresent( + propertyBuilder -> propertyNameToPropertyBuilder.put(property, propertyBuilder)); + return maybePropertyBuilder.isPresent(); } /** @@ -431,12 +440,12 @@ class BuilderMethodClassifier { * using a method like {@code ImmutableList.copyOf} or {@code Optional.of}, when the returned * function will be something like {@code s -> "Optional.of(" + s + ")"}. */ - private Optional<Copier> getSetterFunction( - ExecutableElement valueGetter, ExecutableElement setter) { + private Optional<Copier> getSetterFunction(E propertyElement, ExecutableElement setter) { VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters()); boolean nullableParameter = nullableAnnotationFor(parameterElement, parameterElement.asType()).isPresent(); - TypeMirror targetType = getterToPropertyType.get(valueGetter); + String property = propertyElements().inverse().get(propertyElement); + TypeMirror targetType = rewrittenPropertyTypes.get(property); ExecutableType finalSetter = MoreTypes.asExecutable( typeUtils.asMemberOf(MoreTypes.asDeclared(builderType.asType()), setter)); @@ -448,14 +457,14 @@ class BuilderMethodClassifier { && typeUtils.isAssignable(targetType, parameterType)) { if (nullableParameter) { boolean nullableProperty = - nullableAnnotationFor(valueGetter, valueGetter.getReturnType()).isPresent(); + nullableAnnotationFor(propertyElement, originalPropertyType(propertyElement)) + .isPresent(); if (!nullableProperty) { errorReporter.reportError( setter, - "[AutoValueNullNotNull] Parameter of setter method is @Nullable but property method" - + " %s.%s() is not", - autoValueClass, - valueGetter.getSimpleName()); + "[%sNullNotNull] Parameter of setter method is @Nullable but %s is not", + autoWhat(), + propertyString(propertyElement)); return Optional.empty(); } } @@ -465,12 +474,15 @@ class BuilderMethodClassifier { // Parameter type is not equal to property type, but might be convertible with copyOf. ImmutableList<ExecutableElement> copyOfMethods = copyOfMethods(targetType, nullableParameter); if (!copyOfMethods.isEmpty()) { - return getConvertingSetterFunction(copyOfMethods, valueGetter, setter, parameterType); + return getConvertingSetterFunction(copyOfMethods, propertyElement, setter, parameterType); } errorReporter.reportError( setter, - "[AutoValueGetVsSet] Parameter type %s of setter method should be %s to match getter %s.%s", - parameterType, targetType, autoValueClass, valueGetter.getSimpleName()); + "[%sGetVsSet] Parameter type %s of setter method should be %s to match %s", + autoWhat(), + parameterType, + targetType, + propertyString(propertyElement)); return Optional.empty(); } @@ -481,10 +493,11 @@ class BuilderMethodClassifier { */ private Optional<Copier> getConvertingSetterFunction( ImmutableList<ExecutableElement> copyOfMethods, - ExecutableElement valueGetter, + E propertyElement, ExecutableElement setter, TypeMirror parameterType) { - DeclaredType targetType = MoreTypes.asDeclared(getterToPropertyType.get(valueGetter)); + String property = propertyElements().inverse().get(propertyElement); + DeclaredType targetType = MoreTypes.asDeclared(rewrittenPropertyTypes.get(property)); for (ExecutableElement copyOfMethod : copyOfMethods) { Optional<Copier> function = getConvertingSetterFunction(copyOfMethod, targetType, parameterType); @@ -495,12 +508,12 @@ class BuilderMethodClassifier { String targetTypeSimpleName = targetType.asElement().getSimpleName().toString(); errorReporter.reportError( setter, - "[AutoValueGetVsSetOrConvert] Parameter type %s of setter method should be %s to match" - + " getter %s.%s, or it should be a type that can be passed to %s.%s to produce %s", + "[%sGetVsSetOrConvert] Parameter type %s of setter method should be %s to match %s, or it" + + " should be a type that can be passed to %s.%s to produce %s", + autoWhat(), parameterType, targetType, - autoValueClass, - valueGetter.getSimpleName(), + propertyString(propertyElement), targetTypeSimpleName, copyOfMethods.get(0).getSimpleName(), targetType); @@ -635,7 +648,76 @@ class BuilderMethodClassifier { return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); } - private String typeParamsString() { - return TypeSimplifier.actualTypeParametersString(autoValueClass); + /** + * True if the given property is nullable, either because its type has a {@code @Nullable} type + * annotation, or because its getter method has a {@code @Nullable} method annotation. + */ + private boolean propertyIsNullable(String property) { + E propertyElement = propertyElements().get(property); + return Stream.of(propertyElement, originalPropertyType(propertyElement)) + .flatMap(ac -> ac.getAnnotationMirrors().stream()) + .map(a -> a.getAnnotationType().asElement().getSimpleName()) + .anyMatch(n -> n.contentEquals("Nullable")); } + + /** + * Returns a map from property names to the corresponding source program elements. For AutoValue, + * these elements are the abstract getter methods in the {@code @AutoValue} class. For + * AutoBuilder, they are the parameters of the constructor or method that the generated builder + * will call. + */ + abstract ImmutableBiMap<String, E> propertyElements(); + + /** + * Returns the property type as it appears on the original source program element. This can be + * different from the type stored in {@link #rewrittenPropertyTypes} since that one will refer to + * type variables in the builder rather than in the original class. Also, {@link + * #rewrittenPropertyTypes} will not have type annotations even if they were present on the + * original element, so {@code originalPropertyType} is the right thing to use for those. + */ + abstract TypeMirror originalPropertyType(E propertyElement); + + /** + * A string identifying the given property element, which is a method for AutoValue or a parameter + * for AutoBuilder. + */ + abstract String propertyString(E propertyElement); + + /** + * Returns the name of the property that the given no-arg builder method queries, if + * any. For example, if your {@code @AutoValue} class has a method {@code abstract String + * getBar()} then an abstract method in its builder with the same signature will query the {@code + * bar} property. + */ + abstract Optional<String> propertyForBuilderGetter(ExecutableElement method); + + /** + * Checks for failed JavaBean usage when a method that looks like a setter doesn't actually match + * anything, and emits a compiler Note if detected. A frequent source of problems is where the + * JavaBeans conventions have been followed for most but not all getters. Then AutoValue considers + * that they haven't been followed at all, so you might have a property called getFoo where you + * thought it was called just foo, and you might not understand why your setter called setFoo is + * rejected (it would have to be called setGetFoo). + * + * <p>This is not relevant for AutoBuilder, which uses parameter names rather than getters. The + * parameter names are unambiguously the same as the property names. + */ + abstract void checkForFailedJavaBean(ExecutableElement rejectedSetter); + + /** + * A string describing what sort of Auto this is, {@code "AutoValue"} or {@code "AutoBuilder"}. + */ + abstract String autoWhat(); + + /** + * A string describing what a builder getter must match: a property method for AutoValue, a + * parameter for AutoBuilder. + */ + abstract String getterMustMatch(); + + /** + * A string describing what a property builder for property {@code foo} must match, {@code foo() + * or getFoo()} for AutoValue, {@code foo} for AutoBuilder. + */ + abstract String fooBuilderMustMatch(); } |