Procházet zdrojové kódy

Introduce inspection NOT_CONSTRUCTABLE_TYPE for PluginData.value

Him188 před 5 roky
rodič
revize
7244cb76c4

+ 10 - 4
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt

@@ -12,16 +12,17 @@
 package net.mamoe.mirai.console.compiler.common
 
 import net.mamoe.mirai.console.util.ConsoleExperimentalApi
+import kotlin.annotation.AnnotationTarget.*
 
 /**
  * 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断.
  */
 @ConsoleExperimentalApi
 @Target(
-    AnnotationTarget.VALUE_PARAMETER,
-    AnnotationTarget.PROPERTY,
-    AnnotationTarget.FIELD,
-    //AnnotationTarget.EXPRESSION
+    VALUE_PARAMETER,
+    PROPERTY, FIELD,
+    FUNCTION,
+    TYPE, TYPE_PARAMETER
 )
 @Retention(AnnotationRetention.BINARY)
 public annotation class ResolveContext(
@@ -34,5 +35,10 @@ public annotation class ResolveContext(
         PLUGIN_ID,
         PLUGIN_NAME,
         PLUGIN_VERSION,
+
+        /**
+         * Custom serializers allowed
+         */
+        RESTRICTED_NO_ARG_CONSTRUCTOR
     }
 }

+ 6 - 1
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt

@@ -18,9 +18,13 @@
 package net.mamoe.mirai.console.data
 
 import kotlinx.serialization.KSerializer
+import net.mamoe.mirai.console.compiler.common.ResolveContext
+import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_NO_ARG_CONSTRUCTOR
 import net.mamoe.mirai.console.data.java.JAutoSavePluginData
 import net.mamoe.mirai.console.internal.data.*
+import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
 import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
+import net.mamoe.mirai.console.plugin.jvm.reloadPluginData
 import net.mamoe.mirai.console.util.ConsoleExperimentalApi
 import kotlin.internal.LowPriorityInOverloadResolution
 import kotlin.reflect.KClass
