diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java index e526e2a2941..9f85056c88f 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java @@ -63,7 +63,7 @@ public class JetPsiFactory { } //the pair contains the first and the last elements of a range - public static Pair createColon(Project project) { + public static Pair createColonAndWhiteSpaces(Project project) { JetProperty property = createProperty(project, "val x : Int"); return Pair.create(property.findElementAt(5), property.findElementAt(7)); } diff --git a/idea/src/org/jetbrains/jet/plugin/intentions/JetTypeLookupExpression.java b/idea/src/org/jetbrains/jet/plugin/intentions/JetTypeLookupExpression.java new file mode 100644 index 00000000000..b4dfb821a7a --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/intentions/JetTypeLookupExpression.java @@ -0,0 +1,100 @@ +/* + * Copyright 2010-2013 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.jet.plugin.intentions; + +import com.intellij.codeInsight.completion.InsertHandler; +import com.intellij.codeInsight.completion.InsertionContext; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.codeInsight.template.Expression; +import com.intellij.codeInsight.template.ExpressionContext; +import com.intellij.codeInsight.template.Result; +import com.intellij.codeInsight.template.TextResult; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; +import com.intellij.codeInsight.template.impl.TemplateState; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; + +import java.util.Iterator; +import java.util.List; + + +public abstract class JetTypeLookupExpression extends Expression { + + protected final LookupElement[] lookupItems; + + protected final T defaultItem; + + private final String advertisementText; + + public JetTypeLookupExpression( + List lookupItems, + T defaultItem, + String advertisement + ) { + this.advertisementText = advertisement; + this.defaultItem = defaultItem; + this.lookupItems = initLookupItems(lookupItems); + + } + + private LookupElement[] initLookupItems(List lookupItems) { + LookupElement[] lookupElements = new LookupElement[lookupItems.size()]; + Iterator iterator = lookupItems.iterator(); + for (int i = 0; i < lookupElements.length; i++) { + final T suggestion = iterator.next(); + lookupElements[i] = LookupElementBuilder.create(suggestion, getLookupString(suggestion)).withInsertHandler( + new InsertHandler() { + @Override + public void handleInsert(InsertionContext context, LookupElement item) { + Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(context.getEditor()); + TemplateState templateState = TemplateManagerImpl.getTemplateState(topLevelEditor); + if (templateState != null) { + TextRange range = templateState.getCurrentVariableRange(); + if (range != null) { + topLevelEditor.getDocument() + .replaceString(range.getStartOffset(), range.getEndOffset(), getResult((T) item.getObject())); + } + } + } + }); + } + return lookupElements; + } + + public LookupElement[] calculateLookupItems(ExpressionContext context) { + return lookupItems.length > 1 ? lookupItems : null; + } + + public Result calculateQuickResult(ExpressionContext context) { + return calculateResult(context); + } + + public Result calculateResult(ExpressionContext context) { + return new TextResult(getLookupString(defaultItem)); + } + + @Override + public String getAdvertisingText() { + return advertisementText; + } + + protected abstract String getLookupString(T element); + + protected abstract String getResult(T element); +} diff --git a/idea/src/org/jetbrains/jet/plugin/intentions/SpecifyTypeExplicitlyAction.java b/idea/src/org/jetbrains/jet/plugin/intentions/SpecifyTypeExplicitlyAction.java index 2e07f1cadc7..21403b4dcfe 100644 --- a/idea/src/org/jetbrains/jet/plugin/intentions/SpecifyTypeExplicitlyAction.java +++ b/idea/src/org/jetbrains/jet/plugin/intentions/SpecifyTypeExplicitlyAction.java @@ -16,18 +16,25 @@ package org.jetbrains.jet.plugin.intentions; +import com.google.common.collect.Lists; import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; -import com.intellij.lang.ASTNode; +import com.intellij.codeInsight.template.Expression; +import com.intellij.codeInsight.template.Template; +import com.intellij.codeInsight.template.TemplateBuilderImpl; +import com.intellij.codeInsight.template.TemplateEditingAdapter; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.JetNodeTypes; -import org.jetbrains.jet.lang.descriptors.*; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor; +import org.jetbrains.jet.lang.descriptors.VariableDescriptor; import org.jetbrains.jet.lang.diagnostics.Diagnostic; import org.jetbrains.jet.lang.diagnostics.Errors; import org.jetbrains.jet.lang.psi.*; @@ -35,12 +42,14 @@ import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.lang.types.ErrorUtils; import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.TypeConstructor; +import org.jetbrains.jet.lang.types.TypeUtils; import org.jetbrains.jet.plugin.JetBundle; import org.jetbrains.jet.plugin.codeInsight.ReferenceToClassesShortening; import org.jetbrains.jet.plugin.project.AnalyzeSingleFileUtil; import org.jetbrains.jet.renderer.DescriptorRenderer; -import java.util.Collections; +import java.util.*; public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction { @NotNull @@ -61,7 +70,7 @@ public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction { if (parent instanceof JetProperty) { JetProperty property = (JetProperty) parent; if (property.getTypeRef() == null) { - addTypeAnnotation(project, property, type); + addTypeAnnotation(project, editor, property, type); } else { removeTypeAnnotation(property); @@ -70,7 +79,7 @@ public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction { else if (parent instanceof JetParameter) { JetParameter parameter = (JetParameter) parent; if (parameter.getTypeReference() == null) { - addTypeAnnotation(project, parameter, type); + addTypeAnnotation(project, editor, parameter, type); } else { removeTypeAnnotation(parameter); @@ -79,7 +88,7 @@ public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction { else if (parent instanceof JetNamedFunction) { JetNamedFunction function = (JetNamedFunction) parent; assert function.getReturnTypeRef() == null; - addTypeAnnotation(project, function, type); + addTypeAnnotation(project, editor, function, type); } else { throw new IllegalStateException("Unexpected parent: " + parent); @@ -160,58 +169,100 @@ public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction { return type == null ? ErrorUtils.createErrorType("null type") : type; } - public static void addTypeAnnotation(Project project, JetProperty property, @NotNull JetType exprType) { - if (property.getTypeRef() != null) return; + public static void addTypeAnnotation( + @NotNull Project project, + @NotNull Editor editor, + @NotNull JetProperty property, + @NotNull JetType exprType + ) { + if (property.getTypeRef() != null) { + return; + } + PsiElement anchor = property.getNameIdentifier(); - if (anchor == null) return; - anchor = anchor.getNextSibling(); - if (anchor != null) { - if (!(anchor instanceof PsiWhiteSpace)) { - return; - } - } - - // For enum entry expression, we want type "MyEntry" instead of "MyEntry..ENTRY1" - ClassifierDescriptor typeClassifier = exprType.getConstructor().getDeclarationDescriptor(); - assert typeClassifier != null; - if (DescriptorUtils.isEnumEntry(typeClassifier)) { - ClassDescriptor enumClass = (ClassDescriptor) typeClassifier.getContainingDeclaration().getContainingDeclaration(); - assert enumClass != null; - exprType = enumClass.getDefaultType(); - } - - JetTypeReference typeReference = JetPsiFactory.createType(project, DescriptorRenderer.TEXT.renderType(exprType)); - ASTNode colon = JetPsiFactory.createColonNode(project); - ASTNode anchorNode = anchor == null ? null : anchor.getNode().getTreeNext(); if (anchor == null) { - property.getNode().addChild(JetPsiFactory.createWhiteSpace(project).getNode(), anchorNode); + return; } - property.getNode().addChild(colon, anchorNode); - property.getNode().addChild(JetPsiFactory.createWhiteSpace(project).getNode(), anchorNode); - property.getNode().addChild(typeReference.getNode(), anchorNode); - property.getNode().addChild(JetPsiFactory.createWhiteSpace(project).getNode(), anchorNode); - if (anchor != null) { - anchor.delete(); - } - ReferenceToClassesShortening.compactReferenceToClasses(Collections.singletonList(property)); + + addTypeAnnotation(project, editor, property, anchor, exprType); } - public static void addTypeAnnotation(Project project, JetFunction function, @NotNull JetType exprType) { - JetTypeReference typeReference = JetPsiFactory.createType(project, DescriptorRenderer.TEXT.renderType(exprType)); - Pair colon = JetPsiFactory.createColon(project); + public static void addTypeAnnotation(Project project, Editor editor, JetFunction function, @NotNull JetType exprType) { JetParameterList valueParameterList = function.getValueParameterList(); assert valueParameterList != null; - function.addAfter(typeReference, valueParameterList); - function.addRangeAfter(colon.getFirst(), colon.getSecond(), valueParameterList); - ReferenceToClassesShortening.compactReferenceToClasses(Collections.singletonList(function)); + addTypeAnnotation(project, editor, function, valueParameterList, exprType); } - public static void addTypeAnnotation(Project project, JetParameter parameter, @NotNull JetType exprType) { - JetTypeReference typeReference = JetPsiFactory.createType(project, DescriptorRenderer.TEXT.renderType(exprType)); - Pair colon = JetPsiFactory.createColon(project); - parameter.addAfter(typeReference, parameter.getNameIdentifier()); - parameter.addRangeAfter(colon.getFirst(), colon.getSecond(), parameter.getNameIdentifier()); - ReferenceToClassesShortening.compactReferenceToClasses(Collections.singletonList(parameter)); + public static void addTypeAnnotation(Project project, Editor editor, JetParameter parameter, @NotNull JetType exprType) { + addTypeAnnotation(project, editor, parameter, parameter.getNameIdentifier(), exprType); + } + + private static void addTypeAnnotation( + @NotNull Project project, + @NotNull Editor editor, + @NotNull final JetNamedDeclaration namedDeclaration, + @NotNull PsiElement anchor, + @NotNull JetType exprType + ) { + TypeConstructor constructor = exprType.getConstructor(); + boolean isAnonymous = DescriptorUtils.isAnonymous(constructor.getDeclarationDescriptor()); + + Set allSupertypes = TypeUtils.getAllSupertypes(exprType); + List types = isAnonymous ? new ArrayList() : Lists.newArrayList(exprType); + types.addAll(allSupertypes); + + Expression expression = new JetTypeLookupExpression( + types, + types.iterator().next(), + JetBundle.message("specify.type.explicitly.add.action.name") + ) { + @Override + protected String getLookupString(JetType element) { + return DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(element); + } + + @Override + protected String getResult(JetType element) { + return DescriptorRenderer.TEXT.renderType(element); + } + }; + + JetTypeReference typeReference = JetPsiFactory.createType(project, "Any"); + namedDeclaration.addAfter(typeReference, anchor); + Pair colon = JetPsiFactory.createColonAndWhiteSpaces(project); + namedDeclaration.addRangeAfter(colon.getFirst(), colon.getSecond(), anchor); + + PsiDocumentManager.getInstance(project).commitAllDocuments(); + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()); + + JetTypeReference newTypeRef = getTypeRef(namedDeclaration); + TemplateBuilderImpl builder = new TemplateBuilderImpl(newTypeRef); + builder.replaceElement(newTypeRef, expression); + + editor.getCaretModel().moveToOffset(newTypeRef.getNode().getStartOffset()); + + TemplateManagerImpl manager = new TemplateManagerImpl(project); + manager.startTemplate(editor, builder.buildInlineTemplate(), new TemplateEditingAdapter() { + @Override + public void templateFinished(Template template, boolean brokenOff) { + ReferenceToClassesShortening.compactReferenceToClasses(Collections.singletonList(namedDeclaration)); + } + }); + } + + @Nullable + private static JetTypeReference getTypeRef(@NotNull JetNamedDeclaration namedDeclaration) { + if (namedDeclaration instanceof JetProperty) { + return ((JetProperty) namedDeclaration).getTypeRef(); + } + else if (namedDeclaration instanceof JetParameter) { + return ((JetParameter) namedDeclaration).getTypeReference(); + } + else if (namedDeclaration instanceof JetFunction) { + return ((JetFunction) namedDeclaration).getReturnTypeRef(); + } + assert false : "Wrong namedDeclaration: " + namedDeclaration.getText(); + return null; } private static void removeTypeAnnotation(@NotNull JetNamedDeclaration property, @Nullable JetTypeReference typeReference) { diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java index 5a8a56af89a..a535790c6af 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java @@ -173,6 +173,7 @@ public class QuickFixes { factories.put(PACKAGE_MEMBER_CANNOT_BE_PROTECTED, removeProtectedModifierFactory); actions.put(PUBLIC_MEMBER_SHOULD_SPECIFY_TYPE, new SpecifyTypeExplicitlyFix()); + actions.put(AMBIGUOUS_ANONYMOUS_TYPE_INFERRED, new SpecifyTypeExplicitlyFix()); factories.put(ELSE_MISPLACED_IN_WHEN, MoveWhenElseBranchFix.createFactory()); factories.put(NO_ELSE_IN_WHEN, AddWhenElseBranchFix.createFactory()); diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/RemovePartsFromPropertyFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/RemovePartsFromPropertyFix.java index 94277b39b47..6854ba50f3c 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/RemovePartsFromPropertyFix.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/RemovePartsFromPropertyFix.java @@ -34,14 +34,14 @@ public class RemovePartsFromPropertyFix extends JetIntentionAction private final boolean removeInitializer; private final boolean removeGetter; private final boolean removeSetter; - + private RemovePartsFromPropertyFix(@NotNull JetProperty element, boolean removeInitializer, boolean removeGetter, boolean removeSetter) { super(element); this.removeInitializer = removeInitializer; this.removeGetter = removeGetter; this.removeSetter = removeSetter; } - + private RemovePartsFromPropertyFix(@NotNull JetProperty element) { this(element, element.getInitializer() != null, element.getGetter() != null && element.getGetter().getBodyExpression() != null, @@ -117,7 +117,7 @@ public class RemovePartsFromPropertyFix extends JetIntentionAction } element = (JetProperty) element.replace(newElement); if (typeToAdd != null) { - SpecifyTypeExplicitlyAction.addTypeAnnotation(project, element, typeToAdd); + SpecifyTypeExplicitlyAction.addTypeAnnotation(project, editor, element, typeToAdd); } } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/SpecifyTypeExplicitlyFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/SpecifyTypeExplicitlyFix.java index 82af373b67b..1508919f76c 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/SpecifyTypeExplicitlyFix.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/SpecifyTypeExplicitlyFix.java @@ -46,10 +46,10 @@ public class SpecifyTypeExplicitlyFix extends PsiElementBaseIntentionAction { JetNamedDeclaration declaration = PsiTreeUtil.getParentOfType(element, JetProperty.class, JetNamedFunction.class); JetType type = getTypeForDeclaration(declaration); if (declaration instanceof JetProperty) { - addTypeAnnotation(project, (JetProperty) declaration, type); + addTypeAnnotation(project, editor, (JetProperty) declaration, type); } else if (declaration instanceof JetNamedFunction) { - addTypeAnnotation(project, (JetNamedFunction) declaration, type); + addTypeAnnotation(project, editor, (JetNamedFunction) declaration, type); } else { assert false : "Couldn't find property or function"; diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetInplaceVariableIntroducer.java b/idea/src/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetInplaceVariableIntroducer.java index 6e91f4523d4..a0252f73bd9 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetInplaceVariableIntroducer.java +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetInplaceVariableIntroducer.java @@ -16,8 +16,6 @@ package org.jetbrains.jet.plugin.refactoring.introduceVariable; -import com.intellij.codeInsight.template.Template; -import com.intellij.codeInsight.template.TemplateManager; import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.codeInsight.template.impl.TemplateState; import com.intellij.lang.ASTNode; @@ -25,9 +23,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.markup.HighlighterTargetArea; import com.intellij.openapi.editor.markup.RangeHighlighter; -import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; @@ -35,7 +31,6 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiNamedElement; -import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer; import com.intellij.ui.NonFocusableCheckBox; import org.jetbrains.annotations.Nullable; @@ -127,7 +122,7 @@ public class JetInplaceVariableIntroducer extends InplaceVariableIntroducer = object : B, A {} +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeAddition/afterAmbiguousPropertyReturnType.kt b/idea/testData/quickfix/typeAddition/afterAmbiguousPropertyReturnType.kt new file mode 100644 index 00000000000..304923b09b8 --- /dev/null +++ b/idea/testData/quickfix/typeAddition/afterAmbiguousPropertyReturnType.kt @@ -0,0 +1,10 @@ +// "Specify type explicitly" "true" +package a + +trait A {} + +trait B {} + +class C { + val property: B = object : B, A {} +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeAddition/beforeAmbiguousFunctionReturnType.kt b/idea/testData/quickfix/typeAddition/beforeAmbiguousFunctionReturnType.kt new file mode 100644 index 00000000000..ba122d2879f --- /dev/null +++ b/idea/testData/quickfix/typeAddition/beforeAmbiguousFunctionReturnType.kt @@ -0,0 +1,10 @@ +// "Specify return type explicitly" "true" +package a + +trait A {} + +trait B {} + +class C { + fun property() = object : B, A {} +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeAddition/beforeAmbiguousPropertyReturnType.kt b/idea/testData/quickfix/typeAddition/beforeAmbiguousPropertyReturnType.kt new file mode 100644 index 00000000000..63463fad0d6 --- /dev/null +++ b/idea/testData/quickfix/typeAddition/beforeAmbiguousPropertyReturnType.kt @@ -0,0 +1,10 @@ +// "Specify type explicitly" "true" +package a + +trait A {} + +trait B {} + +class C { + val property = object : B, A {} +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java index 19bc2a6a1cd..4c4305f12c4 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java @@ -693,6 +693,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/quickfix/typeAddition"), Pattern.compile("^before(\\w+)\\.kt$"), true); } + @TestMetadata("beforeAmbiguousFunctionReturnType.kt") + public void testAmbiguousFunctionReturnType() throws Exception { + doTest("idea/testData/quickfix/typeAddition/beforeAmbiguousFunctionReturnType.kt"); + } + + @TestMetadata("beforeAmbiguousPropertyReturnType.kt") + public void testAmbiguousPropertyReturnType() throws Exception { + doTest("idea/testData/quickfix/typeAddition/beforeAmbiguousPropertyReturnType.kt"); + } + @TestMetadata("beforeNoAddErrorType.kt") public void testNoAddErrorType() throws Exception { doTest("idea/testData/quickfix/typeAddition/beforeNoAddErrorType.kt");