JS: prohibit spread operator and destructuring declaration in dynamic values. See KT-15283

This commit is contained in:
Alexey Andreev
2016-12-26 17:44:59 +03:00
parent 31d0e0b7c4
commit a4b2abc7d5
11 changed files with 122 additions and 39 deletions
@@ -68,7 +68,10 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
put(ErrorsJs.UNCHECKED_CAST_TO_NATIVE_INTERFACE, "Unchecked cast to native interface: {0} to {1}", RENDER_TYPE, RENDER_TYPE)
put(ErrorsJs.NATIVE_INTERFACE_AS_REIFIED_TYPE_ARGUMENT, "Cannot pass native interface {0} for reified type parameter", RENDER_TYPE)
put(ErrorsJs.EXTERNAL_TYPE_EXTENDS_NON_EXTERNAL_TYPE, "External type extends non-external type")
put(ErrorsJs.WRONG_OPERATION_WITH_DYNAMIC, "Wrong operation with dynamic value: {0}", Renderers.STRING)
put(ErrorsJs.SPREAD_OPERATOR_IN_DYNAMIC_CALL, "Can't apply spread operator in dynamic call")
put(ErrorsJs.RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION, "Runtime annotation can't be put on external declaration")
put(ErrorsJs.RUNTIME_ANNOTATION_NOT_SUPPORTED, "Reflection is not supported in JavaScript target, therefore you won't be able " +
"to read this annotation in run-time")
@@ -68,7 +68,10 @@ public interface ErrorsJs {
DiagnosticFactory1<PsiElement, KotlinType> NATIVE_INTERFACE_AS_REIFIED_TYPE_ARGUMENT = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<KtElement> EXTERNAL_TYPE_EXTENDS_NON_EXTERNAL_TYPE = DiagnosticFactory0.create(
ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory1<PsiElement, String> WRONG_OPERATION_WITH_DYNAMIC = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<PsiElement> SPREAD_OPERATOR_IN_DYNAMIC_CALL = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION = DiagnosticFactory0.create(
ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory0<PsiElement> RUNTIME_ANNOTATION_NOT_SUPPORTED = DiagnosticFactory0.create(WARNING, DECLARATION_SIGNATURE_OR_DEFAULT);
@@ -18,37 +18,64 @@ package org.jetbrains.kotlin.js.resolve.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtArrayAccessExpression
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtWhenConditionInRange
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.types.isDynamic
object JsDynamicCallChecker : CallChecker {
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val callee = resolvedCall.resultingDescriptor
if (callee.dispatchReceiverParameter?.type?.isDynamic() != true) return
if (!callee.isDynamic()) {
return checkSpreadOperator(resolvedCall, context)
}
val element = resolvedCall.call.callElement
if (element is KtArrayAccessExpression && element.indexExpressions.size > 1) {
context.trace.report(ErrorsJs.WRONG_OPERATION_WITH_DYNAMIC.on(reportOn, "indexed access with more than one index"))
when (element) {
is KtArrayAccessExpression -> {
if (element.indexExpressions.size > 1) {
context.trace.report(ErrorsJs.WRONG_OPERATION_WITH_DYNAMIC.on(reportOn, "indexed access with more than one index"))
}
}
is KtWhenConditionInRange -> {
reportInOperation(context, reportOn)
}
is KtBinaryExpression -> {
val token = element.operationToken
when (token) {
in OperatorConventions.IN_OPERATIONS -> {
reportInOperation(context, reportOn)
}
KtTokens.RANGE -> {
context.trace.report(ErrorsJs.WRONG_OPERATION_WITH_DYNAMIC.on(reportOn, "`..` operation"))
}
}
}
is KtDestructuringDeclarationEntry -> {
if (!reportedOn(context, element.node.treeParent.psi)) {
context.trace.report(ErrorsJs.WRONG_OPERATION_WITH_DYNAMIC.on(element.parent, "destructuring declaration"))
}
}
}
if (element is KtWhenConditionInRange) {
reportInOperation(context, reportOn)
for (argument in resolvedCall.call.valueArguments) {
argument.getSpreadElement()?.let {
context.trace.report(ErrorsJs.SPREAD_OPERATOR_IN_DYNAMIC_CALL.on(it))
}
}
else if (element is KtBinaryExpression) {
val token = element.operationToken
when (token) {
in OperatorConventions.IN_OPERATIONS -> {
reportInOperation(context, reportOn)
}
KtTokens.RANGE -> {
context.trace.report(ErrorsJs.WRONG_OPERATION_WITH_DYNAMIC.on(reportOn, "`..` operation"))
}
}
private fun checkSpreadOperator(resolvedCall: ResolvedCall<*>, context: CallCheckerContext) {
for (arg in resolvedCall.call.valueArguments) {
val argExpression = arg.getArgumentExpression() ?: continue
if (context.trace.bindingContext.getType(argExpression)?.isDynamic() == true && arg.getSpreadElement() != null) {
context.trace.report(ErrorsJs.WRONG_OPERATION_WITH_DYNAMIC.on(arg.asElement(), "spread operator"))
}
}
}
@@ -56,4 +83,7 @@ object JsDynamicCallChecker : CallChecker {
private fun reportInOperation(context: CallCheckerContext, reportOn: PsiElement) {
context.trace.report(ErrorsJs.WRONG_OPERATION_WITH_DYNAMIC.on(reportOn, "`in` operation"))
}
private fun reportedOn(context: CallCheckerContext, element: PsiElement) =
context.trace.bindingContext.diagnostics.forElement(element).any { it.factory == ErrorsJs.WRONG_OPERATION_WITH_DYNAMIC }
}