Flexible types supported in Extract Function when used from debugger/evaluate expression

This commit is contained in:
Andrey Breslav
2014-10-17 14:07:13 +04:00
committed by Pavel V. Talanov
parent 925177f3d8
commit d4d909de5f
6 changed files with 56 additions and 33 deletions
@@ -42,8 +42,8 @@ public fun JetElement.getLazyResolveSession(): ResolveSessionForBodies {
return KotlinCacheService.getInstance(getProject()).getLazyResolveSession(this) return KotlinCacheService.getInstance(getProject()).getLazyResolveSession(this)
} }
public fun JetElement.getAnalysisResults(): AnalyzeExhaust { public fun JetElement.getAnalysisResults(vararg extraFiles: JetFile): AnalyzeExhaust {
return KotlinCacheService.getInstance(getProject()).getAnalysisResults(listOf(this)) return KotlinCacheService.getInstance(getProject()).getAnalysisResults(listOf(this) + extraFiles.toList())
} }
public fun JetElement.getBindingContext(): BindingContext { public fun JetElement.getBindingContext(): BindingContext {
@@ -105,10 +105,11 @@ public class KotlinCacheService(val project: Project) {
private fun getGlobalCache(platform: TargetPlatform) = globalCachesPerPlatform[platform]!!.modulesCache private fun getGlobalCache(platform: TargetPlatform) = globalCachesPerPlatform[platform]!!.modulesCache
private fun getGlobalLibrariesCache(platform: TargetPlatform) = globalCachesPerPlatform[platform]!!.librariesCache private fun getGlobalLibrariesCache(platform: TargetPlatform) = globalCachesPerPlatform[platform]!!.librariesCache
private val syntheticFileCaches = object : SLRUCache<JetFile, KotlinResolveCache>(2, 3) { private val syntheticFileCaches = object : SLRUCache<Set<JetFile>, KotlinResolveCache>(2, 3) {
override fun createValue(file: JetFile?): KotlinResolveCache { override fun createValue(files: Set<JetFile>): KotlinResolveCache {
val targetPlatform = TargetPlatformDetector.getPlatform(file!!) // we assume that all files come from the same module
val syntheticFileModule = file.getModuleInfo() val targetPlatform = files.map { TargetPlatformDetector.getPlatform(it) }.toSet().single()
val syntheticFileModule = files.map { it.getModuleInfo() }.toSet().single()
return when { return when {
syntheticFileModule is ModuleSourceInfo -> { syntheticFileModule is ModuleSourceInfo -> {
val dependentModules = syntheticFileModule.getDependentModules() val dependentModules = syntheticFileModule.getDependentModules()
@@ -116,7 +117,7 @@ public class KotlinCacheService(val project: Project) {
project, project,
globalResolveSessionProvider( globalResolveSessionProvider(
targetPlatform, targetPlatform,
syntheticFiles = listOf(file), syntheticFiles = files,
reuseDataFromCache = getGlobalCache(targetPlatform), reuseDataFromCache = getGlobalCache(targetPlatform),
moduleFilter = { it in dependentModules }, moduleFilter = { it in dependentModules },
dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT)
@@ -129,7 +130,7 @@ public class KotlinCacheService(val project: Project) {
project, project,
globalResolveSessionProvider( globalResolveSessionProvider(
targetPlatform, targetPlatform,
syntheticFiles = listOf(file), syntheticFiles = files,
reuseDataFromCache = getGlobalLibrariesCache(targetPlatform), reuseDataFromCache = getGlobalLibrariesCache(targetPlatform),
moduleFilter = { it == syntheticFileModule }, moduleFilter = { it == syntheticFileModule },
dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT)
@@ -141,12 +142,12 @@ public class KotlinCacheService(val project: Project) {
//NOTE: this code should not be called for sdk or library classes //NOTE: this code should not be called for sdk or library classes
// currently the only known scenario is when we cannot determine that file is a library source // currently the only known scenario is when we cannot determine that file is a library source
// (file under both classes and sources root) // (file under both classes and sources root)
LOG.warn("Creating cache with synthetic file ($file) in classes of library $syntheticFileModule") LOG.warn("Creating cache with synthetic files ($files) in classes of library $syntheticFileModule")
KotlinResolveCache( KotlinResolveCache(
project, project,
globalResolveSessionProvider( globalResolveSessionProvider(
targetPlatform, targetPlatform,
syntheticFiles = listOf(file), syntheticFiles = files,
moduleFilter = { true }, moduleFilter = { true },
dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT)
) )
@@ -158,9 +159,9 @@ public class KotlinCacheService(val project: Project) {
} }
} }
private fun getCacheForSyntheticFile(file: JetFile): KotlinResolveCache { private fun getCacheForSyntheticFile(files: Set<JetFile>): KotlinResolveCache {
return synchronized(syntheticFileCaches) { return synchronized(syntheticFileCaches) {
syntheticFileCaches[file] syntheticFileCaches[files]
} }
} }
@@ -171,7 +172,7 @@ public class KotlinCacheService(val project: Project) {
public fun getLazyResolveSession(element: JetElement): ResolveSessionForBodies { public fun getLazyResolveSession(element: JetElement): ResolveSessionForBodies {
val file = element.getContainingJetFile() val file = element.getContainingJetFile()
if (!ProjectRootsUtil.isInProjectSource(file)) { if (!ProjectRootsUtil.isInProjectSource(file)) {
return getCacheForSyntheticFile(file).getLazyResolveSession(file) return getCacheForSyntheticFile(setOf(file)).getLazyResolveSession(file)
} }
return getGlobalLazyResolveSession(file, TargetPlatformDetector.getPlatform(file)) return getGlobalLazyResolveSession(file, TargetPlatformDetector.getPlatform(file))
@@ -180,11 +181,15 @@ public class KotlinCacheService(val project: Project) {
public fun getAnalysisResults(elements: Collection<JetElement>): AnalyzeExhaust { public fun getAnalysisResults(elements: Collection<JetElement>): AnalyzeExhaust {
if (elements.isEmpty()) return AnalyzeExhaust.EMPTY if (elements.isEmpty()) return AnalyzeExhaust.EMPTY
val firstFile = elements.first().getContainingJetFile() val files = elements.map { it.getContainingJetFile() }.toSet()
if (elements.size == 1 && (!ProjectRootsUtil.isInProjectSource(firstFile) && firstFile !is JetCodeFragment)) { if (files.all { !ProjectRootsUtil.isInProjectSource(it) }
return getCacheForSyntheticFile(firstFile).getAnalysisResultsForElements(elements) // TODO: decide what to do with this
// && files.all { it !is JetCodeFragment }
) {
return getCacheForSyntheticFile(files).getAnalysisResultsForElements(elements)
} }
val firstFile = elements.first().getContainingJetFile()
return getGlobalCache(TargetPlatformDetector.getPlatform(firstFile)).getAnalysisResultsForElements(elements) return getGlobalCache(TargetPlatformDetector.getPlatform(firstFile)).getAnalysisResultsForElements(elements)
} }
@@ -71,7 +71,7 @@ import org.jetbrains.jet.lang.resolve.java.structure.impl.JavaClassImpl
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.jet.lang.resolve.DescriptorUtils import org.jetbrains.jet.lang.resolve.DescriptorUtils
import org.jetbrains.jet.codegen.StackValue import org.jetbrains.jet.codegen.StackValue
import org.jetbrains.jet.analyzer.AnalyzeExhaust import org.jetbrains.jet.lang.types.Flexibility
private val RECEIVER_NAME = "\$receiver" private val RECEIVER_NAME = "\$receiver"
private val THIS_NAME = "this" private val THIS_NAME = "this"
@@ -232,9 +232,8 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
return runReadAction { return runReadAction {
val file = createFileForDebugger(codeFragment, extractedFunction) val file = createFileForDebugger(codeFragment, extractedFunction)
file.checkForErrors() val analyzeExhaust = file.checkForErrors()
val analyzeExhaust = file.getAnalysisResults()
val state = GenerationState( val state = GenerationState(
file.getProject(), file.getProject(),
ClassBuilderFactories.BINARIES, ClassBuilderFactories.BINARIES,
@@ -259,7 +258,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
throw EvaluateExceptionUtil.createEvaluateException(e) throw EvaluateExceptionUtil.createEvaluateException(e)
} }
private fun JetFile.checkForErrors() { private fun JetFile.checkForErrors() =
runReadAction { runReadAction {
try { try {
AnalyzingUtils.checkForSyntacticErrors(this) AnalyzingUtils.checkForSyntacticErrors(this)
@@ -268,7 +267,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
throw EvaluateExceptionUtil.createEvaluateException(e.getMessage()) throw EvaluateExceptionUtil.createEvaluateException(e.getMessage())
} }
val analyzeExhaust = this.getAnalysisResults() val analyzeExhaust = this.getAnalysisResults(createFlexibleTypesFile())
if (analyzeExhaust.isError()) { if (analyzeExhaust.isError()) {
throw EvaluateExceptionUtil.createEvaluateException(analyzeExhaust.getError()) throw EvaluateExceptionUtil.createEvaluateException(analyzeExhaust.getError())
} }
@@ -277,8 +276,9 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
bindingContext.getDiagnostics().firstOrNull { it.getSeverity() == Severity.ERROR }?.let { bindingContext.getDiagnostics().firstOrNull { it.getSeverity() == Severity.ERROR }?.let {
throw EvaluateExceptionUtil.createEvaluateException(DefaultErrorMessages.RENDERER.render(it)) throw EvaluateExceptionUtil.createEvaluateException(DefaultErrorMessages.RENDERER.render(it))
} }
analyzeExhaust
} }
}
} }
} }
@@ -304,12 +304,28 @@ private fun createFileForDebugger(codeFragment: JetCodeFragment,
assert(extractedFunctionText != null, "Text of extracted function shouldn't be null") assert(extractedFunctionText != null, "Text of extracted function shouldn't be null")
fileText = fileText.replace("!FUNCTION!", extractedFunction.getText()!!) fileText = fileText.replace("!FUNCTION!", extractedFunction.getText()!!)
val virtualFile = LightVirtualFile("debugFile.kt", JetLanguage.INSTANCE, fileText) val jetFile = codeFragment.createJetFile("debugFile.kt", fileText)
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET)
val jetFile = (PsiFileFactory.getInstance(codeFragment.getProject()) as PsiFileFactoryImpl)
.trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false) as JetFile
jetFile.skipVisibilityCheck = true jetFile.skipVisibilityCheck = true
jetFile.analysisContext = codeFragment return jetFile
}
private fun PsiElement.createFlexibleTypesFile(): JetFile {
return createJetFile(
"FLEXIBLE_TYPES.kt",
"""
package ${Flexibility.FLEXIBLE_TYPE_CLASSIFIER.getPackageFqName()}
public class ${Flexibility.FLEXIBLE_TYPE_CLASSIFIER.getRelativeClassName()}<L, U>
"""
)
}
private fun PsiElement.createJetFile(fileName: String, fileText: String): JetFile {
// Not using JetPsiFactory because we need a virtual file attached to the JetFile
val virtualFile = LightVirtualFile(fileName, JetLanguage.INSTANCE, fileText)
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET)
val jetFile = (PsiFileFactory.getInstance(getProject()) as PsiFileFactoryImpl)
.trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false) as JetFile
jetFile.analysisContext = this
return jetFile return jetFile
} }
@@ -107,7 +107,7 @@ fun getFunctionForExtractedFragment(
} }
return validationResult.descriptor return validationResult.descriptor
.generateDeclaration(ExtractionGeneratorOptions(inTempFile = true)) .generateDeclaration(ExtractionGeneratorOptions(inTempFile = true, flexibleTypesAllowed = true))
.declaration as JetNamedFunction .declaration as JetNamedFunction
} }
@@ -318,7 +318,8 @@ data class ExtractableCodeDescriptor(
data class ExtractionGeneratorOptions( data class ExtractionGeneratorOptions(
val inTempFile: Boolean = false, val inTempFile: Boolean = false,
val extractAsProperty: Boolean = false val extractAsProperty: Boolean = false,
val flexibleTypesAllowed: Boolean = false
) { ) {
class object { class object {
val DEFAULT = ExtractionGeneratorOptions() val DEFAULT = ExtractionGeneratorOptions()
@@ -56,7 +56,6 @@ import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange
import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange.Match import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange.Match
import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult.WeaklyMatched import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult.WeaklyMatched
import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult.StronglyMatched import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult.StronglyMatched
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody
import org.jetbrains.jet.plugin.util.IdeDescriptorRenderers import org.jetbrains.jet.plugin.util.IdeDescriptorRenderers
import org.jetbrains.jet.lang.psi.psiUtil.parents import org.jetbrains.jet.lang.psi.psiUtil.parents
import java.util.ArrayList import java.util.ArrayList
@@ -64,7 +63,9 @@ import java.util.ArrayList
fun ExtractableCodeDescriptor.getDeclarationText( fun ExtractableCodeDescriptor.getDeclarationText(
options: ExtractionGeneratorOptions = ExtractionGeneratorOptions.DEFAULT, options: ExtractionGeneratorOptions = ExtractionGeneratorOptions.DEFAULT,
withBody: Boolean = true, withBody: Boolean = true,
descriptorRenderer: DescriptorRenderer = IdeDescriptorRenderers.SOURCE_CODE descriptorRenderer: DescriptorRenderer = if (options.flexibleTypesAllowed)
DescriptorRenderer.FLEXIBLE_TYPES_FOR_CODE
else IdeDescriptorRenderers.SOURCE_CODE
): String { ): String {
if (!canGenerateProperty() && options.extractAsProperty) { if (!canGenerateProperty() && options.extractAsProperty) {
throw IllegalArgumentException("Can't generate property: ${extractionData.getCodeFragmentText()}") throw IllegalArgumentException("Can't generate property: ${extractionData.getCodeFragmentText()}")
@@ -344,7 +345,7 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
fun createDeclaration(): JetNamedDeclaration { fun createDeclaration(): JetNamedDeclaration {
return with(extractionData) { return with(extractionData) {
if (options.inTempFile) { if (options.inTempFile) {
createTemporaryDeclaration("${getDeclarationText()}\n") createTemporaryDeclaration("${getDeclarationText(options)}\n")
} }
else { else {
psiFactory.createDeclaration(getDeclarationText(options)) psiFactory.createDeclaration(getDeclarationText(options))
@@ -276,6 +276,6 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
@NotNull @NotNull
public ExtractionGeneratorOptions getGeneratorOptions() { public ExtractionGeneratorOptions getGeneratorOptions() {
return new ExtractionGeneratorOptions(false, propertyCheckBox.isSelected()); return new ExtractionGeneratorOptions(false, propertyCheckBox.isSelected(), false);
} }
} }