CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-kotlin--kotlin-scripting-ide-services

Kotlin Scripting Compiler extension providing code completion and static analysis for IDE integration

Pending
Overview
Eval results
Files

call-types.mddocs/

Call Type Detection

Advanced call context analysis for determining the type of call being made and providing appropriate completion suggestions. This system analyzes the syntactic context around the cursor to provide context-aware completions.

Capabilities

CallType

Sealed class hierarchy representing different types of calls that can occur in Kotlin code.

/**
 * Represents the type of call being made in Kotlin code
 * Used for context-aware completion and analysis
 */
sealed class CallType<TReceiver : KtElement?>

/**
 * Unknown or unrecognized call context
 */
object UNKNOWN : CallType<Nothing?>

/**
 * Default call context (no specific receiver)
 */
object DEFAULT : CallType<Nothing?>

/**
 * Dot call (receiver.member)
 */
object DOT : CallType<KtExpression>

/**
 * Safe call (receiver?.member)
 */
object SAFE : CallType<KtExpression>

/**
 * Super member access (super.member)
 */
object SUPER_MEMBERS : CallType<KtSuperExpression>

/**
 * Infix function call (receiver infix function)
 */
object INFIX : CallType<KtExpression>

/**
 * Operator call (receiver + other)
 */
object OPERATOR : CallType<KtExpression>

/**
 * Callable reference (::member or receiver::member)
 */
object CALLABLE_REFERENCE : CallType<KtExpression?>

/**
 * Import directive context (import package.member)
 */
object IMPORT_DIRECTIVE : CallType<Nothing?>

/**
 * Package directive context (package name.subpackage)
 */
object PACKAGE_DIRECTIVE : CallType<Nothing?>

/**
 * Type context (for type annotations, generic parameters)
 */
object TYPE : CallType<Nothing?>

/**
 * Delegate context (by delegate)
 */
object DELEGATE : CallType<Nothing?>

/**
 * Annotation context (@Annotation)
 */
object ANNOTATION : CallType<Nothing?>

CallTypeAndReceiver

Combines call type information with receiver details for comprehensive context analysis.

/**
 * Combines call type with receiver information for completion context
 * Provides both the type of call and the receiver element
 */
sealed class CallTypeAndReceiver<TReceiver : KtElement?, out TCallType : CallType<TReceiver>> {
    abstract val callType: TCallType
    abstract val receiver: TReceiver
    
    /**
     * Detects the call type and receiver for a given expression
     * @param expression - The simple name expression to analyze
     * @returns CallTypeAndReceiver instance with detected context
     */
    companion object {
        fun detect(expression: KtSimpleNameExpression): CallTypeAndReceiver<*, *>
    }
}

/**
 * Dot call with receiver (receiver.member)
 */
data class DOT<TReceiver : KtExpression>(
    override val receiver: TReceiver
) : CallTypeAndReceiver<TReceiver, CallType.DOT>() {
    override val callType: CallType.DOT get() = CallType.DOT
}

/**
 * Safe call with receiver (receiver?.member)
 */
data class SAFE<TReceiver : KtExpression>(
    override val receiver: TReceiver
) : CallTypeAndReceiver<TReceiver, CallType.SAFE>() {
    override val callType: CallType.SAFE get() = CallType.SAFE
}

/**
 * Super member access (super.member)
 */
data class SUPER_MEMBERS<TReceiver : KtSuperExpression>(
    override val receiver: TReceiver
) : CallTypeAndReceiver<TReceiver, CallType.SUPER_MEMBERS>() {
    override val callType: CallType.SUPER_MEMBERS get() = CallType.SUPER_MEMBERS
}

/**
 * Infix call with receiver (receiver infix function)
 */
data class INFIX<TReceiver : KtExpression>(
    override val receiver: TReceiver
) : CallTypeAndReceiver<TReceiver, CallType.INFIX>() {
    override val callType: CallType.INFIX get() = CallType.INFIX
}

/**
 * Operator call with receiver (receiver + other)
 */  
