Smart completion: auto-casted types supported

This commit is contained in:
Valentin Kipyatkov
2013-11-18 16:02:34 +04:00
parent 7bc8f9f5ff
commit acb5bb2b07
11 changed files with 220 additions and 51 deletions
@@ -2,4 +2,7 @@
<item name='com.google.common.collect.Multimap java.util.Set&lt;K&gt; keySet()'> <item name='com.google.common.collect.Multimap java.util.Set&lt;K&gt; keySet()'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </item>
<item name='com.google.common.collect.SetMultimap java.util.Map&lt;K,java.util.Collection&lt;V&gt;&gt; asMap()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root> </root>
@@ -8,12 +8,12 @@ import org.jetbrains.jet.plugin.project.CancelableResolveSession
import org.jetbrains.jet.lang.types.* import org.jetbrains.jet.lang.types.*
import org.jetbrains.jet.lang.types.checker.JetTypeChecker import org.jetbrains.jet.lang.types.checker.JetTypeChecker
import com.intellij.codeInsight.lookup.* import com.intellij.codeInsight.lookup.*
import java.util.ArrayList
import org.jetbrains.jet.renderer.DescriptorRenderer import org.jetbrains.jet.renderer.DescriptorRenderer
import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.completion.InsertHandler
import org.jetbrains.jet.plugin.completion.handlers.* import org.jetbrains.jet.plugin.completion.handlers.*
import com.intellij.codeInsight.completion.JavaPsiClassReferenceElement import com.google.common.collect.SetMultimap
import java.util.*
import org.jetbrains.jet.lang.resolve.calls.autocasts.*
trait SmartCompletionData{ trait SmartCompletionData{
fun accepts(descriptor: DeclarationDescriptor): Boolean fun accepts(descriptor: DeclarationDescriptor): Boolean
@@ -22,7 +22,17 @@ trait SmartCompletionData{
fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession: CancelableResolveSession): SmartCompletionData? { fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession: CancelableResolveSession): SmartCompletionData? {
val parent = expression.getParent() val parent = expression.getParent()
val expressionWithType = if (parent is JetQualifiedExpression) parent else expression val expressionWithType: JetExpression;
val receiver: JetExpression?
if (parent is JetQualifiedExpression) {
expressionWithType = parent
receiver = parent.getReceiverExpression()
}
else {
expressionWithType = expression
receiver = null
}
val bindingContext = resolveSession.resolveToElement(expressionWithType) val bindingContext = resolveSession.resolveToElement(expressionWithType)
val expectedType: JetType? = bindingContext.get(BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType) val expectedType: JetType? = bindingContext.get(BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType)
if (expectedType == null) return null if (expectedType == null) return null
@@ -31,11 +41,71 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession
val additionalElements = ArrayList<LookupElement>() val additionalElements = ArrayList<LookupElement>()
if (expression == expressionWithType) { // no qualifier if (receiver == null) {
typeInstantiationItems(expectedType, resolveSession, bindingContext).toCollection(additionalElements)
}
val dataFlowInfo = bindingContext.get(BindingContext.EXPRESSION_DATA_FLOW_INFO, expressionWithType)
val (variableToTypes: Map<VariableDescriptor, Collection<JetType>>, notNullVariables: Set<VariableDescriptor>) = processDataFlowInfo(dataFlowInfo, receiver, bindingContext)
fun typesOf(descriptor: DeclarationDescriptor): Iterable<JetType> {
if (descriptor is CallableDescriptor) {
var returnType = descriptor.getReturnType()
if (descriptor is VariableDescriptor) {
if (notNullVariables.contains(descriptor) && returnType != null) {
returnType = TypeUtils.makeNotNullable(returnType!!)
}
val autoCastTypes = variableToTypes[descriptor]
if (autoCastTypes != null && !autoCastTypes.isEmpty()) {
return autoCastTypes + returnType.toList()
}
}
return returnType.toList()
}
else {
return listOf()
}
}
return object: SmartCompletionData {
override fun accepts(descriptor: DeclarationDescriptor)
= !itemsToSkip.contains(descriptor) && typesOf(descriptor).any { JetTypeChecker.INSTANCE.isSubtypeOf(it, expectedType) }
override val additionalElements = additionalElements
}
}
private fun calcItemsToSkip(expression: JetExpression, resolveSession: CancelableResolveSession): Collection<DeclarationDescriptor> {
val parent = expression.getParent()
when(parent) {
is JetProperty -> {
//TODO: this can be filtered out by ordinary completion
if (expression == parent.getInitializer()) {
return resolveSession.resolveToElement(parent).get(BindingContext.DECLARATION_TO_DESCRIPTOR, parent).toList()
}
}
is JetBinaryExpression -> {
if (parent.getRight() == expression && parent.getOperationToken() == JetTokens.EQ) {
val left = parent.getLeft()
if (left is JetReferenceExpression) {
return resolveSession.resolveToElement(left).get(BindingContext.REFERENCE_TARGET, left).toList()
}
}
}
}
return listOf()
}
private fun typeInstantiationItems(expectedType: JetType, resolveSession: CancelableResolveSession, bindingContext: BindingContext): Iterable<LookupElement> {
val typeConstructor: TypeConstructor = expectedType.getConstructor() val typeConstructor: TypeConstructor = expectedType.getConstructor()
val classifier: ClassifierDescriptor? = typeConstructor.getDeclarationDescriptor() val classifier: ClassifierDescriptor? = typeConstructor.getDeclarationDescriptor()
if (classifier is ClassDescriptor) { if (!(classifier is ClassDescriptor)) return listOf()
if (classifier.getModality() != Modality.ABSTRACT){ if (classifier.getModality() == Modality.ABSTRACT) return listOf()
//TODO: check for constructor's visibility
val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, classifier) val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, classifier)
val typeArgs = expectedType.getArguments() val typeArgs = expectedType.getArguments()
@@ -59,52 +129,65 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession
//TODO: very bad code //TODO: very bad code
if (lookupElement is LookupElementBuilder) { if (lookupElement is LookupElementBuilder) {
additionalElements.add(lookupElement.withPresentableText(presentableText).withInsertHandler(insertHandler)) return listOf(lookupElement.withPresentableText(presentableText).withInsertHandler(insertHandler))
} }
else if (lookupElement is JavaPsiClassReferenceElement) { else if (lookupElement is JavaPsiClassReferenceElement) {
additionalElements.add(lookupElement.setPresentableText(presentableText).setInsertHandler(insertHandler)) return listOf(lookupElement.setPresentableText(presentableText).setInsertHandler(insertHandler))
}
}
}
} }
return object: SmartCompletionData{ return listOf()
override fun accepts(descriptor: DeclarationDescriptor): Boolean { }
if (itemsToSkip.contains(descriptor)) return false
if (descriptor is CallableDescriptor) { private data class ProcessDataFlowInfoResult(
val returnType = descriptor.getReturnType() val variableToTypes: Map<VariableDescriptor, Collection<JetType>> = Collections.emptyMap(),
return returnType != null && JetTypeChecker.INSTANCE.isSubtypeOf(returnType, expectedType) val notNullVariables: Set<VariableDescriptor> = Collections.emptySet()
)
private fun processDataFlowInfo(dataFlowInfo: DataFlowInfo?, receiver: JetExpression?, bindingContext: BindingContext): ProcessDataFlowInfoResult {
if (dataFlowInfo != null) {
val dataFlowValueToVariable: (DataFlowValue) -> VariableDescriptor?
if (receiver != null) {
val receiverType = bindingContext.get(BindingContext.EXPRESSION_TYPE, receiver)
if (receiverType != null) {
val receiverId = DataFlowValueFactory.createDataFlowValue(receiver, receiverType, bindingContext).getId()
dataFlowValueToVariable = {(value) ->
val id = value.getId()
if (id is com.intellij.openapi.util.Pair<*, *> && id.first == receiverId) id.second as? VariableDescriptor else null
}
} }
else { else {
return false return ProcessDataFlowInfoResult()
}
}
else {
dataFlowValueToVariable = {(value) -> value.getId() as? VariableDescriptor }
}
val variableToType = HashMap<VariableDescriptor, Collection<JetType>>()
val typeInfo: SetMultimap<DataFlowValue, JetType> = dataFlowInfo.getCompleteTypeInfo()
for ((dataFlowValue, types) in typeInfo.asMap().entrySet()) {
val variable = dataFlowValueToVariable.invoke(dataFlowValue)
if (variable != null) {
variableToType[variable] = types
} }
} }
override val additionalElements: Iterable<LookupElement> = additionalElements val nullabilityInfo: Map<DataFlowValue, Nullability> = dataFlowInfo.getCompleteNullabilityInfo()
val notNullVariables = nullabilityInfo.iterator()
.filter { it.getValue() == Nullability.NOT_NULL }
.map { dataFlowValueToVariable(it.getKey()) }
.filterNotNullTo(HashSet<VariableDescriptor>())
return ProcessDataFlowInfoResult(variableToType, notNullVariables)
} }
return ProcessDataFlowInfoResult()
} }
private fun <T : Any> T?.toList(): List<T> = if (this != null) listOf(this) else listOf() private fun <T : Any> T?.toList(): List<T> = if (this != null) listOf(this) else listOf()
private fun calcItemsToSkip(expression: JetExpression, resolveSession: CancelableResolveSession): Collection<DeclarationDescriptor> { private fun <T> MutableCollection<T>.addAll(iterator: Iterator<T>) {
val parent = expression.getParent() for (item in iterator) {
when(parent) { add(item)
is JetProperty -> {
//TODO: this can be filtered out by ordinary completion
if (expression == parent.getInitializer()) {
return resolveSession.resolveToElement(parent).get(BindingContext.DECLARATION_TO_DESCRIPTOR, parent).toList()
} }
}
is JetBinaryExpression -> {
if (parent.getRight() == expression && parent.getOperationToken() == JetTokens.EQ) {
val left = parent.getLeft()
if (left is JetReferenceExpression) {
return resolveSession.resolveToElement(left).get(BindingContext.REFERENCE_TARGET, left).toList()
}
}
}
}
return listOf()
} }
@@ -0,0 +1,12 @@
open class Foo{
fun f() {
if (this is Bar){
var a : Bar = <caret>
}
}
}
class Bar : Foo
// EXIST: this
@@ -1,4 +1,4 @@
fun f(p: Object) { fun f(p: Any) {
if (p is String){ if (p is String){
var a : String = <caret> var a : String = <caret>
} }
@@ -0,0 +1,10 @@
class Foo(val prop1 : Any, val prop2 : Any){
fun f(p1: Foo, p2: Foo) {
if (p1.prop1 is String && p2.prop2 is String && prop2 is String){
var a : String = p1.<caret>
}
}
}
// EXIST: prop1
// ABSENT: prop2
@@ -0,0 +1,8 @@
fun String?.foo(){
if (this != null){
val s : String = <caret>
}
}
// EXIST: this
@@ -0,0 +1,7 @@
fun f(p: String?) {
if (p != null){
var a : String = <caret>
}
}
// EXIST: p
@@ -0,0 +1,10 @@
class Foo(val prop1 : String?, val prop2 : String?){
fun f(p1: Foo, p2: Foo) {
if (p1.prop1 != null && p2.prop2 != null && prop2 != null){
var a : String = p1.<caret>
}
}
}
// EXIST: prop1
// ABSENT: prop2
@@ -0,0 +1,11 @@
fun fail() : Nothing{
throw RuntimeException()
}
fun f(p : String) {
var a : String = <caret>
}
// EXIST: p
// ABSENT: fail
// ABSENT: error
@@ -0,0 +1,5 @@
fun String.foo(){
val s : String = <caret>
}
// EXIST: this
@@ -36,6 +36,26 @@ public class JetSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/completion/smart"), Pattern.compile("^(.+)\\.kt$"), true); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/completion/smart"), Pattern.compile("^(.+)\\.kt$"), true);
} }
@TestMetadata("AutoCastedType.kt")
public void testAutoCastedType() throws Exception {
doTest("idea/testData/completion/smart/AutoCastedType.kt");
}
@TestMetadata("AutoCastedTypeWithQualifier.kt")
public void testAutoCastedTypeWithQualifier() throws Exception {
doTest("idea/testData/completion/smart/AutoCastedTypeWithQualifier.kt");
}
@TestMetadata("AutoNotNullType.kt")
public void testAutoNotNullType() throws Exception {
doTest("idea/testData/completion/smart/AutoNotNullType.kt");
}
@TestMetadata("AutoNotNullTypeWithQualifier.kt")
public void testAutoNotNullTypeWithQualifier() throws Exception {
doTest("idea/testData/completion/smart/AutoNotNullTypeWithQualifier.kt");
}
@TestMetadata("ChainedCall.kt") @TestMetadata("ChainedCall.kt")
public void testChainedCall() throws Exception { public void testChainedCall() throws Exception {
doTest("idea/testData/completion/smart/ChainedCall.kt"); doTest("idea/testData/completion/smart/ChainedCall.kt");