/*
 * Copyright 2021 Google LLC
 *
 * 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 com.google.auto.value.processor;

import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.Compiler.javac;

import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import javax.tools.JavaFileObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class AutoBuilderCompilationTest {
  private static final JavaFileObject EXPECTED_SIMPLE_OUTPUT =
      JavaFileObjects.forSourceLines(
          "foo.bar.AutoBuilder_Baz_Builder",
          "package foo.bar;",
          "",
          GeneratedImport.importGeneratedAnnotationType(),
          "",
          "@Generated(\"" + AutoBuilderProcessor.class.getName() + "\")",
          "class AutoBuilder_Baz_Builder implements Baz.Builder {",
          "  private Integer anInt;",
          "  private String aString;",
          "",
          "  AutoBuilder_Baz_Builder() {}",
          "",
          "  @Override public Baz.Builder setAnInt(int anInt) {",
          "    this.anInt = anInt;",
          "    return this;",
          "  }",
          "",
          "  @Override public Baz.Builder setAString(String aString) {",
          "    if (aString == null) {",
          "      throw new NullPointerException(\"Null aString\");",
          "    }",
          "    this.aString = aString;",
          "    return this;",
          "  }",
          "",
          "  @Override",
          "  public Baz build() {",
          "    if (this.anInt == null",
          "        || this.aString == null) {",
          "      StringBuilder missing = new StringBuilder();",
          "      if (this.anInt == null) {",
          "        missing.append(\" anInt\");",
          "      }",
          "      if (this.aString == null) {",
          "        missing.append(\" aString\");",
          "      }",
          "      throw new IllegalStateException(\"Missing required properties:\" + missing);",
          "    }",
          "    return new Baz(",
          "        this.anInt,",
          "        this.aString);",
          "  }",
          "}");

  @Test
  public void simpleSuccess() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public class Baz {",
            "  private final int anInt;",
            "  private final String aString;",
            "",
            "  public Baz(int anInt, String aString) {",
            "    this.anInt = anInt;",
            "    this.aString = aString;",
            "  }",
            "",
            "  public int anInt() {",
            "    return anInt;",
            "  }",
            "",
            "  public String aString() {",
            "    return aString;",
            "  }",
            "",
            "  public static Builder builder() {",
            "    return new AutoBuilder_Baz_Builder();",
            "  }",
            "",
            "  @AutoBuilder",
            "  public interface Builder {",
            "    Builder setAnInt(int x);",
            "    Builder setAString(String x);",
            "    Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation)
        .generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder")
        .hasSourceEquivalentTo(EXPECTED_SIMPLE_OUTPUT);
  }

  @Test
  public void simpleRecord() {
    double version = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value());
    assume().that(version).isAtLeast(16.0);
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public record Baz(int anInt, String aString) {",
            "  public static Builder builder() {",
            "    return new AutoBuilder_Baz_Builder();",
            "  }",
            "",
            "  @AutoBuilder",
            "  public interface Builder {",
            "    Builder setAnInt(int x);",
            "    Builder setAString(String x);",
            "    Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation)
        .generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder")
        .hasSourceEquivalentTo(EXPECTED_SIMPLE_OUTPUT);
  }

  @Test
  public void buildOtherPackage() {
    JavaFileObject built =
        JavaFileObjects.forSourceLines(
            "com.example.Built",
            "package com.example;",
            "",
            "public class Built {",
            "  private final int anInt;",
            "  private final String aString;",
            "",
            "  public Built(int anInt, String aString) {",
            "    this.anInt = anInt;",
            "    this.aString = aString;",
            "  }",
            "}");
    JavaFileObject builder =
        JavaFileObjects.forSourceLines(
            "foo.bar.Builder",
            "package foo.bar;",
            "",
            "import com.example.Built;",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "@AutoBuilder(ofClass = Built.class)",
            "public interface Builder {",
            "  public static Builder builder() {",
            "    return new AutoBuilder_Builder();",
            "  }",
            "",
            "  Builder setAnInt(int x);",
            "  Builder setAString(String x);",
            "  Built build();",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(built, builder);
    assertThat(compilation).succeededWithoutWarnings();
    assertThat(compilation).generatedSourceFile("foo.bar.AutoBuilder_Builder");
  }

  @Test
  public void autoBuilderOnEnum() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "@AutoBuilder",
            "public enum Baz {",
            "  ZIG, ZAG, DUSTIN,",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderWrongType] @AutoBuilder only applies to classes and interfaces")
        .inFile(javaFileObject)
        .onLineContaining("enum Baz");
  }

  @Test
  public void autoBuilderPrivate() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public class Baz {",
            "  @AutoBuilder",
            "  private interface Builder {",
            "    Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining("[AutoBuilderPrivate] @AutoBuilder class must not be private")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder");
  }

  @Test
  public void autoBuilderNestedInPrivate() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public class Baz {",
            "  private static class Private {",
            "    @AutoBuilder",
            "    public interface Builder {",
            "      Baz build();",
            "    }",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderInPrivate] @AutoBuilder class must not be nested in a private class")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder");
  }

  @Test
  public void autoBuilderInner() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public class Baz {",
            "  @AutoBuilder",
            "  abstract class Builder {",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining("[AutoBuilderInner] Nested @AutoBuilder class must be static")
        .inFile(javaFileObject)
        .onLineContaining("class Builder");
  }

  @Test
  public void innerConstructor() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public class Baz {",
            "  class Inner {}",
            "",
            "  @AutoBuilder(ofClass = Inner.class)",
            "  interface Builder {",
            "    abstract Inner build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining("[AutoBuilderInner] Nested @AutoBuilder ofClass class must be static")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder");
  }

  @Test
  public void noVisibleConstructor() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public class Baz {",
            "  @AutoBuilder(ofClass = System.class)",
            "  interface Builder {",
            "    abstract System build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining("[AutoBuilderNoVisible] No visible constructor for java.lang.System")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder");
  }

  @Test
  public void noVisibleMethod() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public class Baz {",
            "  private static Baz of() {",
            "    return new Baz();",
            "  }",
            "",
            "  @AutoBuilder(callMethod = \"of\")",
            "  interface Builder {",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderNoVisible] No visible static method named \"of\" for foo.bar.Baz")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder");
  }

  @Test
  public void methodNotStatic() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public class Baz {",
            "  Baz of() {",
            "    return this;",
            "  }",
            "",
            "  @AutoBuilder(callMethod = \"of\")",
            "  interface Builder {",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderNoVisible] No visible static method named \"of\" for foo.bar.Baz")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder");
  }

  @Test
  public void noMatchingConstructor() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public class Baz {",
            "  public Baz(int notMe) {}",
            "",
            "  public Baz(String notMeEither) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    Builder setBuh(String x);",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderNoMatch] Property names do not correspond to the parameter names of any"
                + " constructor:\n"
                + "    Baz(int notMe)\n"
                + "    Baz(java.lang.String notMeEither)")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder");
  }

  @Test
  public void twoMatchingConstructors() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public class Baz {",
            "  public Baz() {}",
            "",
            "  public Baz(int buh) {}",
            "",
            "  public Baz(String buh) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    Builder setBuh(String x);",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderAmbiguous] Property names correspond to more than one constructor:\n"
                + "    Baz(int buh)\n"
                + "    Baz(java.lang.String buh)")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder");
  }

  @Test
  public void constructInterface() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "public interface Baz {",
            "  @AutoBuilder",
            "  interface Builder {",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderEnclosing] @AutoBuilder must specify ofClass=Something.class or it must"
                + " be nested inside the class to be built; actually nested inside interface"
                + " foo.bar.Baz")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder");
  }

  @Test
  public void inconsistentSetPrefix() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "class Baz {",
            "  Baz(int one, int two) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    abstract Builder one(int x);",
            "    abstract Builder setTwo(int x);",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderSetNotSet] If any setter methods use the setFoo convention then all must")
        .inFile(javaFileObject)
        .onLineContaining("Builder one(int x)");
  }

  @Test
  public void missingSetter() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "class Baz {",
            "  Baz(int one, int two) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    abstract Builder one(int x);",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderBuilderMissingMethod] Expected a method with this signature:"
                + " foo.bar.Baz.Builder two(int), or a twoBuilder() method")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder");
  }

  @Test
  public void tooManyArgs() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "class Baz {",
            "  Baz(int one, int two) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    abstract Builder one(int x);",
            "    abstract Builder two(int x);",
            "    abstract Builder many(int x, int y);",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining("[AutoBuilderBuilderArgs] Builder methods must have 0 or 1 parameters")
        .inFile(javaFileObject)
        .onLineContaining("many(int x, int y)");
  }

  @Test
  public void alienNoArgMethod() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "class Baz {",
            "  Baz(int one, int two) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    abstract Builder one(int x);",
            "    abstract Builder two(int x);",
            "    abstract String alien();",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderBuilderNoArg] Method without arguments should be a build method returning"
                + " foo.bar.Baz, or a getter method with the same name and type as a parameter of"
                + " Baz(int one, int two), or fooBuilder() where foo is a parameter of Baz(int"
                + " one, int two)")
        .inFile(javaFileObject)
        .onLineContaining("String alien()");
  }

  @Test
  public void alienOneArgMethod() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "class Baz {",
            "  Baz(int one, int two) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    abstract Builder one(int x);",
            "    abstract Builder two(int x);",
            "    abstract Builder three(int x);",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderBuilderWhatProp] Method three does not correspond to "
                + "a parameter of Baz(int one, int two)")
        .inFile(javaFileObject)
        .onLineContaining("three(int x)");
  }

  @Test
  public void setterReturnType() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "class Baz {",
            "  Baz(int one, int two) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    abstract Builder one(int x);",
            "    abstract void two(int x);",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderBuilderRet] Setter methods must return foo.bar.Baz.Builder")
        .inFile(javaFileObject)
        .onLineContaining("two(int x)");
  }

  @Test
  public void nullableSetterForNonNullableParameter() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "import org.jspecify.nullness.Nullable;",
            "",
            "class Baz {",
            "  Baz(String thing) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    abstract Builder thing(@Nullable String x);",
            "    abstract Baz build();",
            "  }",
            "}");
    JavaFileObject nullableFileObject =
        JavaFileObjects.forSourceLines(
            "org.jspecify.nullness.Nullable",
            "package org.jspecify.nullness;",
            "",
            "import java.lang.annotation.ElementType;",
            "import java.lang.annotation.Target;",
            "",
            "@Target(ElementType.TYPE_USE)",
            "public @interface Nullable {}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject, nullableFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderNullNotNull] Parameter of setter method is @Nullable but parameter"
                + " \"thing\" of Baz(java.lang.String thing) is not")
        .inFile(javaFileObject)
        .onLineContaining("thing(@Nullable String x)");
  }

  @Test
  public void setterWrongType() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "",
            "class Baz {",
            "  Baz(int up, int down) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    abstract Builder up(int x);",
            "    abstract Builder down(String x);",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderGetVsSet] Parameter type java.lang.String of setter method should be int"
                + " to match parameter \"down\" of Baz(int up, int down)")
        .inFile(javaFileObject)
        .onLineContaining("down(String x)");
  }

  @Test
  public void setterWrongTypeEvenWithConversion() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "import java.util.Optional;",
            "",
            "class Baz {",
            "  Baz(Optional<String> maybe) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder {",
            "    abstract Builder maybe(int x);",
            "    abstract Baz build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderGetVsSetOrConvert] Parameter type int of setter method should be"
                + " java.util.Optional<java.lang.String> to match parameter \"maybe\" of"
                + " Baz(java.util.Optional<java.lang.String> maybe), or it should be a type that"
                + " can be passed to Optional.of to produce java.util.Optional<java.lang.String>")
        .inFile(javaFileObject)
        .onLineContaining("maybe(int x)");
  }

  @Test
  public void typeParamMismatch() {
    JavaFileObject javaFileObject =
        JavaFileObjects.forSourceLines(
            "foo.bar.Baz",
            "package foo.bar;",
            "",
            "import com.google.auto.value.AutoBuilder;",
            "import java.util.Optional;",
            "",
            "class Baz<T> {",
            "  Baz(T param) {}",
            "",
            "  @AutoBuilder",
            "  interface Builder<E> {",
            "    abstract Builder<E> param(E param);",
            "    abstract Baz<E> build();",
            "  }",
            "}");
    Compilation compilation =
        javac()
            .withProcessors(new AutoBuilderProcessor())
            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
            .compile(javaFileObject);
    assertThat(compilation).failed();
    assertThat(compilation)
        .hadErrorContaining(
            "[AutoBuilderTypeParams] Builder type parameters <E> must match type parameters <T> of"
                + " Baz(T param)")
        .inFile(javaFileObject)
        .onLineContaining("interface Builder<E>");
  }
}
