diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 6640e660ee5..a43d800e30f 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -1129,6 +1129,10 @@ fun main(args: Array) { testClass() { model("expression/cast/cases") } + + testClass() { + model("reflection/light/cases") + } } testGroup("js/js.tests/test", "compiler/testData") { diff --git a/js/js.libraries/src/core/reflection.kt b/js/js.libraries/src/core/reflection.kt new file mode 100644 index 00000000000..d39a65d9881 --- /dev/null +++ b/js/js.libraries/src/core/reflection.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2016 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 kotlin.js + +@native +interface JsClass { + val name: String +} + +@native +fun jsClass(): JsClass = noImpl + +val T.jsClass: JsClass + get() = js("Object").getPrototypeOf(this).constructor diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LightReflectionTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LightReflectionTestGenerated.java new file mode 100644 index 00000000000..ba024a92962 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/LightReflectionTestGenerated.java @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2016 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.kotlin.js.test.semantics; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("js/js.translator/testData/reflection/light/cases") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class LightReflectionTestGenerated extends AbstractLightReflectionTest { + public void testAllFilesPresentInCases() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/reflection/light/cases"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("jsClass.kt") + public void testJsClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/reflection/light/cases/jsClass.kt"); + doTest(fileName); + } + + @TestMetadata("jsClassName.kt") + public void testJsClassName() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/reflection/light/cases/jsClassName.kt"); + doTest(fileName); + } + + @TestMetadata("jsClassOnReifiedType.kt") + public void testJsClassOnReifiedType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/reflection/light/cases/jsClassOnReifiedType.kt"); + doTest(fileName); + } +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt index 092ba3ab6d4..850da232d22 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 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. @@ -16,7 +16,10 @@ package org.jetbrains.kotlin.js.test.semantics -import org.jetbrains.kotlin.js.test.* +import org.jetbrains.kotlin.js.test.AbstractSingleFileTranslationWithDirectivesTest +import org.jetbrains.kotlin.js.test.KotlinJSMultiFileTest +import org.jetbrains.kotlin.js.test.MultipleModulesTranslationTest +import org.jetbrains.kotlin.js.test.SingleFileTranslationTest abstract class AbstractBlackBoxTest(d: String) : SingleFileTranslationTest(d) { override fun doTest(filename: String) = checkBlackBoxIsOkByPath(filename) @@ -60,6 +63,8 @@ abstract class AbstractLocalClassesTest : AbstractBlackBoxTest("localClasses/") abstract class AbstractNonLocalReturnsTest : KotlinJSMultiFileTest("inline.generated/nonLocalReturns/") -public abstract class AbstractRttiTest : SingleFileTranslationTest("rtti/") +abstract class AbstractRttiTest : SingleFileTranslationTest("rtti/") -public abstract class AbstractCastTest : SingleFileTranslationTest("expression/cast/") +abstract class AbstractCastTest : SingleFileTranslationTest("expression/cast/") + +abstract class AbstractLightReflectionTest : SingleFileTranslationTest("reflection/light/") diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java index 5be4b2814f9..4aee7e42f91 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 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. @@ -128,6 +128,9 @@ public final class FunctionTranslator extends AbstractTranslator { for (TypeParameterDescriptor type : descriptor.getTypeParameters()) { if (type.isReified()) { + JsName paramNameForType = context().getNameForDescriptor(type); + jsParameters.add(new JsParameter(paramNameForType)); + String suggestedName = Namer.isInstanceSuggestedName(type); JsName paramName = functionObject.getScope().declareName(suggestedName); jsParameters.add(new JsParameter(paramName)); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/TopLevelFIF.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/TopLevelFIF.java index ceff44088de..3f7e01ae814 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/TopLevelFIF.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/TopLevelFIF.java @@ -22,8 +22,10 @@ import com.google.dart.compiler.backend.js.ast.JsNew; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.PrimitiveType; +import org.jetbrains.kotlin.descriptors.CallableDescriptor; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.descriptors.PropertyDescriptor; +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor; import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt; import org.jetbrains.kotlin.js.patterns.DescriptorPredicate; import org.jetbrains.kotlin.js.patterns.NamePredicate; @@ -35,6 +37,7 @@ import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntri import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils; import org.jetbrains.kotlin.js.translate.utils.BindingUtils; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; +import org.jetbrains.kotlin.js.translate.utils.UtilsKt; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.KtQualifiedExpression; @@ -45,6 +48,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue; import org.jetbrains.kotlin.types.KotlinType; import java.util.List; +import java.util.Map; import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.FQ_NAMES; import static org.jetbrains.kotlin.js.patterns.PatternBuilder.pattern; @@ -140,6 +144,30 @@ public final class TopLevelFIF extends CompositeFIF { } }; + private static final FunctionIntrinsic JS_CLASS_FUN_INTRINSIC = new FunctionIntrinsic() { + @NotNull + @Override + public JsExpression apply( + @NotNull CallInfo callInfo, @NotNull List arguments, @NotNull TranslationContext context + ) { + ResolvedCall resolvedCall = callInfo.getResolvedCall(); + Map typeArguments = resolvedCall.getTypeArguments(); + + assert typeArguments.size() == 1; + KotlinType type = typeArguments.values().iterator().next(); + + return UtilsKt.getReferenceToJsClass(type, context); + } + + @NotNull + @Override + public JsExpression apply( + @Nullable JsExpression receiver, @NotNull List arguments, @NotNull TranslationContext context + ) { + throw new IllegalStateException(); + } + }; + @NotNull public static final KotlinFunctionIntrinsic TO_STRING = new KotlinFunctionIntrinsic("toString"); @@ -166,6 +194,8 @@ public final class TopLevelFIF extends CompositeFIF { add(pattern("kotlin.js", "Json", "set"), ArrayFIF.SET_INTRINSIC); add(pattern("kotlin", "Throwable", "getMessage"), MESSAGE_PROPERTY_INTRINSIC); + + add(pattern("kotlin.js", "jsClass"), JS_CLASS_FUN_INTRINSIC); } private abstract static class NativeMapGetSet extends CallParametersAwareFunctionIntrinsic { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt index c10bf2c5868..b0834bafc59 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallArgumentTranslator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 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. @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.js.translate.general.Translation 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.js.translate.utils.getReferenceToJsClass import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.resolve.calls.model.* import org.jetbrains.kotlin.types.KotlinType @@ -316,6 +317,8 @@ private fun Map.addReifiedTypeArgsTo( val argumentType = get(param) ?: continue + reifiedTypeArguments.add(getReferenceToJsClass(argumentType, context)) + val isCheckCallable = patternTranslator.getIsTypeCheckCallable(argumentType) reifiedTypeArguments.add(isCheckCallable) } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt index c3c642c7eb1..f1673320cf7 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 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. @@ -17,13 +17,16 @@ package org.jetbrains.kotlin.js.translate.utils import com.google.dart.compiler.backend.js.ast.* -import com.google.dart.compiler.backend.js.ast.metadata.synthetic import com.intellij.util.SmartList +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunction import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.types.KotlinType fun generateDelegateCall( fromDescriptor: FunctionDescriptor, @@ -76,3 +79,29 @@ fun List.splitToRanges(classifier: (T) -> S): List, S>> { result += Pair(subList(lastIndex, size), lastClass) return result } + +fun getReferenceToJsClass(type: KotlinType, context: TranslationContext): JsNameRef { + val referenceToJsClass: JsNameRef + + val classifierDescriptor = type.constructor.declarationDescriptor + + if (classifierDescriptor is ClassDescriptor) { + val reference = context.getQualifiedReference(classifierDescriptor) + if (classifierDescriptor.kind == ClassKind.OBJECT) { + referenceToJsClass = JsAstUtils.pureFqn("constructor", JsInvocation(JsNameRef("getPrototypeOf", JsNameRef("Object")), reference)) + } + else { + referenceToJsClass = reference + } + } + else if (classifierDescriptor is TypeParameterDescriptor) { + assert(classifierDescriptor.isReified) + + referenceToJsClass = context.getNameForDescriptor(classifierDescriptor).makeRef() + } + else { + throw IllegalStateException("Can't get reference for $type") + } + + return referenceToJsClass +} diff --git a/js/js.translator/testData/kotlin_lib_ecma5.js b/js/js.translator/testData/kotlin_lib_ecma5.js index c68f2530115..66b39a14950 100644 --- a/js/js.translator/testData/kotlin_lib_ecma5.js +++ b/js/js.translator/testData/kotlin_lib_ecma5.js @@ -192,7 +192,7 @@ } Kotlin.createTraitNow = function (bases, properties, staticProperties) { - var obj = function () {}; + var obj = {}; obj.$metadata$ = computeMetadata(bases, properties, staticProperties); obj.$metadata$.type = Kotlin.TYPE.TRAIT; @@ -301,6 +301,7 @@ Kotlin.createTrait = function (basesFun, properties, staticProperties) { function $o() { var klass = Kotlin.createTraitNow(getBases(basesFun), properties, staticProperties); + klass.name = $o.className; Object.defineProperty(this, $o.className, {value: klass}); return klass; } @@ -320,8 +321,8 @@ Kotlin.createObject = function (basesFun, constructor, functions, staticProperties) { constructor = constructor || function() {}; function $o() { - var klass = Kotlin.createClassNow(getBases(basesFun), null, functions, staticProperties); - var obj = new klass(); + var klass = Kotlin.createClassNow(getBases(basesFun), constructor, functions, staticProperties); + var obj = Object.create(klass.prototype); var metadata = klass.$metadata$; metadata.type = Kotlin.TYPE.OBJECT; Object.defineProperty(this, $o.className, {value: obj}); diff --git a/js/js.translator/testData/reflection/light/_commonFiles/declarations.kt b/js/js.translator/testData/reflection/light/_commonFiles/declarations.kt new file mode 100644 index 00000000000..623d0cbdf73 --- /dev/null +++ b/js/js.translator/testData/reflection/light/_commonFiles/declarations.kt @@ -0,0 +1,20 @@ +package foo + +open class A + +class B : A() { + val a = 1 +} + +object O + +interface I + +enum class E { + X, + Y { + val a = 1 + }, + Z {} +} + diff --git a/js/js.translator/testData/reflection/light/cases/jsClass.kt b/js/js.translator/testData/reflection/light/cases/jsClass.kt new file mode 100644 index 00000000000..2a3f594a4c6 --- /dev/null +++ b/js/js.translator/testData/reflection/light/cases/jsClass.kt @@ -0,0 +1,23 @@ +package foo + +fun check(x: JsClass, y: JsClass, shouldBeEquals: Boolean = true) { + assertNotEquals(null, x) + assertNotEquals(null, y) + if (shouldBeEquals) + assertEquals(x, y) + else + assertNotEquals(x, y) +} + +fun box(): String { + check(jsClass(), A().jsClass) + check(jsClass(), B().jsClass) + check(jsClass(), O.jsClass) + assertNotEquals(null, jsClass()) + check(jsClass(), E.X.jsClass) + check(jsClass(), E.Y.jsClass, shouldBeEquals = false) +// TODO uncomment after KT-13338 is fixed +// check(jsClass(), E.Z.jsClass) + + return "OK" +} diff --git a/js/js.translator/testData/reflection/light/cases/jsClassName.kt b/js/js.translator/testData/reflection/light/cases/jsClassName.kt new file mode 100644 index 00000000000..7d94415f866 --- /dev/null +++ b/js/js.translator/testData/reflection/light/cases/jsClassName.kt @@ -0,0 +1,26 @@ +package foo + +fun testWithInstance() { + assertEquals("A", A().jsClass.name) + assertEquals("B", B().jsClass.name) + assertEquals("O", O.jsClass.name) + assertEquals("E", E.X.jsClass.name) + assertEquals("Y", E.Y.jsClass.name) +// TODO uncomment after KT-13338 is fixed +// assertEquals("E", E.Z.jsClass.name) +} + +fun testWithClassReference() { + assertEquals("A", jsClass().name) + assertEquals("B", jsClass().name) + assertEquals("O", jsClass().name) + assertEquals("I", jsClass().name) + assertEquals("E", jsClass().name) +} + +fun box(): String { + testWithInstance() + testWithClassReference() + + return "OK" +} diff --git a/js/js.translator/testData/reflection/light/cases/jsClassOnReifiedType.kt b/js/js.translator/testData/reflection/light/cases/jsClassOnReifiedType.kt new file mode 100644 index 00000000000..d1503a4659f --- /dev/null +++ b/js/js.translator/testData/reflection/light/cases/jsClassOnReifiedType.kt @@ -0,0 +1,15 @@ +package foo + +inline fun foo(): JsClass { + val T = 1 + return jsClass() +} + +fun box(): String { + assertEquals(jsClass(), foo()) + assertEquals(jsClass(), foo()) + assertEquals(jsClass(), foo()) + assertEquals(jsClass(), foo()) + + return "OK" +}