Skip to content

Commit

Permalink
Comment checker
Browse files Browse the repository at this point in the history
  • Loading branch information
JavierSegoviaCordoba committed Feb 17, 2024
1 parent d5e2ddc commit cb52b74
Show file tree
Hide file tree
Showing 54 changed files with 2,121 additions and 1,468 deletions.
13 changes: 7 additions & 6 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[versions]
hubdle = "0.5.0+2.0.0-dev-10501-SNAPSHOT"
hubdleCatalog = "0.1.18+2.0.0-dev-10501-SNAPSHOT"
javiersc-kotlin-kotlinCompilerExtensions = "0.1.1+2.0.0-dev-10501-SNAPSHOT"
javiersc-kotlin-kotlinStdlibAndTest = "0.1.0-SNAPSHOT"
hubdle = "0.6.4+2.0.0-dev-15455-SNAPSHOT"
hubdleCatalog = "0.2.5+2.0.0-dev-15455-SNAPSHOT"
javiersc-kotlin-compiler-extensions = "0.1.1+2.0.0-dev-15455-SNAPSHOT"
javiersc-kotlin = "0.1.0-SNAPSHOT"

[libraries]
hubdle-catalog = { module = "com.javiersc.hubdle:hubdle-version-catalog", version.ref = "hubdleCatalog" }
javiersc-kotlinCompilerExtensions = { module = "com.javiersc.kotlin:kotlin-compiler-extensions", version.ref = "javiersc-kotlin-kotlinCompilerExtensions" }
javiersc-kotlinStdlib = { module = "com.javiersc.kotlin:kotlin-stdlib", version.ref = "javiersc-kotlin-kotlinStdlibAndTest" }
javiersc-kotlin-compiler-extensions = { module = "com.javiersc.kotlin:kotlin-compiler-extensions", version.ref = "javiersc-kotlin-compiler-extensions" }
javiersc-kotlin-compiler-test-extensions = { module = "com.javiersc.kotlin:kotlin-compiler-test-extensions", version.ref = "javiersc-kotlin-compiler-extensions" }
javiersc-kotlin-stdlib = { module = "com.javiersc.kotlin:kotlin-stdlib", version.ref = "javiersc-kotlin" }

[plugins]
javiersc-hubdle = { id = "com.javiersc.hubdle", version.ref = "hubdle" }
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
6 changes: 4 additions & 2 deletions kopy-compiler/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion

