From 312bd6f828ad0664ad2176771a6769f9f3b6e6a6 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Thu, 16 May 2013 19:07:53 +0400 Subject: [PATCH] Get PsiMethod wrappers for properties and property accessors --- .../jetbrains/jet/asJava/LightClassUtil.java | 180 ++++++++++++++---- .../javaFacade/wrapValTopLevelProperty.kt | 1 + .../javaFacade/wrapVarPropertyInClass.kt | 3 + .../javaFacade/wrapVarPropertyInLocalClass.kt | 5 + .../wrapVarPropertyWithAccessorsInTrait.kt | 5 + .../javaFacade/wrapVarTopLevelAccessor.kt | 3 + .../javaFacade/wrapVarTopLevelProperty.kt | 1 + .../plugin/javaFacade/JetJavaFacadeTest.java | 74 ++++++- 8 files changed, 225 insertions(+), 47 deletions(-) create mode 100644 idea/testData/javaFacade/wrapValTopLevelProperty.kt create mode 100644 idea/testData/javaFacade/wrapVarPropertyInClass.kt create mode 100644 idea/testData/javaFacade/wrapVarPropertyInLocalClass.kt create mode 100644 idea/testData/javaFacade/wrapVarPropertyWithAccessorsInTrait.kt create mode 100644 idea/testData/javaFacade/wrapVarTopLevelAccessor.kt create mode 100644 idea/testData/javaFacade/wrapVarTopLevelProperty.kt diff --git a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/LightClassUtil.java b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/LightClassUtil.java index 686b92d5cc9..04104a33439 100644 --- a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/LightClassUtil.java +++ b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/LightClassUtil.java @@ -31,10 +31,13 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.PsiFileStub; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.codegen.binding.PsiCodegenPredictor; import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.java.JetClsMethod; +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.types.lang.KotlinBuiltIns; @@ -42,6 +45,7 @@ import org.jetbrains.jet.utils.KotlinVfsUtil; import java.net.MalformedURLException; import java.net.URL; +import java.util.*; public class LightClassUtil { private static final Logger LOG = Logger.getInstance(LightClassUtil.class); @@ -116,42 +120,70 @@ public class LightClassUtil { return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getPsiClass(classOrObject); } + @Nullable + public static PsiMethod getLightClassAccessorMethod(@NotNull JetPropertyAccessor accessor) { + return getPsiMethodWrapper(accessor); + } + + @NotNull + public static PropertyAccessorsPsiMethods getLightClassPropertyMethods(@NotNull JetProperty property) { + JetPropertyAccessor getter = property.getGetter(); + JetPropertyAccessor setter = property.getSetter(); + + PsiMethod getterWrapper = getter != null ? getLightClassAccessorMethod(getter) : null; + PsiMethod setterWrapper = setter != null ? getLightClassAccessorMethod(setter) : null; + + if (getterWrapper == null || setterWrapper == null) { + List wrappers = getPsiMethodWrappers(property, true); + assert wrappers.size() <= 2 : "Maximum two wrappers are expected to be generated for property"; + + for (PsiMethod wrapper : wrappers) { + if (wrapper.getName().startsWith(JvmAbi.SETTER_PREFIX)) { + assert setterWrapper == null : String.format( + "Setter accessor isn't expected to be reassigned (old: %s, new: %s)", setterWrapper, wrapper); + + setterWrapper = wrapper; + } + else { + assert getterWrapper == null : String.format( + "Getter accessor isn't expected to be reassigned (old: %s, new: %s)", getterWrapper, wrapper); + + getterWrapper = wrapper; + } + } + } + + return new PropertyAccessorsPsiMethods(getterWrapper, setterWrapper); + } + @Nullable public static PsiMethod getLightClassMethod(@NotNull JetNamedFunction function) { - //noinspection unchecked - if (PsiTreeUtil.getParentOfType(function, JetFunction.class, JetProperty.class) != null) { - // Don't genClassOrObject 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; + return getPsiMethodWrapper(function); + } + + @Nullable + private static PsiMethod getPsiMethodWrapper(@NotNull JetDeclaration declaration) { + List wrappers = getPsiMethodWrappers(declaration, false); + return !wrappers.isEmpty() ? wrappers.get(0) : null; + } + + @NotNull + private static List getPsiMethodWrappers(@NotNull JetDeclaration declaration, boolean collectAll) { + PsiClass psiClass = getWrappingClass(declaration); + if (psiClass == null) { + return Collections.emptyList(); } - Project project = function.getProject(); - - PsiElement parent = function.getParent(); - PsiClass psiClass; - if (parent == function.getContainingFile()) { - // top-level function - JvmClassName jvmClassName = PsiCodegenPredictor.getPredefinedJvmClassName((JetFile) parent, true); - if (jvmClassName == null) return null; - - String fqName = jvmClassName.getFqName().getFqName(); - psiClass = JavaElementFinder.getInstance(project).findClass(fqName, GlobalSearchScope.allScope(project)); - } - else { - if (!(parent instanceof JetClassBody)) return null; - assert parent.getParent() instanceof JetClassOrObject; - - // function in a class - JetClassOrObject classOrObject = (JetClassOrObject) parent.getParent(); - psiClass = getPsiClass(classOrObject); - } - - if (psiClass == null) return null; - + List methods = new SmartList(); for (PsiMethod method : psiClass.getMethods()) { try { - if (method instanceof PsiCompiledElement && ((PsiCompiledElement) method).getMirror() == function) { - return method; + if (method instanceof JetClsMethod) { + if (method instanceof PsiCompiledElement && ((PsiCompiledElement) method).getMirror() == declaration) { + methods.add(method); + if (!collectAll) { + return methods; + } + } } } catch (ProcessCanceledException e) { @@ -159,24 +191,94 @@ public class LightClassUtil { } catch (Throwable e) { throw new IllegalStateException( - "Error while wrapping function " + function.getName() + + "Error while wrapping declaration " + declaration.getName() + "Context\n:" + String.format("=== In file ===\n" + - "%s\n" + - "===On element===\n" + - "%s\n" + - "===WrappedElement===\n" + - "%s\n", - function.getContainingFile().getText(), - function.getText(), - method.toString()), + "%s\n" + + "=== On element ===\n" + + "%s\n" + + "=== WrappedElement ===\n" + + "%s\n", + declaration.getContainingFile().getText(), + declaration.getText(), + method.toString()), e ); } } + return methods; + } + + @Nullable + private static PsiClass getWrappingClass(@NotNull JetDeclaration declaration) { + if (declaration instanceof JetPropertyAccessor) { + PsiElement propertyParent = declaration.getParent(); + assert propertyParent instanceof JetProperty : "JetProperty is expected to be parent of accessor"; + + declaration = (JetProperty) propertyParent; + } + + //noinspection unchecked + if (PsiTreeUtil.getParentOfType(declaration, JetFunction.class, JetProperty.class) != null) { + // Can't get 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; + } + + PsiElement parent = declaration.getParent(); + + if (parent instanceof JetFile) { + // top-level declaration + JvmClassName jvmName = PsiCodegenPredictor.getPredefinedJvmClassName((JetFile) parent, true); + if (jvmName != null) { + Project project = declaration.getProject(); + + String fqName = jvmName.getFqName().getFqName(); + return JavaElementFinder.getInstance(project).findClass(fqName, GlobalSearchScope.allScope(project)); + } + } + else if (parent instanceof JetClassBody) { + assert parent.getParent() instanceof JetClassOrObject; + return getPsiClass((JetClassOrObject) parent.getParent()); + } + return null; } + public static class PropertyAccessorsPsiMethods implements Iterable { + private final PsiMethod getter; + private final PsiMethod setter; + private final Collection accessors = new ArrayList(2); + + PropertyAccessorsPsiMethods(@Nullable PsiMethod getter, @Nullable PsiMethod setter) { + this.getter = getter; + if (getter != null) { + accessors.add(getter); + } + + this.setter = setter; + if (setter != null) { + accessors.add(setter); + } + } + + @Nullable + public PsiMethod getGetter() { + return getter; + } + + @Nullable + public PsiMethod getSetter() { + return setter; + } + + @NotNull + @Override + public Iterator iterator() { + return accessors.iterator(); + } + } + private LightClassUtil() {} } diff --git a/idea/testData/javaFacade/wrapValTopLevelProperty.kt b/idea/testData/javaFacade/wrapValTopLevelProperty.kt new file mode 100644 index 00000000000..5d903b0a451 --- /dev/null +++ b/idea/testData/javaFacade/wrapValTopLevelProperty.kt @@ -0,0 +1 @@ +val test: Int? = null \ No newline at end of file diff --git a/idea/testData/javaFacade/wrapVarPropertyInClass.kt b/idea/testData/javaFacade/wrapVarPropertyInClass.kt new file mode 100644 index 00000000000..8aaf0e814fe --- /dev/null +++ b/idea/testData/javaFacade/wrapVarPropertyInClass.kt @@ -0,0 +1,3 @@ +class Test { + var test = "Test" +} \ No newline at end of file diff --git a/idea/testData/javaFacade/wrapVarPropertyInLocalClass.kt b/idea/testData/javaFacade/wrapVarPropertyInLocalClass.kt new file mode 100644 index 00000000000..e89a96f78cf --- /dev/null +++ b/idea/testData/javaFacade/wrapVarPropertyInLocalClass.kt @@ -0,0 +1,5 @@ +fun test() { + class Test { + var someProperty = "No wrapper is expected" + } +} \ No newline at end of file diff --git a/idea/testData/javaFacade/wrapVarPropertyWithAccessorsInTrait.kt b/idea/testData/javaFacade/wrapVarPropertyWithAccessorsInTrait.kt new file mode 100644 index 00000000000..9b324ad7dc7 --- /dev/null +++ b/idea/testData/javaFacade/wrapVarPropertyWithAccessorsInTrait.kt @@ -0,0 +1,5 @@ +trait Hello { + var some: String + get() = "Hi" + set(value) {} +} \ No newline at end of file diff --git a/idea/testData/javaFacade/wrapVarTopLevelAccessor.kt b/idea/testData/javaFacade/wrapVarTopLevelAccessor.kt new file mode 100644 index 00000000000..f8ae61889b9 --- /dev/null +++ b/idea/testData/javaFacade/wrapVarTopLevelAccessor.kt @@ -0,0 +1,3 @@ +var some: String + get() = "Hi" + set(value) {} diff --git a/idea/testData/javaFacade/wrapVarTopLevelProperty.kt b/idea/testData/javaFacade/wrapVarTopLevelProperty.kt new file mode 100644 index 00000000000..c242b701c67 --- /dev/null +++ b/idea/testData/javaFacade/wrapVarTopLevelProperty.kt @@ -0,0 +1 @@ +var test: Int? = null \ 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 29d19e723a7..8f444c4fa6e 100644 --- a/idea/tests/org/jetbrains/jet/plugin/javaFacade/JetJavaFacadeTest.java +++ b/idea/tests/org/jetbrains/jet/plugin/javaFacade/JetJavaFacadeTest.java @@ -24,8 +24,7 @@ import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.asJava.KotlinLightClass; import org.jetbrains.jet.asJava.LightClassUtil; -import org.jetbrains.jet.lang.psi.JetClass; -import org.jetbrains.jet.lang.psi.JetNamedFunction; +import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.java.JvmAbi; import org.jetbrains.jet.plugin.JetLightProjectDescriptor; import org.jetbrains.jet.plugin.PluginTestCaseBase; @@ -99,6 +98,30 @@ public class JetJavaFacadeTest extends LightCodeInsightFixtureTestCase { doTestWrapMethod(true); } + public void testWrapValTopLevelProperty() { + doTestWrapProperty(true, false); + } + + public void testWrapVarPropertyInClass() { + doTestWrapProperty(true, true); + } + + public void testWrapVarPropertyWithAccessorsInTrait() { + doTestWrapProperty(true, true); + } + + public void testWrapVarTopLevelProperty() { + doTestWrapProperty(true, true); + } + + public void testWrapVarPropertyInLocalClass() { + doTestWrapProperty(false, false); + } + + public void testWrapVarTopLevelAccessor() { + doTestWrapPropertyAccessor(true); + } + public void testEa38770() { myFixture.configureByFile(getTestName(true) + ".kt"); @@ -166,6 +189,37 @@ public class JetJavaFacadeTest extends LightCodeInsightFixtureTestCase { } private void doTestWrapMethod(boolean shouldBeWrapped) { + JetNamedFunction jetFunction = getPreparedElement(JetNamedFunction.class); + + // Should not fail! + PsiMethod psiMethod = LightClassUtil.getLightClassMethod(jetFunction); + + checkDeclarationMethodWrapped(shouldBeWrapped, jetFunction, psiMethod); + } + + private void doTestWrapProperty(boolean shouldWrapGetter, boolean shouldWrapSetter) { + JetProperty jetProperty = getPreparedElement(JetProperty.class); + + // Should not fail! + LightClassUtil.PropertyAccessorsPsiMethods propertyAccessors = LightClassUtil.getLightClassPropertyMethods(jetProperty); + + JetPropertyAccessor getter = jetProperty.getGetter(); + JetPropertyAccessor setter = jetProperty.getSetter(); + + checkDeclarationMethodWrapped(shouldWrapGetter, getter != null ? getter : jetProperty, propertyAccessors.getGetter()); + checkDeclarationMethodWrapped(shouldWrapSetter, setter != null ? setter : jetProperty, propertyAccessors.getSetter()); + } + + private void doTestWrapPropertyAccessor(boolean shouldWrapAccessor) { + JetPropertyAccessor jetPropertyAccessor = getPreparedElement(JetPropertyAccessor.class); + + // Should not fail! + PsiMethod propertyAccessors = LightClassUtil.getLightClassAccessorMethod(jetPropertyAccessor); + checkDeclarationMethodWrapped(shouldWrapAccessor, jetPropertyAccessor, propertyAccessors); + } + + @NotNull + private T getPreparedElement(Class elementClass) { myFixture.configureByFile(getTestName(true) + ".kt"); int offset = myFixture.getEditor().getCaretModel().getOffset(); @@ -173,16 +227,20 @@ public class JetJavaFacadeTest extends LightCodeInsightFixtureTestCase { 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); + T caretElement = PsiTreeUtil.getParentOfType(elementAt, elementClass); + assertNotNull( + String.format("Caret should be placed to element of type: %s, but was at element '%s' of type %s", + elementClass, elementAt, elementAt.getClass()), + caretElement); - // Should not fail! - PsiMethod psiMethod = LightClassUtil.getLightClassMethod(jetFunction); + return caretElement; + } + private static void checkDeclarationMethodWrapped(boolean shouldBeWrapped, JetDeclaration declaration, PsiMethod psiMethod) { if (shouldBeWrapped) { - assertNotNull(String.format("Failed to wrap jetFunction '%s' to method", jetFunction.getText()), psiMethod); + assertNotNull(String.format("Failed to wrap declaration '%s' to method", declaration.getText()), psiMethod); assertInstanceOf(psiMethod, PsiCompiledElement.class); - assertEquals("Invalid original element for generated method", ((PsiCompiledElement) psiMethod).getMirror(), jetFunction); + assertEquals("Invalid original element for generated method", ((PsiCompiledElement) psiMethod).getMirror(), declaration); } else { assertNull("There should be no wrapper for given method", psiMethod);