Initial implementation of KT-4046 Support 'Create constructor matching superclass'

#KT-4046 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-05-08 19:49:40 +03:00
parent 734b502ad6
commit 00dcbd4d93
25 changed files with 308 additions and 34 deletions

View File

@@ -1,6 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="valentin">
<words>
<w>delegator</w>
<w>funs</w>
<w>initializers</w>
<w>inserter</w>

View File

@@ -5,13 +5,7 @@
<val name="value" val="&quot;fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean&quot;"/>
</annotation>
</item>
<item
name='com.intellij.codeInsight.intention.IntentionAction void invoke(com.intellij.openapi.project.Project, com.intellij.openapi.editor.Editor, com.intellij.psi.PsiFile)'>
<annotation name='jet.runtime.typeinfo.KotlinSignature'>
<val name="value" val="&quot;fun invoke(project: Project, editor: Editor, file: PsiFile): Unit&quot;"/>
</annotation>
</item>
<item
<item
name='com.intellij.codeInsight.intention.PsiElementBaseIntentionAction boolean isAvailable(com.intellij.openapi.project.Project, com.intellij.openapi.editor.Editor, com.intellij.psi.PsiElement) 1'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -78,6 +78,24 @@ public class JetClass extends JetTypeParameterListOwnerStub<KotlinClassStub> imp
return list.getParameters();
}
@NotNull
public JetPrimaryConstructor getOrCreatePrimaryConstructor() {
JetPrimaryConstructor constructor = getPrimaryConstructor();
if (constructor != null) return constructor;
PsiElement anchor = getTypeParameterList();
if (anchor == null) anchor = getNameIdentifier();
if (anchor == null) anchor = getLastChild();
return (JetPrimaryConstructor) addAfter(new JetPsiFactory(getProject()).createPrimaryConstructor(), anchor);
}
@NotNull
public JetParameterList getOrCreatePrimaryConstructorParameterList() {
JetPrimaryConstructor constructor = getOrCreatePrimaryConstructor();
JetParameterList parameterList = constructor.getValueParameterList();
if (parameterList != null) return parameterList;
return (JetParameterList) constructor.add(new JetPsiFactory(getProject()).createParameterList("()"));
}
@Override
@Nullable
public JetDelegationSpecifierList getDelegationSpecifierList() {

View File

@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.psi;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.lexer.JetTokens;
import org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub;
import org.jetbrains.kotlin.psi.stubs.elements.JetStubElementTypes;
@@ -44,6 +45,57 @@ public class JetParameterList extends JetElementImplStub<KotlinPlaceHolderStub<J
return getStubOrPsiChildrenAsList(JetStubElementTypes.VALUE_PARAMETER);
}
@NotNull
public JetParameter addParameter(@NotNull JetParameter parameter) {
return addParameterBefore(parameter, null);
}
@NotNull
public JetParameter addParameterAfter(@NotNull JetParameter parameter, @Nullable JetParameter anchor) {
assert anchor == null || anchor.getParent() == this;
List<JetParameter> parameters = getParameters();
if (parameters.isEmpty()) {
if (getFirstChild().getNode().getElementType() == JetTokens.LPAR) {
return (JetParameter) addAfter(parameter, getFirstChild());
}
else {
return (JetParameter) add(parameter);
}
}
else {
PsiElement comma = new JetPsiFactory(getProject()).createComma();
if (anchor != null) {
comma = addAfter(comma, anchor);
return (JetParameter) addAfter(parameter, comma);
}
else {
comma = addBefore(comma, parameters.get(0));
return (JetParameter) addBefore(parameter, comma);
}
}
}
@NotNull
public JetParameter addParameterBefore(@NotNull JetParameter parameter, @Nullable JetParameter anchor) {
List<JetParameter> parameters = getParameters();
JetParameter anchorAfter;
if (parameters.isEmpty()) {
assert anchor == null;
anchorAfter = null;
}
else {
if (anchor != null) {
int index = parameters.indexOf(anchor);
assert index >= 0;
anchorAfter = index > 0 ? parameters.get(index - 1) : null;
}
else {
anchorAfter = parameters.get(parameters.size() - 1);
}
}
return addParameterAfter(parameter, anchorAfter);
}
// this method needed only for migrate lambda syntax
@Deprecated
public boolean isParenthesized() {

View File

@@ -82,8 +82,7 @@ public object HeuristicSignatures {
val type = typeFromText(typeStr, typeParameters, moduleDescriptor, project)
// now substitute type parameters with actual arguments
val typeArgs = ownerType.getArguments()
val typeArgsMap = typeParameters.indices.map { typeParameters[it] to typeArgs[it] }.toMap()
val typeArgsMap = typeParameters.zip(ownerType.getArguments()).toMap()
val substitutor = TypeUtils.makeSubstitutorForTypeParametersMap(typeArgsMap)
return substitutor.substitute(type, Variance.INVARIANT)
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.isVisible
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.TypeUtils
import java.util.ArrayList
public class AddConstructorParametersFromSuperFix private(
element: JetDelegatorToSuperClass,
val classDeclaration: JetClass,
val superConstructor: ConstructorDescriptor
) : JetIntentionAction<JetDelegatorToSuperClass>(element) {
override fun getFamilyName() = "Add constructor parameters from superclass"
override fun getText() = "Add constructor parameters and use them"
override fun invoke(project: Project, editor: Editor?, file: JetFile) {
val factory = JetPsiFactory(project)
val renderer = IdeDescriptorRenderers.SOURCE_CODE
val superParameters = superConstructor.getValueParameters()
val parameterNames = ArrayList<String>()
val typeRefsToShorten = ArrayList<JetTypeReference>()
if (!superParameters.isEmpty()) {
val parameterList = classDeclaration.getOrCreatePrimaryConstructorParameterList()
for (parameter in superParameters) {
//TODO: what if type is error?
val name = renderer.renderName(parameter.getName())
val parameterText = name + ":" + renderer.renderType(parameter.getType())
val newParameter = parameterList.addParameter(factory.createParameter(parameterText))
typeRefsToShorten.add(newParameter.getTypeReference())
parameterNames.add(name)
}
}
val delegatorCall = factory.createDelegatorToSuperCall(element.getText() + "(" + parameterNames.joinToString(",") + ")")
element.replace(delegatorCall)
ShortenReferences.DEFAULT.process(typeRefsToShorten)
}
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): JetIntentionAction<JetDelegatorToSuperClass>? {
val delegator = diagnostic.getPsiElement() as JetDelegatorToSuperClass
val classDeclaration = delegator.getParent().getParent() as? JetClass ?: return null
val typeRef = delegator.getTypeReference() ?: return null
val type = typeRef.analyze()[BindingContext.TYPE, typeRef] ?: return null
if (type.isError()) return null
val superClass = (type.getConstructor().getDeclarationDescriptor() as? ClassDescriptor) ?: return null
val classDescriptor = delegator.getResolutionFacade().resolveToDescriptor(classDeclaration) as ClassDescriptor
val constructors = superClass.getConstructors().filter { it.isVisible(classDescriptor) }
val constructorToUse = constructors.singleOrNull()
?: constructors.singleOrNull { it.isPrimary() } //TODO: should we select it automatically in this case?
?: return null
//TODO: choose among multiple
if (constructorToUse.getValueParameters().isEmpty()) return null
val superType = classDescriptor.getTypeConstructor().getSupertypes().first { it.getConstructor().getDeclarationDescriptor() == superClass }
val typeArgsMap = superClass.getTypeConstructor().getParameters().zip(superType.getArguments()).toMap()
val substitutor = TypeUtils.makeSubstitutorForTypeParametersMap(typeArgsMap)
val substitutedConstructor = constructorToUse.substitute(substitutor) as ConstructorDescriptor
return AddConstructorParametersFromSuperFix(delegator, classDeclaration, substitutedConstructor)
}
}
}

