Light class support for nested classes

This commit is contained in:
Andrey Breslav
2012-12-25 16:02:49 +04:00
parent a577ccabab
commit 59c94732a7
3 changed files with 107 additions and 78 deletions
@@ -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<CachedValue<PsiJavaFileStub>> JAVA_API_STUB = Key.create("JAVA_API_STUB");
private final FqName fqName;
private final JetClassOrObject jetClassOrObject;
private final CachedValue<PsiClass> 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();
}
}
}
@@ -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<JetFile> files;
private final int hashCode;
private final CachedValue<PsiClass> delegate;
private final CachedValue<PsiJavaFileStub> javaFileStub;
public KotlinLightClassForPackage(@NotNull PsiManager manager, @NotNull FqName fqName, @NotNull Collection<JetFile> files) {
public KotlinLightClassForPackage(@NotNull PsiManager manager, @NotNull FqName packageFqName, @NotNull Collection<JetFile> 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;
}
@@ -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<PsiClass> {
public class KotlinLightClassProvider implements CachedValueProvider<PsiJavaFileStub> {
@NotNull
public static KotlinLightClassProvider createForPackageClass(
@NotNull Project project,
@NotNull FqName packageFqName,
@NotNull Collection<JetFile> 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<PsiClass> {
});
}
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<PsiClass> {
private static final Logger LOG = Logger.getInstance(KotlinLightClassProvider.class);
private final Collection<JetFile> 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<JetFile> files,
@NotNull StubGenerationStrategy stubGenerationStrategy
) {
this.files = files;
this.fqName = fqName;
this.packageFqName = packageFqName;
this.project = project;
this.stubGenerationStrategy = stubGenerationStrategy;
}
@Nullable
@Override
public Result<PsiClass> compute() {
checkForBuiltIns(fqName, files);
public Result<PsiJavaFileStub> compute() {
checkForBuiltIns(packageFqName, files);
LightClassConstructionContext context = LightClassGenerationSupport.getInstance(project).analyzeRelevantCode(files);
@@ -121,7 +119,7 @@ public class KotlinLightClassProvider implements CachedValueProvider<PsiClass> {
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<StubElement> stubStack = new Stack<StubElement>();
@@ -143,26 +141,21 @@ public class KotlinLightClassProvider implements CachedValueProvider<PsiClass> {
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<Object> dependencies = Lists.<Object>newArrayList(files);
dependencies.add(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
return Result.create(psiClass, files);
return Result.<PsiJavaFileStub>create(javaFileStub, dependencies);
}
private static void checkForBuiltIns(@NotNull FqName classFqName, @NotNull Collection<JetFile> files) {
private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> 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<PsiClass> {
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);