// Test field initialization
// fields, initializers, static initializers, constructors.

import org.checkerframework.checker.nullness.qual.*;

interface FunctionInit<T extends @Nullable Object, R> {
  R apply(T t);
}

interface Consumer<T> {
  void consume(T t);
}

public class LambdaInit {
  String f1;
  String f2 = "";
  @Nullable String f3 = "";

  String f1b;
  FunctionInit<String, String> ff0 =
      s -> {
        // :: error: (dereference.of.nullable)
        f1.toString();
        // :: error: (dereference.of.nullable)
        f1b.toString();
        f2.toString();
        // :: error: (dereference.of.nullable)
        f3.toString();
        return "";
      };
  // Test field value refinement after initializer. f1b should still be @Nullable in the lambda.
  Object o1 = f1b = "";

  String f4;

  {
    f3 = "";
    f4 = "";
    FunctionInit<String, String> ff0 =
        s -> {
          // :: error: (dereference.of.nullable)
          f1.toString();
          f2.toString();
          // :: error: (dereference.of.nullable)
          f3.toString();
          f4.toString();
          return "";
        };
  }

  String f5;

  @SuppressWarnings("initialization.fields.uninitialized") // f1 is not initialized
  LambdaInit() {
    f5 = "";
    FunctionInit<String, String> ff0 =
        s -> {
          // :: error: (dereference.of.nullable)
          f1.toString();
          f2.toString();
          // :: error: (dereference.of.nullable)
          f3.toString();
          f5.toString();
          return "";
        };
  }

  // Test for https://github.com/typetools/checker-framework/issues/5194 .
  Object o =
      new Object() {
        @Override
        public String toString() {
          // BUG: this should not yield a warning.
          // :: error: (dereference.of.nullable)
          f1.toString();
          f2.toString();
          return "";
        }
      };

  //  Works!
  void method() {
    FunctionInit<String, String> ff0 =
        s -> {
          f1.toString();
          f2.toString();
          // :: error: (dereference.of.nullable)
          f3.toString();
          return "";
        };
  }

  // Test for nested
  class Nested {
    FunctionInit<String, String> ff0 =
        s -> {
          f1.toString();
          f2.toString();
          // :: error: (dereference.of.nullable)
          f3.toString();
          return "";
        };

    String f4;

    {
      f3 = "";
      f4 = "";
      FunctionInit<String, String> ff0 =
          s -> {
            f1.toString();
            f2.toString();
            // :: error: (dereference.of.nullable)
            f3.toString();
            f4.toString();
            return "";
          };
    }

    String f5;

    Nested() {
      f5 = "";
      FunctionInit<String, String> ff0 =
          s -> {
            f1.toString();
            f2.toString();
            // :: error: (dereference.of.nullable)
            f3.toString();
            f5.toString();
            return "";
          };
    }

    void method() {
      FunctionInit<String, String> ff0 =
          s -> {
            f1.toString();
            f2.toString();
            // :: error: (dereference.of.nullable)
            f3.toString();
            return "";
          };
    }
  }

  // Test for nested in a lambda
  Consumer<String> func =
      s -> {
        Consumer<String> ff0 =
            s2 -> {
              // :: error: (dereference.of.nullable)
              f1.toString();
              f2.toString();
              // :: error: (dereference.of.nullable)
              f3.toString();
            };
      };

  // Tests for static initializers.
  // :: error: (initialization.static.field.uninitialized)
  static String sf1;
  static String sf2 = "";
  static @Nullable String sf3 = "";
  static String sf1b;
  static FunctionInit<String, String> sff0 =
      s -> {

        // This is an issue with static initializers in general
        // // :: error: (dereference.of.nullable)
        sf1.toString();
        // This is an issue with static initializers in general
        // // :: error: (dereference.of.nullable)
        sf1b.toString();
        sf2.toString();
        // :: error: (dereference.of.nullable)
        sf3.toString();
        return "";
      };
  // Test field value refinement after initializer. f1b should still be null.
  static Object so1 = sf1b = "";

  static String sf4;

  static {
    sf3 = "";
    sf4 = "";
    FunctionInit<String, String> sff0 =
        s -> {

          // This is an issue with static initializers in general
          // // :: error: (dereference.of.nullable)
          sf1.toString();
          sf2.toString();
          // :: error: (dereference.of.nullable)
          sf3.toString();
          sf4.toString();
          return "";
        };
  }
}
