From ae818808c61d5ef09ae00cbb6372c57f8e52df2b Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Mon, 28 Oct 2024 23:09:05 +0100 Subject: [PATCH 1/2] fix assign bin operator parsing --- src/main/grammars/MoveLexer.flex | 25 ++++++++++++++----- src/main/grammars/MoveParser.bnf | 11 ++++++++ .../kotlin/org/move/lang/core/MvTokenType.kt | 6 +++-- .../org/move/ide/formatter/FormatterTest.kt | 1 + .../compound_assignments.move | 5 ++++ .../compound_assignments_after.move | 5 ++++ .../lang/parser/complete/assign_bin_expr.txt | 24 ++++++------------ 7 files changed, 53 insertions(+), 24 deletions(-) create mode 100644 src/test/resources/org/move/ide/formatter.fixtures/compound_assignments.move create mode 100644 src/test/resources/org/move/ide/formatter.fixtures/compound_assignments_after.move diff --git a/src/main/grammars/MoveLexer.flex b/src/main/grammars/MoveLexer.flex index 35dab039b..cc02cbfe9 100644 --- a/src/main/grammars/MoveLexer.flex +++ b/src/main/grammars/MoveLexer.flex @@ -106,20 +106,33 @@ QUOTE_IDENTIFIER=\'[_a-zA-Z][_a-zA-Z0-9]* "!=" { return NOT_EQ; } "!" { return EXCL; } + + "<" { return LT; } + ">" { return GT; } + "@" { return AT; } + "#" { return HASH; } + "=>" { return FAT_ARROW; } + + "+=" { return PLUS_EQ; } + "-=" { return MINUS_EQ; } + "*=" { return MUL_EQ; } + "/=" { return DIV_EQ; } + "%=" { return MODULO_EQ; } + "+" { return PLUS; } "-" { return MINUS; } "*" { return MUL; } "/" { return DIV; } "%" { return MODULO; } - "^" { return XOR; } - "<" { return LT; } - ">" { return GT; } + "&=" { return AND_EQ; } + "|=" { return OR_EQ; } + "^=" { return XOR_EQ; } + "&" { return AND; } "|" { return OR; } - "@" { return AT; } - "#" { return HASH; } - "=>" { return FAT_ARROW; } + "^" { return XOR; } + // keywords "script" { return SCRIPT_KW; } diff --git a/src/main/grammars/MoveParser.bnf b/src/main/grammars/MoveParser.bnf index 2fad60270..4281ea1d2 100644 --- a/src/main/grammars/MoveParser.bnf +++ b/src/main/grammars/MoveParser.bnf @@ -77,6 +77,7 @@ DOT = '.' EXCL = '!' + PLUS = '+' MINUS = '-' XOR = '^' @@ -101,6 +102,16 @@ DOT_DOT = '..' FAT_ARROW = '=>' + PLUS_EQ = '+=' + MINUS_EQ = '-=' + MUL_EQ = '*=' + DIV_EQ = '/=' + MODULO_EQ = '%=' + + AND_EQ = '&=' + OR_EQ = '|=' + XOR_EQ = '^=' + ADDRESS = 'address_kw' HAS = 'has_kw' IS = 'is_kw' diff --git a/src/main/kotlin/org/move/lang/core/MvTokenType.kt b/src/main/kotlin/org/move/lang/core/MvTokenType.kt index 3b45f502d..6a9bc58a0 100644 --- a/src/main/kotlin/org/move/lang/core/MvTokenType.kt +++ b/src/main/kotlin/org/move/lang/core/MvTokenType.kt @@ -8,7 +8,7 @@ import org.move.lang.MoveParserDefinition.Companion.EOL_COMMENT import org.move.lang.MoveParserDefinition.Companion.EOL_DOC_COMMENT import org.move.lang.MvElementTypes.* -class MvTokenType(debugName: String) : IElementType(debugName, MoveLanguage) +class MvTokenType(debugName: String): IElementType(debugName, MoveLanguage) fun tokenSetOf(vararg tokens: IElementType) = TokenSet.create(*tokens) @@ -37,7 +37,9 @@ val MOVE_BINARY_OPS = tokenSetOf( OR, AND, XOR, MUL, DIV, MODULO, PLUS, MINUS, - XOR, AND, OR + XOR, AND, OR, + PLUS_EQ, MINUS_EQ, MUL_EQ, DIV_EQ, MODULO_EQ, + AND_EQ, OR_EQ, XOR_EQ, GT_GT_EQ, LT_LT_EQ, ) val LIST_OPEN_SYMBOLS = tokenSetOf(L_PAREN, LT) diff --git a/src/test/kotlin/org/move/ide/formatter/FormatterTest.kt b/src/test/kotlin/org/move/ide/formatter/FormatterTest.kt index 09ee8a418..b0522b941 100644 --- a/src/test/kotlin/org/move/ide/formatter/FormatterTest.kt +++ b/src/test/kotlin/org/move/ide/formatter/FormatterTest.kt @@ -14,4 +14,5 @@ class FormatterTest : MvFormatterTestBase() { fun `test inner block`() = doTest() fun `test expressions`() = doTest() fun `test chop wraps`() = doTest() + fun `test compound assignments`() = doTest() } diff --git a/src/test/resources/org/move/ide/formatter.fixtures/compound_assignments.move b/src/test/resources/org/move/ide/formatter.fixtures/compound_assignments.move new file mode 100644 index 000000000..de1f7079b --- /dev/null +++ b/src/test/resources/org/move/ide/formatter.fixtures/compound_assignments.move @@ -0,0 +1,5 @@ +module 0x1::compound_assignments { + fun main() { + x += 1; + } +} diff --git a/src/test/resources/org/move/ide/formatter.fixtures/compound_assignments_after.move b/src/test/resources/org/move/ide/formatter.fixtures/compound_assignments_after.move new file mode 100644 index 000000000..20fb287b0 --- /dev/null +++ b/src/test/resources/org/move/ide/formatter.fixtures/compound_assignments_after.move @@ -0,0 +1,5 @@ +module 0x1::compound_assignments { + fun main() { + x += 1; + } +} diff --git a/src/test/resources/org/move/lang/parser/complete/assign_bin_expr.txt b/src/test/resources/org/move/lang/parser/complete/assign_bin_expr.txt index 9b82118b0..b4fa028d1 100644 --- a/src/test/resources/org/move/lang/parser/complete/assign_bin_expr.txt +++ b/src/test/resources/org/move/lang/parser/complete/assign_bin_expr.txt @@ -27,8 +27,7 @@ FILE PsiElement(IDENTIFIER)('x') PsiWhiteSpace(' ') MvBinaryOpImpl(BINARY_OP) - PsiElement(+)('+') - PsiElement(=)('=') + PsiElement(+=)('+=') PsiWhiteSpace(' ') MvLitExprImpl(LIT_EXPR) PsiElement(INTEGER_LITERAL)('1') @@ -41,8 +40,7 @@ FILE PsiElement(IDENTIFIER)('x') PsiWhiteSpace(' ') MvBinaryOpImpl(BINARY_OP) - PsiElement(-)('-') - PsiElement(=)('=') + PsiElement(-=)('-=') PsiWhiteSpace(' ') MvLitExprImpl(LIT_EXPR) PsiElement(INTEGER_LITERAL)('1') @@ -55,8 +53,7 @@ FILE PsiElement(IDENTIFIER)('x') PsiWhiteSpace(' ') MvBinaryOpImpl(BINARY_OP) - PsiElement(*)('*') - PsiElement(=)('=') + PsiElement(*=)('*=') PsiWhiteSpace(' ') MvLitExprImpl(LIT_EXPR) PsiElement(INTEGER_LITERAL)('1') @@ -69,8 +66,7 @@ FILE PsiElement(IDENTIFIER)('x') PsiWhiteSpace(' ') MvBinaryOpImpl(BINARY_OP) - PsiElement(/)('/') - PsiElement(=)('=') + PsiElement(/=)('/=') PsiWhiteSpace(' ') MvLitExprImpl(LIT_EXPR) PsiElement(INTEGER_LITERAL)('1') @@ -83,8 +79,7 @@ FILE PsiElement(IDENTIFIER)('x') PsiWhiteSpace(' ') MvBinaryOpImpl(BINARY_OP) - PsiElement(%)('%') - PsiElement(=)('=') + PsiElement(%=)('%=') PsiWhiteSpace(' ') MvLitExprImpl(LIT_EXPR) PsiElement(INTEGER_LITERAL)('1') @@ -97,8 +92,7 @@ FILE PsiElement(IDENTIFIER)('x') PsiWhiteSpace(' ') MvBinaryOpImpl(BINARY_OP) - PsiElement(&)('&') - PsiElement(=)('=') + PsiElement(&=)('&=') PsiWhiteSpace(' ') MvLitExprImpl(LIT_EXPR) PsiElement(INTEGER_LITERAL)('1') @@ -111,8 +105,7 @@ FILE PsiElement(IDENTIFIER)('x') PsiWhiteSpace(' ') MvBinaryOpImpl(BINARY_OP) - PsiElement(|)('|') - PsiElement(=)('=') + PsiElement(|=)('|=') PsiWhiteSpace(' ') MvLitExprImpl(LIT_EXPR) PsiElement(INTEGER_LITERAL)('1') @@ -125,8 +118,7 @@ FILE PsiElement(IDENTIFIER)('x') PsiWhiteSpace(' ') MvBinaryOpImpl(BINARY_OP) - PsiElement(^)('^') - PsiElement(=)('=') + PsiElement(^=)('^=') PsiWhiteSpace(' ') MvLitExprImpl(LIT_EXPR) PsiElement(INTEGER_LITERAL)('1') From f8731e6481c50319b494d8b2ce23ec7889442ae9 Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Mon, 28 Oct 2024 23:39:12 +0100 Subject: [PATCH 2/2] replace with compound operator inspection --- ...ReplaceWithCompoundAssignmentInspection.kt | 56 +++++++++++++++ .../kotlin/org/move/lang/core/MvTokenType.kt | 5 ++ .../move/lang/core/psi/ext/MvBinaryExpr.kt | 3 + src/main/resources/META-INF/plugin.xml | 4 ++ .../MvReplaceWithCompoundAssignment.html | 5 ++ ...aceWithCompoundAssignmentInspectionTest.kt | 72 +++++++++++++++++++ 6 files changed, 145 insertions(+) create mode 100644 src/main/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspection.kt create mode 100644 src/main/resources/inspectionDescriptions/MvReplaceWithCompoundAssignment.html create mode 100644 src/test/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspectionTest.kt diff --git a/src/main/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspection.kt b/src/main/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspection.kt new file mode 100644 index 000000000..eedd0aa9b --- /dev/null +++ b/src/main/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspection.kt @@ -0,0 +1,56 @@ +package org.move.ide.inspections.compilerV2 + +import com.intellij.codeInsight.PsiEquivalenceUtil +import com.intellij.codeInspection.ProblemHighlightType.WEAK_WARNING +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import org.move.ide.inspections.DiagnosticFix +import org.move.lang.core.MOVE_ARITHMETIC_BINARY_OPS +import org.move.lang.core.psi.MvAssignmentExpr +import org.move.lang.core.psi.MvBinaryExpr +import org.move.lang.core.psi.ext.elementType +import org.move.lang.core.psi.ext.operator +import org.move.lang.core.psi.psiFactory + +class MvReplaceWithCompoundAssignmentInspection: + Move2OnlyInspectionBase(MvAssignmentExpr::class.java) { + + override fun visitTargetElement(element: MvAssignmentExpr, holder: ProblemsHolder, isOnTheFly: Boolean) { + val lhsExpr = element.expr + val initializerExpr = element.initializer.expr ?: return + if (initializerExpr is MvBinaryExpr + && initializerExpr.operator.elementType in MOVE_ARITHMETIC_BINARY_OPS + ) { + // take lhs of binary plus expr + val argumentExpr = initializerExpr.left + if (PsiEquivalenceUtil.areElementsEquivalent(lhsExpr, argumentExpr)) { + val op = initializerExpr.operator.text + holder.registerProblem( + element, + "Can be replaced with compound assignment", + WEAK_WARNING, + ReplaceWithCompoundAssignmentFix(element, op) + ) + } + } + } + + class ReplaceWithCompoundAssignmentFix(assignmentExpr: MvAssignmentExpr, val op: String): + DiagnosticFix(assignmentExpr) { + + override fun getText(): String = "Replace with compound assignment expr" + + override fun invoke(project: Project, file: PsiFile, element: MvAssignmentExpr) { + val lhsExpr = element.expr + val rhsExpr = (element.initializer.expr as? MvBinaryExpr)?.right ?: return + + val psiFactory = project.psiFactory + val assignBinExpr = psiFactory.expr("x $op= 1") + assignBinExpr.left.replace(lhsExpr) + assignBinExpr.right?.replace(rhsExpr) + + element.replace(assignBinExpr) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/move/lang/core/MvTokenType.kt b/src/main/kotlin/org/move/lang/core/MvTokenType.kt index 6a9bc58a0..5295ac513 100644 --- a/src/main/kotlin/org/move/lang/core/MvTokenType.kt +++ b/src/main/kotlin/org/move/lang/core/MvTokenType.kt @@ -28,6 +28,11 @@ val TYPES = tokenSetOf(PATH_TYPE, REF_TYPE, TUPLE_TYPE) val MOVE_COMMENTS = tokenSetOf(BLOCK_COMMENT, EOL_COMMENT, EOL_DOC_COMMENT) +val MOVE_ARITHMETIC_BINARY_OPS = tokenSetOf( + PLUS, MINUS, MUL, DIV, MODULO, + AND, OR, XOR, + LT_LT, GT_GT, +) val MOVE_BINARY_OPS = tokenSetOf( OR_OR, AND_AND, EQ_EQ_GT, LT_EQ_EQ_GT, diff --git a/src/main/kotlin/org/move/lang/core/psi/ext/MvBinaryExpr.kt b/src/main/kotlin/org/move/lang/core/psi/ext/MvBinaryExpr.kt index d5883e061..cd7c718dc 100644 --- a/src/main/kotlin/org/move/lang/core/psi/ext/MvBinaryExpr.kt +++ b/src/main/kotlin/org/move/lang/core/psi/ext/MvBinaryExpr.kt @@ -1,10 +1,13 @@ package org.move.lang.core.psi.ext import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement import org.move.lang.MvElementTypes.BINARY_OP import org.move.lang.core.psi.MvBinaryExpr import org.move.lang.core.psi.MvElementImpl +val MvBinaryExpr.operator: PsiElement get() = binaryOp.operator + abstract class MvBinaryExprMixin(node: ASTNode): MvElementImpl(node), MvBinaryExpr { override fun toString(): String { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 0165711a9..1e985a27f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -295,6 +295,10 @@ displayName="Convert to index expr" enabledByDefault="true" level="WEAK WARNING" implementationClass="org.move.ide.inspections.compilerV2.MvReplaceWithIndexExprInspection" /> + + +Replaces `x = x + 1` with `x += 1` + + \ No newline at end of file diff --git a/src/test/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspectionTest.kt b/src/test/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspectionTest.kt new file mode 100644 index 000000000..87f3f8352 --- /dev/null +++ b/src/test/kotlin/org/move/ide/inspections/compilerV2/MvReplaceWithCompoundAssignmentInspectionTest.kt @@ -0,0 +1,72 @@ +package org.move.ide.inspections.compilerV2 + +import org.intellij.lang.annotations.Language +import org.move.utils.tests.MoveV2 +import org.move.utils.tests.annotation.InspectionTestBase + +@MoveV2 +class MvReplaceWithCompoundAssignmentInspectionTest: + InspectionTestBase(MvReplaceWithCompoundAssignmentInspection::class) { + + fun `test replace variable assignment with plus`() = doFixTest( + """ + module 0x1::m { + fun main() { + let x = 1; + /*caret*/x = x + 1; + } + } + """, """ + module 0x1::m { + fun main() { + let x = 1; + x += 1; + } + } + """ + ) + + fun `test replace variable assignment with left shift`() = doFixTest( + """ + module 0x1::m { + fun main() { + let x = 1; + /*caret*/x = x << 1; + } + } + """, """ + module 0x1::m { + fun main() { + let x = 1; + x <<= 1; + } + } + """ + ) + + fun `test replace deref assignment with plus`() = doFixTest( + """ + module 0x1::m { + fun main(p: &u8) { + /*caret*/*p = *p + 1; + } + } + """, """ + module 0x1::m { + fun main(p: &u8) { + *p += 1; + } + } + """ + ) + + private fun doTest(@Language("Move") text: String) = + checkByText(text, checkWarn = false, checkWeakWarn = true) + + private fun doFixTest( + @Language("Move") before: String, + @Language("Move") after: String, + ) = + checkFixByText("Replace with compound assignment expr", before, after, + checkWarn = false, checkWeakWarn = true) +} \ No newline at end of file