KT-2752: declare properties as a pair of functions when accessors are marked with @JsName

This commit is contained in:
Alexey Andreev
2016-06-02 15:01:47 +03:00
parent 6e1a0901ac
commit be0013d9f2
17 changed files with 322 additions and 39 deletions
@@ -0,0 +1,7 @@
package foo
class A {
var x: Int
<!JS_NAME_CLASH!>@JsName("xx") get() = 0<!>
<!JS_NAME_CLASH!>@JsName("xx") set(value) {}<!>
}
@@ -0,0 +1,12 @@
package
package foo {
public final class A {
public constructor A()
public final var x: kotlin.Int
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
}
}
@@ -331,6 +331,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes
doTest(fileName);
}
@TestMetadata("jsNameOnAccessors.kt")
public void testJsNameOnAccessors() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/jsNameOnAccessors.kt");
doTest(fileName);
}
@TestMetadata("jsNameOnPropertyAndAccessor.kt")
public void testJsNameOnPropertyAndAccessor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/jsNameOnPropertyAndAccessor.kt");
@@ -36,6 +36,7 @@ object JsNameChecker : SimpleDeclarationChecker {
}
}
if (AnnotationsUtils.getJsName(descriptor) == null) return
val jsNamePsi = AnnotationsUtils.getJsNameAnnotationPsi(bindingContext, declaration) ?: return
when (descriptor) {
@@ -45,7 +46,7 @@ object JsNameChecker : SimpleDeclarationChecker {
}
}
is PropertyAccessorDescriptor -> {
if (!descriptor.isDefault && AnnotationsUtils.getJsName(descriptor.correspondingProperty) != null) {
if (AnnotationsUtils.getJsName(descriptor.correspondingProperty) != null) {
diagnosticHolder.report(ErrorsJs.JS_NAME_ON_ACCESSOR_AND_PROPERTY.on(jsNamePsi))
}
}
@@ -97,15 +97,17 @@ class JsNameClashChecker : SimpleDeclarationChecker {
}
private fun collect(descriptor: DeclarationDescriptor, target: MutableMap<String, DeclarationDescriptor>) {
if (descriptor is PropertyDescriptor && descriptor.isExtension) {
descriptor.accessors.forEach { collect(it, target) }
}
else {
val fqn = fqnGenerator.generate(descriptor)
if (fqn.shared && isOpaque(fqn.descriptor)) {
target[fqn.names.last()] = fqn.descriptor
if (descriptor is PropertyDescriptor) {
if (descriptor.isExtension || AnnotationsUtils.hasJsNameInAccessors(descriptor)) {
descriptor.accessors.forEach { collect(it, target) }
return
}
}
val fqn = fqnGenerator.generate(descriptor)
if (fqn.shared && isOpaque(fqn.descriptor)) {
target[fqn.names.last()] = fqn.descriptor
}
}
private fun isOpaque(descriptor: DeclarationDescriptor) =
@@ -20,10 +20,7 @@ import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.js.PredefinedAnnotation;
import org.jetbrains.kotlin.name.FqName;
@@ -143,6 +140,12 @@ public final class AnnotationsUtils {
AnnotationDescriptor annotation = getAnnotationByName(descriptor, new FqName(JS_NAME));
if (annotation == null) return null;
if (descriptor instanceof PropertyAccessorDescriptor) {
PropertyAccessorDescriptor accessor = (PropertyAccessorDescriptor) descriptor;
AnnotationDescriptor propertyAnnotation = getAnnotationByName(accessor.getCorrespondingProperty(), new FqName(JS_NAME));
if (propertyAnnotation == annotation) return null;
}
ConstantValue<?> value = annotation.getAllValueArguments().values().iterator().next();
assert value != null : "JsName annotation should always declare string parameter";
@@ -202,4 +205,11 @@ public final class AnnotationsUtils {
ClassDescriptor containingClass = DescriptorUtils.getContainingClass(descriptor);
return containingClass != null && hasAnnotationOrInsideAnnotatedClass(containingClass, fqName);
}
public static boolean hasJsNameInAccessors(@NotNull PropertyDescriptor property) {
for (PropertyAccessorDescriptor accessor : property.getAccessors()) {
if (getJsName(accessor) != null) return true;
}
return false;
}
}
@@ -25,12 +25,11 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.psi.KtSuperExpression
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
@@ -67,7 +66,7 @@ fun VariableAccessInfo.isGetAccess(): Boolean = value == null
fun VariableAccessInfo.getAccessDescriptor(): DeclarationDescriptor {
val descriptor = variableDescriptor
if (descriptor is PropertyDescriptor && descriptor.isExtension) {
if (descriptor is PropertyDescriptor && (descriptor.isExtension || TranslationUtils.shouldGenerateAccessors(descriptor))) {
val propertyAccessorDescriptor = if (isGetAccess()) descriptor.getter else descriptor.setter
return propertyAccessorDescriptor!!
}
@@ -24,7 +24,9 @@ import com.google.dart.compiler.backend.js.ast.metadata.sideEffects
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.Namer.getCapturedVarAccessor
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.fqnWithoutSideEffects
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.resolve.BindingContextUtils.isVarCapturedInClosure
@@ -37,16 +39,36 @@ object NativeVariableAccessCase : VariableAccessCase() {
}
override fun VariableAccessInfo.dispatchReceiver(): JsExpression {
return constructAccessExpression(JsNameRef(variableName, dispatchReceiver!!))
val descriptor = resolvedCall.resultingDescriptor
return if (descriptor is PropertyDescriptor && TranslationUtils.shouldGenerateAccessors(descriptor)) {
val methodRef = context.getNameForDescriptor(getAccessDescriptor())
JsInvocation(fqnWithoutSideEffects(methodRef, dispatchReceiver!!), *additionalArguments.toTypedArray())
}
else {
constructAccessExpression(JsNameRef(variableName, dispatchReceiver!!))
}
}
override fun VariableAccessInfo.noReceivers(): JsExpression {
return constructAccessExpression(variableName.makeRef())
val descriptor = resolvedCall.resultingDescriptor
return if (descriptor is PropertyDescriptor && TranslationUtils.shouldGenerateAccessors(descriptor)) {
val methodRef = context.aliasOrValue(callableDescriptor) { context.getQualifiedReference(getAccessDescriptor()) }
JsInvocation(methodRef, *additionalArguments.toTypedArray())
}
else {
constructAccessExpression(variableName.makeRef())
}
}
}
object DefaultVariableAccessCase : VariableAccessCase() {
override fun VariableAccessInfo.noReceivers(): JsExpression {
val descriptor = resolvedCall.resultingDescriptor
if (descriptor is PropertyDescriptor && TranslationUtils.shouldGenerateAccessors(descriptor)) {
val methodRef = context.aliasOrValue(callableDescriptor) { context.getQualifiedReference(getAccessDescriptor()) }
return JsInvocation(methodRef, *additionalArguments.toTypedArray())
}
val functionRef = context.aliasOrValue(callableDescriptor) {
context.getQualifiedReference(variableDescriptor)
}
@@ -73,30 +95,28 @@ object DefaultVariableAccessCase : VariableAccessCase() {
}
override fun VariableAccessInfo.dispatchReceiver(): JsExpression {
val accessor = JsNameRef(variableName, dispatchReceiver!!)
val descriptor = callableDescriptor
if (descriptor is PropertyDescriptor && !JsDescriptorUtils.sideEffectsPossibleOnRead(descriptor)) {
accessor.sideEffects = SideEffectKind.DEPENDS_ON_STATE
val descriptor = resolvedCall.resultingDescriptor
return if (descriptor is PropertyDescriptor && TranslationUtils.shouldGenerateAccessors(descriptor)) {
val callExpr = pureFqn(context.getNameForDescriptor(getAccessDescriptor()), dispatchReceiver!!)
JsInvocation(callExpr, *additionalArguments.toTypedArray())
}
else {
val accessor = JsNameRef(variableName, dispatchReceiver!!)
if (descriptor is PropertyDescriptor && !JsDescriptorUtils.sideEffectsPossibleOnRead(descriptor)) {
accessor.sideEffects = SideEffectKind.DEPENDS_ON_STATE
}
constructAccessExpression(accessor)
}
return constructAccessExpression(accessor)
}
override fun VariableAccessInfo.extensionReceiver(): JsExpression {
val functionRef = context.aliasOrValue(callableDescriptor) { context.getQualifiedReference(getAccessDescriptor()) }
return if (isGetAccess()) {
JsInvocation(functionRef, extensionReceiver!!)
} else {
JsInvocation(functionRef, extensionReceiver!!, value!!)
}
return JsInvocation(functionRef, extensionReceiver!!, *additionalArguments.toTypedArray())
}
override fun VariableAccessInfo.bothReceivers(): JsExpression {
val funRef = JsNameRef(context.getNameForDescriptor(getAccessDescriptor()), dispatchReceiver!!)
return if (isGetAccess()) {
JsInvocation(funRef, extensionReceiver!!)
} else {
JsInvocation(funRef, extensionReceiver!!, value!!)
}
return JsInvocation(funRef, extensionReceiver!!, *additionalArguments.toTypedArray())
}
}
@@ -128,14 +148,25 @@ object DelegatePropertyAccessIntrinsic : DelegateIntrinsic<VariableAccessInfo> {
object SuperPropertyAccessCase : VariableAccessCase() {
override fun VariableAccessInfo.dispatchReceiver(): JsExpression {
val variableName = context.program().getStringLiteral(this.variableName.ident)
val descriptor = resolvedCall.resultingDescriptor
return if (isGetAccess())
JsInvocation(context.namer().callGetProperty, dispatchReceiver!!, calleeOwner, variableName)
else
JsInvocation(context.namer().callSetProperty, dispatchReceiver!!, calleeOwner, variableName, value!!)
return if (descriptor is PropertyDescriptor && TranslationUtils.shouldGenerateAccessors(descriptor)) {
val accessor = getAccessDescriptor()
val prototype = fqnWithoutSideEffects(Namer.getPrototypeName(), context.getQualifiedReference(descriptor.containingDeclaration))
val funRef = Namer.getFunctionCallRef(fqnWithoutSideEffects(context.getNameForDescriptor(accessor), prototype))
val arguments = listOf(dispatchReceiver!!) + additionalArguments
JsInvocation(funRef, *arguments.toTypedArray())
}
else {
val callExpr = if (isGetAccess()) context.namer().callGetProperty else context.namer().callSetProperty
val arguments = listOf(dispatchReceiver!!, calleeOwner, variableName) + additionalArguments
JsInvocation(callExpr, *arguments.toTypedArray())
}
}
}
private val VariableAccessInfo.additionalArguments: List<JsExpression> get() = value?.let { listOf(it) }.orEmpty()
fun VariableAccessInfo.translateVariableAccess(): JsExpression {
val intrinsic = DelegatePropertyAccessIntrinsic.intrinsic(this)
@@ -236,7 +236,7 @@ public final class StaticContext {
expression = Namer.kotlinObject();
partNames = getNameForFQNPart(part);
}
else if (isNativeObject(part.getDescriptor()) && !isNativeObject(part.getScope())) {
else if (isNative(part.getDescriptor()) && !isNativeObject(part.getScope())) {
expression = null;
partNames = getNameForFQNPart(part);
}
@@ -258,6 +258,17 @@ public final class StaticContext {
}
}
private static boolean isNative(DeclarationDescriptor descriptor) {
if (isNativeObject(descriptor)) return true;
if (descriptor instanceof PropertyAccessorDescriptor) {
PropertyAccessorDescriptor accessor = (PropertyAccessorDescriptor) descriptor;
return isNative(accessor.getCorrespondingProperty());
}
return false;
}
@NotNull
public JsNameRef getQualifiedReference(@NotNull FqName packageFqName) {
JsName packageName = getNameForPackage(packageFqName);
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.js.translate.general.AbstractTranslator
import org.jetbrains.kotlin.js.translate.general.Translation
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.assignmentToBackingField
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.backingFieldReference
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.translateFunctionAsEcma5PropertyDescriptor
@@ -72,7 +73,7 @@ fun MutableList<JsPropertyInitializer>.addGetterAndSetter(
generateSetter: () -> JsPropertyInitializer
) {
val to: MutableList<JsPropertyInitializer>
if (!descriptor.isExtension) {
if (!descriptor.isExtension && !TranslationUtils.shouldGenerateAccessors(descriptor)) {
to = SmartList<JsPropertyInitializer>()
this.add(JsPropertyInitializer(context.getNameForDescriptor(descriptor).makeRef(), JsObjectLiteral(to, true)))
}
@@ -54,7 +54,10 @@ public final class TranslationUtils {
public static JsPropertyInitializer translateFunctionAsEcma5PropertyDescriptor(@NotNull JsFunction function,
@NotNull FunctionDescriptor descriptor,
@NotNull TranslationContext context) {
if (DescriptorUtils.isExtension(descriptor)) {
if (DescriptorUtils.isExtension(descriptor) ||
descriptor instanceof PropertyAccessorDescriptor &&
shouldGenerateAccessors(((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty())
) {
return translateExtensionFunctionAsEcma5DataDescriptor(function, descriptor, context);
}
else {
@@ -299,4 +302,24 @@ public final class TranslationUtils {
DeclarationDescriptor descriptor = context.bindingContext().get(BindingContext.REFERENCE_TARGET, ((KtSimpleNameExpression) expression));
return !(descriptor instanceof LocalVariableDescriptor) || !((LocalVariableDescriptor) descriptor).isDelegated();
}
public static boolean shouldGenerateAccessors(@NotNull CallableDescriptor descriptor) {
if (descriptor instanceof PropertyDescriptor) {
return shouldGenerateAccessors((PropertyDescriptor) descriptor);
}
else if (descriptor instanceof PropertyAccessorDescriptor) {
return shouldGenerateAccessors(((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty());
}
else {
return false;
}
}
private static boolean shouldGenerateAccessors(@NotNull PropertyDescriptor property) {
if (AnnotationsUtils.hasJsNameInAccessors(property)) return true;
for (PropertyDescriptor overriddenProperty : property.getOverriddenDescriptors()) {
if (shouldGenerateAccessors(overriddenProperty)) return true;
}
return false;
}
}
@@ -0,0 +1,21 @@
package foo
import kotlin.reflect.KProperty
class A {
@JsName("xx") val x: Int by B()
}
class B {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = 23
}
fun box(): String {
val a = A()
assertEquals(23, a.x)
val d: dynamic = a
assertEquals(23, d.xx)
return "OK"
}
@@ -0,0 +1,29 @@
package foo
@native class A {
val xx: Int
@JsName("getX") get
var yy: Int
@JsName("getY") get
@JsName("setY") set
}
@native var zz: Int
@JsName("getZ") get
@JsName("setZ") set
fun box(): String {
val a = A()
assertEquals(23, a.xx)
assertEquals(0, a.yy)
a.yy = 42
assertEquals(42, a.yy)
assertEquals(32, zz)
zz = 232
assertEquals(232, zz)
return "OK"
}
@@ -0,0 +1,20 @@
function A() {
this.y_ = 0;
}
A.prototype.getX = function() {
return 23;
};
A.prototype.getY = function() {
return this.y_;
};
A.prototype.setY = function(y) {
return this.y_ = y;
};
var z_ = 32;
function getZ() {
return z_;
}
function setZ(z) {
z_ = z;
}
@@ -0,0 +1,37 @@
package foo
class A {
val x: Int
@JsName("getX_") get() = 23
var y: Int = 0
@JsName("getY_") get() = field + 10
@JsName("setY_") set(value) {
field = value
}
}
val A.z: Int
@JsName("getZ_") get() = 42
fun getPackage() = js("return Kotlin.modules.JS_TESTS.foo")
fun box(): String {
val a = A()
assertEquals(23, a.x)
assertEquals(10, a.y)
a.y = 13
assertEquals(23, a.y)
assertEquals(42, a.z)
val d: dynamic = A()
assertEquals(23, d.getX_())
assertEquals(10, d.getY_())
d.setY_(13)
assertEquals(23, d.getY_())
assertEquals(42, getPackage().getZ_(d))
return "OK"
}
@@ -0,0 +1,30 @@
package foo
val x: Int
@JsName("getX_") get() = 23
var y: Int = 0
@JsName("getY_") get() = field + 10
@JsName("setY_") set(value) {
field = value
}
fun getPackage() = js("return Kotlin.modules.JS_TESTS.foo")
fun box(): String {
assertEquals(23, x)
assertEquals(10, y)
y = 13
assertEquals(23, y)
y = 0
val d = getPackage()
assertEquals(23, d.getX_())
assertEquals(10, d.getY_())
d.setY_(13)
assertEquals(23, d.getY_())
return "OK"
}
@@ -0,0 +1,43 @@
package foo
open class A {
open val x: Int
@JsName("getX_") get() = 23
open var y: Int = 0
@JsName("getY_") get() = field + 10
@JsName("setY_") set(value) {
field = value
}
}
class B : A() {
override val x: Int
get() = 42
override var y: Int
get() = super.y + 5
set(value) {
super.y = value + 2
}
}
fun getPackage() = js("return Kotlin.modules.JS_TESTS.foo")
fun box(): String {
val a = B()
assertEquals(42, a.x)
assertEquals(15, a.y)
a.y = 13
assertEquals(30, a.y)
val d: dynamic = B()
assertEquals(42, d.getX_())
assertEquals(15, d.getY_())
d.setY_(13)
assertEquals(30, d.getY_())
return "OK"
}