hubdle {
Expand All @@ -8,6 +9,7 @@ hubdle {
api()
}
explicitApi()
format.isEnabled = false
languageSettings { //
experimentalContracts()
}
Expand All @@ -23,15 +25,15 @@ hubdle {
compiler {
mainClass.set("com.javiersc.kotlin.kopy.compiler.GenerateKotlinCompilerTestsKt")
generateTestOnSync(false)
testDependencies(hubdle.javiersc.kotlin.kotlinStdlib)
testDependencies(hubdle.javiersc.kotlin.stdlib)
testProjects(projects.kopyRuntime)
}
contextReceivers()
serialization()
}
main { //
dependencies { //
implementation(hubdle.javiersc.kotlin.kotlinCompilerExtensions)
implementation(hubdle.javiersc.kotlin.compiler.extensions)
implementation(projects.kopyRuntime)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.javiersc.kotlin.kopy.compiler.fir

import com.javiersc.kotlin.kopy.compiler.fir.checker.FirKopyCheckerExtension
import com.javiersc.kotlin.kopy.compiler.fir.generation.FirKopySupertypeGenerationExtension
import com.javiersc.kotlin.kopy.compiler.fir.generation.FirKopyDeclarationGenerationExtension
import org.jetbrains.kotlin.GeneratedDeclarationKey
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar

Expand All @@ -19,6 +20,8 @@ internal class FirKopyExtension(
}

private fun ExtensionRegistrarContext.registerGenerators() {
+::FirKopySupertypeGenerationExtension
+::FirKopyDeclarationGenerationExtension
}
}

internal object Key : GeneratedDeclarationKey()
Original file line number Diff line number Diff line change
@@ -1,48 +1,182 @@
package com.javiersc.kotlin.kopy.compiler.fir.checker

import com.javiersc.kotlin.compiler.extensions.common.fqName
import com.javiersc.kotlin.kopy.KopyFunctionInvoke
import com.javiersc.kotlin.compiler.extensions.common.classId
import com.javiersc.kotlin.compiler.extensions.fir.asFirOrNull
import com.javiersc.kotlin.kopy.Kopy
import com.javiersc.kotlin.kopy.KopyFunctionSet
import com.javiersc.kotlin.kopy.KopyFunctionUpdate
import com.javiersc.kotlin.kopy.compiler.fir.checker.BreakingCallsChecker.CheckerResult.Failure
import com.javiersc.kotlin.kopy.compiler.fir.checker.BreakingCallsChecker.CheckerResult.Ignore
import com.javiersc.kotlin.kopy.compiler.fir.checker.BreakingCallsChecker.CheckerResult.Success
import com.javiersc.kotlin.kopy.compiler.fir.errors.FirKopyError
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.DEFAULT
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirCallChecker
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
import org.jetbrains.kotlin.fir.declarations.utils.isData
import org.jetbrains.kotlin.fir.expressions.FirCall
import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar
import org.jetbrains.kotlin.fir.resolve.fqName
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirPropertyAccessExpression
import org.jetbrains.kotlin.fir.expressions.FirResolvable
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
import org.jetbrains.kotlin.fir.getOwnerLookupTag
import org.jetbrains.kotlin.fir.references.symbol
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.dfa.symbol
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.types.resolvedType
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
import org.jetbrains.kotlin.name.ClassId

internal class FirKopyCheckerExtension(
session: FirSession,
) : FirAdditionalCheckersExtension(session) {

override val expressionCheckers: ExpressionCheckers =
object : ExpressionCheckers() {
override val callCheckers: Set<FirCallChecker> =
setOf(
object : FirCallChecker() {
override fun check(
expression: FirCall,
context: CheckerContext,
reporter: DiagnosticReporter
) {
if (
expression.annotations.any {
it.fqName(session) == fqName<KopyFunctionInvoke>()
}
)
println()
}
}
override val expressionCheckers: ExpressionCheckers = FirKopyExpressionCheckers
}

private object FirKopyExpressionCheckers : ExpressionCheckers() {
override val callCheckers: Set<FirCallChecker> =
setOf(
BreakingCallsChecker,
)
}

private object BreakingCallsChecker : FirCallChecker(MppCheckerKind.Common) {

override fun check(expression: FirCall, context: CheckerContext, reporter: DiagnosticReporter) {
when (val checkerResult: CheckerResult = expression.isBreakingCallsChain(context)) {
is Ignore -> return
is Success -> return
is Failure.BrokenChain -> {
reporter.reportOn(
source = checkerResult.source,
factory = FirKopyError.INVALID_CALL_CHAIN,
a = checkerResult.element.render(),
context = context,
positioningStrategy = DEFAULT,
)
}
is Failure.MissingDataClass -> {
reporter.reportOn(
source = checkerResult.source,
factory = FirKopyError.MISSING_DATA_CLASS,
a = checkerResult.element.render(),
context = context,
positioningStrategy = DEFAULT,
)
}
is Failure.MissingKopyAnnotation -> {
reporter.reportOn(
source = checkerResult.source,
factory = FirKopyError.MISSING_KOPY_ANNOTATION,
a = checkerResult.element.render(),
context = context,
positioningStrategy = DEFAULT,
)
}
}
}

private val FirCall.isKopyFunctionSetOrUpdateCall: Boolean
get() =
asFirOrNull<FirFunctionCall>()
?.calleeReference
?.symbol
?.resolvedAnnotationClassIds
?.firstOrNull()
.let { it == classId<KopyFunctionSet>() || it == classId<KopyFunctionUpdate>() }

private fun FirCall.isBreakingCallsChain(context: CheckerContext): CheckerResult {
if (!isKopyFunctionSetOrUpdateCall) return Ignore

val session: FirSession = context.session
val setOrUpdateCall: FirFunctionCall = asFirOrNull() ?: return Failure.BrokenChain(this)
val extensionReceiver: FirPropertyAccessExpression =
setOrUpdateCall.extensionReceiver?.asFirOrNull<FirPropertyAccessExpression>()
?: return Failure.BrokenChain(setOrUpdateCall)
val extensionReceiverOwnerAnnotations: List<ClassId> =
extensionReceiver.symbol
?.getOwnerLookupTag()
?.toFirRegularClassSymbol(session)
?.resolvedAnnotationClassIds
.orEmpty()
if (!extensionReceiverOwnerAnnotations.any { it == classId<Kopy>() }) {
return Failure.MissingKopyAnnotation(extensionReceiver)
}
val updateOrSetThisBoundSymbol: FirBasedSymbol<*> =
setOrUpdateCall.dispatchReceiver
?.asFirOrNull<FirThisReceiverExpression>()
?.calleeReference
?.boundSymbol ?: return Failure.BrokenChain(setOrUpdateCall)

val extensionDispatchReceiver: FirExpression =
extensionReceiver.dispatchReceiver
?: return Failure.BrokenChain(extensionReceiver.calleeReference)

val checkerResult: CheckerResult =
extensionDispatchReceiver.isBreakingChainCall(session, updateOrSetThisBoundSymbol)
return checkerResult
}

private fun FirExpression.isBreakingChainCall(
session: FirSession,
updateOrSetThisBoundSymbol: FirBasedSymbol<*>
): CheckerResult {
val thisBoundSymbol: FirBasedSymbol<*>? =
this.asFirOrNull<FirThisReceiverExpression>()?.calleeReference?.boundSymbol
if (updateOrSetThisBoundSymbol == thisBoundSymbol) return Success

if (this !is FirPropertyAccessExpression) return Failure.BrokenChain(this)

override fun FirDeclarationPredicateRegistrar.registerPredicates() {
// DeclarationPredicate.create {
// // metaAnnotated(fqName<KopyFunction>(), includeItself = true)
// // parentAnnotated(fqName<KopyFunction>())
// // annotated(fqName<KopyFunction>())
// // ancestorAnnotated(fqName<KopyFunction>())
// }
val isDataClass: Boolean = this.resolvedType.toRegularClassSymbol(session)?.isData ?: false

val receiver: FirExpression? = this.dispatchReceiver

val dispatcherBoundSymbol: FirBasedSymbol<*>? =
receiver?.asFirOrNull<FirThisReceiverExpression>()?.calleeReference?.boundSymbol

val hasSameBoundSymbol: Boolean = dispatcherBoundSymbol == updateOrSetThisBoundSymbol

return when {
hasSameBoundSymbol -> Success
receiver == null -> Failure.BrokenChain(this)
receiver !is FirPropertyAccessExpression -> {
val element = receiver.asFirOrNull<FirResolvable>()?.calleeReference ?: receiver
Failure.BrokenChain(element)
}
!isDataClass -> Failure.MissingDataClass(this.calleeReference)
else -> receiver.isBreakingChainCall(session, updateOrSetThisBoundSymbol)
}
}

sealed interface CheckerResult {

data object Ignore : CheckerResult

data object Success : CheckerResult

sealed interface Failure : CheckerResult {

val element: FirElement

val source: KtSourceElement
get() = element.source ?: error("No source for $this")

data class BrokenChain(override val element: FirElement) : Failure

data class MissingDataClass(override val element: FirElement) : Failure

data class MissingKopyAnnotation(override val element: FirElement) : Failure
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.javiersc.kotlin.kopy.compiler.fir.errors

import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory1
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.error1
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
import org.jetbrains.kotlin.diagnostics.rendering.Renderer
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory

internal object FirKopyError : BaseDiagnosticRendererFactory() {

init {
RootDiagnosticRendererFactory.registerFactory(FirKopyError)
}

val INVALID_CALL_CHAIN: KtDiagnosticFactory1<String> by error1<PsiElement, String>()

val MISSING_DATA_CLASS: KtDiagnosticFactory1<String> by error1<PsiElement, String>()

val MISSING_KOPY_ANNOTATION: KtDiagnosticFactory1<String> by error1<PsiElement, String>()

override val MAP: KtDiagnosticFactoryToRendererMap = rendererMap { map ->
map.put(
factory = INVALID_CALL_CHAIN,
message = "Call chain broken at `{0}`",
rendererA = Renderer { t: String -> t },
)
map.put(
factory = MISSING_DATA_CLASS,
message = "The property `{0}` does not belong to a data class",
rendererA = Renderer { t: String -> t },
)
map.put(
factory = MISSING_KOPY_ANNOTATION,
message = "The property `{0}` does not belong to a data class annotated with `@Kopy`",
rendererA = Renderer { t: String -> t },
)
}

private fun rendererMap(
block: (KtDiagnosticFactoryToRendererMap) -> Unit
): KtDiagnosticFactoryToRendererMap = KtDiagnosticFactoryToRendererMap("FirKopy").also(block)
}
Loading

0 comments on commit cb52b74

Please sign in to comment.