Merge "Add super type impl - Generate AppFunctionInvoker Impl" into androidx-main
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
index 81f6291..6151d45 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
@@ -19,6 +19,7 @@
 import androidx.appfunctions.compiler.core.ProcessingException
 import androidx.appfunctions.compiler.core.logException
 import androidx.appfunctions.compiler.processors.AppFunctionIdProcessor
+import androidx.appfunctions.compiler.processors.AppFunctionInventoryProcessor
 import com.google.devtools.ksp.processing.KSPLogger
 import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.processing.SymbolProcessor
@@ -51,10 +52,9 @@
 
         override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
             val idProcessor = AppFunctionIdProcessor(environment.codeGenerator)
+            val inventoryProcessor = AppFunctionInventoryProcessor(environment.codeGenerator)
             return AppFunctionCompiler(
-                listOf(
-                    idProcessor,
-                ),
+                listOf(idProcessor, inventoryProcessor),
                 environment.logger,
             )
         }
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
index f3f5fbb..fe0d3fa 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
@@ -22,6 +22,8 @@
 object IntrospectionHelper {
     // Package names
     private const val APP_FUNCTIONS_PACKAGE_NAME = "androidx.appfunctions"
+    private const val APP_FUNCTIONS_INTERNAL_PACKAGE_NAME = "androidx.appfunctions.internal"
+    private const val APP_FUNCTIONS_METADATA_PACKAGE_NAME = "androidx.appfunctions.metadata"
 
     // Annotation classes
     object AppFunctionAnnotation {
@@ -30,4 +32,8 @@
 
     // Classes
     val APP_FUNCTION_CONTEXT_CLASS = ClassName(APP_FUNCTIONS_PACKAGE_NAME, "AppFunctionContext")
+    val APP_FUNCTION_INVENTORY_CLASS =
+        ClassName(APP_FUNCTIONS_INTERNAL_PACKAGE_NAME, "AppFunctionInventory")
+    val APP_FUNCTION_METADATA_CLASS =
+        ClassName(APP_FUNCTIONS_METADATA_PACKAGE_NAME, "AppFunctionMetadata")
 }
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionInventoryProcessor.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionInventoryProcessor.kt
new file mode 100644
index 0000000..cedc597
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionInventoryProcessor.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2025 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 androidx.appfunctions.compiler.processors
+
+import androidx.appfunctions.compiler.AppFunctionCompiler
+import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver
+import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver.AnnotatedAppFunctions
+import androidx.appfunctions.compiler.core.IntrospectionHelper
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.buildCodeBlock
+
+/**
+ * Generates implementations for the AppFunctionInventory interface.
+ *
+ * It resolves all functions in a class annotated with `@AppFunction`, and generates the
+ * corresponding metadata for those functions.
+ */
+class AppFunctionInventoryProcessor(
+    private val codeGenerator: CodeGenerator,
+) : SymbolProcessor {
+    override fun process(resolver: Resolver): List<KSAnnotated> {
+        val appFunctionSymbolResolver = AppFunctionSymbolResolver(resolver)
+        val appFunctionClasses = appFunctionSymbolResolver.resolveAnnotatedAppFunctions()
+        for (appFunctionClass in appFunctionClasses) {
+            generateAppFunctionInventoryClass(appFunctionClass)
+        }
+        return emptyList()
+    }
+
+    private fun generateAppFunctionInventoryClass(appFunctionClass: AnnotatedAppFunctions) {
+        val originalPackageName = appFunctionClass.classDeclaration.packageName.asString()
+        val originalClassName = appFunctionClass.classDeclaration.simpleName.asString()
+
+        val inventoryClassName = getAppFunctionInventoryClassName(originalClassName)
+        val inventoryClassBuilder = TypeSpec.classBuilder(inventoryClassName)
+        inventoryClassBuilder.addSuperinterface(IntrospectionHelper.APP_FUNCTION_INVENTORY_CLASS)
+        inventoryClassBuilder.addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION)
+        inventoryClassBuilder.addProperty(buildFunctionIdToMetadataMapProperty())
+
+        val fileSpec =
+            FileSpec.builder(originalPackageName, inventoryClassName)
+                .addType(inventoryClassBuilder.build())
+                .build()
+        codeGenerator
+            .createNewFile(
+                Dependencies(
+                    aggregating = false,
+                    checkNotNull(appFunctionClass.classDeclaration.containingFile)
+                ),
+                originalPackageName,
+                inventoryClassName
+            )
+            .bufferedWriter()
+            .use { fileSpec.writeTo(it) }
+    }
+
+    /** Creates the `functionIdToMetadataMap` property of the `AppFunctionInventory`. */
+    private fun buildFunctionIdToMetadataMapProperty(): PropertySpec {
+        return PropertySpec.builder(
+                "functionIdToMetadataMap",
+                Map::class.asClassName()
+                    .parameterizedBy(
+                        String::class.asClassName(),
+                        IntrospectionHelper.APP_FUNCTION_METADATA_CLASS
+                    ),
+            )
+            .addModifiers(KModifier.OVERRIDE)
+            // TODO: Actually build map properties
+            .initializer(buildCodeBlock { addStatement("mapOf()") })
+            .build()
+    }
+
+    private fun getAppFunctionInventoryClassName(functionClassName: String): String {
+        return "$%s_AppFunctionInventory_Impl".format(functionClassName)
+    }
+}
diff --git a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
index 3e628c7..8860052 100644
--- a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
+++ b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
@@ -43,7 +43,7 @@
     }
 
     @Test
