diff --git a/annotations/com/google/common/collect/annotations.xml b/annotations/com/google/common/collect/annotations.xml index feadffd5917..ba6fd63e92d 100644 --- a/annotations/com/google/common/collect/annotations.xml +++ b/annotations/com/google/common/collect/annotations.xml @@ -2,4 +2,7 @@ + + + \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt index 828d0e69a22..a3533a3648c 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt @@ -8,12 +8,12 @@ import org.jetbrains.jet.plugin.project.CancelableResolveSession import org.jetbrains.jet.lang.types.* import org.jetbrains.jet.lang.types.checker.JetTypeChecker import com.intellij.codeInsight.lookup.* -import java.util.ArrayList import org.jetbrains.jet.renderer.DescriptorRenderer -import com.intellij.codeInsight.completion.InsertionContext -import com.intellij.codeInsight.completion.InsertHandler +import com.intellij.codeInsight.completion.* 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{ fun accepts(descriptor: DeclarationDescriptor): Boolean @@ -22,7 +22,17 @@ trait SmartCompletionData{ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession: CancelableResolveSession): SmartCompletionData? { 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 expectedType: JetType? = bindingContext.get(BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType) if (expectedType == null) return null @@ -31,62 +41,41 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession val additionalElements = ArrayList() - if (expression == expressionWithType) { // no qualifier - val typeConstructor: TypeConstructor = expectedType.getConstructor() - val classifier: ClassifierDescriptor? = typeConstructor.getDeclarationDescriptor() - if (classifier is ClassDescriptor) { - if (classifier.getModality() != Modality.ABSTRACT){ - val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, classifier) + if (receiver == null) { + typeInstantiationItems(expectedType, resolveSession, bindingContext).toCollection(additionalElements) + } - val typeArgs = expectedType.getArguments() - //TODO: shouldn't be method in DescriptorRenderer to render type arguments? - val typeArgsText = - if (typeArgs.isEmpty()) - "" - else - typeArgs.map { DescriptorRenderer.TEXT.renderType(it.getType()) }.makeString(", ", "<", ">") - val presentableText = lookupElement.getLookupString() + typeArgsText + "()" + val dataFlowInfo = bindingContext.get(BindingContext.EXPRESSION_DATA_FLOW_INFO, expressionWithType) + val (variableToTypes: Map>, notNullVariables: Set) = processDataFlowInfo(dataFlowInfo, receiver, bindingContext) - val constructors: Collection = classifier.getConstructors() - val caretPosition = - if (constructors.size == 0) - CaretPosition.AFTER_BRACKETS - else if (constructors.size == 1) - if (constructors.first().getValueParameters().isEmpty()) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS - else - CaretPosition.IN_BRACKETS - val insertHandler = JetFunctionInsertHandler(caretPosition, BracketType.PARENTHESIS) - - //TODO: very bad code - if (lookupElement is LookupElementBuilder) { - additionalElements.add(lookupElement.withPresentableText(presentableText).withInsertHandler(insertHandler)) + fun typesOf(descriptor: DeclarationDescriptor): Iterable { + if (descriptor is CallableDescriptor) { + var returnType = descriptor.getReturnType() + if (descriptor is VariableDescriptor) { + if (notNullVariables.contains(descriptor) && returnType != null) { + returnType = TypeUtils.makeNotNullable(returnType!!) } - else if (lookupElement is JavaPsiClassReferenceElement) { - additionalElements.add(lookupElement.setPresentableText(presentableText).setInsertHandler(insertHandler)) + + 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): Boolean { - if (itemsToSkip.contains(descriptor)) return false + return object: SmartCompletionData { + override fun accepts(descriptor: DeclarationDescriptor) + = !itemsToSkip.contains(descriptor) && typesOf(descriptor).any { JetTypeChecker.INSTANCE.isSubtypeOf(it, expectedType) } - if (descriptor is CallableDescriptor) { - val returnType = descriptor.getReturnType() - return returnType != null && JetTypeChecker.INSTANCE.isSubtypeOf(returnType, expectedType) - } - else { - return false - } - } - - override val additionalElements: Iterable = additionalElements + override val additionalElements = additionalElements } } -private fun T?.toList(): List = if (this != null) listOf(this) else listOf() - private fun calcItemsToSkip(expression: JetExpression, resolveSession: CancelableResolveSession): Collection { val parent = expression.getParent() when(parent) { @@ -107,4 +96,98 @@ private fun calcItemsToSkip(expression: JetExpression, resolveSession: Cancelabl } } return listOf() -} \ No newline at end of file +} + +private fun typeInstantiationItems(expectedType: JetType, resolveSession: CancelableResolveSession, bindingContext: BindingContext): Iterable { + val typeConstructor: TypeConstructor = expectedType.getConstructor() + val classifier: ClassifierDescriptor? = typeConstructor.getDeclarationDescriptor() + if (!(classifier is ClassDescriptor)) return listOf() + if (classifier.getModality() == Modality.ABSTRACT) return listOf() + + //TODO: check for constructor's visibility + + val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, classifier) + + val typeArgs = expectedType.getArguments() + //TODO: shouldn't be method in DescriptorRenderer to render type arguments? + val typeArgsText = + if (typeArgs.isEmpty()) + "" + else + typeArgs.map { DescriptorRenderer.TEXT.renderType(it.getType()) }.makeString(", ", "<", ">") + val presentableText = lookupElement.getLookupString() + typeArgsText + "()" + + val constructors: Collection = classifier.getConstructors() + val caretPosition = + if (constructors.size == 0) + CaretPosition.AFTER_BRACKETS + else if (constructors.size == 1) + if (constructors.first().getValueParameters().isEmpty()) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS + else + CaretPosition.IN_BRACKETS + val insertHandler = JetFunctionInsertHandler(caretPosition, BracketType.PARENTHESIS) + + //TODO: very bad code + if (lookupElement is LookupElementBuilder) { + return listOf(lookupElement.withPresentableText(presentableText).withInsertHandler(insertHandler)) + } + else if (lookupElement is JavaPsiClassReferenceElement) { + return listOf(lookupElement.setPresentableText(presentableText).setInsertHandler(insertHandler)) + } + + return listOf() +} + +private data class ProcessDataFlowInfoResult( + val variableToTypes: Map> = Collections.emptyMap(), + val notNullVariables: Set = 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 { + return ProcessDataFlowInfoResult() + } + } + else { + dataFlowValueToVariable = {(value) -> value.getId() as? VariableDescriptor } + } + + val variableToType = HashMap>() + val typeInfo: SetMultimap = dataFlowInfo.getCompleteTypeInfo() + for ((dataFlowValue, types) in typeInfo.asMap().entrySet()) { + val variable = dataFlowValueToVariable.invoke(dataFlowValue) + if (variable != null) { + variableToType[variable] = types + } + } + + val nullabilityInfo: Map = dataFlowInfo.getCompleteNullabilityInfo() + val notNullVariables = nullabilityInfo.iterator() + .filter { it.getValue() == Nullability.NOT_NULL } + .map { dataFlowValueToVariable(it.getKey()) } + .filterNotNullTo(HashSet()) + + return ProcessDataFlowInfoResult(variableToType, notNullVariables) + } + + return ProcessDataFlowInfoResult() +} + +private fun T?.toList(): List = if (this != null) listOf(this) else listOf() + +private fun MutableCollection.addAll(iterator: Iterator) { + for (item in iterator) { + add(item) + } +} diff --git a/idea/testData/completion/smart/AutoCastedThisType.kt.todo b/idea/testData/completion/smart/AutoCastedThisType.kt.todo new file mode 100644 index 00000000000..1bb33e51e24 --- /dev/null +++ b/idea/testData/completion/smart/AutoCastedThisType.kt.todo @@ -0,0 +1,12 @@ +open class Foo{ + fun f() { + if (this is Bar){ + var a : Bar = + } + } +} + +class Bar : Foo + + +// EXIST: this diff --git a/idea/testData/completion/smart/AutoCastedType.kt.todo b/idea/testData/completion/smart/AutoCastedType.kt similarity index 80% rename from idea/testData/completion/smart/AutoCastedType.kt.todo rename to idea/testData/completion/smart/AutoCastedType.kt index 69155765f83..c40ffef2065 100644 --- a/idea/testData/completion/smart/AutoCastedType.kt.todo +++ b/idea/testData/completion/smart/AutoCastedType.kt @@ -1,4 +1,4 @@ -fun f(p: Object) { +fun f(p: Any) { if (p is String){ var a : String = } diff --git a/idea/testData/completion/smart/AutoCastedTypeWithQualifier.kt b/idea/testData/completion/smart/AutoCastedTypeWithQualifier.kt new file mode 100644 index 00000000000..fa674a8dfaf --- /dev/null +++ b/idea/testData/completion/smart/AutoCastedTypeWithQualifier.kt @@ -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. + } + } +} + +// EXIST: prop1 +// ABSENT: prop2 diff --git a/idea/testData/completion/smart/AutoNotNullThisType.kt.todo b/idea/testData/completion/smart/AutoNotNullThisType.kt.todo new file mode 100644 index 00000000000..792b1c22833 --- /dev/null +++ b/idea/testData/completion/smart/AutoNotNullThisType.kt.todo @@ -0,0 +1,8 @@ +fun String?.foo(){ + if (this != null){ + val s : String = + } +} + + +// EXIST: this diff --git a/idea/testData/completion/smart/AutoNotNullType.kt b/idea/testData/completion/smart/AutoNotNullType.kt new file mode 100644 index 00000000000..bd76e0933c3 --- /dev/null +++ b/idea/testData/completion/smart/AutoNotNullType.kt @@ -0,0 +1,7 @@ +fun f(p: String?) { + if (p != null){ + var a : String = + } +} + +// EXIST: p diff --git a/idea/testData/completion/smart/AutoNotNullTypeWithQualifier.kt b/idea/testData/completion/smart/AutoNotNullTypeWithQualifier.kt new file mode 100644 index 00000000000..18781eed32b --- /dev/null +++ b/idea/testData/completion/smart/AutoNotNullTypeWithQualifier.kt @@ -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. + } + } +} + +// EXIST: prop1 +// ABSENT: prop2 diff --git a/idea/testData/completion/smart/NoNothing.kt.todo b/idea/testData/completion/smart/NoNothing.kt.todo new file mode 100644 index 00000000000..a73c44df95f --- /dev/null +++ b/idea/testData/completion/smart/NoNothing.kt.todo @@ -0,0 +1,11 @@ +fun fail() : Nothing{ + throw RuntimeException() +} + +fun f(p : String) { + var a : String = +} + +// EXIST: p +// ABSENT: fail +// ABSENT: error diff --git a/idea/testData/completion/smart/This.kt.todo b/idea/testData/completion/smart/This.kt.todo new file mode 100644 index 00000000000..af370491d15 --- /dev/null +++ b/idea/testData/completion/smart/This.kt.todo @@ -0,0 +1,5 @@ +fun String.foo(){ + val s : String = +} + +// EXIST: this diff --git a/idea/tests/org/jetbrains/jet/completion/JetSmartCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/JetSmartCompletionTestGenerated.java index 81a715d86fe..59385506720 100644 --- a/idea/tests/org/jetbrains/jet/completion/JetSmartCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/JetSmartCompletionTestGenerated.java @@ -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); } + @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") public void testChainedCall() throws Exception { doTest("idea/testData/completion/smart/ChainedCall.kt");