Create From Usage: Do not suggest "val" for references used as assignment left-hand side

This commit is contained in:
Alexey Sedunov
2014-10-03 16:38:38 +04:00
parent 58126b28ca
commit 865c793561
26 changed files with 180 additions and 49 deletions
@@ -345,4 +345,9 @@ public fun PsiElement.siblings(forward: Boolean = true, withItself: Boolean = tr
public fun PsiElement.parents(withItself: Boolean = true): Stream<PsiElement> {
val stream = stream(this) { if (it is PsiFile) null else it.getParent() }
return if (withItself) stream else stream.drop(1)
}
public fun JetExpression.getAssignmentByLHS(): JetBinaryExpression? {
val parent = getParent() as? JetBinaryExpression ?: return null
return if (JetPsiUtil.isAssignment(parent) && parent.getLeft() == this) parent else null
}
@@ -243,8 +243,10 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
with (config) {
val ownerTypeString = if (isExtension) "${receiverTypeCandidate!!.renderedType!!}." else ""
val paramList = when (callableInfo.kind) {
CallableKind.FUNCTION -> "(${callableInfo.parameterInfos.indices.map { i -> "p$i: Any" }.joinToString(", ")})"
CallableKind.PROPERTY -> ""
CallableKind.FUNCTION ->
"(${(callableInfo as FunctionInfo).parameterInfos.indices.map { i -> "p$i: Any" }.joinToString(", ")})"
CallableKind.PROPERTY ->
""
}
val returnTypeString = if (isUnit) "" else ": Any"
val header = "$ownerTypeString${callableInfo.name}$paramList$returnTypeString"
@@ -253,7 +255,10 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
val declaration = when (callableInfo.kind) {
CallableKind.FUNCTION -> psiFactory.createFunction("fun $header {}")
CallableKind.PROPERTY -> psiFactory.createProperty("val $header")
CallableKind.PROPERTY -> {
val valVar = if ((callableInfo as PropertyInfo).writable) "var" else "val"
psiFactory.createProperty("$valVar $header")
}
}
val newLine = psiFactory.createNewLine()
@@ -433,7 +438,9 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
}
private fun setupValVarTemplate(builder: TemplateBuilder, property: JetProperty) {
builder.replaceElement(property.getValOrVarNode().getPsi()!!, ValVarExpression)
if (!(config.callableInfo as PropertyInfo).writable) {
builder.replaceElement(property.getValOrVarNode().getPsi()!!, ValVarExpression)
}
}
private fun setupTypeParameterListTemplate(builder: TemplateBuilderImpl, declaration: JetCallableDeclaration): TypeParameterListExpression {
@@ -71,30 +71,31 @@ enum class CallableKind {
PROPERTY
}
class CallableInfo (
abstract class CallableInfo (
val name: String,
val kind: CallableKind,
val receiverTypeInfo: TypeInfo,
val returnTypeInfo: TypeInfo,
val possibleContainers: List<JetElement>,
val parameterInfos: List<ParameterInfo> = Collections.emptyList()
val possibleContainers: List<JetElement>
) {
{
if (kind == CallableKind.PROPERTY) assert (parameterInfos.isEmpty(), "$kind: Parameters are not allowed")
}
abstract val kind: CallableKind
abstract val parameterInfos: List<ParameterInfo>
}
fun createFunctionInfo(name: String,
receiverTypeInfo: TypeInfo,
returnTypeInfo: TypeInfo,
possibleContainers: List<JetElement> = Collections.emptyList(),
parameterInfos: List<ParameterInfo> = Collections.emptyList()): CallableInfo {
return CallableInfo(name, CallableKind.FUNCTION, receiverTypeInfo, returnTypeInfo, possibleContainers, parameterInfos)
class FunctionInfo(name: String,
receiverTypeInfo: TypeInfo,
returnTypeInfo: TypeInfo,
possibleContainers: List<JetElement> = Collections.emptyList(),
override val parameterInfos: List<ParameterInfo> = Collections.emptyList()
) : CallableInfo(name, receiverTypeInfo, returnTypeInfo, possibleContainers) {
override val kind: CallableKind get() = CallableKind.FUNCTION
}
fun createPropertyInfo(name: String,
receiverTypeInfo: TypeInfo,
returnTypeInfo: TypeInfo,
possibleContainers: List<JetElement>): CallableInfo {
return CallableInfo(name, CallableKind.PROPERTY, receiverTypeInfo, returnTypeInfo, possibleContainers)
class PropertyInfo(name: String,
receiverTypeInfo: TypeInfo,
returnTypeInfo: TypeInfo,
val writable: Boolean,
possibleContainers: List<JetElement> = Collections.emptyList()
) : CallableInfo(name, receiverTypeInfo, returnTypeInfo, possibleContainers) {
override val kind: CallableKind get() = CallableKind.PROPERTY
override val parameterInfos: List<ParameterInfo> get() = Collections.emptyList()
}
@@ -22,6 +22,7 @@ import org.jetbrains.jet.lang.types.checker.JetTypeChecker
import org.jetbrains.jet.lang.types.Variance
import org.jetbrains.jet.lang.types.TypeProjectionImpl
import org.jetbrains.jet.lang.types.JetTypeImpl
import org.jetbrains.jet.lang.psi.psiUtil.getAssignmentByLHS
private fun JetType.contains(inner: JetType): Boolean {
return this == inner || getArguments().any { inner in it.getType() }
@@ -179,4 +180,6 @@ private fun JetType.substitute(substitution: JetTypeSubstitution, variance: Vari
}
return JetTypeImpl(getAnnotations(), getConstructor(), isNullable(), newArguments, getMemberScope())
}
}
}
fun JetExpression.getExpressionForTypeGuess() = getAssignmentByLHS()?.getRight() ?: this
@@ -38,6 +38,6 @@ public object CreateBinaryOperationActionFactory: JetSingleIntentionActionFactor
else -> TypeInfo(callExpr, Variance.OUT_VARIANCE)
}
val parameters = Collections.singletonList(ParameterInfo(TypeInfo(argumentExpr, Variance.IN_VARIANCE)))
return CreateFunctionFromUsageFix(callExpr, createFunctionInfo(operationName, receiverType, returnType, Collections.emptyList(), parameters))
return CreateFunctionFromUsageFix(callExpr, FunctionInfo(operationName, receiverType, returnType, Collections.emptyList(), parameters))
}
}
@@ -34,6 +34,6 @@ object CreateComponentFunctionActionFactory : JetSingleIntentionActionFactory()
val entry = entries[componentNumber]
val returnType = TypeInfo(entry, Variance.OUT_VARIANCE)
return CreateFunctionFromUsageFix(multiDeclaration!!, createFunctionInfo(name.getIdentifier(), ownerType, returnType))
return CreateFunctionFromUsageFix(multiDeclaration!!, FunctionInfo(name.getIdentifier(), ownerType, returnType))
}
}
@@ -21,6 +21,7 @@ import java.util.Collections
import org.jetbrains.jet.plugin.refactoring.getExtractionContainers
import org.jetbrains.jet.lang.psi.JetClassBody
import org.jetbrains.jet.lang.psi.JetFile
import org.jetbrains.jet.lang.psi.psiUtil.getAssignmentByLHS
object CreateFunctionOrPropertyFromCallActionFactory : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
@@ -70,8 +71,6 @@ object CreateFunctionOrPropertyFromCallActionFactory : JetSingleIntentionActionF
}
else Collections.emptyList()
val returnType = TypeInfo(fullCallExpr, Variance.OUT_VARIANCE)
val callableInfo = when (callExpr) {
is JetCallExpression -> {
val anyType = KotlinBuiltIns.getInstance().getNullableAnyType()
@@ -81,10 +80,18 @@ object CreateFunctionOrPropertyFromCallActionFactory : JetSingleIntentionActionF
it.getArgumentName()?.getReferenceExpression()?.getReferencedName()
)
}
createFunctionInfo(calleeExpr.getReferencedName(), receiverType, returnType, possibleContainers, parameters)
val returnType = TypeInfo(fullCallExpr, Variance.OUT_VARIANCE)
FunctionInfo(calleeExpr.getReferencedName(), receiverType, returnType, possibleContainers, parameters)
}
is JetSimpleNameExpression -> createPropertyInfo(calleeExpr.getReferencedName(), receiverType, returnType, possibleContainers)
is JetSimpleNameExpression -> {
val varExpected = fullCallExpr.getAssignmentByLHS() != null
val returnType = TypeInfo(
fullCallExpr.getExpressionForTypeGuess(),
if (varExpected) Variance.INVARIANT else Variance.OUT_VARIANCE
)
PropertyInfo(calleeExpr.getReferencedName(), receiverType, returnType, varExpected, possibleContainers)
}
else -> return null
}
@@ -20,6 +20,6 @@ object CreateGetFunctionActionFactory : JetSingleIntentionActionFactory() {
}
val returnType = TypeInfo(accessExpr, Variance.OUT_VARIANCE)
return CreateFunctionFromUsageFix(accessExpr, createFunctionInfo("get", arrayType, returnType, Collections.emptyList(), parameters))
return CreateFunctionFromUsageFix(accessExpr, FunctionInfo("get", arrayType, returnType, Collections.emptyList(), parameters))
}
}
@@ -18,6 +18,6 @@ object CreateHasNextFunctionActionFactory : JetSingleIntentionActionFactory() {
val forExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass<JetForExpression>()) ?: return null
val returnType = TypeInfo(KotlinBuiltIns.getInstance().getBooleanType(), Variance.OUT_VARIANCE)
return CreateFunctionFromUsageFix(forExpr, createFunctionInfo("hasNext", ownerType, returnType))
return CreateFunctionFromUsageFix(forExpr, FunctionInfo("hasNext", ownerType, returnType))
}
}
@@ -30,6 +30,6 @@ object CreateInvokeFunctionActionFactory : JetSingleIntentionActionFactory() {
}
val returnType = TypeInfo(callExpr, Variance.OUT_VARIANCE)
return CreateFunctionFromUsageFix(callExpr, createFunctionInfo("invoke", receiverType, returnType, Collections.emptyList(), parameters))
return CreateFunctionFromUsageFix(callExpr, FunctionInfo("invoke", receiverType, returnType, Collections.emptyList(), parameters))
}
}
@@ -32,6 +32,6 @@ object CreateIteratorFunctionActionFactory : JetSingleIntentionActionFactory() {
val returnJetTypeArguments = Collections.singletonList(returnJetTypeParameterType)
val newReturnJetType = JetTypeImpl(returnJetType.getAnnotations(), returnJetType.getConstructor(), returnJetType.isNullable(), returnJetTypeArguments, returnJetType.getMemberScope())
val returnType = TypeInfo(newReturnJetType, Variance.OUT_VARIANCE)
return CreateFunctionFromUsageFix(forExpr, createFunctionInfo("iterator", iterableType, returnType))
return CreateFunctionFromUsageFix(forExpr, FunctionInfo("iterator", iterableType, returnType))
}
}
@@ -19,6 +19,6 @@ object CreateNextFunctionActionFactory : JetSingleIntentionActionFactory() {
val forExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass<JetForExpression>()) ?: return null
val variableExpr: JetExpression = ((forExpr.getLoopParameter() ?: forExpr.getMultiParameter()) ?: return null) as JetExpression
val returnType = TypeInfo(variableExpr, Variance.OUT_VARIANCE)
return CreateFunctionFromUsageFix(forExpr, createFunctionInfo("next", ownerType, returnType))
return CreateFunctionFromUsageFix(forExpr, FunctionInfo("next", ownerType, returnType))
}
}
@@ -28,6 +28,6 @@ object CreateSetFunctionActionFactory : JetSingleIntentionActionFactory() {
parameters.add(ParameterInfo(valType, "value"))
val returnType = TypeInfo(KotlinBuiltIns.getInstance().getUnitType(), Variance.OUT_VARIANCE)
return CreateFunctionFromUsageFix(accessExpr, createFunctionInfo("set", arrayType, returnType, Collections.emptyList(), parameters))
return CreateFunctionFromUsageFix(accessExpr, FunctionInfo("set", arrayType, returnType, Collections.emptyList(), parameters))
}
}
@@ -23,6 +23,6 @@ public object CreateUnaryOperationActionFactory: JetSingleIntentionActionFactory
val receiverType = TypeInfo(receiverExpr, Variance.IN_VARIANCE)
val returnType = if (incDec) TypeInfo.ByReceiverType(Variance.OUT_VARIANCE) else TypeInfo(callExpr, Variance.OUT_VARIANCE)
return CreateFunctionFromUsageFix(callExpr, createFunctionInfo(operationName.asString(), receiverType, returnType))
return CreateFunctionFromUsageFix(callExpr, FunctionInfo(operationName.asString(), receiverType, returnType))
}
}
@@ -12,7 +12,6 @@ import org.jetbrains.jet.plugin.quickfix.QuickFixUtil
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElement
import org.jetbrains.jet.lang.psi.JetBlockExpression
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.createPropertyInfo
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.TypeInfo
import org.jetbrains.jet.lang.types.Variance
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.CallableBuilderConfiguration
@@ -20,13 +19,14 @@ import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.createB
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.CallablePlacement
import com.intellij.openapi.command.CommandProcessor
import org.jetbrains.jet.lang.psi.psiUtil.parents
import org.jetbrains.jet.lang.psi.JetFunction
import org.jetbrains.jet.lang.psi.JetPropertyAccessor
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.plugin.intentions.ConvertToBlockBodyAction
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody
import org.jetbrains.jet.plugin.refactoring.getExtractionContainers
import org.jetbrains.jet.lang.psi.JetClassBody
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.PropertyInfo
import org.jetbrains.jet.lang.psi.psiUtil.getAssignmentByLHS
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.getExpressionForTypeGuess
object CreateLocalVariableActionFactory: JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
@@ -37,9 +37,13 @@ object CreateLocalVariableActionFactory: JetSingleIntentionActionFactory() {
.filter { it is JetBlockExpression || it is JetDeclarationWithBody }
.firstOrNull() as? JetElement ?: return null
val varExpected = refExpr.getAssignmentByLHS() != null
val typeInfo = TypeInfo(
refExpr.getExpressionForTypeGuess(),
if (varExpected) Variance.INVARIANT else Variance.OUT_VARIANCE
)
val containers = refExpr.getExtractionContainers().filterNot { it is JetClassBody || it is JetFile }
val propertyInfo =
createPropertyInfo(refExpr.getReferencedName(), TypeInfo.Empty, TypeInfo(refExpr, Variance.OUT_VARIANCE), containers)
val propertyInfo = PropertyInfo(refExpr.getReferencedName(), TypeInfo.Empty, typeInfo, varExpected, containers)
return object: CreateFromUsageFixBase(refExpr) {
override fun getText(): String = JetBundle.message("create.local.variable.from.usage", propertyInfo.name)
@@ -37,8 +37,9 @@ import org.jetbrains.jet.plugin.refactoring.changeSignature.JetValVar
import org.jetbrains.jet.lang.psi.JetEnumEntry
import org.jetbrains.jet.lang.psi.JetClassOrObject
import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils
import org.jetbrains.jet.lang.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.jet.lang.psi.JetNamedFunction
import org.jetbrains.jet.lang.psi.psiUtil.getAssignmentByLHS
import org.jetbrains.jet.plugin.quickfix.createFromUsage.callableBuilder.getExpressionForTypeGuess
object CreateParameterActionFactory: JetSingleIntentionActionFactory() {
private fun JetType.hasTypeParametersToAdd(functionDescriptor: FunctionDescriptor, context: BindingContext): Boolean {
@@ -69,7 +70,9 @@ object CreateParameterActionFactory: JetSingleIntentionActionFactory() {
val refExpr = QuickFixUtil.getParentElementOfType(diagnostic, javaClass<JetSimpleNameExpression>()) ?: return null
if (refExpr.getQualifiedElement() != refExpr) return null
val paramType = refExpr.guessTypes(context).let {
val varExpected = refExpr.getAssignmentByLHS() != null
val paramType = refExpr.getExpressionForTypeGuess().guessTypes(context).let {
when (it.size) {
0 -> KotlinBuiltIns.getInstance().getAnyType()
1 -> it.first()
@@ -80,7 +83,7 @@ object CreateParameterActionFactory: JetSingleIntentionActionFactory() {
val parameterInfo = JetParameterInfo(refExpr.getReferencedName(), paramType)
fun chooseContainingClass(it: PsiElement): JetClass? {
parameterInfo.setValOrVar(JetValVar.Val)
parameterInfo.setValOrVar(if (varExpected) JetValVar.Var else JetValVar.Val)
return it.parents(false).filterIsInstance(javaClass<JetClassOrObject>()).firstOrNull() as? JetClass
}
@@ -89,11 +92,11 @@ object CreateParameterActionFactory: JetSingleIntentionActionFactory() {
.filter { it is JetNamedFunction || it is JetPropertyAccessor || it is JetClassBody || it is JetClassInitializer }
.firstOrNull()
?.let {
when (it) {
is JetPropertyAccessor -> chooseContainingClass(it)
is JetClassInitializer ->
it.getParent()?.getParent() as? JetClass
is JetClassBody -> {
when {
it is JetNamedFunction && varExpected,
it is JetPropertyAccessor -> chooseContainingClass(it)
it is JetClassInitializer -> it.getParent()?.getParent() as? JetClass
it is JetClassBody -> {
val klass = it.getParent() as? JetClass
when {
klass is JetEnumEntry -> chooseContainingClass(klass)
@@ -0,0 +1,7 @@
// "Create local variable 'foo'" "true"
fun test(n: Int) {
var foo: Int
foo = n + 1
}
@@ -0,0 +1,5 @@
// "Create local variable 'foo'" "true"
fun test(n: Int) {
<caret>foo = n + 1
}
@@ -0,0 +1,7 @@
// "Create parameter 'foo'" "true"
class A(var foo: Int) {
fun test(n: Int) {
foo = n + 1
}
}
@@ -0,0 +1,8 @@
// "Create parameter 'foo'" "false"
// ACTION: Create local variable 'foo'
// ACTION: Create property 'foo' from usage
// ERROR: Unresolved reference: foo
fun test(n: Int) {
<caret>foo = n + 1
}
@@ -0,0 +1,7 @@
// "Create parameter 'foo'" "true"
class A {
fun test(n: Int) {
<caret>foo = n + 1
}
}
@@ -0,0 +1,10 @@
// "Create property 'foo' from usage" "true"
// ERROR: Property must be initialized
class A<T>(val n: T)
fun test() {
2.foo = A("2")
}
var Int.foo: A<String>
@@ -0,0 +1,11 @@
// "Create property 'foo' from usage" "true"
// ERROR: Property must be initialized or be abstract
class A<T>(val n: T) {
var foo: String
}
fun test() {
A(1).foo = "1"
}
@@ -0,0 +1,8 @@
// "Create property 'foo' from usage" "true"
// ERROR: Property must be initialized
class A<T>(val n: T)
fun test() {
2.<caret>foo = A("2")
}
@@ -0,0 +1,8 @@
// "Create property 'foo' from usage" "true"
// ERROR: Property must be initialized or be abstract
class A<T>(val n: T)
fun test() {
A(1).<caret>foo = "1"
}
@@ -1179,6 +1179,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createVariable/localVariable"), Pattern.compile("^before(\\w+)\\.kt$"), true);
}
@TestMetadata("beforeAssignedInFun.kt")
public void testAssignedInFun() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/localVariable/beforeAssignedInFun.kt");
doTest(fileName);
}
@TestMetadata("beforeInAccessor.kt")
public void testInAccessor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/localVariable/beforeInAccessor.kt");
@@ -1261,6 +1267,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createVariable/parameter"), Pattern.compile("^before(\\w+)\\.kt$"), true);
}
@TestMetadata("beforeAssignedInFun.kt")
public void testAssignedInFun() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeAssignedInFun.kt");
doTest(fileName);
}
@TestMetadata("beforeAssignedInFunInClass.kt")
public void testAssignedInFunInClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeAssignedInFunInClass.kt");
doTest(fileName);
}
@TestMetadata("beforeInAccessorInClass.kt")
public void testInAccessorInClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter/beforeInAccessorInClass.kt");
@@ -1559,6 +1577,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("beforeVarOnLibType.kt")
public void testVarOnLibType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/beforeVarOnLibType.kt");
doTest(fileName);
}
@TestMetadata("beforeVarOnUserType.kt")
public void testVarOnUserType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/property/beforeVarOnUserType.kt");
doTest(fileName);
}
}
}