[JS IR] Allow restriction of function argument by external type
Add a special annotation @JsExternalTypeArgument for marking function parameters. The marked parameter accepts an argument with an external type only. ^KT-57479 Fixed
This commit is contained in:
committed by
Space Team
parent
4819593bf4
commit
e8be3043cc
+7
@@ -5047,6 +5047,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirJsErrors.JS_EXTERNAL_ARGUMENT) { firDiagnostic ->
|
||||
JsExternalArgumentImpl(
|
||||
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
|
||||
firDiagnostic as KtPsiDiagnostic,
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirJsErrors.NESTED_JS_EXPORT) { firDiagnostic ->
|
||||
NestedJsExportImpl(
|
||||
firDiagnostic as KtPsiDiagnostic,
|
||||
|
||||
+5
@@ -3513,6 +3513,11 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
|
||||
abstract val kid: KtClassLikeSymbol
|
||||
}
|
||||
|
||||
abstract class JsExternalArgument : KtFirDiagnostic<KtExpression>() {
|
||||
override val diagnosticClass get() = JsExternalArgument::class
|
||||
abstract val argType: KtType
|
||||
}
|
||||
|
||||
abstract class NestedJsExport : KtFirDiagnostic<KtElement>() {
|
||||
override val diagnosticClass get() = NestedJsExport::class
|
||||
}
|
||||
|
||||
+6
@@ -4249,6 +4249,12 @@ internal class JsExternalInheritorsOnlyImpl(
|
||||
override val token: KtLifetimeToken,
|
||||
) : KtFirDiagnostic.JsExternalInheritorsOnly(), KtAbstractFirDiagnostic<KtDeclaration>
|
||||
|
||||
internal class JsExternalArgumentImpl(
|
||||
override val argType: KtType,
|
||||
override val firDiagnostic: KtPsiDiagnostic,
|
||||
override val token: KtLifetimeToken,
|
||||
) : KtFirDiagnostic.JsExternalArgument(), KtAbstractFirDiagnostic<KtExpression>
|
||||
|
||||
internal class NestedJsExportImpl(
|
||||
override val firDiagnostic: KtPsiDiagnostic,
|
||||
override val token: KtLifetimeToken,
|
||||
|
||||
+3
@@ -99,6 +99,9 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") {
|
||||
parameter<FirClassLikeSymbol<*>>("parent")
|
||||
parameter<FirClassLikeSymbol<*>>("kid")
|
||||
}
|
||||
val JS_EXTERNAL_ARGUMENT by error<KtExpression>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) {
|
||||
parameter<ConeKotlinType>("argType")
|
||||
}
|
||||
}
|
||||
|
||||
val EXPORT by object : DiagnosticGroup("Export") {
|
||||
|
||||
+1
@@ -80,6 +80,7 @@ object FirJsErrors {
|
||||
val UNCHECKED_CAST_TO_EXTERNAL_INTERFACE by warning2<KtElement, ConeKotlinType, ConeKotlinType>()
|
||||
val EXTERNAL_INTERFACE_AS_CLASS_LITERAL by error0<KtElement>()
|
||||
val JS_EXTERNAL_INHERITORS_ONLY by error2<KtDeclaration, FirClassLikeSymbol<*>, FirClassLikeSymbol<*>>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
|
||||
val JS_EXTERNAL_ARGUMENT by error1<KtExpression, ConeKotlinType>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
|
||||
|
||||
// Export
|
||||
val NESTED_JS_EXPORT by error0<KtElement>()
|
||||
|
||||
+6
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.INLINE_CLASS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.ENUM_CLASS_IN_EXTERNAL_DECLARATION_WARNING
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_EXTERNAL_INHERITORS_ONLY
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.INLINE_EXTERNAL_DECLARATION
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_EXTERNAL_ARGUMENT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_MODULE_PROHIBITED_ON_NON_NATIVE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_MODULE_PROHIBITED_ON_VAR
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NESTED_JS_MODULE_PROHIBITED
|
||||
@@ -163,6 +164,11 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
|
||||
FirDiagnosticRenderers.RENDER_CLASS_OR_OBJECT_NAME,
|
||||
FirDiagnosticRenderers.RENDER_CLASS_OR_OBJECT_NAME
|
||||
)
|
||||
map.put(
|
||||
JS_EXTERNAL_ARGUMENT,
|
||||
"Expected argument with external type, but type {0} is non-external",
|
||||
FirDiagnosticRenderers.RENDER_TYPE
|
||||
)
|
||||
map.put(JS_NAME_PROHIBITED_FOR_EXTENSION_PROPERTY, "@JsName is prohibited for extension properties")
|
||||
map.put(JS_NAME_IS_NOT_ON_ALL_ACCESSORS, "@JsName should be on all the property accessors")
|
||||
map.put(JS_NAME_PROHIBITED_FOR_NAMED_NATIVE, "@JsName is prohibited for external declaration with explicit name")
|
||||
|
||||
+5
@@ -25,4 +25,9 @@ object JsExpressionCheckers : ExpressionCheckers() {
|
||||
get() = setOf(
|
||||
FirJsDynamicCallChecker,
|
||||
)
|
||||
|
||||
override val callCheckers: Set<FirCallChecker>
|
||||
get() = setOf(
|
||||
FirJsExternalArgumentCallChecker
|
||||
)
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.js.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirCallChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.js.checkers.isEffectivelyExternal
|
||||
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
|
||||
import org.jetbrains.kotlin.fir.expressions.FirCall
|
||||
import org.jetbrains.kotlin.fir.expressions.resolvedArgumentMapping
|
||||
import org.jetbrains.kotlin.fir.expressions.unwrapArgument
|
||||
import org.jetbrains.kotlin.fir.types.ConeDynamicType
|
||||
import org.jetbrains.kotlin.fir.types.coneTypeOrNull
|
||||
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
|
||||
import org.jetbrains.kotlin.name.JsStandardClassIds.Annotations.JsExternalArgument
|
||||
|
||||
object FirJsExternalArgumentCallChecker : FirCallChecker() {
|
||||
override fun check(expression: FirCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val arguments = expression.resolvedArgumentMapping ?: return
|
||||
for ((argument, parameter) in arguments) {
|
||||
if (parameter.hasAnnotation(JsExternalArgument, context.session)) {
|
||||
val unwrappedArg = argument.unwrapArgument()
|
||||
val type = unwrappedArg.typeRef.coneTypeOrNull ?: continue
|
||||
val symbol = type.toRegularClassSymbol(context.session)
|
||||
if (symbol?.isEffectivelyExternal(context.session) == false || type is ConeDynamicType) {
|
||||
reporter.reportOn(
|
||||
unwrappedArg.source,
|
||||
FirJsErrors.JS_EXTERNAL_ARGUMENT,
|
||||
type,
|
||||
context
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// FIR_IDENTICAL
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun anyExample(@JsExternalArgument x : Any) = x
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun anyOrNullExample(@JsExternalArgument x : Any?) = x
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun <T> genericExample(@JsExternalArgument x : T) = x
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun <T> genericOrNullExample(@JsExternalArgument x : T?) = x
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun dynamicExample(@JsExternalArgument x : dynamic) = x
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun severalParams(@JsExternalArgument x : Any?, @JsExternalArgument y : Any?, @JsExternalArgument z : dynamic) = x ?: y ?: z
|
||||
|
||||
external interface ExternalInterface
|
||||
|
||||
fun boxExternalInterface(i: ExternalInterface) {
|
||||
anyExample(i)
|
||||
anyOrNullExample(i)
|
||||
genericExample(i)
|
||||
genericOrNullExample(i)
|
||||
dynamicExample(i)
|
||||
}
|
||||
|
||||
fun boxExternalInterfaceOrNull(iOrNull: ExternalInterface?) {
|
||||
anyOrNullExample(iOrNull)
|
||||
genericExample(iOrNull)
|
||||
genericOrNullExample(iOrNull)
|
||||
dynamicExample(iOrNull)
|
||||
}
|
||||
|
||||
external class ExternalClass
|
||||
|
||||
fun boxExternalClass(c: ExternalClass) {
|
||||
anyExample(c)
|
||||
anyOrNullExample(c)
|
||||
genericExample(c)
|
||||
genericOrNullExample(c)
|
||||
dynamicExample(c)
|
||||
}
|
||||
|
||||
external object ExternalObject
|
||||
|
||||
fun boxExternalObject() {
|
||||
anyExample(ExternalObject)
|
||||
anyOrNullExample(ExternalObject)
|
||||
genericExample(ExternalObject)
|
||||
genericOrNullExample(ExternalObject)
|
||||
dynamicExample(ExternalObject)
|
||||
}
|
||||
|
||||
interface Interface
|
||||
|
||||
fun boxInterface(i: Interface) {
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>i<!>)
|
||||
anyOrNullExample(<!JS_EXTERNAL_ARGUMENT!>i<!>)
|
||||
genericExample(<!JS_EXTERNAL_ARGUMENT!>i<!>)
|
||||
genericOrNullExample(<!JS_EXTERNAL_ARGUMENT!>i<!>)
|
||||
dynamicExample(<!JS_EXTERNAL_ARGUMENT!>i<!>)
|
||||
}
|
||||
|
||||
fun boxInterfaceOrNull(iOrNull: Interface?) {
|
||||
anyOrNullExample(<!JS_EXTERNAL_ARGUMENT!>iOrNull<!>)
|
||||
genericExample(<!JS_EXTERNAL_ARGUMENT!>iOrNull<!>)
|
||||
genericOrNullExample(<!JS_EXTERNAL_ARGUMENT!>iOrNull<!>)
|
||||
dynamicExample(<!JS_EXTERNAL_ARGUMENT!>iOrNull<!>)
|
||||
}
|
||||
|
||||
class Class
|
||||
|
||||
fun boxInterface(c: Class) {
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>c<!>)
|
||||
anyOrNullExample(<!JS_EXTERNAL_ARGUMENT!>c<!>)
|
||||
genericExample(<!JS_EXTERNAL_ARGUMENT!>c<!>)
|
||||
genericOrNullExample(<!JS_EXTERNAL_ARGUMENT!>c<!>)
|
||||
dynamicExample(<!JS_EXTERNAL_ARGUMENT!>c<!>)
|
||||
}
|
||||
|
||||
object Object
|
||||
|
||||
fun boxObject() {
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>Object<!>)
|
||||
anyOrNullExample(<!JS_EXTERNAL_ARGUMENT!>Object<!>)
|
||||
genericExample(<!JS_EXTERNAL_ARGUMENT!>Object<!>)
|
||||
genericOrNullExample(<!JS_EXTERNAL_ARGUMENT!>Object<!>)
|
||||
dynamicExample(<!JS_EXTERNAL_ARGUMENT!>Object<!>)
|
||||
}
|
||||
|
||||
fun boxDynamic(d: dynamic) {
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>d<!>)
|
||||
anyOrNullExample(<!JS_EXTERNAL_ARGUMENT!>d<!>)
|
||||
genericExample(<!JS_EXTERNAL_ARGUMENT!>d<!>)
|
||||
genericOrNullExample(<!JS_EXTERNAL_ARGUMENT!>d<!>)
|
||||
dynamicExample(<!JS_EXTERNAL_ARGUMENT!>d<!>)
|
||||
}
|
||||
|
||||
fun boxPrimitiveTypes() {
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>1<!>)
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>1.2<!>)
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>true<!>)
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>"hello"<!>)
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>{}<!>)
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>{}()<!>)
|
||||
anyOrNullExample(<!JS_EXTERNAL_ARGUMENT!>null<!>)
|
||||
}
|
||||
|
||||
fun boxArgExpression(i: Interface, iOrNull: Interface?) {
|
||||
anyExample(<!JS_EXTERNAL_ARGUMENT!>iOrNull ?: i<!>)
|
||||
}
|
||||
|
||||
fun boxNamedArgExpression(i: Interface, iOrNull: Interface?, d: dynamic) {
|
||||
anyExample(x = <!JS_EXTERNAL_ARGUMENT!>iOrNull ?: i<!>)
|
||||
|
||||
severalParams(
|
||||
x = <!JS_EXTERNAL_ARGUMENT!>i<!>,
|
||||
y = <!JS_EXTERNAL_ARGUMENT!>iOrNull ?: i<!>,
|
||||
z = <!JS_EXTERNAL_ARGUMENT!>iOrNull ?: d<!>
|
||||
)
|
||||
|
||||
severalParams(
|
||||
z = <!JS_EXTERNAL_ARGUMENT!>iOrNull ?: d<!>,
|
||||
x = <!JS_EXTERNAL_ARGUMENT!>i<!>,
|
||||
y = <!JS_EXTERNAL_ARGUMENT!>iOrNull ?: i<!>
|
||||
)
|
||||
|
||||
severalParams(
|
||||
<!JS_EXTERNAL_ARGUMENT!>i<!>,
|
||||
z = <!JS_EXTERNAL_ARGUMENT!>iOrNull ?: d<!>,
|
||||
y = <!JS_EXTERNAL_ARGUMENT!>iOrNull ?: i<!>
|
||||
)
|
||||
|
||||
severalParams(
|
||||
x = <!JS_EXTERNAL_ARGUMENT!>i<!>,
|
||||
y = ExternalObject,
|
||||
z = <!JS_EXTERNAL_ARGUMENT!>iOrNull ?: d<!>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package
|
||||
|
||||
@kotlin.OptIn(markerClass = {kotlin.ExperimentalStdlibApi::class}) public fun anyExample(/*0*/ @kotlin.js.JsExternalArgument x: kotlin.Any): kotlin.Any
|
||||
@kotlin.OptIn(markerClass = {kotlin.ExperimentalStdlibApi::class}) public fun anyOrNullExample(/*0*/ @kotlin.js.JsExternalArgument x: kotlin.Any?): kotlin.Any?
|
||||
public fun boxArgExpression(/*0*/ i: Interface, /*1*/ iOrNull: Interface?): kotlin.Unit
|
||||
public fun boxDynamic(/*0*/ d: dynamic): kotlin.Unit
|
||||
public fun boxExternalClass(/*0*/ c: ExternalClass): kotlin.Unit
|
||||
public fun boxExternalInterface(/*0*/ i: ExternalInterface): kotlin.Unit
|
||||
public fun boxExternalInterfaceOrNull(/*0*/ iOrNull: ExternalInterface?): kotlin.Unit
|
||||
public fun boxExternalObject(): kotlin.Unit
|
||||
public fun boxInterface(/*0*/ c: Class): kotlin.Unit
|
||||
public fun boxInterface(/*0*/ i: Interface): kotlin.Unit
|
||||
public fun boxInterfaceOrNull(/*0*/ iOrNull: Interface?): kotlin.Unit
|
||||
public fun boxNamedArgExpression(/*0*/ i: Interface, /*1*/ iOrNull: Interface?, /*2*/ d: dynamic): kotlin.Unit
|
||||
public fun boxObject(): kotlin.Unit
|
||||
public fun boxPrimitiveTypes(): kotlin.Unit
|
||||
@kotlin.OptIn(markerClass = {kotlin.ExperimentalStdlibApi::class}) public fun dynamicExample(/*0*/ @kotlin.js.JsExternalArgument x: dynamic): dynamic
|
||||
@kotlin.OptIn(markerClass = {kotlin.ExperimentalStdlibApi::class}) public fun </*0*/ T> genericExample(/*0*/ @kotlin.js.JsExternalArgument x: T): T
|
||||
@kotlin.OptIn(markerClass = {kotlin.ExperimentalStdlibApi::class}) public fun </*0*/ T> genericOrNullExample(/*0*/ @kotlin.js.JsExternalArgument x: T?): T?
|
||||
@kotlin.OptIn(markerClass = {kotlin.ExperimentalStdlibApi::class}) public fun severalParams(/*0*/ @kotlin.js.JsExternalArgument x: kotlin.Any?, /*1*/ @kotlin.js.JsExternalArgument y: kotlin.Any?, /*2*/ @kotlin.js.JsExternalArgument z: dynamic): dynamic
|
||||
|
||||
public final class Class {
|
||||
public constructor Class()
|
||||
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public final external class ExternalClass {
|
||||
public constructor ExternalClass()
|
||||
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public external interface ExternalInterface {
|
||||
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public external object ExternalObject {
|
||||
private constructor ExternalObject()
|
||||
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public interface Interface {
|
||||
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public object Object {
|
||||
private constructor Object()
|
||||
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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
+6
@@ -48,6 +48,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes
|
||||
runTest("compiler/testData/diagnostics/testsWithJsStdLib/implementingFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("jsExternalArgument.kt")
|
||||
public void testJsExternalArgument() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithJsStdLib/jsExternalArgument.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("jsExternalInheritorsOnly.kt")
|
||||
public void testJsExternalInheritorsOnly() throws Exception {
|
||||
|
||||
@@ -44,6 +44,9 @@ object JsStandardClassIds {
|
||||
@JvmField
|
||||
val JsExternalInheritorsOnly = "JsExternalInheritorsOnly".jsId()
|
||||
|
||||
@JvmField
|
||||
val JsExternalArgument = "JsExternalArgument".jsId()
|
||||
|
||||
@JvmField
|
||||
val JsExportIgnore = JsExport.createNestedClassId(Name.identifier("Ignore"))
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ object JsPlatformConfigurator : PlatformConfiguratorBase(
|
||||
JsModuleCallChecker,
|
||||
JsDynamicCallChecker,
|
||||
JsDefinedExternallyCallChecker,
|
||||
LateinitIntrinsicApplicabilityChecker(isWarningInPre19 = true)
|
||||
LateinitIntrinsicApplicabilityChecker(isWarningInPre19 = true),
|
||||
JsExternalArgumentCallChecker
|
||||
),
|
||||
) {
|
||||
override fun configureModuleComponents(container: StorageComponentContainer) {
|
||||
|
||||
+4
@@ -114,6 +114,10 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
|
||||
Renderers.DECLARATION_NAME_WITH_KIND,
|
||||
Renderers.DECLARATION_NAME_WITH_KIND)
|
||||
|
||||
put(ErrorsJs.JS_EXTERNAL_ARGUMENT,
|
||||
"Expected argument with external type, but type {0} is non-external",
|
||||
Renderers.RENDER_TYPE)
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,9 @@ public interface ErrorsJs {
|
||||
DiagnosticFactory2<PsiElement, DeclarationDescriptor, DeclarationDescriptor> JS_EXTERNAL_INHERITORS_ONLY = DiagnosticFactory2.create(
|
||||
ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||
|
||||
DiagnosticFactory1<PsiElement, KotlinType> JS_EXTERNAL_ARGUMENT = DiagnosticFactory1.create(
|
||||
ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
Object _initializer = new Object() {
|
||||
{
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.resolve.diagnostics
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
|
||||
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.util.getParameterForArgument
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
|
||||
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
||||
|
||||
object JsExternalArgumentCallChecker : CallChecker {
|
||||
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
|
||||
for (argument in resolvedCall.call.valueArguments) {
|
||||
val parameter = resolvedCall.getParameterForArgument(argument) ?: continue
|
||||
if (AnnotationsUtils.isJsExternalArgument(parameter)) {
|
||||
val argExpression = argument.getArgumentExpression() ?: continue
|
||||
val argumentType = context.trace.bindingContext.getType(argExpression) ?: continue
|
||||
val declaration = argumentType.makeNotNullable().constructor.declarationDescriptor as? ClassDescriptor ?: continue
|
||||
if (!declaration.isEffectivelyExternal()) {
|
||||
context.trace.report(ErrorsJs.JS_EXTERNAL_ARGUMENT.on(argExpression, argumentType))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ public final class AnnotationsUtils {
|
||||
private static final FqName JS_NON_MODULE_ANNOTATION = Annotations.JsNonModule.asSingleFqName();
|
||||
private static final FqName JS_QUALIFIER_ANNOTATION = Annotations.JsQualifier.asSingleFqName();
|
||||
private static final FqName JS_EXTERNAL_INHERITORS_ONLY = Annotations.JsExternalInheritorsOnly.asSingleFqName();
|
||||
private static final FqName JS_EXTERNAL_ARGUMENT = Annotations.JsExternalArgument.asSingleFqName();
|
||||
|
||||
private AnnotationsUtils() {
|
||||
}
|
||||
@@ -263,6 +264,10 @@ public final class AnnotationsUtils {
|
||||
return declaration.getAnnotations().hasAnnotation(JS_EXTERNAL_INHERITORS_ONLY);
|
||||
}
|
||||
|
||||
public static boolean isJsExternalArgument(@NotNull ValueParameterDescriptor declaration) {
|
||||
return declaration.getAnnotations().hasAnnotation(JS_EXTERNAL_ARGUMENT);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String extractSingleStringArgument(@NotNull AnnotationDescriptor annotation) {
|
||||
if (annotation.getAllValueArguments().isEmpty()) return null;
|
||||
|
||||
+6
@@ -49,6 +49,12 @@ public class FirPsiJsOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiJ
|
||||
runTest("compiler/testData/diagnostics/testsWithJsStdLib/implementingFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("jsExternalArgument.kt")
|
||||
public void testJsExternalArgument() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithJsStdLib/jsExternalArgument.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("jsExternalInheritorsOnly.kt")
|
||||
public void testJsExternalInheritorsOnly() throws Exception {
|
||||
|
||||
@@ -250,6 +250,14 @@ public final annotation class JsExport : kotlin.Annotation {
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.ExperimentalStdlibApi
|
||||
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
|
||||
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.VALUE_PARAMETER})
|
||||
@kotlin.SinceKotlin(version = "1.9")
|
||||
public final annotation class JsExternalArgument : kotlin.Annotation {
|
||||
public constructor JsExternalArgument()
|
||||
}
|
||||
|
||||
@kotlin.ExperimentalStdlibApi
|
||||
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
|
||||
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS})
|
||||
|
||||
@@ -249,6 +249,14 @@ public final annotation class JsExport : kotlin.Annotation {
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.ExperimentalStdlibApi
|
||||
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
|
||||
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.VALUE_PARAMETER})
|
||||
@kotlin.SinceKotlin(version = "1.9")
|
||||
public final annotation class JsExternalArgument : kotlin.Annotation {
|
||||
public constructor JsExternalArgument()
|
||||
}
|
||||
|
||||
@kotlin.ExperimentalStdlibApi
|
||||
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
|
||||
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS})
|
||||
|
||||
@@ -252,3 +252,41 @@ public annotation class EagerInitialization
|
||||
@Target(CLASS)
|
||||
@SinceKotlin("1.9")
|
||||
public annotation class JsExternalInheritorsOnly
|
||||
|
||||
|
||||
/**
|
||||
* When placed on a function parameter, requires the type of the passed argument to be external.
|
||||
*
|
||||
* The compiler mangles identifiers of properties from non-external interfaces and classes,
|
||||
* and doesn't mangle from external ones. Requiring a type of the passing argument being external is necessary
|
||||
* to avoid non-obvious bugs when identifier has an unstable and unpredictable name in generated JS code.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
*
|
||||
* @OptIn(ExperimentalStdlibApi::class)
|
||||
* fun extractUuid(@JsExternalArgument x: dynamic) = x.uuid as String
|
||||
*
|
||||
* external interface User {
|
||||
* val uuid: String
|
||||
* }
|
||||
*
|
||||
* interface Owner {
|
||||
* val uuid: String
|
||||
* }
|
||||
*
|
||||
* fun checkUser(user: User, owner: Owner): Boolean {
|
||||
* val userUuid = extractUuid(user) // OK
|
||||
* val ownerUuid = extractUuid(owner) // Compilation error! Possible bug
|
||||
* return userUuid == ownerUuid
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This annotation is experimental, meaning that the restrictions mentioned above are subject to change.
|
||||
*/
|
||||
@ExperimentalStdlibApi
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(VALUE_PARAMETER)
|
||||
@SinceKotlin("1.9")
|
||||
public annotation class JsExternalArgument
|
||||
|
||||
Reference in New Issue
Block a user