diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java index 8cf65d29b97..7b9c96e2b18 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java @@ -47,6 +47,9 @@ import static org.jetbrains.jet.lang.diagnostics.Severity.WARNING; */ public interface Errors { + // TODO: Temporary error message: to deprecate tuples we report this error and provide a quick fix + SimpleDiagnosticFactory TUPLES_ARE_NOT_SUPPORTED = SimpleDiagnosticFactory.create(ERROR); + DiagnosticFactory1 EXCEPTION_WHILE_ANALYZING = DiagnosticFactory1.create(ERROR); UnresolvedReferenceDiagnosticFactory UNRESOLVED_REFERENCE = UnresolvedReferenceDiagnosticFactory.create(); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java index 62cbb5db66a..e0c5ab364eb 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/rendering/DefaultErrorMessages.java @@ -45,6 +45,10 @@ public class DefaultErrorMessages { public static final DiagnosticRenderer RENDERER = new DispatchingDiagnosticRenderer(MAP); static { + + // TODO: remove when tuples are completely dropped + MAP.put(TUPLES_ARE_NOT_SUPPORTED, "Tuples are not supported. Press Alt+Enter to replace tuples with library classes"); + MAP.put(EXCEPTION_WHILE_ANALYZING, "{0}", new Renderer() { @NotNull @Override diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TypeResolver.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TypeResolver.java index 71576375ebe..c2f5f64d4f2 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TypeResolver.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TypeResolver.java @@ -37,6 +37,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import static org.jetbrains.jet.lang.diagnostics.Errors.TUPLES_ARE_NOT_SUPPORTED; import static org.jetbrains.jet.lang.diagnostics.Errors.UNSUPPORTED; import static org.jetbrains.jet.lang.diagnostics.Errors.WRONG_NUMBER_OF_TYPE_ARGUMENTS; @@ -180,6 +181,9 @@ public class TypeResolver { @Override public void visitTupleType(JetTupleType type) { + // TODO: remove this method completely when tuples are droppped + trace.report(TUPLES_ARE_NOT_SUPPORTED.on(type)); + // TODO labels result[0] = JetStandardClasses.getTupleType(resolveTypes(scope, type.getComponentTypeRefs(), trace, checkBounds)); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/BasicExpressionTypingVisitor.java index 6e8ade4d8ba..efc5025c772 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/BasicExpressionTypingVisitor.java @@ -356,6 +356,9 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { @Override public JetTypeInfo visitTupleExpression(JetTupleExpression expression, ExpressionTypingContext context) { + // TODO: remove this method completely when tuples are droppped + context.trace.report(TUPLES_ARE_NOT_SUPPORTED.on(expression)); + List entries = expression.getEntries(); List types = new ArrayList(); for (JetExpression entry : entries) { diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties index 8bdad14cc43..fa7e4f725fe 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties @@ -92,3 +92,4 @@ options.jet.attribute.descriptor.constructor.call=Constructor call options.jet.attribute.descriptor.auto.casted=Smart-cast value options.jet.attribute.descriptor.label=Label change.to.function.invocation=Change to function invocation +migrate.tuple=Migrate tuples in project\: e.g., \#(,) and \#(,,) will be replaced by Pair and Triple diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/MigrateTuplesInProjectFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/MigrateTuplesInProjectFix.java new file mode 100644 index 00000000000..8f646dbc466 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/MigrateTuplesInProjectFix.java @@ -0,0 +1,250 @@ +/* + * Copyright 2010-2012 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.quickfix; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +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.jet.analyzer.AnalyzeExhaust; +import org.jetbrains.jet.lang.ModuleConfiguration; +import org.jetbrains.jet.lang.descriptors.ClassDescriptor; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.diagnostics.Diagnostic; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.BodiesResolveContext; +import org.jetbrains.jet.lang.resolve.DelegatingBindingTrace; +import org.jetbrains.jet.lang.types.lang.JetStandardClasses; +import org.jetbrains.jet.plugin.JetBundle; +import org.jetbrains.jet.plugin.project.AnalyzerFacadeProvider; +import org.jetbrains.jet.plugin.project.PluginJetFilesProvider; + +import java.util.Collection; +import java.util.Collections; + +/** + * @author abreslav + */ +public class MigrateTuplesInProjectFix extends JetIntentionAction { + public MigrateTuplesInProjectFix(@NotNull PsiElement element) { + super(element); + } + + @NotNull + @Override + public String getText() { + return JetBundle.message("migrate.tuple"); + } + + @NotNull + @Override + public String getFamilyName() { + return JetBundle.message("migrate.tuple"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return super.isAvailable(project, editor, file) + && (file instanceof JetFile) + && (element instanceof JetTupleExpression || element instanceof JetTupleType); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, final PsiFile file) throws IncorrectOperationException { + JetFile initialFile = (JetFile) file; + Collection files = PluginJetFilesProvider.WHOLE_PROJECT_DECLARATION_PROVIDER.fun(initialFile); + + AnalyzeExhaust exhaust = analyzeFiles(initialFile, files); + + for (JetFile jetFile : files) { + replaceTupleComponentCalls(jetFile, exhaust.getBindingContext()); + replaceTuple(jetFile); + } + } + + public static AnalyzeExhaust analyzeFiles(JetFile initialFile, Collection files) { + AnalyzeExhaust analyzeExhaustHeaders = AnalyzerFacadeProvider.getAnalyzerFacadeForFile(initialFile).analyzeFiles( + initialFile.getProject(), + files, + Collections.emptyList(), + Predicates.alwaysFalse()); + + BodiesResolveContext context = analyzeExhaustHeaders.getBodiesResolveContext(); + ModuleConfiguration moduleConfiguration = analyzeExhaustHeaders.getModuleConfiguration(); + assert context != null : "Headers resolver should prepare and stored information for bodies resolve"; + + // Need to resolve bodies in given file and all in the same package + return AnalyzerFacadeProvider.getAnalyzerFacadeForFile(initialFile).analyzeBodiesInFiles( + initialFile.getProject(), + Collections.emptyList(), + Predicates.alwaysTrue(), + new DelegatingBindingTrace(analyzeExhaustHeaders.getBindingContext()), + context, + moduleConfiguration); + } + + private void replaceTupleComponentCalls(JetFile file, final BindingContext context) { + for (JetDeclaration declaration : file.getDeclarations()) { + declaration.acceptChildren(new JetVisitorVoid() { + + @Override + public void visitSimpleNameExpression(JetSimpleNameExpression expression) { + DeclarationDescriptor referenceTarget = context.get(BindingContext.REFERENCE_TARGET, expression); + if (referenceTarget == null) return; + + DeclarationDescriptor containingDeclaration = referenceTarget.getContainingDeclaration(); + if (containingDeclaration == null) return; + + ImmutableSet supportedTupleClasses = + ImmutableSet.of(JetStandardClasses.getTuple(2), JetStandardClasses.getTuple(3)); + //noinspection SuspiciousMethodCalls + if (!supportedTupleClasses.contains(containingDeclaration)) return; + + ImmutableMap supportedComponents = ImmutableMap.builder() + .put("_1", "first") + .put("_2", "second") + .put("_3", "third") + .build(); + String newName = supportedComponents.get(expression.getReferencedName()); + if (newName == null) return; + + expression.replace(JetPsiFactory.createExpression(expression.getProject(), newName)); + } + + @Override + public void visitElement(PsiElement element) { + element.acceptChildren(this); + } + }); + } + } + + private void replaceTuple(JetFile file) { + for (JetDeclaration declaration : file.getDeclarations()) { + declaration.acceptChildren(new JetVisitorVoid() { + + @Override + public void visitTupleExpression(JetTupleExpression expression) { + expression.acceptChildren(this); + + int size = expression.getEntries().size(); + switch (size) { + case 0: + expression.replace(JetPsiFactory.createExpression(expression.getProject(), "Unit.VALUE")); + return; + case 1: + expression.replace(expression.getEntries().get(0)); + return; + + case 2: + replaceWithExpression(expression, "Pair"); + return; + + case 3: + replaceWithExpression(expression, "Triple"); + return; + + default: + // Can't do anything + } + } + + @Override + public void visitTupleType(JetTupleType type) { + type.acceptChildren(this); + + int size = type.getComponentTypeRefs().size(); + switch (size) { + case 0: + type.replace(JetPsiFactory.createType(type.getProject(), "Unit").getTypeElement()); + return; + case 1: + type.replace(type.getComponentTypeRefs().get(0).getTypeElement()); + return; + + case 2: + replaceWithType(type, "Pair"); + return; + + case 3: + replaceWithType(type, "Triple"); + return; + + default: + // Can't do anything + } + } + + @Override + public void visitElement(PsiElement element) { + element.acceptChildren(this); + } + }); + } + } + + private static void replaceWithExpression(JetTupleExpression expression, String name) { + String tupleContents = getTupleContents(expression.getText()); + if (tupleContents == null) return; + + String newText = name + "(" + tupleContents + ")"; + JetExpression newExpression = JetPsiFactory.createExpression(expression.getProject(), newText); + + expression.replace(newExpression); + } + + private static void replaceWithType(JetTupleType type, String name) { + String tupleContents = getTupleContents(type.getText()); + if (tupleContents == null) return; + + String newText = name + "<" + tupleContents + ">"; + JetTypeReference newExpression = JetPsiFactory.createType(type.getProject(), newText); + + type.replace(newExpression.getTypeElement()); + } + + @Nullable + private static String getTupleContents(String text) { + int indexOfHash = text.indexOf("#("); + assert indexOfHash >= 0; + String noOpenPar = text.substring(indexOfHash + 2); + int lastClosingPar = noOpenPar.lastIndexOf(')'); + if (lastClosingPar < 0) { + return null; + } + return noOpenPar.substring(0, lastClosingPar); + } + + public static JetIntentionActionFactory createFactory() { + return new JetIntentionActionFactory() { + @Nullable + @Override + public JetIntentionAction createAction(Diagnostic diagnostic) { + PsiElement element = diagnostic.getPsiElement(); + return new MigrateTuplesInProjectFix(element); + } + }; + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java index 381a1b60a2f..bfe1109201f 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java @@ -123,6 +123,8 @@ public class QuickFixes { factories.put(CANNOT_CHANGE_ACCESS_PRIVILEGE, ChangeVisibilityModifierFix.createFactory()); factories.put(CANNOT_WEAKEN_ACCESS_PRIVILEGE, ChangeVisibilityModifierFix.createFactory()); + factories.put(TUPLES_ARE_NOT_SUPPORTED, MigrateTuplesInProjectFix.createFactory()); + ImplementMethodsHandler implementMethodsHandler = new ImplementMethodsHandler(); actions.put(ABSTRACT_MEMBER_NOT_IMPLEMENTED, implementMethodsHandler); actions.put(MANY_IMPL_MEMBER_NOT_IMPLEMENTED, implementMethodsHandler); diff --git a/idea/testData/quickfix/migration/afterTuplesWithRuntime.kt b/idea/testData/quickfix/migration/afterTuplesWithRuntime.kt new file mode 100644 index 00000000000..971ea0e0911 --- /dev/null +++ b/idea/testData/quickfix/migration/afterTuplesWithRuntime.kt @@ -0,0 +1,53 @@ +// "Migrate tuples in project: e.g., #(,) and #(,,) will be replaced by Pair and Triple" "true" +// ERROR: Tuples are not supported. Press Alt+Enter to replace tuples with library classes +// ERROR: Tuples are not supported. Press Alt+Enter to replace tuples with library classes + +fun foo2() : Pair { + return Pair(1, 1) +} + +fun test2() { + foo2().first + foo2().second +} + +fun foo3() : Triple> { + return Triple(1, 1, arrayList("")) +} + +fun test3() { + foo3().first + foo3().second + foo3().third +} + +fun foo0(): Unit { + return Unit.VALUE +} + +fun foo4(): #(Int, Int, Int, Int) { + return #(1, 2, 3, 4) +} + +fun test4() { + foo4()._1 + foo4()._2 + foo4()._3 + foo4()._4 +} + +fun fooRec() : Triple, List>> { + Pair(1, Pair(1, "")) + throw Exception() +} + +fun foo1(): Int { + return 1 +} + +class Fake(val _1: Int, val _2: String) + +fun testFake(f: Fake) { + f._1 + f._2 +} \ No newline at end of file diff --git a/idea/testData/quickfix/migration/beforeTuplesWithRuntime.kt b/idea/testData/quickfix/migration/beforeTuplesWithRuntime.kt new file mode 100644 index 00000000000..6d60be6f52e --- /dev/null +++ b/idea/testData/quickfix/migration/beforeTuplesWithRuntime.kt @@ -0,0 +1,53 @@ +// "Migrate tuples in project: e.g., #(,) and #(,,) will be replaced by Pair and Triple" "true" +// ERROR: Tuples are not supported. Press Alt+Enter to replace tuples with library classes +// ERROR: Tuples are not supported. Press Alt+Enter to replace tuples with library classes + +fun foo2() : #(Int, Int) { + return #(1, 1) +} + +fun test2() { + foo2()._1 + foo2()._2 +} + +fun foo3() : #(Int, Int, List) { + return #(1, 1, arrayList("")) +} + +fun test3() { + foo3()._1 + foo3()._2 + foo3()._3 +} + +fun foo0(): #() { + return #() +} + +fun foo4(): #(Int, Int, Int, Int) { + return #(1, 2, 3, 4) +} + +fun test4() { + foo4()._1 + foo4()._2 + foo4()._3 + foo4()._4 +} + +fun fooRec() : #(Int, #(Int, String), List<#(Int, Int)>) { + #(1, #(1, "")) + throw Exception() +} + +fun foo1(): #(Int) { + return #(1) +} + +class Fake(val _1: Int, val _2: String) + +fun testFake(f: Fake) { + f._1 + f._2 +} \ No newline at end of file