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");