Introduce inspection NOT_CONSTRUCTABLE_TYPE for PluginData.value

This commit is contained in:
Him188 2020-09-18 15:36:03 +08:00
parent e4f37b9a52
commit 7244cb76c4
13 changed files with 160 additions and 38 deletions

View File

@ -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
}
}

View File

@ -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() }

View File

@ -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

View File

@ -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() {

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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")

View File

@ -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,
)

View File

@ -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())
}
}

View File

@ -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()
}
}
}
}

View File

@ -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

View File

@ -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())
}