Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ Changelog
**Unreleased**
--------------

### Enhancements

- **[FIR]** Detect and report circuit factory class name collisions from overloads of conflicting `@CircuitInject`-annotated functions.

### Fixes

- **[FIR]** Fix missing contribution hints for assisted factories
- **[FIR]** Fix missing contribution hints for assisted factories.
- **[FIR]** Fix not propagating map keys and qualifiers if they're on the bound type arg rather than the class when `generateContributionProviders` is enabled.
- **[FIR]** Gracefully handle unresolved generic supertype type args.
- **[FIR]** Disable contribution providers on private constructors.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/file1.kt:(234,243): error: Multiple @CircuitInject-annotated functions named Favorites were found. This will create conflicts in Circuit FIR code gen, please deduplicate names.
/file2.kt:(280,289): error: Multiple @CircuitInject-annotated functions named Favorites were found. This will create conflicts in Circuit FIR code gen, please deduplicate names.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// ENABLE_CIRCUIT
// RENDER_DIAGNOSTICS_FULL_TEXT

// FILE: sources.kt

import com.slack.circuit.runtime.CircuitUiState
import com.slack.circuit.runtime.screen.Screen

data class FavoritesScreen(val userId: String) : Screen {
data class State(val count: Int) : CircuitUiState
}

// FILE: file1.kt
import com.slack.circuit.codegen.annotations.CircuitInject
import androidx.compose.runtime.Composable

@CircuitInject(FavoritesScreen::class, AppScope::class)
@Composable
fun <!CIRCUIT_INJECT_ERROR!>Favorites<!>(): FavoritesScreen.State {
TODO()
}

// FILE: file2.kt
import com.slack.circuit.codegen.annotations.CircuitInject
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

@CircuitInject(FavoritesScreen::class, AppScope::class)
@Composable
fun <!CIRCUIT_INJECT_ERROR!>Favorites<!>(state: FavoritesScreen.State, modifier: Modifier = Modifier) {

}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import org.jetbrains.kotlin.fir.plugin.createTopLevelClass
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.getSuperTypes
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
Expand Down Expand Up @@ -98,26 +99,22 @@ public class CircuitFirExtension(session: FirSession, compatContext: CompatConte
private val symbols by lazy { session.circuitFirSymbols }

// Caches for discovered @CircuitInject-annotated elements
private val annotatedSymbols by lazy {
session.predicateBasedProvider
.getSymbolsByPredicate(CircuitSymbols.circuitInjectPredicate)
.toList()
private val annotatedSymbols: List<FirBasedSymbol<*>> by lazy {
findCircuitInjectSymbols(session)
}

private val annotatedClasses by lazy {
private val annotatedClasses: Set<FirRegularClassSymbol> by lazy {
annotatedSymbols
.filterIsInstance<FirRegularClassSymbol>()
// Only read actual declarations to avoid duplicate, plus that's what IR sees
.filterNot { it.rawStatus.isExpect }
.toSet()
.filterNotTo(mutableSetOf()) { it.rawStatus.isExpect }
}

private val annotatedFunctions by lazy {
annotatedSymbols
.filterIsInstance<FirNamedFunctionSymbol>()
.filter { it.callableId.classId == null } // Only top-level functions
.filterNot { it.rawStatus.isExpect }
.toList()
}

// Map from factory ClassId -> annotated function (for top-level function factories)
Expand Down Expand Up @@ -489,6 +486,21 @@ public class CircuitFirExtension(session: FirSession, compatContext: CompatConte
// ClassId for ContributesIntoSet annotation
private val contributesIntoSetClassId =
ClassId(Symbols.FqNames.metroRuntimePackage, Name.identifier("ContributesIntoSet"))

fun findCircuitInjectSymbols(session: FirSession): List<FirBasedSymbol<*>> {
return session.predicateBasedProvider.getSymbolsByPredicate(
CircuitSymbols.circuitInjectPredicate
)
}

fun findCircuitInjectFunctions(
annotatedSymbols: List<FirBasedSymbol<*>>
): List<FirNamedFunctionSymbol> {
return annotatedSymbols
.filterIsInstance<FirNamedFunctionSymbol>()
.filter { it.callableId.classId == null } // Only top-level functions
.filterNot { it.rawStatus.isExpect }
}
}

private fun findTargetForFactory(factoryClassId: ClassId): CircuitFactoryTarget? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,38 @@ internal object CircuitInjectCallableChecker :

context(context: CheckerContext, reporter: DiagnosticReporter)
override fun check(declaration: FirCallableDeclaration) {
val source = declaration.source ?: return
if (declaration !is FirFunction) return

val session = context.session
val circuitSymbols = session.circuitFirSymbols ?: return

if (declaration !is FirFunction) return
if (!declaration.hasAnnotation(CircuitClassIds.CircuitInject, session)) return

val source = declaration.source ?: return
val circuitSymbols = session.circuitFirSymbols ?: return

// Check if we have multiple declarations that match this
val circuitInjectDeclarationsByName =
CircuitFirExtension.findCircuitInjectFunctions(
CircuitFirExtension.findCircuitInjectSymbols(session)
)
.groupBy { it.name }

// TODO this seems expensive to do there. Maybe FirLanguageVersionSettingsChecker?
circuitInjectDeclarationsByName[declaration.symbol.name]?.let { functions ->
if (functions.size > 1) {
for (function in functions) {
if (function == declaration.symbol) {
reporter.reportOn(
function.source,
CIRCUIT_INJECT_ERROR,
"Multiple @CircuitInject-annotated functions named ${declaration.symbol.name} were found. " +
"This will create conflicts in Circuit FIR code gen, please deduplicate names.",
)
}
}
}
}

val returnTypeRef = declaration.returnTypeRef
val returnType = returnTypeRef.coneType

Expand Down