data class OPERATOR<TReceiver : KtExpression>(
    override val receiver: TReceiver
) : CallTypeAndReceiver<TReceiver, CallType.OPERATOR>() {
    override val callType: CallType.OPERATOR get() = CallType.OPERATOR
}

/**
 * Callable reference (::member or receiver::member)
 */
data class CALLABLE_REFERENCE<TReceiver : KtExpression?>(
    override val receiver: TReceiver
) : CallTypeAndReceiver<TReceiver, CallType.CALLABLE_REFERENCE>() {
    override val callType: CallType.CALLABLE_REFERENCE get() = CallType.CALLABLE_REFERENCE
}

/**
 * Default context (no specific receiver)
 */
object DEFAULT : CallTypeAndReceiver<Nothing?, CallType.DEFAULT>() {
    override val callType: CallType.DEFAULT get() = CallType.DEFAULT
    override val receiver: Nothing? get() = null
}

/**
 * Unknown context
 */
object UNKNOWN : CallTypeAndReceiver<Nothing?, CallType.UNKNOWN>() {
    override val callType: CallType.UNKNOWN get() = CallType.UNKNOWN
    override val receiver: Nothing? get() = null
}

Usage Examples:

// Detect call type from expression
val expression: KtSimpleNameExpression = // PSI element at cursor
val callTypeAndReceiver = CallTypeAndReceiver.detect(expression)

when (callTypeAndReceiver) {
    is CallTypeAndReceiver.DOT -> {
        println("Dot call on receiver: ${callTypeAndReceiver.receiver.text}")
        // Provide member completions for the receiver type
    }
    is CallTypeAndReceiver.SAFE -> {
        println("Safe call on receiver: ${callTypeAndReceiver.receiver.text}")
        // Provide nullable member completions
    }
    is CallTypeAndReceiver.DEFAULT -> {
        println("Default context - no specific receiver")
        // Provide local variables, functions, and imports
    }
    is CallTypeAndReceiver.SUPER_MEMBERS -> {
        println("Super member access")
        // Provide super class member completions
    }
    else -> {
        println("Other call type: ${callTypeAndReceiver.callType}")
    }
}

ReceiverType

Data class representing receiver type information for completion context.

/**
 * Represents receiver type information for completion
 * Contains the Kotlin type and additional metadata about the receiver
 */
data class ReceiverType(
    /** The Kotlin type of the receiver */
    val type: KotlinType,
    /** Index of the receiver in implicit receiver chain */
    val receiverIndex: Int,
    /** Optional implicit receiver value */
    val implicitValue: ReceiverValue?
) {
    /**
     * Extracts DSL markers from the receiver type
     * @returns Collection of annotation class IDs representing DSL markers
     */
    fun extractDslMarkers(): Collection<ClassId>
}

Receiver Type Resolution

Functions for resolving receiver types from call context:

/**
 * Gets the receiver types for a call type and receiver
 * @param bindingContext - Binding context from analysis
 * @param contextElement - PSI element providing context
 * @param moduleDescriptor - Module descriptor for resolution
 * @param resolutionFacade - Resolution facade for type resolution
 * @param stableSmartCastsOnly - Whether to only consider stable smart casts
 * @param withImplicitReceiversWhenExplicitPresent - Include implicit receivers even with explicit receiver
 * @returns List of Kotlin types for the receivers
 */
fun CallTypeAndReceiver<*, *>.receiverTypes(
    bindingContext: BindingContext,
    contextElement: PsiElement,
    moduleDescriptor: ModuleDescriptor,
    resolutionFacade: ResolutionFacade,
    stableSmartCastsOnly: Boolean,
    withImplicitReceiversWhenExplicitPresent: Boolean = false
): List<KotlinType>?

/**
 * Gets receiver types with index information
 * @param bindingContext - Binding context from analysis
 * @param contextElement - PSI element providing context  
 * @param moduleDescriptor - Module descriptor for resolution
 * @param resolutionFacade - Resolution facade for type resolution
 * @param stableSmartCastsOnly - Whether to only consider stable smart casts
 * @param withImplicitReceiversWhenExplicitPresent - Include implicit receivers even with explicit receiver
 * @returns List of ReceiverType objects with detailed information
 */
