Kotlin Analysis API Documentation Help

Symbols

Symbols are a core concept in the Analysis API, representing declarations visible to Kotlin. Symbols are created not only for declarations in source files but also for declarations from Kotlin and Java libraries, and those generated by compiler plugins and the compiler itself.

Symbols vs. PSI

While the PSI represents the concrete syntax in Kotlin code, KaSymbol provides access to the semantic information of declarations. The table below summarizes the key differences:

Feature

KtDeclaration

KaSymbol

Focus

Syntax structure of source files

Semantic information container (regardless of declaration origin)

Information

Lexical and syntactic information (text)

Resolved semantic information (name, type, visibility, etc.)

Mutability

Mutable (via PSI modifications)

Immutable (a different symbol is created after PSI modifications)

Lifetime

Tied to a containing KtFile

Managed by a KaSession

Semantic information

In the context of the Analysis API symbols, the word "semantic" refers to the meaning and relationships of declarations within the code from the compiler's standpoint, as opposed to their purely syntactic representation.

Here are a couple of examples of semantic symbol information that is unavailable in KtDeclaration (which primarily focuses on syntactic structure).

Resolved types

While a KtCallableDeclaration can provide the declared type of a function or property, KaSymbol provides the resolved type, which takes into account imports, aliases, and other parts of the lexical context.

In the following function, the PSI can only say that the text parameter has some type called String, while the The Analysis API can check that it indeed is kotlin.String, and not something like my.app.String.

fun print(text: String) {}

Visibility and accessibility

KaSymbol provides information about the visibility of a symbol (public, private, etc.), even if it is not explicitly stated in the PSI.

abstract class Base { protected abstract fun check(value: String): Boolean } class Impl : Base() { override fun check(value: String) = value.isNotEmpty() }

Even though there is no protected modifier for Impl.check(), the compiler knows it is there.

By providing access to this semantic information, the Analysis API symbols enable a more powerful and comprehensive analysis of Kotlin code.

Kinds of symbols

The graph below shows the relation between high-level parts of the KaSymbol type hierarchy.

KaSymbol
KaFileSymbol
KaPackageSymbol
KaDeclarationSymbol
KaClassifierSymbol
KaCallableSymbol
KaScriptSymbol

KaSymbol is a root type for the whole symbol hierarchy. Here are a few subtypes of it:

  • KaFileSymbol: Represents a Kotlin file.

  • KaPackageSymbol: Represents a package with declarations and subpackages.

  • KaDeclarationSymbol: Represents a declaration in a Kotlin file.

  • KaClassifierSymbol: Represents a class, type alias, or a type parameter.

  • KaCallableSymbol: Represents a function or a variable.

  • KaScriptSymbol: Represents a script declaration (implicitly nested in a script file).

See more information on specific subtypes of KaSymbol in dedicated documentation pages.

Getting a KaSymbol

To get a KaSymbol instance, use the symbol extension property:

// Let's say you have a KtNamedFunction val ktNamedFunction: KtNamedFunction = ... analyze(ktNamedFunction) { val functionSymbol = ktNamedFunction.symbol // Although the PSI element is named 'KtNamedFunction', // it can be an anonymous one (the name is 'null') if (functionSymbol is KaNamedFunctionSymbol) { // Here we have a truly named function check(functionSymbol.isOperator) } }

This code snippet retrieves the symbol for a KtNamedFunction. You can then check the type of the symbol and access its properties, such as its return type, visibility, and more.

The symbol property is overloaded, and the compiler automatically chooses the most specific variant. For a KtDeclaration, it will return a base KaDeclarationSymbol. However, for a KtTypeAlias the result will be a KaTypeAliasSymbol.

The KtClassOrObject hierarchy includes a KtEnumEntry. In the old compiler, an enum entry was a class with a specific kind. In the Analysis API, though, similarly to the K2 compiler, it is a KaVariableSymbol. Because of this inconsistency, the Analysis API provides specific nullable versions of symbol properties:

public val KtClassOrObject.classSymbol: KaClassSymbol? public val KtClassOrObject.namedClassSymbol: KaNamedClassSymbol?

KaSymbolPointer

KaSymbolPointer is a mechanism for referencing a KaSymbol across different analysis sessions. Since symbols are valid only within the specific analysis session in which they were created, symbol pointers offer a way to persist references and restore the corresponding symbols later.

Creating a pointer

To create a pointer, use the createPointer() function available for all KaSymbol instances:

val symbol: KaFunctionSymbol = ... val pointer = symbol.createPointer()

Restoring a symbol

To restore a symbol from a KaSymbolPointer, use the restoreSymbol() extension function within an analyze block:

analyze(someKtElement) { val restoredSymbol = functionPointer.restoreSymbol() if (restoredSymbol != null) { // Use the restored symbol } }

The restoreSymbol() function attempts to restore the symbol within the current analysis session. It returns the restored symbol, or null if the declaration does not exist anymore, or it cannot be accessed from the use-site session.

Use Cases

Symbol pointers are essential for scenarios where you need to maintain references to symbols across different analysis sessions. Some common use cases include:

  • Caching analysis results: Store symbol pointers for later retrieval and analysis.

  • Cross-module analysis: Reference symbols from different modules within the same project.

  • Long-running operations: Maintain references to symbols during background tasks or across multiple user interactions.

By using symbol pointers, you can ensure that your code maintains valid references to symbols, avoids potential issues with invalidated state, and does not cause memory drain.

Last modified: 30 September 2024