diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt index fc41edc14..61e481370 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt +++ b/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 } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt index 7288eaa3d..4e67fdad3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt +++ b/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 PluginData.value( * 通过具体化类型创建一个 [SerializerAwareValue]. * @see valueFromKType 查看更多实现信息 */ +@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR) @LowPriorityInOverloadResolution public inline fun PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue = valueImpl(typeOf0(), T::class).also { it.value.apply() } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 3349612e6..56e868d6d 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/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 diff --git a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java index b8e6b9be8..001c7d4d5 100644 --- a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java +++ b/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 ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 NOT_CONSTRUCTABLE_TYPE = DiagnosticFactory1.create(ERROR); @Deprecated Object _init = new Object() { diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt index a8af96814..6353a08cb 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/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 diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt index fa59374bd..18ae85598 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt +++ b/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 { diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt index 4f2e6b05c..670ccba04 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt +++ b/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) \ No newline at end of file +fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName) + + +val PsiElement.allChildrenWithSelf: Sequence + get() = sequence { + yield(this@allChildrenWithSelf) + for (child in children) { + yieldAll(child.allChildrenWithSelf) + } + } + + +inline fun PsiElement.findParent(): E? = this.parents.filterIsInstance().firstOrNull() + + +val PsiElement.parents: Sequence + 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 \ No newline at end of file diff --git a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts index d26b54d1d..4e4358b16 100644 --- a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts +++ b/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") diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 9f1bdcbd5..5ab9b553e 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/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() + val pp by value() +} - } -} \ No newline at end of file +data class HasDefaultValue( + val x: Int = 0, +) + +data class NoDefaultValue( + val y: Int, +) \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt index 7e023b444..24cc9ef81 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt +++ b/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()) } } \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt new file mode 100644 index 000000000..9b0552829 --- /dev/null +++ b/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() + + 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() + } + } + } +} \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt index f941bea98..b69cd1f4d 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt +++ b/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 diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt index d2e240cd4..4d3614456 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt +++ b/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 PsiElement.findParent(): E? = this.parents.filterIsInstance().firstOrNull() - val KtClassOrObject.allSuperNames: Sequence get() = allSuperTypes.mapNotNull { it.getKotlinFqName() } -val PsiElement.parents: Sequence - 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 - get() = sequence { - yield(this@allChildrenWithSelf) - for (child in children) { - yieldAll(child.allChildrenWithSelf) - } - } - fun KtDeclaration.resolveAllCalls(bindingContext: BindingContext): Sequence> { return allChildrenWithSelf .filterIsInstance() .mapNotNull { it.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext) } } +fun KtDeclaration.resolveAllCallsWithElement(bindingContext: BindingContext): Sequence, KtCallExpression>> { + return allChildrenWithSelf + .filterIsInstance() + .mapNotNull { + val callee = it.calleeExpression ?: return@mapNotNull null + val resolved = callee.getResolvedCallOrResolveToCall(bindingContext) ?: return@mapNotNull null + + resolved to it + } +} + fun ResolvedCall<*>.valueParametersWithArguments(): List> { return this.valueParameters.zip(this.valueArgumentsByIndex?.mapNotNull { it.arguments.firstOrNull() }.orEmpty()) }