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

View File

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

View File

@ -9,7 +9,7 @@
object Versions { object Versions {
const val core = "1.3.0" 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 consoleGraphical = "0.0.7"
const val consoleTerminal = "0.1.0" const val consoleTerminal = "0.1.0"
const val consolePure = console const val consolePure = console

View File

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

View File

@ -10,6 +10,7 @@
package net.mamoe.mirai.console.compiler.common.diagnostics 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.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.DefaultErrorMessages
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.rendering.Renderers import org.jetbrains.kotlin.diagnostics.rendering.Renderers
@ -21,6 +22,12 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
"{0}", "{0}",
Renderers.STRING Renderers.STRING
) )
put(
NOT_CONSTRUCTABLE_TYPE,
"类型 {0} 无法通过反射直接构造, 需要提供默认值.",
Renderers.STRING
)
} }
override fun getMap() = MAP 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 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") 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 // Resolve
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -44,6 +50,7 @@ enum class ResolveContextKind {
PLUGIN_NAME, PLUGIN_NAME,
PLUGIN_VERSION, PLUGIN_VERSION,
RESTRICTED_NO_ARG_CONSTRUCTOR
; ;
companion object { companion object {

View File

@ -9,8 +9,41 @@
package net.mamoe.mirai.console.compiler.common.resolve 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.descriptors.annotations.Annotated
import org.jetbrains.kotlin.name.FqName 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.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")) compileOnly(kotlin("stdlib-jdk8"))
val core = "1.3.0" 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-console:$console")
compileOnly("net.mamoe:mirai-core:$core") compileOnly("net.mamoe:mirai-core:$core")

View File

@ -1,5 +1,7 @@
package org.example.myplugin 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.JvmPluginDescription
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
@ -19,16 +21,15 @@ object MyPluginMain : KotlinPlugin(
} }
} }
object MyPluginMain2 : KotlinPlugin( object DataTest : AutoSavePluginConfig() {
JvmPluginDescription( val p by value<HasDefaultValue>()
"", val pp by value<NoDefaultValue>()
"0.1.0",
) {
name(".")
id("")
}
) {
fun test() {
}
} }
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 package net.mamoe.mirai.console.intellij
import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker
import net.mamoe.mirai.console.intellij.diagnostics.PluginDescriptionChecker import net.mamoe.mirai.console.intellij.diagnostics.PluginDescriptionChecker
import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.useInstance import org.jetbrains.kotlin.container.useInstance
@ -22,5 +23,6 @@ class IDEContainerContributor : StorageComponentContainerContributor {
moduleDescriptor: ModuleDescriptor, moduleDescriptor: ModuleDescriptor,
) { ) {
container.useInstance(PluginDescriptionChecker()) 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.psi.PsiElement
import com.intellij.util.castSafelyTo import com.intellij.util.castSafelyTo
import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME 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.Icons
import net.mamoe.mirai.console.intellij.resolve.allSuperNames import net.mamoe.mirai.console.intellij.resolve.allSuperNames
import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark 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.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtConstructor 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.PsiDeclarationStatement
import com.intellij.psi.PsiElement 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.castOrNull
import net.mamoe.mirai.console.compiler.common.resolve.COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME 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.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.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor
@ -67,16 +68,8 @@ fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? {
return null 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 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 = fun getElementForLineMark(callElement: PsiElement): PsiElement =
when (callElement) { when (callElement) {
is KtSimpleNameExpression -> callElement.getReferencedNameElement() is KtSimpleNameExpression -> callElement.getReferencedNameElement()
@ -92,20 +85,23 @@ val KtAnnotationEntry.annotationClass: KtClass?
fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean =
this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName } 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<*>> { fun KtDeclaration.resolveAllCalls(bindingContext: BindingContext): Sequence<ResolvedCall<*>> {
return allChildrenWithSelf return allChildrenWithSelf
.filterIsInstance<KtCallExpression>() .filterIsInstance<KtCallExpression>()
.mapNotNull { it.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext) } .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>> { fun ResolvedCall<*>.valueParametersWithArguments(): List<Pair<ValueParameterDescriptor, ValueArgument>> {
return this.valueParameters.zip(this.valueArgumentsByIndex?.mapNotNull { it.arguments.firstOrNull() }.orEmpty()) return this.valueParameters.zip(this.valueArgumentsByIndex?.mapNotNull { it.arguments.firstOrNull() }.orEmpty())
} }