-    fun testSimpleFunction() {
+    fun testSimpleFunction_genAppFunctionIds_success() {
         val report = compilationTestHelper.compileAll(sourceFileNames = listOf("SimpleFunction.KT"))
 
         compilationTestHelper.assertSuccessWithContent(
@@ -54,7 +54,7 @@
     }
 
     @Test
-    fun testMissingFirstParameter() {
+    fun testMissingFirstParameter_hasCompileError() {
         val report =
             compilationTestHelper.compileAll(sourceFileNames = listOf("MissingFirstParameter.KT"))
 
@@ -68,7 +68,7 @@
     }
 
     @Test
-    fun testIncorrectFirstParameter() {
+    fun testIncorrectFirstParameter_hasCompileError() {
         val report =
             compilationTestHelper.compileAll(sourceFileNames = listOf("IncorrectFirstParameter.KT"))
 
@@ -80,4 +80,15 @@
                 "    ^"
         )
     }
+
+    @Test
+    fun testSimpleFunction_genAppFunctionInventoryImpl_success() {
+        val report = compilationTestHelper.compileAll(sourceFileNames = listOf("SimpleFunction.KT"))
+
+        compilationTestHelper.assertSuccessWithContent(
+            report = report,
+            expectGeneratedFileName = "SimpleFunction_AppFunctionInventory_Impl.kt",
+            goldenFileName = "$%s".format("SimpleFunction_AppFunctionInventory_Impl.KT")
+        )
+    }
 }
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/output/$SimpleFunction_AppFunctionInventory_Impl.KT b/appfunctions/appfunctions-compiler/src/test/test-data/output/$SimpleFunction_AppFunctionInventory_Impl.KT
new file mode 100644
index 0000000..e7653db3
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/output/$SimpleFunction_AppFunctionInventory_Impl.KT
@@ -0,0 +1,12 @@
+package com.testdata
+
+import androidx.appfunctions.`internal`.AppFunctionInventory
+import androidx.appfunctions.metadata.AppFunctionMetadata
+import javax.`annotation`.processing.Generated
+import kotlin.String
+import kotlin.collections.Map
+
+@Generated("androidx.appfunctions.compiler.AppFunctionCompiler")
+public class `$SimpleFunction_AppFunctionInventory_Impl` : AppFunctionInventory {
+  override val functionIdToMetadataMap: Map<String, AppFunctionMetadata> = mapOf()
+}