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
@@ -28,12 +28,6 @@ public final fun compareTo(/*0*/ p0: dynamic): dynamic
public final fun compareTo(/*0*/ p0: dynamic): dynamic
public final fun compareTo(/*0*/ p0: dynamic): dynamic
public final fun foo(): dynamic
public final fun component1(): dynamic
public final fun component2(): dynamic
public final fun component3(): dynamic
public final fun foo(): dynamic
public final fun foo(): dynamic
public final fun foo(): dynamic
public final fun inc(): dynamic
public final fun inc(): dynamic
public final fun dec(): dynamic
@@ -55,11 +55,6 @@ fun test(d: dynamic) {
i.<!DEBUG_INFO_DYNAMIC!>foo<!>()
}
val (<!DEBUG_INFO_DYNAMIC!>a<!>, <!DEBUG_INFO_DYNAMIC!>b<!>, <!DEBUG_INFO_DYNAMIC!>c<!>) = d
a.<!DEBUG_INFO_DYNAMIC!>foo<!>()
b.<!DEBUG_INFO_DYNAMIC!>foo<!>()
c.<!DEBUG_INFO_DYNAMIC!>foo<!>()
var dVar = d
dVar<!DEBUG_INFO_DYNAMIC!>++<!>
<!DEBUG_INFO_DYNAMIC!>++<!>dVar
@@ -0,0 +1,9 @@
public final fun component1(): dynamic
public final fun component2(): dynamic
public final fun plus(/*0*/ p0: dynamic): dynamic
public final fun component1(): dynamic
public final fun component2(): dynamic
public final fun plus(/*0*/ p0: dynamic): dynamic
public final fun component1(): dynamic
public final fun component2(): dynamic
public final fun plus(/*0*/ p0: dynamic): dynamic
@@ -0,0 +1,22 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun foo() {
for (<!WRONG_OPERATION_WITH_DYNAMIC!>(x, y)<!> in A()) {
println(x + y)
}
bar { <!WRONG_OPERATION_WITH_DYNAMIC!>(x, y)<!> ->
println(x + y)
}
val x: dynamic = Any()
<!WRONG_OPERATION_WITH_DYNAMIC!>val (y, z) = x<!>
println(y + z)
}
class A {
operator fun iterator(): Iterator<dynamic> = TODO("")
}
fun bar(f: (dynamic) -> Unit): Unit = TODO("")
@@ -0,0 +1,12 @@
package
public fun bar(/*0*/ f: (dynamic) -> kotlin.Unit): kotlin.Unit
public fun foo(): kotlin.Unit
public final class A {
public constructor A()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final operator fun iterator(): kotlin.collections.Iterator<dynamic>
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -1,14 +1,22 @@
fun test(d: dynamic) {
val a = arrayOf(1, 2, 3)
d.foo(*d)
d.foo(*a)
d.foo(1, "2", *a)
d.foo(1, *a) { }
d.foo(*a) { "" }
d.foo(*a, *a)
d.foo(*a, *a) { "" }
d.foo(*a, 1, { "" }, *a)
d.foo(*a, 1)
d.foo(*a, *a, { "" })
}
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>d)
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(1, "2", <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(1, <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a) { }
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a) { "" }
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a) { "" }
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, 1, { "" }, <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a)
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, 1)
d.foo(<!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, <!SPREAD_OPERATOR_IN_DYNAMIC_CALL!>*<!>a, { "" })
bar(d)
bar(d, d)
bar(<!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>)
bar(<!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>, <!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>)
bar(<!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>, 23, <!WRONG_OPERATION_WITH_DYNAMIC!>*d<!>)
}
fun bar(vararg x: Int): Unit = TODO("$x")
@@ -1,3 +1,4 @@
package
public fun bar(/*0*/ vararg x: kotlin.Int /*kotlin.IntArray*/): kotlin.Unit
public fun test(/*0*/ d: dynamic): kotlin.Unit
@@ -110,6 +110,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes
doTest(fileName);
}
@TestMetadata("destructuring.kt")
public void testDestructuring() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.kt");
doTest(fileName);
}
@TestMetadata("dynamicCalls.kt")
public void testDynamicCalls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/dynamicCalls.kt");
@@ -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 }
}