From 94c93ef31ba663beb37139224cc01b5d6f7d7912 Mon Sep 17 00:00:00 2001 From: Erokhin Stanislav Date: Fri, 4 Oct 2013 15:41:07 +0400 Subject: [PATCH] JS backend: fix override property --- .../test/semantics/PropertyOverrideTest.java | 54 ++++++++++ .../k2js/translate/context/Namer.java | 17 ++++ .../k2js/translate/context/StaticContext.java | 4 + .../declaration/PropertyTranslator.java | 2 +- .../translate/reference/CallTranslator.java | 27 ++++- .../translate/utils/JsDescriptorUtils.java | 5 +- .../translate/utils/TranslationUtils.java | 20 +++- .../cases/overrideValWithDefaultValue.kt | 2 +- .../testFiles/kotlin_lib_ecma5.js | 8 ++ .../cases/checkSupertypeOrder.kt | 50 ++++++++++ .../cases/overloadPrivateVal.kt | 28 ++++++ .../cases/overrideExtensionProperty.kt | 25 +++++ .../cases/overrideNotDirectlySuper.kt | 35 +++++++ .../cases/overrideValFromTraits.kt | 99 +++++++++++++++++++ .../cases/overrideValWithBackendFiled.kt | 25 +++++ .../propertyOverride/cases/simpleOverride.kt | 33 +++++++ 16 files changed, 424 insertions(+), 10 deletions(-) create mode 100644 js/js.tests/test/org/jetbrains/k2js/test/semantics/PropertyOverrideTest.java create mode 100644 js/js.translator/testFiles/propertyOverride/cases/checkSupertypeOrder.kt create mode 100644 js/js.translator/testFiles/propertyOverride/cases/overloadPrivateVal.kt create mode 100644 js/js.translator/testFiles/propertyOverride/cases/overrideExtensionProperty.kt create mode 100644 js/js.translator/testFiles/propertyOverride/cases/overrideNotDirectlySuper.kt create mode 100644 js/js.translator/testFiles/propertyOverride/cases/overrideValFromTraits.kt create mode 100644 js/js.translator/testFiles/propertyOverride/cases/overrideValWithBackendFiled.kt create mode 100644 js/js.translator/testFiles/propertyOverride/cases/simpleOverride.kt diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/PropertyOverrideTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/PropertyOverrideTest.java new file mode 100644 index 00000000000..f3b93d8a0e5 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/PropertyOverrideTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2010-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.test.semantics; + +import org.jetbrains.k2js.test.SingleFileTranslationTest; + +public class PropertyOverrideTest extends SingleFileTranslationTest { + + public PropertyOverrideTest() { + super("propertyOverride/"); + } + + public void testOverrideValWithBackendFiled() throws Exception { + checkFooBoxIsOk(); + } + + public void testSimpleOverride() throws Exception { + checkFooBoxIsOk(); + } + + public void testOverloadPrivateVal() throws Exception { + checkFooBoxIsOk(); + } + + public void testOverrideValFromTraits() throws Exception { + checkFooBoxIsOk(); + } + + public void testCheckSupertypeOrder() throws Exception { + checkFooBoxIsOk(); + } + + public void testOverrideNotDirectlySuper() throws Exception { + checkFooBoxIsOk(); + } + + public void testOverrideExtensionProperty() throws Exception { + checkFooBoxIsOk(); + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java index 685ac05f878..6b2659d9af9 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/Namer.java @@ -167,6 +167,10 @@ public final class Namer { private final JsName enumEntriesName; @NotNull private final JsExpression undefinedExpression; + @NotNull + private final JsExpression callGetProperty; + @NotNull + private final JsExpression callSetProperty; @NotNull private final JsName isTypeName; @@ -179,6 +183,9 @@ public final class Namer { definePackage = kotlin("definePackage"); defineRootPackage = kotlin("defineRootPackage"); + callGetProperty = kotlin("callGetter"); + callSetProperty = kotlin("callSetter"); + className = kotlinScope.declareName(CLASS_OBJECT_NAME); enumEntriesName = kotlinScope.declareName(ENUM_ENTRIES_NAME); objectName = kotlinScope.declareName(OBJECT_OBJECT_NAME); @@ -288,4 +295,14 @@ public final class Namer { public JsExpression getUndefinedExpression() { return undefinedExpression; } + + @NotNull + public JsExpression getCallGetProperty() { + return callGetProperty; + } + + @NotNull + public JsExpression getCallSetProperty() { + return callSetProperty; + } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/context/StaticContext.java b/js/js.translator/src/org/jetbrains/k2js/translate/context/StaticContext.java index 8ead4fb4efc..302ab018a9e 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/context/StaticContext.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/context/StaticContext.java @@ -44,6 +44,7 @@ import java.util.Map; import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.*; import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*; +import static org.jetbrains.k2js.translate.utils.TranslationUtils.getMangledName; /** * Aggregates all the static parts of the context. @@ -300,6 +301,9 @@ public final class StaticContext { String propertyName = propertyDescriptor.getName().asString(); if (!isExtension(propertyDescriptor)) { + if (propertyDescriptor.getVisibility() == Visibilities.PRIVATE) { + propertyName = getMangledName(propertyDescriptor, propertyName); + } return declarePropertyOrPropertyAccessorName(descriptor, propertyName, false); } else { if (descriptor instanceof PropertyDescriptor) { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/PropertyTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/PropertyTranslator.java index b1a8eca7684..0e983c5ffba 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/declaration/PropertyTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/declaration/PropertyTranslator.java @@ -58,7 +58,7 @@ public final class PropertyTranslator extends AbstractTranslator { @Nullable JetProperty declaration, @NotNull List result, @NotNull TranslationContext context) { - if (!JsDescriptorUtils.isSimpleProperty(descriptor)) { + if (!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) { new PropertyTranslator(descriptor, declaration, context).translate(result); } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java index 2f061c871bb..d3fac298abd 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java @@ -186,8 +186,12 @@ public final class CallTranslator extends AbstractTranslator { return callType.constructCall(receiver, new CallType.CallConstructor() { @NotNull @Override - public JsExpression construct(@Nullable JsExpression receiver) { // TODO: support super val access - assert receiver != null; + public JsExpression construct(@Nullable JsExpression receiver) { + assert receiver != null : "Receiver for superCall must be not null"; + if (isDirectPropertyAccess()) { + return superPropertyAccess(receiver, getPropertyName()); + } + JsExpression qualifiedCallee = getQualifiedCallee(Namer.getRefToPrototype(receiver)); qualifiedCallee = Namer.getFunctionCallRef(qualifiedCallee); List arguments = new ArrayList(CallTranslator.this.arguments.size() + 1); @@ -198,6 +202,25 @@ public final class CallTranslator extends AbstractTranslator { }, context()); } + @NotNull + private JsStringLiteral getPropertyName() { + assert descriptor instanceof PropertyAccessorDescriptor; + String propertyName = ((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty().getName().asString(); + return context().program().getStringLiteral(propertyName); + } + + @NotNull + private JsExpression superPropertyAccess(@NotNull JsExpression classRef, @NotNull JsStringLiteral propertyName) { + if (descriptor instanceof PropertyGetterDescriptor) { + assert arguments.isEmpty(); + return new JsInvocation(context().namer().getCallGetProperty(), JsLiteral.THIS, classRef, propertyName); + } else { + assert descriptor instanceof PropertySetterDescriptor; + assert arguments.size() == 1; + return new JsInvocation(context().namer().getCallSetProperty(), JsLiteral.THIS, classRef, propertyName, arguments.get(0)); + } + } + @NotNull private JsExpression methodCall(@Nullable JsExpression receiver) { return callType.constructCall(receiver, new CallType.CallConstructor() { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsDescriptorUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsDescriptorUtils.java index 9535521c5f4..9cb6da19221 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsDescriptorUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsDescriptorUtils.java @@ -136,10 +136,11 @@ public final class JsDescriptorUtils { return accessorDescriptor == null || accessorDescriptor.isDefault(); } - public static boolean isSimpleProperty(@NotNull PropertyDescriptor propertyDescriptor) { + public static boolean isSimpleFinalProperty(@NotNull PropertyDescriptor propertyDescriptor) { return !isExtension(propertyDescriptor) && isDefaultAccessor(propertyDescriptor.getGetter()) && - isDefaultAccessor(propertyDescriptor.getSetter()); + isDefaultAccessor(propertyDescriptor.getSetter()) && + !propertyDescriptor.getModality().isOverridable(); } public static boolean isStandardDeclaration(@NotNull DeclarationDescriptor descriptor) { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java index 15b665240db..0550aba5484 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/TranslationUtils.java @@ -23,6 +23,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; import org.jetbrains.jet.lang.descriptors.PropertyDescriptor; import org.jetbrains.jet.lang.descriptors.PropertyGetterDescriptor; +import org.jetbrains.jet.lang.descriptors.Visibilities; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.k2js.translate.context.TemporaryConstVariable; import org.jetbrains.k2js.translate.context.TranslationContext; @@ -33,6 +34,7 @@ import java.util.Collections; import java.util.List; import static com.google.dart.compiler.backend.js.ast.JsBinaryOperator.*; +import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFQName; import static org.jetbrains.k2js.translate.context.Namer.getKotlinBackingFieldName; import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptorForOperationExpression; import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment; @@ -129,14 +131,24 @@ public final class TranslationUtils { return new JsConditional(testExpression, thenExpression, elseExpression); } + @NotNull + public static String getMangledName(@NotNull PropertyDescriptor descriptor, @NotNull String suggestedName) { + int absHashCode = Math.abs(getFQName(descriptor).asString().hashCode()); + return suggestedName + "_" + Integer.toString(absHashCode, Character.MAX_RADIX) + "$"; + } + @NotNull public static JsNameRef backingFieldReference(@NotNull TranslationContext context, @NotNull PropertyDescriptor descriptor) { JsName backingFieldName = context.getNameForDescriptor(descriptor); - if(!JsDescriptorUtils.isSimpleProperty(descriptor)) { - backingFieldName = context.declarePropertyOrPropertyAccessorName(descriptor, - getKotlinBackingFieldName(backingFieldName.getIdent()), - false); + if(!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) { + String backingFieldMangledName; + if (descriptor.getVisibility() != Visibilities.PRIVATE) { + backingFieldMangledName = getMangledName(descriptor, getKotlinBackingFieldName(backingFieldName.getIdent())); + } else { + backingFieldMangledName = getKotlinBackingFieldName(backingFieldName.getIdent()); + } + backingFieldName = context.declarePropertyOrPropertyAccessorName(descriptor, backingFieldMangledName, false); } return new JsNameRef(backingFieldName, JsLiteral.THIS); } diff --git a/js/js.translator/testFiles/defaultArguments/cases/overrideValWithDefaultValue.kt b/js/js.translator/testFiles/defaultArguments/cases/overrideValWithDefaultValue.kt index 852b2ff735d..f2a0255d17d 100644 --- a/js/js.translator/testFiles/defaultArguments/cases/overrideValWithDefaultValue.kt +++ b/js/js.translator/testFiles/defaultArguments/cases/overrideValWithDefaultValue.kt @@ -1,7 +1,7 @@ package foo open class A(open val bar: Int = 2) { - val barA = bar + val barA = $bar } class B(override val bar: Int = 3) : A() diff --git a/js/js.translator/testFiles/kotlin_lib_ecma5.js b/js/js.translator/testFiles/kotlin_lib_ecma5.js index f21b976e023..7990b897963 100644 --- a/js/js.translator/testFiles/kotlin_lib_ecma5.js +++ b/js/js.translator/testFiles/kotlin_lib_ecma5.js @@ -236,6 +236,14 @@ var Kotlin = {}; return obj; }; + Kotlin.callGetter = function(thisObject, klass, propertyName) { + return klass.$metadata$.properties[propertyName].get.call(thisObject); + }; + + Kotlin.callSetter = function(thisObject, klass, propertyName, value) { + klass.$metadata$.properties[propertyName].set.call(thisObject, value); + }; + function isInheritanceFromTrait (objConstructor, trait) { if (isNativeClass(objConstructor) || objConstructor.$metadata$.classIndex < trait.$metadata$.classIndex) { return false; diff --git a/js/js.translator/testFiles/propertyOverride/cases/checkSupertypeOrder.kt b/js/js.translator/testFiles/propertyOverride/cases/checkSupertypeOrder.kt new file mode 100644 index 00000000000..b6707504a3c --- /dev/null +++ b/js/js.translator/testFiles/propertyOverride/cases/checkSupertypeOrder.kt @@ -0,0 +1,50 @@ +package foo + +trait A { + val bal: Int + get() { + return 42 + } +} + +open class B { + open val bal = 239 +} + +class C1: B(), A { + override val bal: Int = 14 + + fun getBalA(): Int { + return super.bal + } + + fun getBalB(): Int { + return super.bal + } +} + +class C2: A, B() { + override val bal: Int = 14 + + fun getBalA(): Int { + return super.bal + } + + fun getBalB(): Int { + return super.bal + } +} + +fun box(): String { + val c1 = C1(); + if (c1.bal != 14) return "c1.bal != 14, it: ${c1.bal}" + if (c1.getBalA() != 42) return "c1.getBalA() != 42, it: ${c1.getBalA()}" + if (c1.getBalB() != 239) return "c1.getBalB() != 239, it: ${c1.getBalB()}" + + val c2 = C2(); + if (c2.bal != 14) return "c2.bal != 14, it: ${c2.bal}" + if (c2.getBalA() != 42) return "c2.getBalA() != 42, it: ${c2.getBalA()}" + if (c2.getBalB() != 239) return "c2.getBalB() != 239, it: ${c2.getBalB()}" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/propertyOverride/cases/overloadPrivateVal.kt b/js/js.translator/testFiles/propertyOverride/cases/overloadPrivateVal.kt new file mode 100644 index 00000000000..52179767ca2 --- /dev/null +++ b/js/js.translator/testFiles/propertyOverride/cases/overloadPrivateVal.kt @@ -0,0 +1,28 @@ +package foo + +open class A { + private val a = 1 + + private val b = 2 + get() { + return $b + 10 + 100 * a + } + fun getBInA(): Int { + return b + } +} + +class B : A() { + val a = 13 + val b = 42 +} + + +fun box(): String { + val b = B() + if (b.getBInA() != 112) return "b.getBInA() != 112, it: ${b.getBInA()}" + + if (b.a != 13) return "b.a != 13, it: ${b.a}" + if (b.b != 42) return "b.b != 42, it: ${b.b}" + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/propertyOverride/cases/overrideExtensionProperty.kt b/js/js.translator/testFiles/propertyOverride/cases/overrideExtensionProperty.kt new file mode 100644 index 00000000000..ad84649f9e7 --- /dev/null +++ b/js/js.translator/testFiles/propertyOverride/cases/overrideExtensionProperty.kt @@ -0,0 +1,25 @@ +package foo + +class T + +open class A { + open val T.foo: Int = 34 + get() { + return $foo + } + fun test(): Int { + return T().foo + } +} + +class B : A() { + override val T.foo: Int = 5 +} + + +fun box(): String { + if (A().test() != 34) return "A().test() != 34, it: ${A().test() != 34}" + if (B().test() != 5) return "B().test() != 5, it: ${B().test() != 5}" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/propertyOverride/cases/overrideNotDirectlySuper.kt b/js/js.translator/testFiles/propertyOverride/cases/overrideNotDirectlySuper.kt new file mode 100644 index 00000000000..b0c82cac5fb --- /dev/null +++ b/js/js.translator/testFiles/propertyOverride/cases/overrideNotDirectlySuper.kt @@ -0,0 +1,35 @@ +package foo + +open class A { + open var a = 1 + fun getAInA(): Int = a +} + +open class AA: A() + +class B : AA() { + override var a = 2 + + fun getSuperA(): Int { + return super.a + } + + fun setSuperA(value: Int) { + super.a = value + } +} + +fun box(): String { + val a = A() + val b = B() + if (a.getAInA() != 1) return "a.getAInA() != 1, it: ${a.getAInA()}" + if (b.getAInA() != 2) return "b.getAInA() != 2, it: ${b.getAInA()}" + + if (b.getSuperA() != 1) return "b.getSuperA() != 1, it: ${b.getSuperA()}" + b.setSuperA(3) + if (b.getSuperA() != 3) return "b.getSuperA() != 3, it: ${b.getSuperA()}" + + if (b.getAInA() != 2) return "b.getAInA() != 2 after b.setAInB(3), it: ${b.getAInA()}" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/propertyOverride/cases/overrideValFromTraits.kt b/js/js.translator/testFiles/propertyOverride/cases/overrideValFromTraits.kt new file mode 100644 index 00000000000..f97f46b56c9 --- /dev/null +++ b/js/js.translator/testFiles/propertyOverride/cases/overrideValFromTraits.kt @@ -0,0 +1,99 @@ +package foo + +trait A { + val bal: Int + get() { + return 5 + } + var bar: Int + get() { + return realBar + } + set(value: Int) { + realBar = value + } + + var realBar: Int +} + + +trait B { + val bal: Int + get() { + return 42 + } + var bar: Int + get() { + return realBar + 1000 + } + set(value: Int) { + realBar = value + 1000 + } + + var realBar: Int +} + +class C: A, B { + override val bal: Int = 1 + override var bar: Int = 2 + get() { + return $bar + 20 + } + set(value: Int) { + $bar = value + 20 + } + override var realBar: Int = 3 + + fun supBalA(): Int { + return super.bal + } + + fun supBalB(): Int { + return super.bal + } + + fun supBarA(): Int { + return super.bar + } + + fun supBarB(): Int { + return super.bar + } + + fun setBarA(value: Int) { + super.bar = value + } + + fun setBarB(value: Int) { + super.bar = value + } +} + +fun box(): String { + val c = C() + + if (c.bal != 1) return "c.bal != 1, it: ${c.bal}" + if (c.bar != 22) return "c.bar != 22, it: ${c.bar}" + if (c.realBar != 3) return "c.realBar != 3, it: ${c.realBar}" + + c.bar = 50 + if (c.bar != 90) return "c.bar != 90, it: ${c.bar}" + + if (c.supBalA() != 5) return "c.supBalA() != 5, it: ${c.supBalA()}" + if (c.supBalB() != 42) return "c.supBalB() != 42, it: ${c.supBalB()}" + + if (c.supBarA() != 3) return "c.supBarA() != 3, it: ${c.supBarA()}" + if (c.supBarB() != 1003) return "c.supBarB() != 1003, it: ${c.supBarB()}" + + c.setBarA(239) + if (c.realBar != 239) return "c.realBar != 239, it: ${c.realBar}" + if (c.supBarA() != 239) return "c.supBarA() != 239, it: ${c.supBarA()}" + if (c.supBarB() != 1239) return "c.supBarB() != 1239, it: ${c.supBarB()}" + + c.setBarB(239) + if (c.realBar != 1239) return "c.realBar != 1239, it: ${c.realBar}" + if (c.supBarA() != 1239) return "c.supBarA() != 1239, it: ${c.supBarA()}" + if (c.supBarB() != 2239) return "c.supBarB() != 2239, it: ${c.supBarB()}" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/propertyOverride/cases/overrideValWithBackendFiled.kt b/js/js.translator/testFiles/propertyOverride/cases/overrideValWithBackendFiled.kt new file mode 100644 index 00000000000..24d012f3438 --- /dev/null +++ b/js/js.translator/testFiles/propertyOverride/cases/overrideValWithBackendFiled.kt @@ -0,0 +1,25 @@ +package foo + +open class A { + open val a = 2 + get() { + return $a + 1 + } +} + +class B : A() { + override val a: Int = 5 + get() { + return super.a + 10 * $a + 100 + } +} + +fun box(): String { + val a = A() + val b = B() + + if (a.a != 3) return "a.a != 3, it: ${a.a}" + if (b.a != 153) return "b.a != 153, it: ${b.a}" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/propertyOverride/cases/simpleOverride.kt b/js/js.translator/testFiles/propertyOverride/cases/simpleOverride.kt new file mode 100644 index 00000000000..23dd36e35c6 --- /dev/null +++ b/js/js.translator/testFiles/propertyOverride/cases/simpleOverride.kt @@ -0,0 +1,33 @@ +package foo + +open class A { + open var a = 1 + fun getAInA(): Int = a +} + +class B : A() { + override var a = 2 + + fun getSuperA(): Int { + return super.a + } + + fun setSuperA(value: Int) { + super.a = value + } +} + +fun box(): String { + val a = A() + val b = B() + if (a.getAInA() != 1) return "a.getAInA() != 1, it: ${a.getAInA()}" + if (b.getAInA() != 2) return "b.getAInA() != 2, it: ${b.getAInA()}" + + if (b.getSuperA() != 1) return "b.getSuperA() != 1, it: ${b.getSuperA()}" + b.setSuperA(3) + if (b.getSuperA() != 3) return "b.getSuperA() != 3, it: ${b.getSuperA()}" + + if (b.getAInA() != 2) return "b.getAInA() != 2 after b.setAInB(3), it: ${b.getAInA()}" + + return "OK" +} \ No newline at end of file