fun CallTypeAndReceiver<*, *>.receiverTypesWithIndex(
    bindingContext: BindingContext,
    contextElement: PsiElement,
    moduleDescriptor: ModuleDescriptor,
    resolutionFacade: ResolutionFacade,
    stableSmartCastsOnly: Boolean,
    withImplicitReceiversWhenExplicitPresent: Boolean = false
): List<ReceiverType>?

Usage Examples:

// Get receiver types for completion
val callTypeAndReceiver = CallTypeAndReceiver.detect(expression)
val receiverTypes = callTypeAndReceiver.receiverTypes(
    bindingContext = bindingContext,
    contextElement = expression,
    moduleDescriptor = moduleDescriptor,
    resolutionFacade = resolutionFacade,
    stableSmartCastsOnly = true
)

receiverTypes?.forEach { receiverType ->
    println("Receiver type: ${receiverType}")
    // Get members for this receiver type
    val members = receiverType.memberScope.getContributedDescriptors()
    // Process members for completion
}

// Get detailed receiver information
val receiverTypesWithIndex = callTypeAndReceiver.receiverTypesWithIndex(
    bindingContext = bindingContext,
    contextElement = expression,
    moduleDescriptor = moduleDescriptor,
    resolutionFacade = resolutionFacade,
    stableSmartCastsOnly = true
)

receiverTypesWithIndex?.forEach { receiverType ->
    println("Receiver ${receiverType.receiverIndex}: ${receiverType.type}")
    receiverType.implicitValue?.let { implicit ->
        println("  Implicit value: $implicit")
    }
    
    // Extract DSL markers for context-aware completion
    val dslMarkers = receiverType.extractDslMarkers()
    if (dslMarkers.isNotEmpty()) {
        println("  DSL markers: ${dslMarkers.joinToString()}")
    }
}

Integration with Completion System

The call type detection system is automatically used by the completion engine:

// Internal usage in completion system
private val getDescriptorsQualified = ResultGetter { element, options ->
    val expression = element.thisOrParent<KtQualifiedExpression>() ?: return@ResultGetter null
    
    val receiverExpression = expression.receiverExpression
    val expressionType = bindingContext.get(BindingContext.EXPRESSION_TYPE_INFO, receiverExpression)?.type
    
    DescriptorsResult(targetElement = expression).apply {
        if (expressionType != null) {
            sortNeeded = false
            descriptors.addAll(
                getVariantsHelper { true }
                    .getReferenceVariants(
                        receiverExpression,
                        CallTypeAndReceiver.DOT(receiverExpression), // Using call type detection
                        DescriptorKindFilter.ALL,
                        ALL_NAME_FILTER,
                        filterOutShadowed = options.filterOutShadowedDescriptors,
                    )
            )
        }
    }
}

Context-Aware Completion Scenarios

Different call types enable different completion strategies:

Dot Call Completion

val list = listOf(1, 2, 3)
list. // DOT call type
// Provides: map, filter, size, isEmpty, etc.

Safe Call Completion

val nullable: String? = "hello"
nullable?. // SAFE call type
// Provides: length, uppercase, lowercase, etc. (nullable context)

Super Member Completion

class Child : Parent() {
    override fun method() {
        super. // SUPER_MEMBERS call type
        // Provides: parent class members only
    }
}

Import Completion

import kotlin.collections. // IMPORT_DIRECTIVE call type
// Provides: package members and subpackages

Type Context Completion

fun method(): List< // TYPE call type
// Provides: type names, generic parameters

Install with Tessl CLI

npx tessl i tessl/maven-org-jetbrains-kotlin--kotlin-scripting-ide-services

docs

call-types.md

completion-config.md

index.md

repl-compiler.md

resolution.md

utilities.md

tile.json