@@ -98,7 +102,7 @@ import kotlin.reflect.full.findAnnotation
  *
  * 要查看详细的解释,请查看 [docs/PluginData.md](https://github.com/mamoe/mirai-console/blob/master/docs/PluginData.md)
  *
- * @see JvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
+ * @see AbstractJvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
  * @see PluginDataStorage [PluginData] 存储仓库
  * @see PluginDataExtensions 相关 [SerializerAwareValue] 映射函数
  */
@@ -321,6 +325,7 @@ public inline fun <reified T> PluginData.value(
  * 通过具体化类型创建一个 [SerializerAwareValue].
  * @see valueFromKType 查看更多实现信息
  */
+@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR)
 @LowPriorityInOverloadResolution
 public inline fun <reified T> PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue<T> =
     valueImpl<T>(typeOf0<T>(), T::class).also { it.value.apply() }

+ 1 - 1
buildSrc/src/main/kotlin/Versions.kt

@@ -9,7 +9,7 @@
 
 object Versions {
     const val core = "1.3.0"
-    const val console = "1.0-RC-dev-2"
+    const val console = "1.0-RC-dev-3"
     const val consoleGraphical = "0.0.7"
     const val consoleTerminal = "0.1.0"
     const val consolePure = console

+ 1 - 0
tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java

@@ -17,6 +17,7 @@ import static org.jetbrains.kotlin.diagnostics.Severity.ERROR;
 
 public interface MiraiConsoleErrors {
     DiagnosticFactory1<PsiElement, String> ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR);
+    DiagnosticFactory1<PsiElement, String> NOT_CONSTRUCTABLE_TYPE = DiagnosticFactory1.create(ERROR);
 
     @Deprecated
     Object _init = new Object() {

+ 7 - 0
tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt

@@ -10,6 +10,7 @@
 package net.mamoe.mirai.console.compiler.common.diagnostics
 
 import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION
+import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE
 import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
 import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
 import org.jetbrains.kotlin.diagnostics.rendering.Renderers
@@ -21,6 +22,12 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
             "{0}",
             Renderers.STRING
         )
+
+        put(
+            NOT_CONSTRUCTABLE_TYPE,
+            "类型 {0} 无法通过反射直接构造, 需要提供默认值.",
+            Renderers.STRING
+        )
     }
 
     override fun getMap() = MAP

+ 7 - 0
tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt

@@ -30,6 +30,12 @@ val PLUGIN_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin")
 val JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription")
 val SIMPLE_JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription")
 
+///////////////////////////////////////////////////////////////////////////
+// PluginData
+///////////////////////////////////////////////////////////////////////////
+
+val PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME = FqName("net.mamoe.mirai.console.data.value")
+
 ///////////////////////////////////////////////////////////////////////////
 // Resolve
 ///////////////////////////////////////////////////////////////////////////
@@ -44,6 +50,7 @@ enum class ResolveContextKind {
     PLUGIN_NAME,
     PLUGIN_VERSION,
 
+    RESTRICTED_NO_ARG_CONSTRUCTOR
     ;
 
     companion object {

+ 34 - 1
tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt

@@ -9,8 +9,41 @@
 
 package net.mamoe.mirai.console.compiler.common.resolve
 
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
 import org.jetbrains.kotlin.descriptors.annotations.Annotated
 import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
 
 fun Annotated.hasAnnotation(fqName: FqName) = this.annotations.hasAnnotation(fqName)
-fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName)
+fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName)
+
+
+val PsiElement.allChildrenWithSelf: Sequence<PsiElement>
+    get() = sequence {
+        yield(this@allChildrenWithSelf)
+        for (child in children) {
+            yieldAll(child.allChildrenWithSelf)
+        }
+    }
+
+
+inline fun <reified E> PsiElement.findParent(): E? = this.parents.filterIsInstance<E>().firstOrNull()
+
+
+val PsiElement.parents: Sequence<PsiElement>
+    get() {
+        val seed = if (this is PsiFile) null else parent
+        return generateSequence(seed) { if (it is PsiFile) null else it.parent }
+    }
+
+
+fun ClassDescriptor.findNoArgConstructor(): ClassConstructorDescriptor? {
+    return constructors.find { desc ->
+        desc.valueParameters.all { it.hasDefaultValue() }
+    }
+}
+
+fun ClassDescriptor.hasNoArgConstructor(): Boolean = this.findNoArgConstructor() != null

+ 1 - 1
tools/intellij-plugin/run/projects/test-project/build.gradle.kts

@@ -22,7 +22,7 @@ dependencies {
     compileOnly(kotlin("stdlib-jdk8"))
 
     val core = "1.3.0"
-    val console = "1.0-RC-dev-2"
+    val console = "1.0-RC-dev-3"
 
     compileOnly("net.mamoe:mirai-console:$console")
     compileOnly("net.mamoe:mirai-core:$core")

+ 13 - 12
tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt

@@ -1,5 +1,7 @@
 package org.example.myplugin
 
+import net.mamoe.mirai.console.data.AutoSavePluginConfig
+import net.mamoe.mirai.console.data.value
 import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
 import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
 
@@ -19,16 +21,15 @@ object MyPluginMain : KotlinPlugin(
     }
 }
 
-object MyPluginMain2 : KotlinPlugin(
-    JvmPluginDescription(
-        "",
-        "0.1.0",
-    ) {
-        name(".")
-        id("")
-    }
-) {
-    fun test() {
+object DataTest : AutoSavePluginConfig() {
+    val p by value<HasDefaultValue>()
+    val pp by value<NoDefaultValue>()
+}
 
-    }
-}
+data class HasDefaultValue(
+    val x: Int = 0,
+)
+
+data class NoDefaultValue(
+    val y: Int,
+)

+ 2 - 0
tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt

@@ -9,6 +9,7 @@
 
 package net.mamoe.mirai.console.intellij
 
+import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker
 import net.mamoe.mirai.console.intellij.diagnostics.PluginDescriptionChecker
 import org.jetbrains.kotlin.container.StorageComponentContainer
 import org.jetbrains.kotlin.container.useInstance
@@ -22,5 +23,6 @@ class IDEContainerContributor : StorageComponentContainerContributor {
         moduleDescriptor: ModuleDescriptor,
     ) {
         container.useInstance(PluginDescriptionChecker())
+        container.useInstance(PluginDataValuesChecker())
     }
 }

+ 64 - 0
tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt

