User: joe
Date: 05 Nov 25 00:48
Revision: 039e8835d95ec2a3e21b865fdd171db50d13bfdb
Summary:
Add inspection for when two @Share annotations have different local types
TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10209&personal=false
Index: src/main/kotlin/platform/mixin/handlers/mixinextras/ShareUtil.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/mixinextras/ShareUtil.kt (revision 039e8835d95ec2a3e21b865fdd171db50d13bfdb)
+++ src/main/kotlin/platform/mixin/handlers/mixinextras/ShareUtil.kt (revision 039e8835d95ec2a3e21b865fdd171db50d13bfdb)
@@ -0,0 +1,128 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2025 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.demonwav.mcdev.platform.mixin.handlers.mixinextras
+
+import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler
+import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
+import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
+import com.demonwav.mcdev.platform.mixin.util.mixinTargets
+import com.demonwav.mcdev.util.constantStringValue
+import com.demonwav.mcdev.util.findContainingClass
+import com.demonwav.mcdev.util.findContainingMethod
+import com.demonwav.mcdev.util.internalName
+import com.demonwav.mcdev.util.mapFirstNotNull
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiClass
+import com.intellij.psi.impl.java.stubs.index.JavaAnnotationIndex
+import com.intellij.psi.search.GlobalSearchScope
+import com.intellij.psi.search.LocalSearchScope
+import com.intellij.psi.util.CachedValueProvider
+import com.intellij.psi.util.CachedValuesManager
+import com.intellij.psi.util.PsiModificationTracker
+import com.intellij.psi.util.PsiTreeUtil
+
+@Service(Service.Level.PROJECT)
+class ShareUtil(private val project: Project) {
+ fun getShares(shareAnnotation: PsiAnnotation): List {
+ val shareClass = shareAnnotation.resolveAnnotationType() ?: return listOf(shareAnnotation)
+ val allShares = getAllShares(shareClass)
+ return shareAnnotation.getShareKeys(project).flatMap { allShares[it] ?: listOf(shareAnnotation) }.distinct()
+ }
+
+ private fun getAllShares(shareClass: PsiClass): Map> {
+ return CachedValuesManager.getManager(project).getCachedValue(shareClass) {
+ CachedValueProvider.Result.create(computeAllShares(shareClass), PsiModificationTracker.MODIFICATION_COUNT)
+ }
+ }
+
+ private fun computeAllShares(shareClass: PsiClass): Map> {
+ val result = hashMapOf>()
+ for (annotation in getUsages(shareClass)) {
+ for (key in annotation.getShareKeys(project)) {
+ result.getOrPut(key) { mutableListOf() } += annotation
+ }
+ }
+ return result
+ }
+
+ private fun getUsages(shareClass: PsiClass): List {
+ return when (val useScope = shareClass.useScope) {
+ is GlobalSearchScope -> JavaAnnotationIndex.getInstance().getAnnotations("Share", project, useScope).filter { annotation ->
+ annotation.nameReferenceElement?.isReferenceTo(shareClass) == true
+ }
+ is LocalSearchScope -> {
+ useScope.scope.flatMap { element ->
+ PsiTreeUtil.collectElementsOfType(element, PsiAnnotation::class.java).filter { annotation ->
+ annotation.nameReferenceElement?.isReferenceTo(shareClass) == true
+ }
+ }
+ }
+ else -> throw IllegalStateException("Unknown scope class ${useScope.javaClass.name}")
+ }
+ }
+
+ private data class ShareKey(
+ private val namespace: String,
+ private val id: String,
+ private val targetOwner: String,
+ private val targetName: String,
+ private val targetDesc: String,
+ )
+
+ companion object {
+ fun getInstance(project: Project): ShareUtil = project.getService(ShareUtil::class.java)
+
+ private val PsiAnnotation.namespace: String?
+ get() = findDeclaredAttributeValue("namespace")?.constantStringValue
+ ?: findContainingClass()?.internalName
+ private val PsiAnnotation.value: String?
+ get() = findDeclaredAttributeValue("value")?.constantStringValue
+
+ private fun PsiAnnotation.getShareKeys(project: Project): List {
+ val method = findContainingMethod() ?: return emptyList()
+ val (injectorAnnotation, injector) = method.annotations.mapFirstNotNull { annotation ->
+ (MixinAnnotationHandler.forMixinAnnotation(annotation, project) as? InjectorAnnotationHandler)?.let { annotation to it }
+ } ?: return emptyList()
+
+ val mixinTargets = method.findContainingClass()?.mixinTargets ?: return emptyList()
+ val namespace = this.namespace ?: return emptyList()
+ val id = this.value ?: return emptyList()
+
+ return mixinTargets.flatMap { targetClass ->
+ injector.resolveTarget(injectorAnnotation, targetClass).mapNotNull { target ->
+ if (target !is MethodTargetMember) {
+ return@mapNotNull null
+ }
+
+ ShareKey(
+ namespace,
+ id,
+ target.classAndMethod.clazz.name,
+ target.classAndMethod.method.name,
+ target.classAndMethod.method.desc,
+ )
+ }
+ }
+ }
+ }
+}
Index: src/main/kotlin/platform/mixin/inspection/mixinextras/ConflictingShareTypeInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/mixinextras/ConflictingShareTypeInspection.kt (revision 039e8835d95ec2a3e21b865fdd171db50d13bfdb)
+++ src/main/kotlin/platform/mixin/inspection/mixinextras/ConflictingShareTypeInspection.kt (revision 039e8835d95ec2a3e21b865fdd171db50d13bfdb)
@@ -0,0 +1,92 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2025 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.demonwav.mcdev.platform.mixin.inspection.mixinextras
+
+import com.demonwav.mcdev.platform.mixin.handlers.mixinextras.ShareUtil
+import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants.MixinExtras.unwrapLocalRef
+import com.demonwav.mcdev.util.equivalentTo
+import com.demonwav.mcdev.util.findContainingClass
+import com.demonwav.mcdev.util.findContainingMethod
+import com.demonwav.mcdev.util.fullQualifiedName
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.JavaElementVisitor
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiParameter
+import com.intellij.psi.PsiType
+import com.intellij.psi.util.PsiTypesUtil
+
+class ConflictingShareTypeInspection : MixinInspection() {
+ override fun getStaticDescription() = "Reports when two @Shares of the same variable have different types"
+
+ override fun buildVisitor(holder: ProblemsHolder) = object : JavaElementVisitor() {
+ override fun visitAnnotation(annotation: PsiAnnotation) {
+ if (!annotation.hasQualifiedName(MixinConstants.MixinExtras.SHARE)) {
+ return
+ }
+
+ val mixinClass = annotation.findContainingClass() ?: return
+ val expectedType = getShareType(annotation) ?: return
+ val differentTypes = mutableSetOf()
+ val differentTypeLocations = mutableSetOf()
+
+ val allShares = ShareUtil.getInstance(holder.project).getShares(annotation)
+ for (otherShare in allShares) {
+ if (otherShare equivalentTo annotation) {
+ continue
+ }
+
+ val otherType = getShareType(otherShare) ?: continue
+ if (otherType != expectedType) {
+ differentTypes += otherType
+
+ val containingMethod = otherShare.findContainingMethod()
+ val containingClass = otherShare.findContainingClass()
+ if (containingClass != null && containingClass != mixinClass) {
+ differentTypeLocations += "class '${containingClass.fullQualifiedName}'"
+ } else if (containingMethod != null) {
+ differentTypeLocations += "method '${containingMethod.name}'"
+ }
+ }
+ }
+
+ if (differentTypes.isNotEmpty()) {
+ val differentTypesStr = differentTypes.joinToString(limit = 3) { it.presentableText }
+ val differentLocationsStr = differentTypeLocations.joinToString(limit = 3)
+ holder.registerProblem(
+ (annotation.parent?.parent as? PsiParameter)?.typeElement ?: annotation,
+ "@Share type ${expectedType.presentableText} is not compatible with other @Share types $differentTypesStr, found in $differentLocationsStr"
+ )
+ }
+ }
+ }
+
+ private fun getShareType(share: PsiAnnotation): PsiType? {
+ val param = share.parent?.parent as? PsiParameter ?: return null
+ val paramType = param.type
+ val paramClass = PsiTypesUtil.getPsiClass(paramType) ?: return null
+ if (paramClass.qualifiedName?.startsWith(MixinConstants.MixinExtras.LOCAL_REF_PACKAGE) != true) {
+ return null
+ }
+ return paramType.unwrapLocalRef()
+ }
+}
Index: src/main/kotlin/platform/mixin/util/MixinConstants.kt
===================================================================
--- src/main/kotlin/platform/mixin/util/MixinConstants.kt (revision 8050bcbf8aab3abaee87054c63468db868a9dda2)
+++ src/main/kotlin/platform/mixin/util/MixinConstants.kt (revision 039e8835d95ec2a3e21b865fdd171db50d13bfdb)
@@ -92,6 +92,7 @@
const val WRAP_METHOD = "com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod"
const val LOCAL = "com.llamalad7.mixinextras.sugar.Local"
const val LOCAL_REF_PACKAGE = "com.llamalad7.mixinextras.sugar.ref."
+ const val SHARE = "com.llamalad7.mixinextras.sugar.Share"
const val EXPRESSION = "com.llamalad7.mixinextras.expression.Expression"
const val DEFINITION = "com.llamalad7.mixinextras.expression.Definition"
const val MIXIN_EXTRAS_CONFIG = "com.llamalad7.mixinextras.config.MixinExtrasConfig"
Index: src/main/resources/META-INF/plugin.xml
===================================================================
--- src/main/resources/META-INF/plugin.xml (revision 8050bcbf8aab3abaee87054c63468db868a9dda2)
+++ src/main/resources/META-INF/plugin.xml (revision 039e8835d95ec2a3e21b865fdd171db50d13bfdb)
@@ -1458,6 +1458,14 @@
level="WARNING"
hasStaticDescription="true"
implementationClass="com.demonwav.mcdev.platform.mixin.inspection.mixinextras.LocalArgsOnlyInspection"/>
+