Add @Immutable

This annotation marks an interface as effectively immutable,
enforcing through an annotation processor that it follow these
guidelines:
 - Only exposes methods and/or static final constants
 - Every exposed type is an @Immutable interface or otherwise immutable class
 - Every method must return a type (no void methods allowed)
 - All inner classes must be @Immutable interfaces

This is in preparation of making PackageState fully immutable.
As a test case it has been marked in this change but has its
errors suppressed using @Immutable.Ignore to have the build work.

Test: atest --host ImmutabilityAnnotationProcessorUnitTests
Test: m services.core.unboosted

Change-Id: I59c615a97d5a30b83a5bcace0e2cee270ea5d1d2
diff --git a/tools/processors/immutability/Android.bp b/tools/processors/immutability/Android.bp
new file mode 100644
index 0000000..01e7a31
--- /dev/null
+++ b/tools/processors/immutability/Android.bp
@@ -0,0 +1,58 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_host {
+    name: "ImmutabilityAnnotationProcessorHostLibrary",
+    srcs: [
+        "src/**/*.kt",
+        "src/**/*.java",
+    ],
+    use_tools_jar: true,
+    // The --add-modules/exports flags below don't work for kotlinc yet, so pin this module to Java
+    // language level 8 (see b/139342589):
+    java_version: "1.8",
+    openjdk9: {
+        javacflags: [
+            "--add-modules=jdk.compiler",
+            "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+            "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+            "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+            "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+        ],
+    },
+}
+
+java_plugin {
+    name: "ImmutabilityAnnotationProcessor",
+    processor_class: "android.processor.immutability.ImmutabilityProcessor",
+    static_libs: ["ImmutabilityAnnotationProcessorHostLibrary"],
+}
+
+java_library {
+    name: "ImmutabilityAnnotation",
+    srcs: ["src/**/Immutable.java"],
+    sdk_version: "core_current",
+    host_supported: true,
+}
+
+java_test_host {
+    name: "ImmutabilityAnnotationProcessorUnitTests",
+
+    srcs: ["test/**/*.kt"],
+
+    static_libs: [
+        "compile-testing-prebuilt",
+        "truth-prebuilt",
+        "junit",
+        "kotlin-reflect",
+        "ImmutabilityAnnotationProcessorHostLibrary",
+    ],
+
+    test_suites: ["general-tests"],
+}
diff --git a/tools/processors/immutability/OWNERS b/tools/processors/immutability/OWNERS
new file mode 100644
index 0000000..86ae581
--- /dev/null
+++ b/tools/processors/immutability/OWNERS
@@ -0,0 +1 @@
+include /PACKAGE_MANAGER_OWNERS
diff --git a/tools/processors/immutability/TEST_MAPPING b/tools/processors/immutability/TEST_MAPPING
new file mode 100644
index 0000000..4e8e238
--- /dev/null
+++ b/tools/processors/immutability/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+    "presubmit": [
+        {
+            "name": "ImmutabilityAnnotationProcessorUnitTests"
+        }
+    ]
+}
diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
new file mode 100644
index 0000000..f7690d2
--- /dev/null
+++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 android.processor.immutability
+
+import com.sun.tools.javac.code.Symbol
+import com.sun.tools.javac.code.Type
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.tools.Diagnostic
+
+val IMMUTABLE_ANNOTATION_NAME = Immutable::class.qualifiedName
+
+class ImmutabilityProcessor : AbstractProcessor() {
+
+    companion object {
+        /**
+         * Types that are already immutable.
+         */
+        private val IGNORED_TYPES = listOf(
+            "java.io.File",
+            "java.lang.Boolean",
+            "java.lang.Byte",
+            "java.lang.CharSequence",
+            "java.lang.Character",
+            "java.lang.Double",
+            "java.lang.Float",
+            "java.lang.Integer",
+            "java.lang.Long",
+            "java.lang.Short",
+            "java.lang.String",
+            "java.lang.Void",
+        )
+    }
+
+    private lateinit var collectionType: TypeMirror
+    private lateinit var mapType: TypeMirror
+
+    private lateinit var ignoredTypes: List<TypeMirror>
+
+    private val seenTypes = mutableSetOf<Type>()
+
+    override fun getSupportedSourceVersion() = SourceVersion.latest()!!
+
+    override fun getSupportedAnnotationTypes() = setOf(Immutable::class.qualifiedName)
+
+    override fun init(processingEnv: ProcessingEnvironment) {
+        super.init(processingEnv)
+        collectionType = processingEnv.erasedType("java.util.Collection")
+        mapType = processingEnv.erasedType("java.util.Map")
+        ignoredTypes = IGNORED_TYPES.map { processingEnv.elementUtils.getTypeElement(it).asType() }
+    }
+
+    override fun process(
+        annotations: MutableSet<out TypeElement>,
+        roundEnvironment: RoundEnvironment
+    ): Boolean {
+        annotations.find {
+            it.qualifiedName.toString() == IMMUTABLE_ANNOTATION_NAME
+        } ?: return false
+        roundEnvironment.getElementsAnnotatedWith(Immutable::class.java)
+            .forEach { visitClass(emptyList(), seenTypes, it, it as Symbol.TypeSymbol) }
+        return true
+    }
+
+    private fun visitClass(
+        parentChain: List<String>,
+        seenTypes: MutableSet<Type>,
+        elementToPrint: Element,
+        classType: Symbol.TypeSymbol,
+    ) {
+        if (!seenTypes.add(classType.asType())) return
+        if (classType.getAnnotation(Immutable.Ignore::class.java) != null) return
+
+        if (classType.getAnnotation(Immutable::class.java) == null) {
+            printError(parentChain, elementToPrint,
+                MessageUtils.classNotImmutableFailure(classType.simpleName.toString()))
+        }
+
+        if (classType.getKind() != ElementKind.INTERFACE) {
+            printError(parentChain, elementToPrint, MessageUtils.nonInterfaceClassFailure())
+        }
+
+        val filteredElements = classType.enclosedElements
+            .filterNot(::isIgnored)
+
+        filteredElements
+            .filter { it.getKind() == ElementKind.FIELD }
+            .forEach {
+                if (it.isStatic) {
+                    if (!it.isPrivate) {
+                        if (!it.modifiers.contains(Modifier.FINAL)) {
+                            printError(parentChain, it, MessageUtils.staticNonFinalFailure())
+                        }
+
+                        visitType(parentChain, seenTypes, it, it.type)
+                    }
+                } else {
+                    printError(parentChain, it, MessageUtils.memberNotMethodFailure())
+                }
+            }
+
+        // Scan inner classes before methods so that any violations isolated to the file prints
+        // the error on the class declaration rather than on the method that returns the type.
+        // Although it doesn't matter too much either way.
+        filteredElements
+            .filter { it.getKind() == ElementKind.CLASS }
+            .map { it as Symbol.ClassSymbol }
+            .forEach {
+                visitClass(parentChain, seenTypes, it, it)
+            }
+
+        val newChain = parentChain + "$classType"
+
+        filteredElements
+            .filter { it.getKind() == ElementKind.METHOD }
+            .map { it as Symbol.MethodSymbol }
+            .forEach {
+                visitMethod(newChain, seenTypes, it)
+            }
+    }
+
+    private fun visitMethod(
+        parentChain: List<String>,
+        seenTypes: MutableSet<Type>,
+        method: Symbol.MethodSymbol,
+    ) {
+        val returnType = method.returnType
+        val typeName = returnType.toString()
+        when (returnType.kind) {
+            TypeKind.BOOLEAN,
+            TypeKind.BYTE,
+            TypeKind.SHORT,
+            TypeKind.INT,
+            TypeKind.LONG,
+            TypeKind.CHAR,
+            TypeKind.FLOAT,
+            TypeKind.DOUBLE,
+            TypeKind.NONE,
+            TypeKind.NULL -> {
+                // Do nothing
+            }
+            TypeKind.VOID -> {
+                if (!method.isConstructor) {
+                    printError(parentChain, method, MessageUtils.voidReturnFailure())
+                }
+            }
+            TypeKind.ARRAY -> {
+                printError(parentChain, method, MessageUtils.arrayFailure())
+            }
+            TypeKind.DECLARED -> {
+                visitType(parentChain, seenTypes, method, method.returnType)
+            }
+            TypeKind.ERROR,
+            TypeKind.TYPEVAR,
+            TypeKind.WILDCARD,
+            TypeKind.PACKAGE,
+            TypeKind.EXECUTABLE,
+            TypeKind.OTHER,
+            TypeKind.UNION,
+            TypeKind.INTERSECTION,
+            // Java 9+
+            // TypeKind.MODULE,
+            null -> printError(parentChain, method,
+                MessageUtils.genericTypeKindFailure(typeName = typeName))
+            else -> printError(parentChain, method,
+                MessageUtils.genericTypeKindFailure(typeName = typeName))
+        }
+    }
+
+    private fun visitType(
+        parentChain: List<String>,
+        seenTypes: MutableSet<Type>,
+        symbol: Symbol,
+        type: Type,
+        nonInterfaceClassFailure: () -> String = { MessageUtils.nonInterfaceReturnFailure() },
+    ) {
+        if (type.isPrimitive) return
+        if (type.isPrimitiveOrVoid) {
+            printError(parentChain, symbol, MessageUtils.voidReturnFailure())
+            return
+        }
+
+        if (ignoredTypes.any { processingEnv.typeUtils.isSameType(it, type) }) {
+            return
+        }
+
+        // Collection (and Map) types are ignored for the interface check as they have immutability
+        // enforced through a runtime exception which must be verified in a separate runtime test
+        val isMap = processingEnv.typeUtils.isAssignable(type, mapType)
+        if (!processingEnv.typeUtils.isAssignable(type, collectionType) && !isMap) {
+            if (type.isInterface) {
+                visitClass(parentChain, seenTypes, symbol,
+                    processingEnv.typeUtils.asElement(type) as Symbol.TypeSymbol)
+            } else {
+                printError(parentChain, symbol, nonInterfaceClassFailure())
+                // If the type already isn't an interface, don't scan deeper children
+                // to avoid printing an excess amount of errors for a known bad type.
+                return
+            }
+        }
+
+        type.typeArguments.forEachIndexed { index, typeArg ->
+            visitType(parentChain, seenTypes, symbol, typeArg) {
+                MessageUtils.nonInterfaceReturnFailure(prefix = when {
+                    !isMap -> ""
+                    index == 0 -> "Key " + typeArg.asElement().simpleName
+                    else -> "Value " + typeArg.asElement().simpleName
+                }, index = index)
+            }
+        }
+    }
+
+    private fun printError(
+        parentChain: List<String>,
+        element: Element,
+        message: String,
+    ) = processingEnv.messager.printMessage(
+        Diagnostic.Kind.ERROR,
+        // Drop one from the parent chain so that the directly enclosing class isn't logged.
+        // It exists in the list at this point in the traversal so that further children can
+        // include the right reference.
+        parentChain.dropLast(1).joinToString() + "\n\t" + message,
+        element,
+    )
+
+    private fun ProcessingEnvironment.erasedType(typeName: String) =
+        typeUtils.erasure(elementUtils.getTypeElement(typeName).asType())
+
+    private fun isIgnored(symbol: Symbol) =
+        symbol.getAnnotation(Immutable.Ignore::class.java) != null
+}
\ No newline at end of file
diff --git a/tools/processors/immutability/src/android/processor/immutability/Immutable.java b/tools/processors/immutability/src/android/processor/immutability/Immutable.java
new file mode 100644
index 0000000..0470faf
--- /dev/null
+++ b/tools/processors/immutability/src/android/processor/immutability/Immutable.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 android.processor.immutability;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Marks a class as immutable. When used with the Immutability processor, verifies at compile that
+ * the class is truly immutable. Immutable is defined as:
+ * <ul>
+ *     <li>Only exposes methods and/or static final constants</li>
+ *     <li>Every exposed type is an @Immutable interface or otherwise immutable class</li>
+ *     <ul>
+ *         <li>Implicitly immutable types like {@link String} are ignored</li>
+ *         <li>{@link Collection} and {@link Map} and their subclasses where immutability is
+ *         enforced at runtime are ignored</li>
+ *     </ul>
+ *     <li>Every method must return a type (no void methods allowed)</li>
+ *     <li>All inner classes must be @Immutable interfaces</li>
+ * </ul>
+ */
+public @interface Immutable {
+
+    /**
+     * Marks a specific class, field, or method as ignored for immutability validation.
+     */
+    @Retention(RetentionPolicy.CLASS) // Not SOURCE as that isn't retained for some reason
+    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
+    @interface Ignore {
+        String reason() default "";
+    }
+}
diff --git a/tools/processors/immutability/src/android/processor/immutability/MessageUtils.kt b/tools/processors/immutability/src/android/processor/immutability/MessageUtils.kt
new file mode 100644
index 0000000..63f5641
--- /dev/null
+++ b/tools/processors/immutability/src/android/processor/immutability/MessageUtils.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 android.processor.immutability
+
+object MessageUtils {
+
+    fun classNotImmutableFailure(className: String) = "$className should be marked @Immutable"
+
+    fun memberNotMethodFailure() = "Member must be a method"
+
+    fun nonInterfaceClassFailure() = "Class was not an interface"
+
+    fun nonInterfaceReturnFailure(prefix: String, index: Int = -1) =
+        if (prefix.isEmpty()) {
+            "Type at index $index was not an interface"
+        } else {
+            "$prefix was not an interface"
+        }
+
+    fun genericTypeKindFailure(typeName: CharSequence) = "TypeKind $typeName unsupported"
+
+    fun arrayFailure() = "Array types are not supported as they can be mutated by callers"
+
+    fun nonInterfaceReturnFailure() = "Must return an interface"
+
+    fun voidReturnFailure() = "Cannot return void"
+
+    fun staticNonFinalFailure() = "Static member must be final"
+}
\ No newline at end of file
diff --git a/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt b/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt
new file mode 100644
index 0000000..7e1e8e1
--- /dev/null
+++ b/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 android.processor
+
+import android.processor.immutability.IMMUTABLE_ANNOTATION_NAME
+import android.processor.immutability.ImmutabilityProcessor
+import android.processor.immutability.MessageUtils
+import com.google.common.truth.Expect
+import com.google.testing.compile.CompilationSubject.assertThat
+import com.google.testing.compile.Compiler.javac
+import com.google.testing.compile.JavaFileObjects
+import org.junit.Rule
+import org.junit.Test
+import javax.tools.JavaFileObject
+
+class ImmutabilityProcessorTest {
+
+    companion object {
+        private const val PACKAGE_PREFIX = "android.processor.immutability"
+        private const val DATA_CLASS_NAME = "DataClass"
+        private val ANNOTATION = JavaFileObjects.forSourceString(IMMUTABLE_ANNOTATION_NAME,
+            /* language=JAVA */ """
+                package $PACKAGE_PREFIX;
+
+                import java.lang.annotation.Retention;
+                import java.lang.annotation.RetentionPolicy;
+
+                @Retention(RetentionPolicy.SOURCE)
+                public @interface Immutable {
+                    @Retention(RetentionPolicy.SOURCE)
+                    @interface Ignore {}
+                }
+            """.trimIndent()
+        )
+    }
+
+    @get:Rule
+    val expect = Expect.create()
+
+    @Test
+    fun validInterface() = test(
+        JavaFileObjects.forSourceString("$PACKAGE_PREFIX.$DATA_CLASS_NAME",
+            /* language=JAVA */ """
+                package $PACKAGE_PREFIX;
+
+                import $IMMUTABLE_ANNOTATION_NAME;
+                import java.util.ArrayList;
+                import java.util.Collections;
+                import java.util.List;
+
+                @Immutable
+                public interface $DATA_CLASS_NAME {
+                    InnerInterface DEFAULT = new InnerInterface() {
+                        @Override
+                        public String getValue() {
+                            return "";
+                        }
+                        @Override
+                        public List<String> getArray() {
+                            return Collections.emptyList();
+                        }
+                    };
+
+                    String getValue();
+                    ArrayList<String> getArray();
+                    InnerInterface getInnerInterface();
+
+                    @Immutable
+                    interface InnerInterface {
+                        String getValue();
+                        List<String> getArray();
+                    }
+                }
+                """.trimIndent()
+        ), errors = emptyList())
+
+    @Test
+    fun abstractClass() = test(
+        JavaFileObjects.forSourceString("$PACKAGE_PREFIX.$DATA_CLASS_NAME",
+            /* language=JAVA */ """
+                package $PACKAGE_PREFIX;
+
+                import $IMMUTABLE_ANNOTATION_NAME;
+                import java.util.Map;
+
+                @Immutable
+                public abstract class $DATA_CLASS_NAME {
+                    public static final String IMMUTABLE = "";
+                    public static final InnerClass NOT_IMMUTABLE = null;
+                    public static InnerClass NOT_FINAL = null;
+
+                    // Field finality doesn't matter, methods are always enforced so that future
+                    // field compaction or deprecation is possible
+                    private final String fieldFinal = "";
+                    private String fieldNonFinal;
+                    public abstract void sideEffect();
+                    public abstract String[] getArray();
+                    public abstract InnerClass getInnerClassOne();
+                    public abstract InnerClass getInnerClassTwo();
+                    @Immutable.Ignore
+                    public abstract InnerClass getIgnored();
+                    public abstract InnerInterface getInnerInterface();
+
+                    public abstract Map<String, String> getValidMap();
+                    public abstract Map<InnerClass, InnerClass> getInvalidMap();
+
+                    public static final class InnerClass {
+                        public String innerField;
+                        public String[] getArray() { return null; }
+                    }
+
+                    public interface InnerInterface {
+                        String[] getArray();
+                        InnerClass getInnerClass();
+                    }
+                }
+                """.trimIndent()
+        ), errors = listOf(
+            nonInterfaceClassFailure(line = 7),
+            nonInterfaceReturnFailure(line = 9),
+            staticNonFinalFailure(line = 10),
+            nonInterfaceReturnFailure(line = 10),
+            memberNotMethodFailure(line = 14),
+            memberNotMethodFailure(line = 15),
+            voidReturnFailure(line = 16),
+            arrayFailure(line = 17),
+            nonInterfaceReturnFailure(line = 18),
+            nonInterfaceReturnFailure(line = 19),
+            nonInterfaceReturnFailure(line = 25,  prefix = "Key InnerClass"),
+            nonInterfaceReturnFailure(line = 25,  prefix = "Value InnerClass"),
+            classNotImmutableFailure(line = 27, className = "InnerClass"),
+            nonInterfaceClassFailure(line = 27),
+            memberNotMethodFailure(line = 28),
+            arrayFailure(line = 29),
+            classNotImmutableFailure(line = 22, className = "InnerInterface"),
+            arrayFailure(line = 33),
+            nonInterfaceReturnFailure(line = 34),
+        ))
+
+    private fun test(source: JavaFileObject, errors: List<CompilationError>) {
+        val compilation = javac()
+            .withProcessors(ImmutabilityProcessor())
+            .compile(listOf(source) + ANNOTATION)
+        errors.forEach {
+            try {
+                assertThat(compilation)
+                    .hadErrorContaining(it.message)
+                    .inFile(source)
+                    .onLine(it.line)
+            } catch (e: AssertionError) {
+                // Wrap the exception so that the line number is logged
+                val wrapped = AssertionError("Expected $it, ${e.message}").apply {
+                    stackTrace = e.stackTrace
+                }
+
+                // Wrap again with Expect so that all errors are reported. This is very bad code
+                // but can only be fixed by updating compile-testing with a better Truth Subject
+                // implementation.
+                expect.that(wrapped).isNull()
+            }
+        }
+
+        try {
+            assertThat(compilation).hadErrorCount(errors.size)
+        } catch (e: AssertionError) {
+            if (expect.hasFailures()) {
+                expect.that(e).isNull()
+            } else throw e
+        }
+    }
+
+    private fun classNotImmutableFailure(line: Long, className: String) =
+        CompilationError(line = line, message = MessageUtils.classNotImmutableFailure(className))
+
+    private fun nonInterfaceClassFailure(line: Long) =
+        CompilationError(line = line, message = MessageUtils.nonInterfaceClassFailure())
+
+    private fun nonInterfaceReturnFailure(line: Long) =
+        CompilationError(line = line, message = MessageUtils.nonInterfaceReturnFailure())
+
+    private fun nonInterfaceReturnFailure(line: Long, prefix: String, index: Int = -1) =
+        CompilationError(
+            line = line,
+            message = MessageUtils.nonInterfaceReturnFailure(prefix = prefix, index = index)
+        )
+
+    private fun memberNotMethodFailure(line: Long) =
+        CompilationError(line = line, message = MessageUtils.memberNotMethodFailure())
+
+    private fun voidReturnFailure(line: Long) =
+        CompilationError(line = line, message = MessageUtils.voidReturnFailure())
+
+    private fun staticNonFinalFailure(line: Long) =
+        CompilationError(line = line, message = MessageUtils.staticNonFinalFailure())
+
+    private fun arrayFailure(line: Long) =
+        CompilationError(line = line, message = MessageUtils.arrayFailure())
+
+    data class CompilationError(
+        val line: Long,
+        val message: String,
+    )
+}
\ No newline at end of file