it.unibo.collektive.frontend.checkers.CheckersUtility.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of compiler-plugin Show documentation
Show all versions of compiler-plugin Show documentation
A Kotlin compiler plugin performing transparent aggregate alignment
The newest version!
package it.unibo.collektive.frontend.checkers
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies
import org.jetbrains.kotlin.diagnostics.warning1
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.toClassLikeSymbol
import org.jetbrains.kotlin.fir.declarations.FirReceiverParameter
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.expressions.unwrapExpression
import org.jetbrains.kotlin.fir.references.toResolvedFunctionSymbol
/**
* Collection of utilities for FIR checkers.
*/
object CheckersUtility {
/**
* Object containing the types of errors/warnings reported by this extension.
*/
object PluginErrors {
/**
* Warning generated on a dot call.
*/
val DOT_CALL_WARNING by warning1(
SourceElementPositioningStrategies.CALL_ELEMENT_WITH_DOT,
)
}
/**
* Checks is a specific receiver parameter is [Aggregate][it.unibo.collektive.aggregate.api.Aggregate]
* (`Aggregate.example()`).
* It uses the [session] of the [CheckerContext] in which this check is performed to get the class symbol of the
* parameter.
*
* It returns **true** if it's an `Aggregate` function, **false** otherwise.
*/
fun FirReceiverParameter.isAggregate(session: FirSession): Boolean =
typeRef.toClassLikeSymbol(session)?.name?.asString() == "Aggregate"
/**
* Checks if the function that is called is an [Aggregate][it.unibo.collektive.aggregate.api.Aggregate] one
* (i.e. is an extension of `Aggregate` or it's a method of the `Aggregate` class).
* It uses the [session] of the [CheckerContext] in which this check is performed to get the class symbol of the
* receiver parameter.
*
* It returns **true** if it's an `Aggregate` function, **false** otherwise.
*/
fun FirFunctionCall.isAggregate(session: FirSession): Boolean {
val callableSymbol = toResolvedCallableSymbol()
return callableSymbol?.receiverParameter?.isAggregate(session) == true ||
callableSymbol?.getContainingClassSymbol(session)?.name?.asString() == "Aggregate"
}
/**
* Checks if the current [CheckerContext] is wrapped inside an
* [Aggregate][it.unibo.collektive.aggregate.api.Aggregate] function.
*
* It returns **true** if it is wrapped inside an `Aggregate` function, **false** otherwise.
*/
fun CheckerContext.isInsideAggregateFunction(): Boolean =
containingElements.any { (it as? FirSimpleFunction)?.receiverParameter?.isAggregate(session) == true }
/**
* Returns wrapping [FirElement]s until it finds the element that satisfies the predicate (which is
* excluded from the result). The context's element is excluded.
*
* If [excludeDotCall] is set to *true*, elements that represent a dot call on the context's element are excluded
* as well. For example, in this code:
* ```kotlin
* for (...) {
* for (...) {
* .map(...)
* }
* }
* ```
* With [excludeDotCall] set to *false*, this method would return `List(FirFunctionCall("map"),
* FirWhileLoop("for"), FirWhileLoop("for"))`, instead with *true* the first `map` would be excluded.
*
* An example of [predicate] is `{ it is FirWhileLoop }`, which makes this method return all the containing
* elements until it finds the first Kotlin `for (...)` that wraps the context's element. If there is no such loop,
* this method returns `null`.
*/
fun CheckerContext.wrappingElementsUntil(
excludeDotCall: Boolean = true,
predicate: (FirElement) -> Boolean,
): List? {
val calleeName = (containingElements.last() as? FirFunctionCall)?.functionName()
return containingElements.takeIf { it.any(predicate) }
?.let { firElements ->
if (excludeDotCall) {
firElements.filterNot {
calleeName == it.receiverName()
}
} else {
firElements
}
}
?.dropLast(1)
?.takeLastWhile { !predicate(it) }
}
/**
* Gets the receiver's name of this element. For example, in this code:
* ```kotlin
* exampleFunction().map { ... }
* ```
* If we call this method on the `map` element, it returns `exampleFunction`.
* If, instead, we call this method on something that is not a function call or that has no receiver, it returns
* `null`.
*/
fun FirElement.receiverName(): String? =
((this as? FirFunctionCall)?.explicitReceiver?.unwrapExpression() as? FirFunctionCall)?.functionName()
/**
* Returns the receiver list only if it doesn't contain any function declaration, or `null` otherwise.
*/
fun List.discardIfFunctionDeclaration(): List? =
takeIf { elements -> elements.none { it is FirSimpleFunction } }
/**
* Returns the receiver list only if it doesn't contain any `aggregate` block.
* For example, if the List represents the containing elements, in this code:
* ```kotlin
* for(...) {
* aggregate {
*
* }
* }
* ```
* the list is *discarded* (i.e. it returns null) because a portion of the containing elements is _outside_ the
* `aggregate` block.
*/
fun List.discardIfOutsideAggregateEntryPoint(): List? =
takeIf { it.none(isFunctionCallsWithName("aggregate")) }
/**
* Returns a predicate for a [FirElement] that is *true* when that element represents a function call that has the
* provided [name].
*/
fun isFunctionCallsWithName(name: String): ((FirElement) -> Boolean) = {
it is FirFunctionCall && it.functionName() == name
}
/**
* Returns the name of the called function in a [FirFunctionCall] element.
*/
fun FirFunctionCall.functionName(): String =
calleeReference.name.asString()
/**
* Returns the fully qualified name of this [FirFunctionCall].
*/
fun FirFunctionCall.fqName(): String {
val callableId = calleeReference.toResolvedFunctionSymbol()?.callableId
val packageName = callableId?.packageName?.asString()
val className = callableId?.className?.asString()
val functionName = callableId?.callableName?.asString()
return if (className != null) {
"$packageName.$className.$functionName"
} else {
"$packageName.$functionName"
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy