Light class support for nested classes
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-14
@@ -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;
|
||||
}
|
||||
|
||||
+26
-49
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user