Resolving Symbols¶
Symbol resolution answers "what declaration does this PSI element refer to?". The result is the literal target of the element — a class, function, property, type parameter, package, or any other declaration. Read Resolution Fundamentals first if you have not.
Tip
Snippets on this page assume an analyze { } block (or context(_: KaSession)) is in scope and that
@OptIn(KaExperimentalApi::class) is applied somewhere up the call chain. Snippets that name the
KtResolvable marker interface additionally require @OptIn(KtExperimentalApi::class).
Entry points¶
For a given resolvable PSI element, prefer the specialized form — an extension on the concrete PSI type with a precisely typed return value:
val constructor: KaConstructorSymbol? = annotationEntry.resolveSuccessfulSymbol()
val function: KaFunctionSymbol? = callElement.resolveSuccessfulSymbol()
val classifier: KaClassifierSymbol? = typeReference.resolveSuccessfulSymbol()
When the PSI type is unknown, fall back to the generic form on KtResolvable via a safe cast:
val symbol: KaSymbol? =
(element as? KtResolvable)?.resolveSuccessfulSymbol()
val symbols: Collection<KaSymbol> =
(element as? KtResolvable)?.resolveSuccessfulSymbols().orEmpty()
resolveSuccessfulSymbol() returns the single resolved symbol, or null if resolution failed or produced more than one
candidate. resolveSuccessfulSymbols() returns every resolved symbol — useful when ambiguity is acceptable.
For the richest form (success / error / diagnostic / candidate symbols), use tryResolveSymbols() and the
KaSymbolResolutionAttempt hierarchy.
Specialized methods¶
Each row below is an extension defined on the listed PSI type. The return type is precisely the kind of symbol that the element can refer to.
Symbols of callables and constructors¶
| PSI element | Returns |
|---|---|
KtAnnotationEntry |
KaConstructorSymbol? |
KtSuperTypeCallEntry |
KaConstructorSymbol? |
KtConstructorDelegationCall |
KaConstructorSymbol? |
KtConstructorDelegationReferenceExpression |
KaConstructorSymbol? |
KtConstructorCalleeExpression |
KaConstructorSymbol? |
KtCallElement |
KaFunctionSymbol? |
KtReturnExpression |
KaFunctionSymbol? |
KtCallableReferenceExpression |
KaCallableSymbol? |
KtQualifiedExpression |
KaCallableSymbol? |
KtDestructuringDeclarationEntry |
KaCallableSymbol? |
KtArrayAccessExpression |
KaNamedFunctionSymbol? |
KtCollectionLiteralExpression |
KaNamedFunctionSymbol? |
KtWhenConditionInRange |
KaNamedFunctionSymbol? |
Symbols of classifiers and labels¶
| PSI element | Returns |
|---|---|
KtTypeReference |
KaClassifierSymbol? |
KtNullableType |
KaClassifierSymbol? |
KtFunctionType |
KaClassSymbol? |
KtClassLiteralExpression |
KaClassifierSymbol? |
KtSuperTypeEntry |
KaClassifierSymbol? |
KtDelegatedSuperTypeEntry |
KaClassifierSymbol? |
KtEnumEntrySuperclassReferenceExpression |
KaNamedClassSymbol? |
KtLabelReferenceExpression |
KaDeclarationSymbol? |
KtInstanceExpressionWithLabel |
KaDeclarationSymbol? |
KtUserType does not have a specialized resolveSuccessfulSymbol() extension. It implements KtResolvable, so the
generic form applies — with the broader return type KaSymbol?. See the
Type-side resolution section below for the user-type subtleties (including resolution to a
KaPackageSymbol for qualified-path prefixes).
A few of these also expose a call counterpart (for example KtCallElement →
resolveSuccessfulCall(): KaFunctionCall<*>?). See Resolving Calls for the call-side specializations.
resolveSuccessfulSymbol shows the literal target¶
The defining principle of symbol resolution: the symbol is what the element itself refers to, not what is invoked
at that site. The clearest illustration is KtNameReferenceExpression.
class MyClass
object MyObject
val c = MyClass()
// ^^^^^^^ resolveSuccessfulSymbol() returns the class `MyClass`
val o = MyObject
// ^^^^^^^^ resolveSuccessfulSymbol() returns the object `MyObject`
For MyClass(), the name reference MyClass literally points to the class, so resolveSuccessfulSymbol() returns a
KaClassLikeSymbol for MyClass. The constructor that is actually invoked only shows up in
call resolution, because invoking a class means calling its constructor.
Tip
The constructor/class split is intentional and documented directly on KtNameReferenceExpression as an
Analysis API Resolver Note:
Unlike other
KtResolvableCallentry points that provide bothresolveSuccessfulCallandresolveSuccessfulSymbolspecializations,KtNameReferenceExpression.resolveSuccessfulCallmay return a differentKaSymbol. For instance, this happens for constructor references. WhileresolveSuccessfulCallreturns aKaConstructorSymbol, this method returns the correspondingKaClassLikeSymbol.
A KtNameReferenceExpression can also resolve to a type, not just a callable — this is why the generic
return type is KaSymbol? rather than KaCallableSymbol?.
The same call-vs-symbol asymmetry appears on two other elements. KtEnumEntrySuperclassReferenceExpression resolves to
the superclass (KaNamedClassSymbol) on the symbol side, but to the delegated constructor on the call side. And
KtOperationReferenceExpression (for example the += of a compound assignment) yields only the operator's own
contributed symbols here, while its resolveSuccessfulCall() mirrors the parent expression — see
Resolving Calls for that detail.
Type-side resolution¶
Symbol resolution is the only resolution flavor that applies to types. KtTypeReference, KtUserType,
KtNullableType, KtFunctionType, and KtClassLiteralExpression all support resolveSuccessfulSymbol() but no
resolveSuccessfulCall(). Of these, only KtUserType lacks a specialized return type — it reuses the generic
KtResolvable.resolveSuccessfulSymbol().
For a well-formed type the result is a KaClassifierSymbol. For a KtFunctionType, the result is the corresponding
FunctionN / SuspendFunctionN class — the return type is narrowed to KaClassSymbol?.
KtUserType delegates to its inner simple-name reference, so calling resolveSuccessfulSymbol() on a user type returns
the same symbol as resolving the inner expression. One subtlety, documented as an Analysis API Resolver Note on
KtUserType itself:
Tip
A KtUserType may also resolve to a KaPackageSymbol when it appears as the package qualifier of a fully qualified
nested type. In that case the user type is not a classifier reference itself but a package portion of one:
When walking type references, expect KaPackageSymbol and handle it explicitly.
Plain form vs try form¶
Use resolveSuccessfulSymbol() / resolveSuccessfulSymbols() when you only care about a valid result and want
failed/ambiguous resolutions to be silently dropped. This is the right default for inspections that must not produce
false positives.
Use tryResolveSymbols() when you want everything the compiler considered. The result is a
KaSymbolResolutionAttempt carrying success, error, or compound-error variants — with diagnostics and candidate
symbols. See KaSymbolResolutionAttempt for the full breakdown.
val attempt = element.tryResolveSymbols() ?: return
// every symbol the compiler considered
val allSymbols = attempt.symbols
// empty on error
val onlySuccessful = attempt.successfulSymbols
The two helpers KaSymbolResolutionAttempt.symbols and KaSymbolResolutionAttempt.successfulSymbols cover the most
common access patterns without forcing pattern matching on the sealed hierarchy.