in plugins/compose/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/k1/ComposableCallChecker.kt [112:287]
override fun check(
resolvedCall: ResolvedCall<*>,
reportOn: PsiElement,
context: CallCheckerContext,
) {
val bindingContext = context.trace.bindingContext
if (
!resolvedCall.isComposableDelegateReference(bindingContext) &&
!resolvedCall.isComposableInvocation()
) {
checkInlineLambdaCall(resolvedCall, reportOn, context)
return
}
warnOnUnstableNamedArguments(resolvedCall, context)
var node: PsiElement? = reportOn
loop@ while (node != null) {
when (node) {
is KtFunctionLiteral -> {
// keep going, as this is a "KtFunction", but we actually want the
// KtLambdaExpression
}
is KtLambdaExpression -> {
val descriptor = bindingContext[BindingContext.FUNCTION, node.functionLiteral]
if (descriptor == null) {
illegalCall(context, reportOn)
return
}
val composable = descriptor.isComposableCallable(bindingContext)
if (composable) return
val arg = getArgumentDescriptor(node.functionLiteral, bindingContext)
if (arg?.type?.hasDisallowComposableCallsAnnotation() == true) {
context.trace.record(
FrontendWritableSlices.LAMBDA_CAPABLE_OF_COMPOSER_CAPTURE,
descriptor,
false
)
context.trace.report(
ComposeErrors.CAPTURED_COMPOSABLE_INVOCATION.on(
reportOn,
arg,
arg.containingDeclaration
)
)
return
}
val isResolvedInline = bindingContext.get(
BindingContext.NEW_INFERENCE_IS_LAMBDA_FOR_OVERLOAD_RESOLUTION_INLINE,
node.functionLiteral
) == true
val isInlined = isResolvedInline || isInlinedArgument(
node.functionLiteral,
bindingContext,
true
)
if (!isInlined) {
illegalCall(context, reportOn)
return
} else {
// since the function is inlined, we continue going up the PSI tree
// until we find a composable context. We also mark this lambda
context.trace.record(
FrontendWritableSlices.LAMBDA_CAPABLE_OF_COMPOSER_CAPTURE,
descriptor,
true
)
}
}
is KtTryExpression -> {
val tryKeyword = node.tryKeyword
if (
node.tryBlock.textRange.contains(reportOn.textRange) &&
tryKeyword != null
) {
context.trace.report(
ComposeErrors.ILLEGAL_TRY_CATCH_AROUND_COMPOSABLE.on(tryKeyword)
)
}
}
is KtFunction -> {
val descriptor = bindingContext[BindingContext.FUNCTION, node]
if (descriptor == null) {
illegalCall(context, reportOn)
return
}
val composable = descriptor.isComposableCallable(bindingContext)
if (!composable) {
illegalCall(context, reportOn, node.nameIdentifier ?: node)
}
if (descriptor.hasReadonlyComposableAnnotation()) {
// enforce that the original call was readonly
if (!resolvedCall.isReadOnlyComposableInvocation()) {
illegalCallMustBeReadonly(
context,
reportOn
)
}
}
return
}
is KtProperty -> {
// NOTE: since we're explicitly going down a different branch for
// KtPropertyAccessor, the ONLY time we make it into this branch is when the
// call was done in the initializer of the property/variable.
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, node]
if (resolvedCall.isComposableDelegateOperator()) {
// The call is initializer for fields like `val foo by composableDelegate()`.
// Creating the property doesn't have any requirements from Compose side,
// we will recheck on the property call site instead.
if (
descriptor is VariableDescriptorWithAccessors &&
descriptor.isDelegated
) {
if (descriptor.isVar) {
// setValue delegate is not allowed for now.
illegalComposableDelegate(context, reportOn)
}
if (descriptor is PropertyDescriptor &&
descriptor.getter?.hasComposableAnnotation() != true
) {
composableExpected(context, node.nameIdentifier ?: node)
}
return
}
}
if (
descriptor !is LocalVariableDescriptor &&
node.annotationEntries.hasComposableAnnotation(bindingContext)
) {
// composables shouldn't have initializers in the first place
illegalCall(context, reportOn)
return
}
}
is KtPropertyAccessor -> {
val property = node.property
val isComposable = node
.annotationEntries.hasComposableAnnotation(bindingContext)
if (!isComposable) {
illegalCall(context, reportOn, property.nameIdentifier ?: property)
}
val descriptor = bindingContext[BindingContext.PROPERTY_ACCESSOR, node]
?: return
if (descriptor.hasReadonlyComposableAnnotation()) {
// enforce that the original call was readonly
if (!resolvedCall.isReadOnlyComposableInvocation()) {
illegalCallMustBeReadonly(
context,
reportOn
)
}
}
return
}
is KtCallableReferenceExpression -> {
illegalComposableFunctionReference(context, node)
return
}
is KtFile -> {
// if we've made it this far, the call was made in a non-composable context.
illegalCall(context, reportOn)
return
}
is KtClass -> {
// composable calls are never allowed in the initializers of a class
illegalCall(context, reportOn)
return
}
}
node = node.parent as? KtElement
}
}