View File

@@ -24,6 +24,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.psi.JetCodeFragment;
import org.jetbrains.kotlin.psi.JetFile;
@@ -42,7 +43,7 @@ public abstract class JetIntentionAction<T extends PsiElement> implements Intent
//Don't override this method. Use the method with JetFile instead.
@Deprecated
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
public void invoke(@NotNull Project project, @Nullable Editor editor, @NotNull PsiFile file) throws IncorrectOperationException {
if (file instanceof JetFile) {
if (FileModificationService.getInstance().prepareFileForWrite(element.getContainingFile())) {
invoke(project, editor, (JetFile) file);
@@ -50,8 +51,7 @@ public abstract class JetIntentionAction<T extends PsiElement> implements Intent
}
}
protected void invoke(@NotNull Project project, Editor editor, JetFile file) throws IncorrectOperationException {
}
protected abstract void invoke(@NotNull Project project, @Nullable Editor editor, @NotNull JetFile file) throws IncorrectOperationException;
@Override
public boolean startInWriteAction() {

View File

@@ -325,5 +325,7 @@ public class QuickFixRegistrar {
QuickFixes.factories.put(DEPRECATED_ANNOTATION_SYNTAX, DeprecatedAnnotationSyntaxFix.Companion);
QuickFixes.factories.put(DEPRECATED_ANNOTATION_SYNTAX, DeprecatedAnnotationSyntaxFix.Companion.createWholeProjectFixFactory());
QuickFixes.factories.put(SUPERTYPE_NOT_INITIALIZED, AddConstructorParametersFromSuperFix.Companion);
}
}

View File

@@ -192,8 +192,6 @@ public open class KotlinIntroduceParameterHandler(
return
}
val parameterList = targetParent.getValueParameterList()
val descriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, targetParent]
val functionDescriptor: FunctionDescriptor =
when (descriptor) {
@@ -250,26 +248,9 @@ public open class KotlinIntroduceParameterHandler(
val addedParameter = if (inplaceIsAvailable) {
runWriteAction {
val newParameterList =
if (parameterList == null) {
val klass = targetParent as? JetClass
val anchor = klass?.getTypeParameterList() ?: klass?.getNameIdentifier()
assert(anchor != null) { "Invalid declaration: ${targetParent.getElementTextWithContext()}" }
val constructor = targetParent.addAfter(psiFactory.createPrimaryConstructor(), anchor) as JetPrimaryConstructor
constructor.getValueParameterList()!!
}
else parameterList
val lastParameter = newParameterList.getChildren().lastOrNull { it is JetParameter } as? JetParameter
if (lastParameter != null) {
val comma = newParameterList.addAfter(psiFactory.createComma(), lastParameter)
newParameterList.addAfter(newParameter, comma) as JetParameter
}
else {
val singleParameterList = psiFactory.createParameterList("(${newParameter.getText()})")
(newParameterList.replace(singleParameterList) as JetParameterList).getParameters().first()
}
val parameterList = targetParent.getValueParameterList()
?: (targetParent as JetClass).getOrCreatePrimaryConstructorParameterList()
parameterList.addParameter(newParameter)
}
}
else newParameter

View File

@@ -0,0 +1,4 @@
// "Add constructor parameters and use them" "true"
import java.io.DataInputStream
class C : DataInputStream<caret>

View File

@@ -0,0 +1,5 @@
// "Add constructor parameters and use them" "true"
import java.io.DataInputStream
import java.io.InputStream
class C(p0: InputStream) : DataInputStream<caret>(p0)

View File

@@ -0,0 +1,4 @@
// "Add constructor parameters and use them" "true"
open class Base(p1: Int, val p2: Int)
class C(p: Int) : Base<caret>

View File

@@ -0,0 +1,4 @@
// "Add constructor parameters and use them" "true"
open class Base(p1: Int, val p2: Int)
class C(p: Int, p1: Int, p2: Int) : Base<caret>(p1, p2)

View File

@@ -0,0 +1,4 @@
// "Add constructor parameters and use them" "true"
open class Base(p1: Int, val p2: Int)
class C : Base<caret>

View File

@@ -0,0 +1,4 @@
// "Add constructor parameters and use them" "true"
open class Base(p1: Int, val p2: Int)
class C(p1: Int, p2: Int) : Base<caret>(p1, p2)

View File

@@ -0,0 +1,6 @@
// "Add constructor parameters and use them" "true"
trait I
open class Base<T1, T2>(p1: T1, p2: T2, p3: Base<T1, T2>?)
class C<T> : I, Base<T, String><caret>

View File

@@ -0,0 +1,6 @@
// "Add constructor parameters and use them" "true"
trait I
open class Base<T1, T2>(p1: T1, p2: T2, p3: Base<T1, T2>?)
class C<T>(p1: T, p2: String, p3: Base<T, String>?) : I, Base<T, String><caret>(p1, p2, p3)

View File

@@ -0,0 +1,4 @@
// "Add constructor parameters and use them" "true"
open class Base(p1: Int, val p2: Int)
class C private : Base<caret>

View File

@@ -0,0 +1,4 @@
// "Add constructor parameters and use them" "true"
open class Base(p1: Int, val p2: Int)
class C private (p1: Int, p2: Int) : Base<caret>(p1, p2)

View File

@@ -0,0 +1,4 @@
// "Add constructor parameters and use them" "true"
open class Base(`fun`: Int, val `class`: Int)
class C : Base<caret>

View File

@@ -0,0 +1,4 @@
// "Add constructor parameters and use them" "true"
open class Base(`fun`: Int, val `class`: Int)
class C(`fun`: Int, `class`: Int) : Base(`fun`, `class`)

View File

@@ -0,0 +1,6 @@
// "Add constructor parameters and use them" "false"
// ACTION: Change to constructor invocation
// ERROR: This type has a constructor, and thus must be initialized here
open class Base
class C : Base<caret>

View File

@@ -0,0 +1,7 @@
// "Add constructor parameters and use them" "true"
open class Base private(p1: Int, val p2: Int) {
private constructor() : this(0, 1)
protected constructor(s: String) : this(s.length(), 1)
}
class C : Base<caret>

View File

@@ -0,0 +1,7 @@
// "Add constructor parameters and use them" "true"
open class Base private(p1: Int, val p2: Int) {
private constructor() : this(0, 1)
protected constructor(s: String) : this(s.length(), 1)
}
class C(s: String) : Base<caret>(s)

View File

@@ -176,6 +176,63 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@TestMetadata("idea/testData/quickfix/addConstructorParametersFromSuper")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class AddConstructorParametersFromSuper extends AbstractQuickFixTest {
@TestMetadata("addImport.kt")
public void testAddImport() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/addImport.kt");
doTest(fileName);
}
@TestMetadata("addParameters.kt")
public void testAddParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/addParameters.kt");
doTest(fileName);
}
public void testAllFilesPresentInAddConstructorParametersFromSuper() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addConstructorParametersFromSuper"), Pattern.compile("^(\\w+)\\.kt$"), true);
}
@TestMetadata("createConstructor.kt")
public void testCreateConstructor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/createConstructor.kt");
doTest(fileName);
}
@TestMetadata("genericClass.kt")
public void testGenericClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/genericClass.kt");
doTest(fileName);
}
@TestMetadata("incompleteConstructor.kt")
public void testIncompleteConstructor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/incompleteConstructor.kt");
doTest(fileName);
}
@TestMetadata("keywordName.kt")
public void testKeywordName() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/keywordName.kt");
doTest(fileName);
}
@TestMetadata("noParameters.kt")
public void testNoParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/noParameters.kt");
doTest(fileName);
}
@TestMetadata("primaryConstructorInaccessible.kt")
public void testPrimaryConstructorInaccessible() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/primaryConstructorInaccessible.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/quickfix/addStarProjections")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)