EA-36473 Wrap method for JetLightClass with invalid fq name

This commit is contained in:
Nikolay Krasko
2012-09-11 21:25:38 +04:00
parent 9a645dca3e
commit b6e2381861
13 changed files with 309 additions and 33 deletions
@@ -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;
}
}
@@ -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.<ConstructorDescriptor>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);
}
}
@@ -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;
}
@@ -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;
}
@@ -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<PsiReference, Re
}
}
}
else if (element instanceof JetFunction) {
final JetFunction function = (JetFunction) element;
else if (element instanceof JetNamedFunction) {
final JetNamedFunction function = (JetNamedFunction) element;
final String name = function.getName();
if (function.getParent() instanceof JetClassBody && name != null) {
if (name != null) {
final PsiMethod method = ApplicationManager.getApplication().runReadAction(new Computable<PsiMethod>() {
@Override
public PsiMethod compute() {
@@ -57,13 +56,9 @@ public class KotlinReferencesSearcher extends QueryExecutorBase<PsiReference, Re
}
});
if (method != null) {
PsiMethod target = JetLightClass.wrapMethod((JetFunction) element);
if (target != null) {
queryParameters.getOptimizer().searchWord(name, queryParameters.getScope(), true, target);
}
queryParameters.getOptimizer().searchWord(name, queryParameters.getScope(), true, method);
}
}
}
}
}
@@ -0,0 +1,7 @@
fun test() {
class Test() {
fun <caret>internalFun() {
}
}
}
@@ -0,0 +1,5 @@
val a = object {
fun <caret>testing() {
}
}
@@ -0,0 +1,6 @@
class Test {
class object {
fun <caret>testing() {
}
}
}
@@ -0,0 +1,3 @@
object SomeObject {
fun <caret>testingInObjectNew() = 12
}
@@ -0,0 +1,4 @@
trait Some {
fun <caret>some() {
}
}
@@ -0,0 +1,3 @@
trait Some {
fun <caret>some()
}
@@ -0,0 +1,2 @@
fun <caret>test() {
}
@@ -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);
}
}
}