From 7719df10027badb005a0d9e03df5215057f1ce6e Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Thu, 18 Oct 2012 17:22:57 +0400 Subject: [PATCH] Properly detect Kotlin built-ins: Built-in classes like jet.Int and jet.Iterable are loaded from source files by KotlinBuiltIns class. It makes no sense to create JetLightClass instances for such classes, moreover, it causes runtime failures. Now we check the location a file was loaded from before creating a light class, and skip if it is a built-in. We also assert this in teh actual generation of light class bodies, and print out OS information if something fails, because our detection strategy may depend on OS-specific behavior. --- .../jet/lang/types/lang/KotlinBuiltIns.java | 40 ++++------- .../jetbrains/jet/asJava/JetLightClass.java | 44 ++++++++++-- .../jetbrains/jet/asJava/LightClassUtil.java | 72 +++++++++++++++++++ .../jetbrains/jet/utils/KotlinVfsUtil.java | 59 +++++++++++++++ 4 files changed, 183 insertions(+), 32 deletions(-) create mode 100644 compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/LightClassUtil.java create mode 100644 compiler/util/src/org/jetbrains/jet/utils/KotlinVfsUtil.java diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/lang/KotlinBuiltIns.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/lang/KotlinBuiltIns.java index f710b325f74..75d09c4829e 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/lang/KotlinBuiltIns.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/lang/KotlinBuiltIns.java @@ -58,25 +58,26 @@ public class KotlinBuiltIns { public static final Name UNIT_ALIAS = Name.identifier("Unit"); public static final JetScope STUB = JetScope.EMPTY; + public static final String BUILT_INS_DIR = "jet"; private static final String BUILT_INS_PACKAGE_NAME_STRING = "jet"; private static final Name BUILT_INS_PACKAGE_NAME = Name.identifier(BUILT_INS_PACKAGE_NAME_STRING); public static final FqName BUILT_INS_PACKAGE_FQ_NAME = FqName.topLevel(BUILT_INS_PACKAGE_NAME); private static final List LIBRARY_FILES = Arrays.asList( - "jet/Library.jet", - "jet/Numbers.jet", - "jet/Ranges.jet", - "jet/Iterables.jet", - "jet/Iterators.jet", - "jet/Arrays.jet", - "jet/Enum.jet", - "jet/Collections.jet", - "jet/Any.jet", - "jet/ExtensionFunctions.jet", - "jet/Functions.jet", - "jet/Nothing.jet", - "jet/Tuples.jet", - "jet/Unit.jet" + BUILT_INS_DIR + "/Library.jet", + BUILT_INS_DIR + "/Numbers.jet", + BUILT_INS_DIR + "/Ranges.jet", + BUILT_INS_DIR + "/Iterables.jet", + BUILT_INS_DIR + "/Iterators.jet", + BUILT_INS_DIR + "/Arrays.jet", + BUILT_INS_DIR + "/Enum.jet", + BUILT_INS_DIR + "/Collections.jet", + BUILT_INS_DIR + "/Any.jet", + BUILT_INS_DIR + "/ExtensionFunctions.jet", + BUILT_INS_DIR + "/Functions.jet", + BUILT_INS_DIR + "/Nothing.jet", + BUILT_INS_DIR + "/Tuples.jet", + BUILT_INS_DIR + "/Unit.jet" ); private static final Map ALIASES = ImmutableMap.builder() @@ -294,22 +295,11 @@ public class KotlinBuiltIns { return getBuiltInsPackage().getQualifiedName(); } - @NotNull - public Collection getAllBuiltInClasses() { - // TODO: used once, but incorrectly in JetShortNamesCache - return getBuiltInsScope().getAllDescriptors(); - } - @NotNull public JetScope getBuiltInsScope() { return getBuiltInsPackage().getMemberScope(); } - public boolean isStandardClass(@NotNull FqName fqName) { - // TODO: this seems to be wrong for ranges, and will certainly be wrong upon renaming jet -> kotlin - return getBuiltInsPackageFqName().equals(fqName.parent()); - } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // GET CLASS diff --git a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JetLightClass.java b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JetLightClass.java index eaa6a5e2ae0..e2c7296de29 100644 --- a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JetLightClass.java +++ b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JetLightClass.java @@ -21,9 +21,12 @@ package org.jetbrains.jet.asJava; import com.intellij.navigation.ItemPresentation; import com.intellij.navigation.ItemPresentationProviders; +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.Key; +import com.intellij.openapi.util.SystemInfo; import com.intellij.psi.*; import com.intellij.psi.impl.PsiClassImplUtil; import com.intellij.psi.impl.java.stubs.PsiClassStub; @@ -50,13 +53,14 @@ 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.KotlinBuiltIns; import org.jetbrains.jet.plugin.JetLanguage; import javax.swing.*; import java.util.Collections; public class JetLightClass extends AbstractLightClass implements JetJavaMirrorMarker { + private static final Logger LOG = Logger.getInstance(JetLightClass.class); + static class JetBadWrapperException extends RuntimeException { private final String context; @@ -92,8 +96,11 @@ public class JetLightClass extends AbstractLightClass implements JetJavaMirrorMa } @Nullable - public static JetLightClass create(PsiManager manager, JetFile file, FqName qualifiedName) { - return KotlinBuiltIns.getInstance().isStandardClass(qualifiedName) ? null : new JetLightClass(manager, file, qualifiedName); + public static JetLightClass create(@NotNull PsiManager manager, @NotNull JetFile file, @NotNull FqName qualifiedName) { + if (LightClassUtil.belongsToKotlinBuiltIns(file)) { + return null; + } + return new JetLightClass(manager, file, qualifiedName); } @Override @@ -167,6 +174,12 @@ public class JetLightClass extends AbstractLightClass implements JetJavaMirrorMa } private PsiJavaFileStub calcStub() { + 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); + } + final PsiJavaFileStubImpl answer = new PsiJavaFileStubImpl(JetPsiUtil.getFQName(file).getFqName(), true); final Project project = getProject(); @@ -205,15 +218,32 @@ public class JetLightClass extends AbstractLightClass implements JetJavaMirrorMa throw new IllegalStateException("failed to analyze: " + context.getError(), context.getError()); } - final GenerationState state = new GenerationState(project, builderFactory, context, Collections.singletonList(file)); - final GenerationStrategy strategy = new LightClassGenerationStrategy(this, stubStack, answer); + try { + GenerationState state = new GenerationState(project, builderFactory, context, Collections.singletonList(file)); + GenerationStrategy strategy = new LightClassGenerationStrategy(this, stubStack, answer); - strategy.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION); - state.getFactory().files(); + strategy.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION); + state.getFactory().files(); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (RuntimeException e) { + logErrorWithOSInfo(e); + throw e; + } return answer; } + private void logErrorWithOSInfo(@Nullable Throwable cause) { + LOG.error( + "Could not generate JetLightClass for " + qualifiedName + " declared in " + file.getVirtualFile().getPath() + "\n" + + "built-ins dir URL is " + LightClassUtil.getBuiltInsDirResourceUrl() + "\n" + + "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION, + cause); + } + @Override public boolean isEquivalentTo(PsiElement another) { return another instanceof PsiClass && Comparing.equal(((PsiClass)another).getQualifiedName(), getQualifiedName()); 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 new file mode 100644 index 00000000000..39da4d1a9ba --- /dev/null +++ b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/LightClassUtil.java @@ -0,0 +1,72 @@ +/* + * 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.asJava; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; +import org.jetbrains.jet.utils.KotlinVfsUtil; + +import java.net.MalformedURLException; +import java.net.URL; + +public class LightClassUtil { + private static final Logger LOG = Logger.getInstance(JetLightClass.class); + private static final String DEFINITION_OF_ANY = "Any.jet"; + + /** + * Checks whether the given file is loaded from the location where Kotlin's built-in classes are defined. + * As of today, this is compiler/frontend/src/jet directory and files such as Any.jet, Nothing.jet etc. + * + * Used to skip JetLightClass creation for built-ins, because built-in classes have no Java counterparts + */ + public static boolean belongsToKotlinBuiltIns(@NotNull JetFile file) { + try { + String jetVfsPathUrl = KotlinVfsUtil.convertFromUrl(getBuiltInsDirResourceUrl()); + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null) { + VirtualFile parent = virtualFile.getParent(); + if (parent != null) { + String fileDirVfsUrl = parent.getUrl() + "/" + DEFINITION_OF_ANY; + if (jetVfsPathUrl.equals(fileDirVfsUrl)) { + return true; + } + } + } + } + catch (MalformedURLException e) { + LOG.error(e); + } + // We deliberately return false on error: who knows what weird URLs we might come across out there + // it would be a pity if no light classes would be created in such cases + return false; + } + + @NotNull + public static URL getBuiltInsDirResourceUrl() { + String pathToAny = "/" + KotlinBuiltIns.BUILT_INS_DIR + "/" + DEFINITION_OF_ANY; + URL url = KotlinBuiltIns.class.getResource(pathToAny); + if (url == null) { + throw new IllegalStateException("Built-ins not found in the classpath: " + pathToAny); + } + return url; + } + + private LightClassUtil() {} +} diff --git a/compiler/util/src/org/jetbrains/jet/utils/KotlinVfsUtil.java b/compiler/util/src/org/jetbrains/jet/utils/KotlinVfsUtil.java new file mode 100644 index 00000000000..cc0e565ba17 --- /dev/null +++ b/compiler/util/src/org/jetbrains/jet/utils/KotlinVfsUtil.java @@ -0,0 +1,59 @@ +/* + * 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.utils; + +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.io.URLUtil; +import org.jetbrains.annotations.NotNull; + +import java.net.MalformedURLException; +import java.net.URL; + +/* + * This code is essentially copied from IntelliJ IDEA's VfsUtil, and slightly restructured + */ +public class KotlinVfsUtil { + private static final String FILE = "file"; + private static final String JAR = "jar"; + private static final String PROTOCOL_DELIMITER = ":"; + + @NotNull + public static String convertFromUrl(@NotNull URL url) throws MalformedURLException { + String protocol = url.getProtocol(); + String path = url.getPath(); + if (JAR.equals(protocol)) { + if (StringUtil.startsWithConcatenationOf(path, FILE, PROTOCOL_DELIMITER)) { + URL subURL = new URL(path); + path = subURL.getPath(); + } + else { + throw new MalformedURLException("Can't parse url: " + url.toExternalForm()); + } + } + if (SystemInfo.isWindows || SystemInfo.isOS2) { + while (path.length() > 0 && path.charAt(0) == '/') { + path = path.substring(1, path.length()); + } + } + + path = URLUtil.unescapePercentSequences(path); + return protocol + "://" + path; + } + + private KotlinVfsUtil() {} +}