From 59c94732a7a6ac2263230bb443e05d4b076dc242 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Tue, 25 Dec 2012 16:02:49 +0400 Subject: [PATCH] Light class support for nested classes --- .../jet/asJava/KotlinLightClass.java | 75 +++++++++++++++---- .../asJava/KotlinLightClassForPackage.java | 35 +++++---- .../jet/asJava/KotlinLightClassProvider.java | 75 +++++++------------ 3 files changed, 107 insertions(+), 78 deletions(-) diff --git a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClass.java b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClass.java index 3d02345cd56..81aebda32ce 100644 --- a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClass.java +++ b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClass.java @@ -16,41 +16,86 @@ package org.jetbrains.jet.asJava; +import com.intellij.openapi.util.Key; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; +import com.intellij.psi.impl.java.stubs.PsiJavaFileStub; import com.intellij.psi.impl.light.AbstractLightClass; import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.CachedValuesManager; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.psi.JetClassBody; +import org.jetbrains.jet.lang.psi.JetClassObject; import org.jetbrains.jet.lang.psi.JetClassOrObject; import org.jetbrains.jet.lang.resolve.java.JetJavaMirrorMarker; import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.plugin.JetLanguage; public class KotlinLightClass extends AbstractLightClass implements JetJavaMirrorMarker { + private final static Key> JAVA_API_STUB = Key.create("JAVA_API_STUB"); - private final FqName fqName; - private final JetClassOrObject jetClassOrObject; - private final CachedValue delegate; + private final FqName classFqName; // FqName of (possibly inner) class + private final JetClassOrObject outermostClassOrObject; // outermost parent of this class (may be equal to this class itself) + private PsiClass delegate; - protected KotlinLightClass(@NotNull PsiManager manager, @NotNull FqName name, @NotNull JetClassOrObject object) { + protected KotlinLightClass(@NotNull PsiManager manager, @NotNull FqName name, @NotNull JetClassOrObject classOrObject) { super(manager, JetLanguage.INSTANCE); - this.fqName = name; - this.jetClassOrObject = object; - KotlinLightClassProvider stubProvider = KotlinLightClassProvider.createForDeclaredClass(getProject(), fqName, jetClassOrObject); - this.delegate = CachedValuesManager.getManager(getProject()).createCachedValue(stubProvider); - } - - @NotNull - @Override - public PsiClass getDelegate() { - return delegate.getValue(); + this.classFqName = name; + this.outermostClassOrObject = getOutermostClassOrObject(classOrObject); + assert outermostClassOrObject != null : "Attempt to build a light class for a local class: " + classOrObject.getText(); } @NotNull @Override public PsiElement copy() { - return new KotlinLightClass(getManager(), fqName, jetClassOrObject); + // It's fine to pass outermostClassOrObject, because getOutermostClassOrObject() will return the same thing + return new KotlinLightClass(getManager(), classFqName, outermostClassOrObject); + } + + @NotNull + @Override + public PsiClass getDelegate() { + if (delegate == null) { + PsiJavaFileStub javaFileStub = CachedValuesManager.getManager(getProject()).getCachedValue( + outermostClassOrObject, + JAVA_API_STUB, + KotlinLightClassProvider.createForDeclaredTopLevelClass(outermostClassOrObject), + /*trackValue = */true); + + PsiClass psiClass = LightClassUtil.findClass(classFqName, javaFileStub); + if (psiClass == null) { + throw new IllegalStateException("Class was not found " + classFqName + " in " + outermostClassOrObject.getText()); + } + delegate = psiClass; + } + + return delegate; + } + + @Nullable + private static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) { + JetClassOrObject current = classOrObject; + while (true) { + PsiElement parent = current.getParent(); + assert classOrObject.getParent() != null : "Class with no parent: " + classOrObject.getText(); + + if (parent instanceof PsiFile) { + return current; + } + if (parent instanceof JetClassObject) { + // current class IS the class object declaration + parent = parent.getParent(); + assert parent instanceof JetClassBody : "Parent of class object is not a class body: " + parent; + } + if (!(parent instanceof JetClassBody)) { + // It is a local class, no legitimate outer + return null; + } + + current = (JetClassOrObject) parent.getParent(); + } } } diff --git a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClassForPackage.java b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClassForPackage.java index 17a4f98ac10..dcb8d84bb02 100644 --- a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClassForPackage.java +++ b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClassForPackage.java @@ -23,6 +23,7 @@ import com.intellij.openapi.util.Comparing; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiManager; +import com.intellij.psi.impl.java.stubs.PsiJavaFileStub; import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.CachedValuesManager; import com.intellij.util.IncorrectOperationException; @@ -33,6 +34,7 @@ import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.psi.JetNamespaceHeader; import org.jetbrains.jet.lang.psi.JetPsiFactory; import org.jetbrains.jet.lang.resolve.java.JetJavaMirrorMarker; +import org.jetbrains.jet.lang.resolve.java.JvmAbi; import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.resolve.name.Name; @@ -41,31 +43,31 @@ import java.util.Collection; public class KotlinLightClassForPackage extends KotlinLightClassForPackageBase implements JetJavaMirrorMarker { - private final FqName fqName; + private final FqName packageFqName; private final Collection files; private final int hashCode; - private final CachedValue delegate; + private final CachedValue javaFileStub; - public KotlinLightClassForPackage(@NotNull PsiManager manager, @NotNull FqName fqName, @NotNull Collection files) { + public KotlinLightClassForPackage(@NotNull PsiManager manager, @NotNull FqName packageFqName, @NotNull Collection files) { super(manager); - this.fqName = fqName; - assert !files.isEmpty() : "No files for package " + fqName; + this.packageFqName = packageFqName; + assert !files.isEmpty() : "No files for package " + packageFqName; this.files = Sets.newHashSet(files); // needed for hashCode this.hashCode = computeHashCode(); - KotlinLightClassProvider stubProvider = KotlinLightClassProvider.createForPackageClass(getProject(), fqName, files); - this.delegate = CachedValuesManager.getManager(getProject()).createCachedValue(stubProvider); + KotlinLightClassProvider stubProvider = KotlinLightClassProvider.createForPackageClass(getProject(), packageFqName, files); + this.javaFileStub = CachedValuesManager.getManager(getProject()).createCachedValue(stubProvider); } @Nullable @Override public String getName() { - return fqName.shortName().getName(); + return packageFqName.shortName().getName(); } @Nullable @Override public String getQualifiedName() { - return fqName.getFqName(); + return packageFqName.getFqName(); } @Override @@ -83,13 +85,18 @@ public class KotlinLightClassForPackage extends KotlinLightClassForPackageBase i @NotNull @Override public PsiElement copy() { - return new KotlinLightClassForPackage(getManager(), fqName, files); + return new KotlinLightClassForPackage(getManager(), packageFqName, files); } @NotNull @Override public PsiClass getDelegate() { - return delegate.getValue(); + FqName packageClassFqName = packageFqName.child(Name.identifier(JvmAbi.PACKAGE_CLASS)); + PsiClass psiClass = LightClassUtil.findClass(packageClassFqName, javaFileStub.getValue()); + if (psiClass == null) { + throw new IllegalStateException("Package class was not found " + packageFqName); + } + return psiClass; } @NotNull @@ -108,7 +115,7 @@ public class KotlinLightClassForPackage extends KotlinLightClassForPackageBase i for (JetFile file : files) { JetNamespaceHeader header = file.getNamespaceHeader(); assert header != null : "Cannot rename a package of a script"; - String newHeaderText = "package " + fqName.parent().child(Name.identifier(name)).toString(); + String newHeaderText = "package " + packageFqName.parent().child(Name.identifier(name)).toString(); JetNamespaceHeader newHeader = JetPsiFactory.createFile(getProject(), newHeaderText).getNamespaceHeader(); assert newHeader != null; header.replace(newHeader); @@ -135,7 +142,7 @@ public class KotlinLightClassForPackage extends KotlinLightClassForPackageBase i private int computeHashCode() { int result = getManager().hashCode(); result = 31 * result + files.hashCode(); - result = 31 * result + fqName.hashCode(); + result = 31 * result + packageFqName.hashCode(); return result; } @@ -151,7 +158,7 @@ public class KotlinLightClassForPackage extends KotlinLightClassForPackageBase i if (this.hashCode != lightClass.hashCode) return false; if (getManager() != lightClass.getManager()) return false; if (!files.equals(lightClass.files)) return false; - if (!fqName.equals(lightClass.fqName)) return false; + if (!packageFqName.equals(lightClass.packageFqName)) return false; return true; } diff --git a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClassProvider.java b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClassProvider.java index 338d9816e30..60668ab5c94 100644 --- a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClassProvider.java +++ b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/KotlinLightClassProvider.java @@ -20,13 +20,10 @@ import com.google.common.collect.Lists; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiClass; -import com.intellij.psi.impl.java.stubs.PsiClassStub; +import com.intellij.psi.impl.java.stubs.PsiJavaFileStub; import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl; -import com.intellij.psi.stubs.PsiFileStub; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.PsiModificationTracker; @@ -41,24 +38,23 @@ import org.jetbrains.jet.codegen.state.Progress; import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor; import org.jetbrains.jet.lang.psi.JetClassOrObject; import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.psi.JetPsiUtil; import org.jetbrains.jet.lang.resolve.BindingContext; -import org.jetbrains.jet.lang.resolve.java.JvmAbi; import org.jetbrains.jet.lang.resolve.name.FqName; -import org.jetbrains.jet.lang.resolve.name.Name; import java.util.Collection; import java.util.Collections; import java.util.List; -public class KotlinLightClassProvider implements CachedValueProvider { +public class KotlinLightClassProvider implements CachedValueProvider { + @NotNull public static KotlinLightClassProvider createForPackageClass( @NotNull Project project, @NotNull FqName packageFqName, @NotNull Collection files ) { - FqName packageClassFqName = packageFqName.child(Name.identifier(JvmAbi.PACKAGE_CLASS)); - return new KotlinLightClassProvider(project, packageClassFqName, files, new StubGenerationStrategy.NoDeclaredClasses() { + return new KotlinLightClassProvider(project, packageFqName, files, new StubGenerationStrategy.NoDeclaredClasses() { @Override public void generate(GenerationState state, GenerationStrategy strategy) { KotlinCodegenFacade.compileCorrectFiles(state, strategy, CompilationErrorHandler.THROW_EXCEPTION); @@ -68,22 +64,24 @@ public class KotlinLightClassProvider implements CachedValueProvider { }); } - public static KotlinLightClassProvider createForDeclaredClass( - @NotNull Project project, - @NotNull final FqName classFqName, - @NotNull final JetClassOrObject jetClassOrObject + @NotNull + public static KotlinLightClassProvider createForDeclaredTopLevelClass( + @NotNull final JetClassOrObject classOrObject ) { - JetFile file = (JetFile) jetClassOrObject.getContainingFile(); - return new KotlinLightClassProvider(project, classFqName, Collections.singletonList(file), new StubGenerationStrategy.WithDeclaredClasses() { + JetFile file = (JetFile) classOrObject.getContainingFile(); + assert classOrObject.getParent() == file : "Not a top-level class: " + classOrObject.getText(); + + final FqName packageFqName = JetPsiUtil.getFQName(file); + + return new KotlinLightClassProvider(classOrObject.getProject(), packageFqName, Collections.singletonList(file), new StubGenerationStrategy.WithDeclaredClasses() { @Override public void generate(GenerationState state, GenerationStrategy strategy) { - FqName packageFqName = classFqName.parent(); NamespaceCodegen namespaceCodegen = state.getFactory().forNamespace(packageFqName, state.getFiles()); NamespaceDescriptor namespaceDescriptor = state.getBindingContext().get(BindingContext.FQNAME_TO_NAMESPACE_DESCRIPTOR, packageFqName); - assert namespaceDescriptor != null : "No package descriptor for " + packageFqName + " for class " + jetClassOrObject.getText(); - namespaceCodegen.generateClassOrObject(namespaceDescriptor, jetClassOrObject); + assert namespaceDescriptor != null : "No package descriptor for " + packageFqName + " for class " + classOrObject.getText(); + namespaceCodegen.generateClassOrObject(namespaceDescriptor, classOrObject); state.getFactory().files(); } @@ -93,26 +91,26 @@ public class KotlinLightClassProvider implements CachedValueProvider { private static final Logger LOG = Logger.getInstance(KotlinLightClassProvider.class); private final Collection files; - private final FqName fqName; + private final FqName packageFqName; private final Project project; private final StubGenerationStrategy stubGenerationStrategy; private KotlinLightClassProvider( @NotNull Project project, - @NotNull FqName fqName, + @NotNull FqName packageFqName, @NotNull Collection files, @NotNull StubGenerationStrategy stubGenerationStrategy ) { this.files = files; - this.fqName = fqName; + this.packageFqName = packageFqName; this.project = project; this.stubGenerationStrategy = stubGenerationStrategy; } @Nullable @Override - public Result compute() { - checkForBuiltIns(fqName, files); + public Result compute() { + checkForBuiltIns(packageFqName, files); LightClassConstructionContext context = LightClassGenerationSupport.getInstance(project).analyzeRelevantCode(files); @@ -121,7 +119,7 @@ public class KotlinLightClassProvider implements CachedValueProvider { throw new IllegalStateException("failed to analyze: " + error, error); } - PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(fqName.parent().getFqName(), true); + PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.getFqName(), true); try { Stack stubStack = new Stack(); @@ -143,26 +141,21 @@ public class KotlinLightClassProvider implements CachedValueProvider { throw e; } catch (RuntimeException e) { - logErrorWithOSInfo(e, fqName, null); + logErrorWithOSInfo(e, packageFqName, null); throw e; } - PsiClass psiClass = findClass(fqName, javaFileStub); - if (psiClass == null) { - throw new IllegalStateException("Class was not found " + fqName + " for files " + files); - } - List dependencies = Lists.newArrayList(files); dependencies.add(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT); - return Result.create(psiClass, files); + return Result.create(javaFileStub, dependencies); } - private static void checkForBuiltIns(@NotNull FqName classFqName, @NotNull Collection files) { + private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection files) { for (JetFile file : files) { if (LightClassUtil.belongsToKotlinBuiltIns(file)) { // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways // If it fails later, there will be an exception logged - logErrorWithOSInfo(null, classFqName, file.getVirtualFile()); + logErrorWithOSInfo(null, fqName, file.getVirtualFile()); } } } @@ -176,22 +169,6 @@ public class KotlinLightClassProvider implements CachedValueProvider { cause); } - @Nullable - private static PsiClass findClass(@NotNull FqName fqn, @NotNull StubElement stub) { - if (stub instanceof PsiClassStub && Comparing.equal(fqn.getFqName(), ((PsiClassStub) stub).getQualifiedName())) { - return (PsiClass)stub.getPsi(); - } - - if (stub instanceof PsiClassStub || stub instanceof PsiFileStub) { - for (StubElement child : stub.getChildrenStubs()) { - PsiClass answer = findClass(fqn, child); - if (answer != null) return answer; - } - } - - return null; - } - private interface StubGenerationStrategy { boolean generateDeclaredClasses(); void generate(GenerationState state, GenerationStrategy strategy);