@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.console.intellij.diagnostics
+
+import com.intellij.psi.PsiElement
+import net.mamoe.mirai.console.compiler.common.castOrNull
+import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors
+import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME
+import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind
+import net.mamoe.mirai.console.compiler.common.resolve.hasNoArgConstructor
+import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKind
+import net.mamoe.mirai.console.intellij.resolve.resolveAllCallsWithElement
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.idea.inspections.collections.isCalling
+import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
+import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
+import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
+import org.jetbrains.kotlin.types.SimpleType
+
+
+class PluginDataValuesChecker : DeclarationChecker {
+    override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
+        val bindingContext = context.bindingContext
+        declaration.resolveAllCallsWithElement(bindingContext)
+            .filter { (call) -> call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME) }
+            .filter { (call) ->
+                call.resultingDescriptor.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR
+            }.flatMap { (call, element) ->
+                call.typeArguments.entries.associateWith { element }.asSequence()
+            }.filter { (e, _) ->
+                val (p, t) = e
+                (p.isReified || p.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR)
+                    && t is SimpleType
+            }.forEach { (e, callExpr) ->
+                val (_, t) = e
+                val classDescriptor = t.constructor.declarationDescriptor?.castOrNull<ClassDescriptor>()
+
+                fun getInspectionTarget(): PsiElement {
+                    return callExpr.typeArguments.find { it.references.firstOrNull()?.canonicalText == t.fqName?.toString() } ?: callExpr
+                }
+
+                fun reportInspection() {
+                    context.report(MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on(
+                        getInspectionTarget(),
+                        t.fqName?.asString().toString())
+                    )
+                }
+
+                when {
+                    classDescriptor == null -> reportInspection()
+                    !classDescriptor.hasNoArgConstructor() -> reportInspection()
+                }
+            }
+    }
+}

+ 1 - 1
tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt

@@ -17,10 +17,10 @@ import com.intellij.openapi.editor.markup.GutterIconRenderer
 import com.intellij.psi.PsiElement
 import com.intellij.util.castSafelyTo
 import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME
+import net.mamoe.mirai.console.compiler.common.resolve.parents
 import net.mamoe.mirai.console.intellij.Icons
 import net.mamoe.mirai.console.intellij.resolve.allSuperNames
 import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark
-import net.mamoe.mirai.console.intellij.resolve.parents
 import org.jetbrains.kotlin.nj2k.postProcessing.resolve
 import org.jetbrains.kotlin.psi.KtClass
 import org.jetbrains.kotlin.psi.KtConstructor

+ 13 - 17
tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt

@@ -11,10 +11,11 @@ package net.mamoe.mirai.console.intellij.resolve
 
 import com.intellij.psi.PsiDeclarationStatement
 import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiFile
 import net.mamoe.mirai.console.compiler.common.castOrNull
 import net.mamoe.mirai.console.compiler.common.resolve.COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME
 import net.mamoe.mirai.console.compiler.common.resolve.SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME
+import net.mamoe.mirai.console.compiler.common.resolve.allChildrenWithSelf
+import net.mamoe.mirai.console.compiler.common.resolve.findParent
 import org.jetbrains.kotlin.descriptors.CallableDescriptor
 import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
 import org.jetbrains.kotlin.descriptors.VariableDescriptor
@@ -67,16 +68,8 @@ fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? {
     return null
 }
 
-inline fun <reified E> PsiElement.findParent(): E? = this.parents.filterIsInstance<E>().firstOrNull()
-
 val KtClassOrObject.allSuperNames: Sequence<FqName> get() = allSuperTypes.mapNotNull { it.getKotlinFqName() }
 
-val PsiElement.parents: Sequence<PsiElement>
-    get() {
-        val seed = if (this is PsiFile) null else parent
-        return generateSequence(seed) { if (it is PsiFile) null else it.parent }
-    }
-
 fun getElementForLineMark(callElement: PsiElement): PsiElement =
     when (callElement) {
         is KtSimpleNameExpression -> callElement.getReferencedNameElement()
@@ -92,20 +85,23 @@ val KtAnnotationEntry.annotationClass: KtClass?
 fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean =
     this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName }
 
-val PsiElement.allChildrenWithSelf: Sequence<PsiElement>
-    get() = sequence {
-        yield(this@allChildrenWithSelf)
-        for (child in children) {
-            yieldAll(child.allChildrenWithSelf)
-        }
-    }
-
 fun KtDeclaration.resolveAllCalls(bindingContext: BindingContext): Sequence<ResolvedCall<*>> {
     return allChildrenWithSelf
         .filterIsInstance<KtCallExpression>()
         .mapNotNull { it.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext) }
 }
 
+fun KtDeclaration.resolveAllCallsWithElement(bindingContext: BindingContext): Sequence<Pair<ResolvedCall<out CallableDescriptor>, KtCallExpression>> {
+    return allChildrenWithSelf
+        .filterIsInstance<KtCallExpression>()
+        .mapNotNull {
+            val callee = it.calleeExpression ?: return@mapNotNull null
+            val resolved = callee.getResolvedCallOrResolveToCall(bindingContext) ?: return@mapNotNull null
+
+            resolved to it
+        }
+}
+
 fun ResolvedCall<*>.valueParametersWithArguments(): List<Pair<ValueParameterDescriptor, ValueArgument>> {
     return this.valueParameters.zip(this.valueArgumentsByIndex?.mapNotNull { it.arguments.firstOrNull() }.orEmpty())
 }