diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/PsiCodegenPredictor.java b/compiler/backend/src/org/jetbrains/jet/codegen/PsiCodegenPredictor.java new file mode 100644 index 00000000000..8832248ae1a --- /dev/null +++ b/compiler/backend/src/org/jetbrains/jet/codegen/PsiCodegenPredictor.java @@ -0,0 +1,176 @@ +/* + * Copyright 2010-2012 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.jet.codegen; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.descriptors.ClassDescriptor; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.BindingTrace; +import org.jetbrains.jet.lang.resolve.java.JvmAbi; +import org.jetbrains.jet.lang.resolve.java.JvmClassName; +import org.jetbrains.jet.lang.resolve.name.FqName; +import org.jetbrains.jet.lang.resolve.name.Name; + +import static org.jetbrains.jet.lang.resolve.BindingContextUtils.descriptorToDeclaration; + +public final class PsiCodegenPredictor { + private PsiCodegenPredictor() { + } + + public static boolean checkPredictedNameFromPsi( + @NotNull BindingTrace bindingTrace, @NotNull DeclarationDescriptor descriptor, JvmClassName nameFromDescriptors + ) { + PsiElement element = descriptorToDeclaration(bindingTrace.getBindingContext(), descriptor); + if (element instanceof JetDeclaration) { + JvmClassName classNameFromPsi = getPredefinedJvmClassName((JetDeclaration) element); + assert classNameFromPsi == null || classNameFromPsi.equals(nameFromDescriptors) : "Invalid algorithm for getting qualified name from psi!"; + } + + return true; + } + + @Nullable + public static JvmClassName getPredefinedJvmClassName(@NotNull JetFile jetFile, boolean withNamespace) { + String packageName = jetFile.getPackageName(); + if (packageName == null) { + return null; + } + + JvmClassName packageJvmName = JvmClassName.byFqNameWithoutInnerClasses(packageName); + return !withNamespace ? packageJvmName : addPackageClass(packageJvmName); + } + + /** + * TODO: Finish this method for all cases. Now it's only used and tested in JetLightClass. + * + * @return null if no prediction can be done. + */ + @Nullable + public static JvmClassName getPredefinedJvmClassName(@NotNull JetDeclaration declaration) { + // TODO: Method won't work for declarations inside class objects + // TODO: Method won't give correct class name for traits implementations + + JetDeclaration parentDeclaration = PsiTreeUtil.getParentOfType(declaration, JetDeclaration.class); + JvmClassName parentClassName = parentDeclaration != null ? + getPredefinedJvmClassName(parentDeclaration) : + getPredefinedJvmClassName((JetFile) declaration.getContainingFile(), false); + if (parentClassName == null) { + return null; + } + + if (declaration instanceof JetClassObject) { + // Get parent and assign Class object prefix + return JvmClassName.byInternalName(parentClassName.getInternalName() + JvmAbi.CLASS_OBJECT_SUFFIX); + } + + if (declaration instanceof JetNamedDeclaration) { + if (!PsiTreeUtil.instanceOf(declaration, JetClass.class, JetObjectDeclaration.class, JetNamedFunction.class, JetProperty.class) || + declaration instanceof JetEnumEntry) { + // Other subclasses are not valid for class name prediction. + // For example EnumEntry, JetFunctionLiteral + return null; + } + + JetNamedDeclaration namedDeclaration = (JetNamedDeclaration) declaration; + Name name = namedDeclaration.getNameAsName(); + if (name == null) { + return null; + } + + FqName fqName = parentClassName.getFqName(); + + if (declaration instanceof JetNamedFunction) { + if (parentDeclaration == null) { + JvmClassName packageClass = addPackageClass(parentClassName); + return JvmClassName.byInternalName(packageClass.getInternalName() + "$" + name.getName()); + } + + if (!(parentDeclaration instanceof JetClass || parentDeclaration instanceof JetObjectDeclaration)) { + // Can't generate predefined name for internal functions + return null; + } + } + + // NOTE: looks like a bug - for class in getter of top level property class name will be $propertyName$ClassName but not + // namespace$propertyName$ClassName + if (declaration instanceof JetProperty) { + return JvmClassName.byInternalName(parentClassName.getInternalName() + "$" + name.getName()); + } + + if (fqName.isRoot()) { + return JvmClassName.byInternalName(name.getName()); + } + + return JvmClassName.byInternalName(parentDeclaration == null ? + parentClassName.getInternalName() + "/" + name.getName() : + parentClassName.getInternalName() + "$" + name.getName()); + } + + return null; + } + + private static JvmClassName addPackageClass(JvmClassName packageName) { + FqName name = packageName.getFqName(); + return name.isRoot() ? + JvmClassName.byFqNameWithoutInnerClasses(JvmAbi.PACKAGE_CLASS) : + JvmClassName.byInternalName(packageName.getInternalName() + "/" + JvmAbi.PACKAGE_CLASS); + } + + public static boolean checkPredictedClassNameForFun( + @NotNull BindingTrace bindingTrace, @NotNull DeclarationDescriptor descriptor, ClassDescriptor classDescriptor + ) { + PsiElement element = descriptorToDeclaration(bindingTrace.getBindingContext(), descriptor); + PsiElement classDeclaration = descriptorToDeclaration(bindingTrace.getBindingContext(), classDescriptor); + if (element instanceof JetNamedFunction && classDeclaration instanceof JetDeclaration) { + JvmClassName classNameFromPsi = getPredefinedJvmClassName((JetDeclaration) classDeclaration); + JvmClassName classNameForFun = getPredefinedJvmClassNameForFun((JetNamedFunction) element); + assert classNameForFun == null || classNameForFun.equals(classNameFromPsi) : "Invalid algorithm for getting enclosing method name!"; + } + + return true; + } + + @Nullable + public static JvmClassName getPredefinedJvmClassNameForFun(@NotNull JetNamedFunction function) { + PsiElement parent = function.getParent(); + if (parent instanceof JetFile) { + return getPredefinedJvmClassName((JetFile) parent, true); + } + + @SuppressWarnings("unchecked") + JetClass containingClass = PsiTreeUtil.getParentOfType(function, JetClass.class, true, JetDeclaration.class); + if (containingClass != null) { + return getPredefinedJvmClassName(containingClass); + } + + @SuppressWarnings("unchecked") + JetObjectDeclaration objectDeclaration = PsiTreeUtil.getParentOfType(function, JetObjectDeclaration.class, true, JetDeclaration.class); + if (objectDeclaration != null) { + if (objectDeclaration.getParent() instanceof JetClassObject) { + return getPredefinedJvmClassName((JetClassObject) objectDeclaration.getParent()); + } + + return getPredefinedJvmClassName(objectDeclaration); + } + + return null; + } +} diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenAnnotatingVisitor.java b/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenAnnotatingVisitor.java index 3896ab56436..b65be0c183e 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenAnnotatingVisitor.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenAnnotatingVisitor.java @@ -17,6 +17,7 @@ package org.jetbrains.jet.codegen.binding; import com.intellij.util.containers.Stack; +import org.jetbrains.jet.codegen.PsiCodegenPredictor; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor; import org.jetbrains.jet.lang.psi.*; @@ -69,6 +70,7 @@ class CodegenAnnotatingVisitor extends JetVisitorVoid { : JetStandardClasses.getFunction(arity)).getDefaultType()), JetScope.EMPTY, Collections.emptySet(), null); + assert PsiCodegenPredictor.checkPredictedClassNameForFun(bindingTrace, funDescriptor, classDescriptor); bindingTrace.record(CLASS_FOR_FUNCTION, funDescriptor, classDescriptor); return classDescriptor; } @@ -134,7 +136,9 @@ class CodegenAnnotatingVisitor extends JetVisitorVoid { super.visitEnumEntry(enumEntry); } else { - bindingTrace.record(FQN, descriptor, bindingTrace.get(FQN, peekFromStack(classStack))); + JvmClassName jvmClassName = bindingTrace.get(FQN, peekFromStack(classStack)); + assert PsiCodegenPredictor.checkPredictedNameFromPsi(bindingTrace, descriptor, jvmClassName); + bindingTrace.record(FQN, descriptor, jvmClassName); } } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenBinding.java b/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenBinding.java index 87a0789269b..05faf962fe6 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenBinding.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/binding/CodegenBinding.java @@ -20,6 +20,7 @@ import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.codegen.CodegenUtil; +import org.jetbrains.jet.codegen.PsiCodegenPredictor; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor; import org.jetbrains.jet.lang.psi.*; @@ -132,6 +133,7 @@ public class CodegenBinding { recordClosure(bindingTrace, null, classDescriptor, null, className, false); + assert PsiCodegenPredictor.checkPredictedClassNameForFun(bindingTrace, scriptDescriptor, classDescriptor); bindingTrace.record(CLASS_FOR_FUNCTION, scriptDescriptor, classDescriptor); } @@ -170,6 +172,7 @@ public class CodegenBinding { final MutableClosure closure = new MutableClosure(superCall, enclosing, enclosingReceiver); + assert PsiCodegenPredictor.checkPredictedNameFromPsi(bindingTrace, classDescriptor, name); bindingTrace.record(FQN, classDescriptor, name); bindingTrace.record(CLOSURE, classDescriptor, closure); @@ -260,6 +263,8 @@ public class CodegenBinding { } name = JvmClassName.byInternalName(getJvmInternalFQNameImpl(bindingTrace, descriptor)); + + assert PsiCodegenPredictor.checkPredictedNameFromPsi(bindingTrace, descriptor, name); bindingTrace.record(FQN, descriptor, name); return name; } diff --git a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JetLightClass.java b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JetLightClass.java index 6be528e7de9..48e0fef892f 100644 --- a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JetLightClass.java +++ b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JetLightClass.java @@ -37,20 +37,15 @@ import com.intellij.util.containers.Stack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.analyzer.AnalyzeExhaust; -import org.jetbrains.jet.codegen.ClassBuilder; -import org.jetbrains.jet.codegen.ClassBuilderFactory; -import org.jetbrains.jet.codegen.ClassBuilderMode; -import org.jetbrains.jet.codegen.CompilationErrorHandler; +import org.jetbrains.jet.codegen.*; import org.jetbrains.jet.codegen.state.GenerationState; import org.jetbrains.jet.codegen.state.GenerationStrategy; import org.jetbrains.jet.lang.BuiltinsScopeExtensionMode; -import org.jetbrains.jet.lang.psi.JetClass; -import org.jetbrains.jet.lang.psi.JetFile; -import org.jetbrains.jet.lang.psi.JetFunction; -import org.jetbrains.jet.lang.psi.JetPsiUtil; +import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM; import org.jetbrains.jet.lang.resolve.java.JetFilesProvider; import org.jetbrains.jet.lang.resolve.java.JetJavaMirrorMarker; +import org.jetbrains.jet.lang.resolve.java.JvmClassName; import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.types.lang.JetStandardClasses; import org.jetbrains.jet.plugin.JetLanguage; @@ -246,15 +241,33 @@ public class JetLightClass extends AbstractLightClass implements JetJavaMirrorMa } @Nullable - public static JetLightClass wrapDelegate(JetClass jetClass) { - if (jetClass == null) return null; - return create(jetClass.getManager(), (JetFile) jetClass.getContainingFile(), JetPsiUtil.getFQName(jetClass)); + public static JetLightClass wrapDelegate(@Nullable JetClass jetClass) { + if (jetClass == null) { + return null; + } + + return wrapDelegate(jetClass, PsiCodegenPredictor.getPredefinedJvmClassName(jetClass)); } @Nullable - public static PsiMethod wrapMethod(JetFunction function) { - JetClass containingClass = PsiTreeUtil.getParentOfType(function, JetClass.class); - JetLightClass wrapper = wrapDelegate(containingClass); + private static JetLightClass wrapDelegate(@NotNull JetDeclaration declaration, @Nullable JvmClassName jvmClassName) { + if (jvmClassName == null) { + return null; + } + + return create(declaration.getManager(), (JetFile) declaration.getContainingFile(), jvmClassName.getFqName()); + } + + @Nullable + public static PsiMethod wrapMethod(@NotNull JetNamedFunction function) { + //noinspection unchecked + if (PsiTreeUtil.getParentOfType(function, JetFunction.class, JetProperty.class) != null) { + // Don't generate method wrappers for internal declarations. Their classes are not generated during calcStub + // with ClassBuilderMode.SIGNATURES mode, and this produces "Class not found exception" in getDelegate() + return null; + } + + JetLightClass wrapper = wrapDelegate(function, PsiCodegenPredictor.getPredefinedJvmClassNameForFun(function)); if (wrapper == null) { return null; } @@ -264,6 +277,7 @@ public class JetLightClass extends AbstractLightClass implements JetJavaMirrorMa return method; } } + return null; } diff --git a/idea/src/org/jetbrains/jet/plugin/search/KotlinReferencesSearcher.java b/idea/src/org/jetbrains/jet/plugin/search/KotlinReferencesSearcher.java index 0cb5be54960..5087b65a489 100644 --- a/idea/src/org/jetbrains/jet/plugin/search/KotlinReferencesSearcher.java +++ b/idea/src/org/jetbrains/jet/plugin/search/KotlinReferencesSearcher.java @@ -27,8 +27,7 @@ import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.asJava.JetLightClass; import org.jetbrains.jet.lang.psi.JetClass; -import org.jetbrains.jet.lang.psi.JetClassBody; -import org.jetbrains.jet.lang.psi.JetFunction; +import org.jetbrains.jet.lang.psi.JetNamedFunction; /** * @author yole @@ -46,10 +45,10 @@ public class KotlinReferencesSearcher extends QueryExecutorBase() { @Override public PsiMethod compute() { @@ -57,13 +56,9 @@ public class KotlinReferencesSearcher extends QueryExecutorBaseinternalFun() { + + } + } +} \ No newline at end of file diff --git a/idea/testData/javaFacade/doNotWrapFunInAnonymousObject.kt b/idea/testData/javaFacade/doNotWrapFunInAnonymousObject.kt new file mode 100644 index 00000000000..91f2f115883 --- /dev/null +++ b/idea/testData/javaFacade/doNotWrapFunInAnonymousObject.kt @@ -0,0 +1,5 @@ +val a = object { + fun testing() { + + } +} \ No newline at end of file diff --git a/idea/testData/javaFacade/wrapFunInClassObject.kt b/idea/testData/javaFacade/wrapFunInClassObject.kt new file mode 100644 index 00000000000..cddbdf5de22 --- /dev/null +++ b/idea/testData/javaFacade/wrapFunInClassObject.kt @@ -0,0 +1,6 @@ +class Test { + class object { + fun testing() { + } + } +} \ No newline at end of file diff --git a/idea/testData/javaFacade/wrapFunInObject.kt b/idea/testData/javaFacade/wrapFunInObject.kt new file mode 100644 index 00000000000..f215360a1e2 --- /dev/null +++ b/idea/testData/javaFacade/wrapFunInObject.kt @@ -0,0 +1,3 @@ +object SomeObject { + fun testingInObjectNew() = 12 +} \ No newline at end of file diff --git a/idea/testData/javaFacade/wrapFunWithImplInTrait.kt b/idea/testData/javaFacade/wrapFunWithImplInTrait.kt new file mode 100644 index 00000000000..0c35751bc7f --- /dev/null +++ b/idea/testData/javaFacade/wrapFunWithImplInTrait.kt @@ -0,0 +1,4 @@ +trait Some { + fun some() { + } +} \ No newline at end of file diff --git a/idea/testData/javaFacade/wrapFunWithoutImplInTrait.kt b/idea/testData/javaFacade/wrapFunWithoutImplInTrait.kt new file mode 100644 index 00000000000..72bf27949e0 --- /dev/null +++ b/idea/testData/javaFacade/wrapFunWithoutImplInTrait.kt @@ -0,0 +1,3 @@ +trait Some { + fun some() +} \ No newline at end of file diff --git a/idea/testData/javaFacade/wrapTopLevelFun.kt b/idea/testData/javaFacade/wrapTopLevelFun.kt new file mode 100644 index 00000000000..b77ceb05ea8 --- /dev/null +++ b/idea/testData/javaFacade/wrapTopLevelFun.kt @@ -0,0 +1,2 @@ +fun test() { +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/javaFacade/JetJavaFacadeTest.java b/idea/tests/org/jetbrains/jet/plugin/javaFacade/JetJavaFacadeTest.java index 1254d9b551f..f53473cf2d0 100644 --- a/idea/tests/org/jetbrains/jet/plugin/javaFacade/JetJavaFacadeTest.java +++ b/idea/tests/org/jetbrains/jet/plugin/javaFacade/JetJavaFacadeTest.java @@ -18,15 +18,15 @@ package org.jetbrains.jet.plugin.javaFacade; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.asJava.JetLightClass; import org.jetbrains.jet.lang.psi.JetClass; import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.psi.JetNamedFunction; import org.jetbrains.jet.lang.psi.JetPsiUtil; -import org.jetbrains.jet.lang.resolve.name.FqName; -import org.jetbrains.jet.lang.resolve.name.Name; import org.jetbrains.jet.plugin.JetLightProjectDescriptor; import org.jetbrains.jet.plugin.PluginTestCaseBase; @@ -46,21 +46,49 @@ public class JetJavaFacadeTest extends LightCodeInsightFixtureTestCase { myFixture.setTestDataPath(PluginTestCaseBase.getTestDataPathBase() + "/javaFacade"); } + public void testDoNotWrapFunFromLocalClass() { + doTestWrapMethod(false); + } + + public void testDoNotWrapFunInAnonymousObject() { + doTestWrapMethod(false); + } + + public void testWrapFunInClassObject() { + doTestWrapMethod(true); + } + + public void testWrapTopLevelFun() { + doTestWrapMethod(true); + } + + public void testWrapFunWithImplInTrait() { + doTestWrapMethod(true); + } + + public void testWrapFunWithoutImplInTrait() { + doTestWrapMethod(true); + } + + public void testWrapFunInObject() { + doTestWrapMethod(true); + } + public void testInnerClass() throws Exception { myFixture.configureByFile(getTestName(true) + ".kt"); - + JavaPsiFacade facade = myFixture.getJavaFacade(); PsiClass mirrorClass = facade.findClass("foo.Outer.Inner", GlobalSearchScope.allScope(getProject())); - + assertNotNull(mirrorClass); PsiMethod[] fun = mirrorClass.findMethodsByName("innerFun", false); - + assertEquals(fun[0].getReturnType(), PsiType.VOID); } - + public void testClassObject() throws Exception { myFixture.configureByFile(getTestName(true) + ".kt"); - + JavaPsiFacade facade = myFixture.getJavaFacade(); PsiClass theClass = facade.findClass("foo.TheClass", GlobalSearchScope.allScope(getProject())); @@ -71,7 +99,7 @@ public class JetJavaFacadeTest extends LightCodeInsightFixtureTestCase { PsiType type = classobj.getType(); assertTrue(type instanceof PsiClassType); - + assertEquals("foo.TheClass.ClassObject$", type.getCanonicalText()); PsiClass classObjectClass = ((PsiClassType) type).resolve(); @@ -98,4 +126,28 @@ public class JetJavaFacadeTest extends LightCodeInsightFixtureTestCase { JetPsiUtil.getFQName(aClass)); assertNull(createdByFactory); } + + private void doTestWrapMethod(boolean shouldBeWrapped) { + myFixture.configureByFile(getTestName(true) + ".kt"); + + int offset = myFixture.getEditor().getCaretModel().getOffset(); + PsiElement elementAt = myFixture.getFile().findElementAt(offset); + + assertNotNull("Caret should be set for tested file", elementAt); + + JetNamedFunction jetFunction = PsiTreeUtil.getParentOfType(elementAt, JetNamedFunction.class); + assertNotNull("Caret should be placed to function definition", jetFunction); + + // Should not fail! + PsiMethod psiMethod = JetLightClass.wrapMethod(jetFunction); + + if (shouldBeWrapped) { + assertNotNull(String.format("Failed to wrap jetFunction '%s' to method", jetFunction.getText()), psiMethod); + assertInstanceOf(psiMethod, PsiCompiledElement.class); + assertEquals("Invalid original element for generated method", ((PsiCompiledElement)psiMethod).getMirror(), jetFunction); + } + else { + assertNull("There should be no wrapper for given method", psiMethod); + } + } }