diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/conventions.dynamic.txt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/conventions.dynamic.txt index c22b8457729..08660b43e2a 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/conventions.dynamic.txt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/conventions.dynamic.txt @@ -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 diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/conventions.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/conventions.kt index d9283cf5a8c..1995e5c534f 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/conventions.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/conventions.kt @@ -55,11 +55,6 @@ fun test(d: dynamic) { i.foo() } - val (a, b, c) = d - a.foo() - b.foo() - c.foo() - var dVar = d dVar++ ++dVar diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.dynamic.txt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.dynamic.txt new file mode 100644 index 00000000000..08fd47150b7 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.dynamic.txt @@ -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 diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.kt new file mode 100644 index 00000000000..523368b8592 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.kt @@ -0,0 +1,22 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +fun foo() { + for ((x, y) in A()) { + println(x + y) + } + + bar { (x, y) -> + println(x + y) + } + + val x: dynamic = Any() + + val (y, z) = x + println(y + z) +} + +class A { + operator fun iterator(): Iterator = TODO("") +} + +fun bar(f: (dynamic) -> Unit): Unit = TODO("") \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.txt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.txt new file mode 100644 index 00000000000..f2704fd38b6 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/destructuring.txt @@ -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 + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/spreadOperator.kt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/spreadOperator.kt index 14973af327e..14455c76030 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/spreadOperator.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/spreadOperator.kt @@ -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, { "" }) -} \ No newline at end of file + 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, { "" }) + + bar(d) + bar(d, d) + bar(*d) + bar(*d, *d) + bar(*d, 23, *d) +} + +fun bar(vararg x: Int): Unit = TODO("$x") \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/spreadOperator.txt b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/spreadOperator.txt index dab53768f2f..9983514c200 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/spreadOperator.txt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/dynamicTypes/spreadOperator.txt @@ -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 diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java index 2a36feefc81..8a4a0cbf81a 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java @@ -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"); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt index 0c7e5d6b91a..c82495c3d66 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.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") diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java index e5bb1c4d979..a9f11b71a42 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java @@ -68,7 +68,10 @@ public interface ErrorsJs { DiagnosticFactory1 NATIVE_INTERFACE_AS_REIFIED_TYPE_ARGUMENT = DiagnosticFactory1.create(ERROR); DiagnosticFactory0 EXTERNAL_TYPE_EXTENDS_NON_EXTERNAL_TYPE = DiagnosticFactory0.create( ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT); + DiagnosticFactory1 WRONG_OPERATION_WITH_DYNAMIC = DiagnosticFactory1.create(ERROR); + DiagnosticFactory0 SPREAD_OPERATOR_IN_DYNAMIC_CALL = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION = DiagnosticFactory0.create( ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory0 RUNTIME_ANNOTATION_NOT_SUPPORTED = DiagnosticFactory0.create(WARNING, DECLARATION_SIGNATURE_OR_DEFAULT); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDynamicCallChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDynamicCallChecker.kt index 963f3e05321..59804ce1161 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDynamicCallChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDynamicCallChecker.kt @@ -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 } } \ No newline at end of file