// 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);
}

// For test purposes, f1 is never initialized
@SuppressWarnings({
    "initialization.fields.uninitialized",
    "initialization.static.fields.uninitialized"
})
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;

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

    //    // This is a bug
    //    // Could probably be fixed with CommittmentTreeAnnotator::visitMethod
    //    // Or more likely, TypeFromTree::212
    //    // AnnotatedTypeFactory::getImplicitReceiverType::1146(there is a todo...)
    //    Object o = new Object() {
    //        @Override
    //        public String toString() {
    //            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.
    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 "";
                };
    }
}
