From 5503d8f27e33c70a168e8871cd0309f627ae4fd1 Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 18 Jan 2024 22:47:28 +0000 Subject: [PATCH 001/100] Start on MixinExtras Expression language --- build.gradle.kts | 5 + src/main/grammars/MEExpressionLexer.flex | 115 ++++++++ src/main/grammars/MEExpressionParser.bnf | 253 ++++++++++++++++++ .../mixin/expression/MEExpressionFileType.kt | 31 +++ .../mixin/expression/MEExpressionLanguage.kt | 25 ++ .../expression/MEExpressionLexerAdapter.kt | 26 ++ .../MEExpressionParserDefinition.kt | 45 ++++ .../MEExpressionSyntaxHighlighter.kt | 154 +++++++++++ .../expression/psi/MEExpressionElementType.kt | 27 ++ .../mixin/expression/psi/MEExpressionFile.kt | 31 +++ .../expression/psi/MEExpressionTokenSets.kt | 70 +++++ .../expression/psi/MEExpressionTokenType.kt | 29 ++ .../psi/mixins/MEBinaryExpressionMixin.kt | 28 ++ .../psi/mixins/MELitExpressionMixin.kt | 28 ++ .../expression/psi/mixins/MENameMixin.kt | 27 ++ .../psi/mixins/MEUnaryExpressionMixin.kt | 28 ++ .../impl/MEBinaryExpressionImplMixin.kt | 54 ++++ .../mixins/impl/MELitExpressionImplMixin.kt | 69 +++++ .../psi/mixins/impl/MENameImplMixin.kt | 30 +++ .../mixins/impl/MEUnaryExpressionImplMixin.kt | 29 ++ src/main/resources/META-INF/plugin.xml | 13 + 21 files changed, 1117 insertions(+) create mode 100644 src/main/grammars/MEExpressionLexer.flex create mode 100644 src/main/grammars/MEExpressionParser.bnf create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionFileType.kt create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionLanguage.kt create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionLexerAdapter.kt create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/MEExpressionElementType.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenType.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MEUnaryExpressionMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt diff --git a/build.gradle.kts b/build.gradle.kts index b4ee71968..49ff0ae36 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -339,6 +339,9 @@ val generateNbttParser by parser("NbttParser", "com/demonwav/mcdev/nbt/lang/gen" val generateLangLexer by lexer("LangLexer", "com/demonwav/mcdev/translations/lang/gen") val generateLangParser by parser("LangParser", "com/demonwav/mcdev/translations/lang/gen") +val generateMEExpressionLexer by lexer("MEExpressionLexer", "com/demonwav/mcdev/platform/mixin/expression/gen") +val generateMEExpressionParser by parser("MEExpressionParser", "com/demonwav/mcdev/platform/mixin/expression/gen") + val generateTranslationTemplateLexer by lexer("TranslationTemplateLexer", "com/demonwav/mcdev/translations/lang/gen") val generate by tasks.registering { @@ -354,6 +357,8 @@ val generate by tasks.registering { generateNbttParser, generateLangLexer, generateLangParser, + generateMEExpressionLexer, + generateMEExpressionParser, generateTranslationTemplateLexer, ) } diff --git a/src/main/grammars/MEExpressionLexer.flex b/src/main/grammars/MEExpressionLexer.flex new file mode 100644 index 000000000..6622013c3 --- /dev/null +++ b/src/main/grammars/MEExpressionLexer.flex @@ -0,0 +1,115 @@ +package com.demonwav.mcdev.platform.mixin.expression; + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes; +import com.intellij.lexer.FlexLexer; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.TokenType; + +%% + +%public +%class MEExpressionLexer +%implements FlexLexer +%unicode +%function advance +%type IElementType +%eof{ return; +%eof} + +WHITE_SPACE=[\ \n\t\r] +RESERVED=assert|break|case|catch|const|continue|default|do|else|finally|for|goto|if|switch|synchronized|try|while|yield|_ +WILDCARD="?" +NEW=new +INSTANCEOF=instanceof +BOOL_LIT=true|false +NULL_LIT=null +RETURN=return +THROW=throw +THIS=this +SUPER=super +CLASS=class +IDENTIFIER=[A-Za-z_][A-Za-z0-9_]* +INT_LIT=([0-9]+|0x[0-9a-fA-F]+) +DEC_LIT=[0-9]*\.[0-9]+ +PLUS="+" +MINUS=- +MULT="*" +DIV="/" +MOD=% +BITWISE_NOT="~" +DOT="." +COMMA=, +LEFT_PAREN="(" +RIGHT_PAREN=")" +LEFT_BRACKET="[" +RIGHT_BRACKET="]" +AT=@ +SHL=<< +SHR=>> +USHR=>>> +LT=< +LE=<= +GT=> +GE=>= +EQ=== +NE="!=" +BITWISE_AND=& +BITWISE_XOR="^" +BITWISE_OR="|" +ASSIGN== + +STRING_TERMINATOR=' +STRING_ESCAPE=\\'|\\\\ + +%state STRING + +%% + + {WHITE_SPACE}+ { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; } + {RESERVED} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RESERVED; } + {WILDCARD} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_WILDCARD; } + {NEW} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NEW; } + {INSTANCEOF} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_INSTANCEOF; } + {BOOL_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BOOL_LIT; } + {NULL_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NULL_LIT; } + {RETURN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RETURN; } + {THROW} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_THROW; } + {THIS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_THIS; } + {SUPER} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SUPER; } + {CLASS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_CLASS; } + {IDENTIFIER} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_IDENTIFIER; } + {INT_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_INT_LIT; } + {DEC_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DEC_LIT; } + {PLUS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_PLUS; } + {MINUS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MINUS; } + {MULT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MULT; } + {DIV} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DIV; } + {MOD} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MOD; } + {BITWISE_NOT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_NOT; } + {DOT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DOT; } + {COMMA} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_COMMA; } + {LEFT_PAREN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_PAREN; } + {RIGHT_PAREN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_PAREN; } + {LEFT_BRACKET} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_BRACKET; } + {RIGHT_BRACKET} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_BRACKET; } + {AT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_AT; } + {SHL} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SHL; } + {SHR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SHR; } + {USHR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_USHR; } + {LT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LT; } + {LE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LE; } + {GT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_GT; } + {GE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_GE; } + {EQ} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_EQ; } + {NE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NE; } + {BITWISE_AND} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_AND; } + {BITWISE_XOR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_XOR; } + {BITWISE_OR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_OR; } + {ASSIGN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_ASSIGN; } + + {STRING_TERMINATOR} { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING_TERMINATOR; } + {STRING_ESCAPE} { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING_ESCAPE; } + {STRING_TERMINATOR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_STRING_TERMINATOR; } + [^'\\]+ { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING; } + +[^] { return TokenType.BAD_CHARACTER; } diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf new file mode 100644 index 000000000..5d87791d4 --- /dev/null +++ b/src/main/grammars/MEExpressionParser.bnf @@ -0,0 +1,253 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +{ + parserClass="com.demonwav.mcdev.platform.mixin.expression.MEExpressionParser" + extends="com.intellij.extapi.psi.ASTWrapperPsiElement" + + psiClassPrefix="ME" + psiImplClassSuffix="Impl" + psiPackage="com.demonwav.mcdev.platform.mixin.expression.psi" + psiImplPackage="com.demonwav.mcdev.platform.mixin.expression.psi.impl" + + elementTypeHolderClass="com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes" + elementTypeClass="com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionElementType" + tokenTypeClass="com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTokenType" + + tokens = [ + TOKEN_RESERVED = "TOKEN_RESERVED" + ] + + extends(".+Expression") = expression + extends(".+Statement") = statement +} + +meExpressionFile ::= statement <> + +statement ::= memberAssignStatement | + arrayStoreStatement | + identifierAssignmentStatement | + returnStatement | + throwStatement | + expressionStatement { + +} + +memberAssignStatement ::= expression TOKEN_DOT name TOKEN_ASSIGN expression { + pin = 4 + methods = [ + receiverExpr = "expression[0]" + memberName = "name" + valueExpr = "expression[1]" + ] +} + +arrayStoreStatement ::= expression TOKEN_LEFT_BRACKET expression TOKEN_RIGHT_BRACKET TOKEN_ASSIGN expression { + pin = 5 + methods = [ + arrayExpr = "expression[0]" + indexExpr = "expression[1]" + valueExpr = "expression[2]" + ] +} + +identifierAssignmentStatement ::= name TOKEN_ASSIGN expression { + pin = 2 + methods = [ + identifier = "name" + valueExpr = "expression" + ] +} + +returnStatement ::= TOKEN_RETURN expression { + pin = 1 + methods = [ + valueExpr = "expression" + ] +} + +throwStatement ::= TOKEN_THROW expression { + pin = 1 + methods = [ + valueExpr = "expression" + ] +} + +expressionStatement ::= expression { +} + +expression ::= capturingExpression | + superCallExpression | + methodCallExpression | + staticMethodCallExpression | + arrayAccessExpression | + classConstantExpression | + memberAccessExpression | + unaryExpression | + castExpression | + parenthesizedExpression | + instantiationExpression | + binaryExpression | + litExpression | + thisExpression | + nameExpression { +} + +capturingExpression ::= TOKEN_AT TOKEN_LEFT_PAREN expression TOKEN_RIGHT_PAREN { + pin = 1 +} + +parenthesizedExpression ::= TOKEN_LEFT_PAREN expression TOKEN_RIGHT_PAREN { + pin = 1 +} + +superCallExpression ::= TOKEN_SUPER TOKEN_DOT name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { + pin = 2 + methods = [ + memberName = "name" + ] +} + +methodCallExpression ::= expression TOKEN_DOT name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { + pin = 4 + methods = [ + receiverExpr = "expression" + memberName = "name" + ] +} + +staticMethodCallExpression ::= name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { + pin = 2 + methods = [ + memberName = "name" + ] +} + +arrayAccessExpression ::= expression TOKEN_LEFT_BRACKET expression TOKEN_RIGHT_BRACKET !TOKEN_ASSIGN { + pin = 2 + methods = [ + arrayExpr = "expression[0]" + indexExpr = "expression[1]" + ] +} + +classConstantExpression ::= name TOKEN_DOT TOKEN_CLASS { + pin = 3 + methods = [ + className = "name" + ] +} + +memberAccessExpression ::= expression TOKEN_DOT name !TOKEN_ASSIGN { + methods = [ + receiverExpr = "expression" + memberName = "name" + ] +} + +unaryExpression ::= (TOKEN_MINUS | TOKEN_BITWISE_NOT) !(TOKEN_DEC_LIT | TOKEN_INT_LIT) expression { + pin = 2 + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEUnaryExpressionMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEUnaryExpressionImplMixin" +} + +castExpression ::= TOKEN_LEFT_PAREN name TOKEN_RIGHT_PAREN expression { + rightAssociative = true + methods = [ + type = "name" + ] +} + +instantiationExpression ::= TOKEN_NEW name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { + rightAssociative = true + pin = 1 + methods = [ + type = "name" + ] +} + +binaryExpression ::= expression binaryOp expression { + pin = 2 + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEBinaryExpressionMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEBinaryExpressionImplMixin" + methods = [ + leftExpr = "expression[0]" + rightExpr = "expression[1]" + ] +} + +private binaryOp ::= multiplicativeOp | + additiveOp | + shiftOp | + comparisonOp | + TOKEN_INSTANCEOF | + equalityOp | + TOKEN_BITWISE_AND | + TOKEN_BITWISE_XOR | + TOKEN_BITWISE_OR + +private multiplicativeOp ::= TOKEN_MULT | TOKEN_DIV | TOKEN_MOD +private additiveOp ::= TOKEN_PLUS | TOKEN_MINUS +private shiftOp ::= TOKEN_SHL | TOKEN_SHR | TOKEN_USHR +private comparisonOp ::= TOKEN_LT | TOKEN_LE | TOKEN_GT | TOKEN_GE +private equalityOp ::= TOKEN_EQ | TOKEN_NE + +litExpression ::= decimalLitExpression | intLitExpression | stringLitExpression | boolLitExpression | nulLLitExpression { + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MELitExpressionMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MELitExpressionImplMixin" +} + +private decimalLitExpression ::= TOKEN_MINUS? TOKEN_DEC_LIT { + extends = litExpression +} + +private intLitExpression ::= TOKEN_MINUS? TOKEN_INT_LIT { + extends = litExpression +} + +private stringLitExpression ::= TOKEN_STRING_TERMINATOR ( TOKEN_STRING | TOKEN_STRING_ESCAPE )* TOKEN_STRING_TERMINATOR { + pin = 1 + extends = litExpression +} + +private boolLitExpression ::= TOKEN_BOOL_LIT { + extends = litExpression +} + +private nulLLitExpression ::= TOKEN_NULL_LIT { + extends = litExpression +} + +thisExpression ::= TOKEN_THIS { +} + +nameExpression ::= name !TOKEN_ASSIGN { + methods = [ + MEName = "name" + ] +} + +name ::= TOKEN_IDENTIFIER | TOKEN_WILDCARD { + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MENameImplMixin" +} + +arguments ::= (expression (TOKEN_COMMA expression)*)? { +} diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionFileType.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionFileType.kt new file mode 100644 index 000000000..87d4bd31c --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionFileType.kt @@ -0,0 +1,31 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.asset.PlatformAssets +import com.intellij.openapi.fileTypes.LanguageFileType + +object MEExpressionFileType : LanguageFileType(MEExpressionLanguage) { + override fun getName() = "MixinExtras Expression File" + override fun getDescription() = "MixinExtras expression file" + override fun getDefaultExtension() = "mixinextrasexpression" + override fun getIcon() = PlatformAssets.MIXIN_ICON +} diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionLanguage.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionLanguage.kt new file mode 100644 index 000000000..af5496462 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionLanguage.kt @@ -0,0 +1,25 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.intellij.lang.Language + +object MEExpressionLanguage : Language("MEExpression") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionLexerAdapter.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionLexerAdapter.kt new file mode 100644 index 000000000..f0879dc1d --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionLexerAdapter.kt @@ -0,0 +1,26 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionLexer +import com.intellij.lexer.FlexAdapter + +class MEExpressionLexerAdapter : FlexAdapter(MEExpressionLexer(null)) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt new file mode 100644 index 000000000..120db410a --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt @@ -0,0 +1,45 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTokenSets +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes +import com.intellij.lang.ASTNode +import com.intellij.lang.ParserDefinition +import com.intellij.openapi.project.Project +import com.intellij.psi.FileViewProvider +import com.intellij.psi.PsiElement +import com.intellij.psi.tree.IFileElementType +import com.intellij.psi.tree.TokenSet + +class MEExpressionParserDefinition : ParserDefinition { + + override fun createLexer(project: Project) = MEExpressionLexerAdapter() + override fun getCommentTokens(): TokenSet = TokenSet.EMPTY + override fun getStringLiteralElements() = MEExpressionTokenSets.STRINGS + override fun createParser(project: Project) = MEExpressionParser() + override fun getFileNodeType() = FILE + override fun createFile(viewProvider: FileViewProvider) = MEExpressionFile(viewProvider) + override fun createElement(node: ASTNode): PsiElement = MEExpressionTypes.Factory.createElement(node) +} + +val FILE = IFileElementType(MEExpressionLanguage) \ No newline at end of file diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt new file mode 100644 index 000000000..8e7d4df59 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt @@ -0,0 +1,154 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTokenSets +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors +import com.intellij.openapi.editor.HighlighterColors +import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey +import com.intellij.openapi.fileTypes.SyntaxHighlighterBase +import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.TokenType +import com.intellij.psi.tree.IElementType + +class MEExpressionSyntaxHighlighter : SyntaxHighlighterBase() { + companion object { + val STRING = createTextAttributesKey( + "MEEXPRESSION_STRING", + DefaultLanguageHighlighterColors.STRING + ) + val STRING_ESCAPE = createTextAttributesKey( + "MEEXPRESSION_STRING_ESCAPE", + DefaultLanguageHighlighterColors.VALID_STRING_ESCAPE + ) + val NUMBER = createTextAttributesKey( + "MEEXPRESSION_NUMBER", + DefaultLanguageHighlighterColors.NUMBER + ) + val KEYWORD = createTextAttributesKey( + "MEEXPRESSION_KEYWORD", + DefaultLanguageHighlighterColors.KEYWORD, + ) + val OPERATOR = createTextAttributesKey( + "MEEXPRESSION_OPERATOR", + DefaultLanguageHighlighterColors.OPERATION_SIGN + ) + val PARENS = createTextAttributesKey( + "MEEXPRESSION_PARENS", + DefaultLanguageHighlighterColors.PARENTHESES + ) + val BRACKETS = createTextAttributesKey( + "MEEXPRESSION_BRACKETS", + DefaultLanguageHighlighterColors.BRACKETS + ) + val DOT = createTextAttributesKey( + "MEEXPRESSION_DOT", + DefaultLanguageHighlighterColors.DOT + ) + val COMMA = createTextAttributesKey( + "MEEXPRESSION_COMMA", + DefaultLanguageHighlighterColors.COMMA + ) + val CAPTURE = createTextAttributesKey( + "MEEXPRESSION_CAPTURE", + DefaultLanguageHighlighterColors.OPERATION_SIGN + ) + val WILDCARD = createTextAttributesKey( + "MEEXPRESSION_WILDCARD", + DefaultLanguageHighlighterColors.OPERATION_SIGN + ) + val IDENTIFIER = createTextAttributesKey( + "MEEXPRESSION_IDENTIFIER", + DefaultLanguageHighlighterColors.IDENTIFIER + ) + val BAD_CHAR = createTextAttributesKey( + "MEEXPRESSION_BAD_CHARACTER", + HighlighterColors.BAD_CHARACTER + ) + + val STRING_KEYS = arrayOf(STRING) + val STRING_ESCAPE_KEYS = arrayOf(STRING_ESCAPE) + val NUMBER_KEYS = arrayOf(NUMBER) + val KEYWORD_KEYS = arrayOf(KEYWORD) + val OPERATOR_KEYS = arrayOf(OPERATOR) + val PARENS_KEYS = arrayOf(PARENS) + val BRACKETS_KEYS = arrayOf(BRACKETS) + val DOT_KEYS = arrayOf(DOT) + val COMMA_KEYS = arrayOf(COMMA) + val CAPTURE_KEYS = arrayOf(CAPTURE) + val WILDCARD_KEYS = arrayOf(WILDCARD) + val IDENTIFIER_KEYS = arrayOf(IDENTIFIER) + val BAD_CHAR_KEYS = arrayOf(BAD_CHAR) + } + + override fun getHighlightingLexer() = MEExpressionLexerAdapter() + override fun getTokenHighlights(tokenType: IElementType): Array { + if (tokenType == MEExpressionTypes.TOKEN_STRING_ESCAPE) { + return STRING_ESCAPE_KEYS + } + if (MEExpressionTokenSets.STRINGS.contains(tokenType)) { + return STRING_KEYS + } + if (tokenType == MEExpressionTypes.TOKEN_IDENTIFIER) { + return IDENTIFIER_KEYS + } + if (MEExpressionTokenSets.NUMBERS.contains(tokenType)) { + return NUMBER_KEYS + } + if (MEExpressionTokenSets.KEYWORDS.contains(tokenType)) { + return KEYWORD_KEYS + } + if (MEExpressionTokenSets.OPERATORS.contains(tokenType)) { + return OPERATOR_KEYS + } + if (MEExpressionTokenSets.PARENS.contains(tokenType)) { + return PARENS_KEYS + } + if (MEExpressionTokenSets.BRACKETS.contains(tokenType)) { + return BRACKETS_KEYS + } + if (tokenType == MEExpressionTypes.TOKEN_DOT) { + return DOT_KEYS + } + if (tokenType == MEExpressionTypes.TOKEN_COMMA) { + return COMMA_KEYS + } + if (tokenType == MEExpressionTypes.TOKEN_AT) { + return CAPTURE_KEYS + } + if (tokenType == MEExpressionTypes.TOKEN_WILDCARD) { + return WILDCARD_KEYS + } + if (tokenType == TokenType.BAD_CHARACTER) { + return BAD_CHAR_KEYS + } + + return TextAttributesKey.EMPTY_ARRAY + } +} + +class MEExpressionSyntaxHighlighterFactory : SyntaxHighlighterFactory() { + override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?) = MEExpressionSyntaxHighlighter() +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionElementType.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionElementType.kt new file mode 100644 index 000000000..697faee8b --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionElementType.kt @@ -0,0 +1,27 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi + +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionLanguage +import com.intellij.psi.tree.IElementType +import org.jetbrains.annotations.NonNls + +class MEExpressionElementType(@NonNls debugName: String) : IElementType(debugName, MEExpressionLanguage) diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt new file mode 100644 index 000000000..66e12e7e9 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt @@ -0,0 +1,31 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi + +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionFileType +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionLanguage +import com.intellij.extapi.psi.PsiFileBase +import com.intellij.psi.FileViewProvider + +class MEExpressionFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, MEExpressionLanguage) { + override fun getFileType() = MEExpressionFileType + override fun toString() = "MixinExtras Expression File" +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt new file mode 100644 index 000000000..0b504bd10 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt @@ -0,0 +1,70 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi + +import com.intellij.psi.tree.TokenSet + +object MEExpressionTokenSets { + val STRINGS = TokenSet.create( + MEExpressionTypes.TOKEN_STRING, + MEExpressionTypes.TOKEN_STRING_ESCAPE, + MEExpressionTypes.TOKEN_STRING_TERMINATOR, + ) + val NUMBERS = TokenSet.create( + MEExpressionTypes.TOKEN_INT_LIT, + MEExpressionTypes.TOKEN_DEC_LIT, + ) + val KEYWORDS = TokenSet.create( + MEExpressionTypes.TOKEN_BOOL_LIT, + MEExpressionTypes.TOKEN_NULL_LIT, + MEExpressionTypes.TOKEN_INSTANCEOF, + MEExpressionTypes.TOKEN_NEW, + MEExpressionTypes.TOKEN_RETURN, + MEExpressionTypes.TOKEN_THROW, + MEExpressionTypes.TOKEN_THIS, + MEExpressionTypes.TOKEN_SUPER, + MEExpressionTypes.TOKEN_CLASS, + MEExpressionTypes.TOKEN_RESERVED, + ) + val OPERATORS = TokenSet.create( + MEExpressionTypes.TOKEN_BITWISE_NOT, + MEExpressionTypes.TOKEN_MULT, + MEExpressionTypes.TOKEN_DIV, + MEExpressionTypes.TOKEN_MOD, + MEExpressionTypes.TOKEN_PLUS, + MEExpressionTypes.TOKEN_MINUS, + MEExpressionTypes.TOKEN_SHL, + MEExpressionTypes.TOKEN_SHR, + MEExpressionTypes.TOKEN_USHR, + MEExpressionTypes.TOKEN_LT, + MEExpressionTypes.TOKEN_LE, + MEExpressionTypes.TOKEN_GT, + MEExpressionTypes.TOKEN_GE, + MEExpressionTypes.TOKEN_EQ, + MEExpressionTypes.TOKEN_NE, + MEExpressionTypes.TOKEN_BITWISE_AND, + MEExpressionTypes.TOKEN_BITWISE_XOR, + MEExpressionTypes.TOKEN_BITWISE_OR, + MEExpressionTypes.TOKEN_ASSIGN, + ) + val PARENS = TokenSet.create(MEExpressionTypes.TOKEN_LEFT_PAREN, MEExpressionTypes.TOKEN_RIGHT_PAREN) + val BRACKETS = TokenSet.create(MEExpressionTypes.TOKEN_LEFT_BRACKET, MEExpressionTypes.TOKEN_RIGHT_BRACKET) +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenType.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenType.kt new file mode 100644 index 000000000..54a746b88 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenType.kt @@ -0,0 +1,29 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi + +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionLanguage +import com.intellij.psi.tree.IElementType +import org.jetbrains.annotations.NonNls + +class MEExpressionTokenType(@NonNls debugName: String) : IElementType(debugName, MEExpressionLanguage) { + override fun toString() = "MEExpressionTokenType.${super.toString()}" +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt new file mode 100644 index 000000000..1dd979a79 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt @@ -0,0 +1,28 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression +import com.intellij.psi.tree.IElementType + +interface MEBinaryExpressionMixin : MEExpression { + val operator: IElementType +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt new file mode 100644 index 000000000..ad44f46cc --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt @@ -0,0 +1,28 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression + +interface MELitExpressionMixin : MEExpression { + val value: Any? + val isNull: Boolean +} \ No newline at end of file diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt new file mode 100644 index 000000000..e63f0e403 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt @@ -0,0 +1,27 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.intellij.psi.PsiElement + +interface MENameMixin : PsiElement { + val isWildcard: Boolean +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEUnaryExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEUnaryExpressionMixin.kt new file mode 100644 index 000000000..e60649d02 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEUnaryExpressionMixin.kt @@ -0,0 +1,28 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression +import com.intellij.psi.tree.IElementType + +interface MEUnaryExpressionMixin : MEExpression { + val operator: IElementType +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt new file mode 100644 index 000000000..311f10006 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt @@ -0,0 +1,54 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEBinaryExpressionMixin +import com.intellij.lang.ASTNode +import com.intellij.psi.tree.TokenSet + +abstract class MEBinaryExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MEBinaryExpressionMixin { + override val operator get() = node.findChildByType(operatorTokens)!!.elementType + + companion object { + private val operatorTokens = TokenSet.create( + MEExpressionTypes.TOKEN_MULT, + MEExpressionTypes.TOKEN_DIV, + MEExpressionTypes.TOKEN_MOD, + MEExpressionTypes.TOKEN_PLUS, + MEExpressionTypes.TOKEN_MINUS, + MEExpressionTypes.TOKEN_SHL, + MEExpressionTypes.TOKEN_SHR, + MEExpressionTypes.TOKEN_USHR, + MEExpressionTypes.TOKEN_LT, + MEExpressionTypes.TOKEN_LE, + MEExpressionTypes.TOKEN_GT, + MEExpressionTypes.TOKEN_GE, + MEExpressionTypes.TOKEN_EQ, + MEExpressionTypes.TOKEN_NE, + MEExpressionTypes.TOKEN_BITWISE_AND, + MEExpressionTypes.TOKEN_BITWISE_XOR, + MEExpressionTypes.TOKEN_BITWISE_OR, + MEExpressionTypes.TOKEN_INSTANCEOF, + ) + } +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt new file mode 100644 index 000000000..443434b12 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt @@ -0,0 +1,69 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MELitExpressionMixin +import com.intellij.lang.ASTNode +import com.intellij.util.IncorrectOperationException + +abstract class MELitExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MELitExpressionMixin { + override val value get() = when (node.firstChildNode.elementType) { + MEExpressionTypes.TOKEN_NULL_LIT -> null + MEExpressionTypes.TOKEN_MINUS -> { + when (node.lastChildNode.elementType) { + MEExpressionTypes.TOKEN_INT_LIT -> { + val text = node.lastChildNode.text + if (text.startsWith("0x")) { + "-${text.substring(2)}".toLongOrNull(16) + } else { + "-$text".toLongOrNull() + } + } + MEExpressionTypes.TOKEN_DEC_LIT -> { + "-${node.lastChildNode.text}".toDoubleOrNull() + } + else -> throw IncorrectOperationException("Invalid number literal format") + } + } + MEExpressionTypes.TOKEN_BOOL_LIT -> node.chars[0] == 't' + MEExpressionTypes.TOKEN_INT_LIT -> { + val text = this.text + if (text.startsWith("0x")) { + text.substring(2).toLongOrNull(16) + } else { + text.toLongOrNull() + } + } + MEExpressionTypes.TOKEN_DEC_LIT -> text.toDoubleOrNull() + else -> { + val text = this.text + if (text.length >= 2) { + text.substring(1, text.length - 1).replace("\\'", "'").replace("\\\\", "\\") + } else { + null + } + } + } + + override val isNull get() = node.firstChildNode.elementType == MEExpressionTypes.TOKEN_NULL_LIT +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt new file mode 100644 index 000000000..9bef628e5 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt @@ -0,0 +1,30 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameMixin +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode + +abstract class MENameImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MENameMixin { + override val isWildcard get() = node.elementType == MEExpressionTypes.TOKEN_WILDCARD +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt new file mode 100644 index 000000000..d7a3b5be4 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt @@ -0,0 +1,29 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEUnaryExpressionMixin +import com.intellij.lang.ASTNode + +abstract class MEUnaryExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MEUnaryExpressionMixin { + override val operator get() = node.firstChildNode.elementType +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index df4f8ce0b..dcd053117 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -457,6 +457,19 @@ + + + + + + + From a9f66e6eb641a9b6d9a701d7d8d4874b1be489b4 Mon Sep 17 00:00:00 2001 From: joe Date: Sat, 20 Jan 2024 16:57:37 +0000 Subject: [PATCH 002/100] MEExpression color settings page --- src/main/kotlin/asset/MCDevBundle.kt | 6 + .../nbt/lang/colors/NbttColorSettingsPage.kt | 24 ++-- .../MEExpressionColorSettingsPage.kt | 128 ++++++++++++++++++ .../MEExpressionSyntaxHighlighter.kt | 16 +++ src/main/resources/META-INF/plugin.xml | 1 + .../messages/MinecraftDevelopment.properties | 19 +++ 6 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt diff --git a/src/main/kotlin/asset/MCDevBundle.kt b/src/main/kotlin/asset/MCDevBundle.kt index 04b9b3d0b..6246c2bae 100644 --- a/src/main/kotlin/asset/MCDevBundle.kt +++ b/src/main/kotlin/asset/MCDevBundle.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.asset import com.intellij.DynamicBundle +import java.util.function.Supplier import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.PropertyKey @@ -36,4 +37,9 @@ object MCDevBundle : DynamicBundle(BUNDLE) { operator fun invoke(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any?): String { return getMessage(key, *params) } + + fun pointer(@PropertyKey(resourceBundle = BUNDLE) key: String) = Supplier { invoke(key) } + + fun pointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any?) = + Supplier { invoke(key, params) } } diff --git a/src/main/kotlin/nbt/lang/colors/NbttColorSettingsPage.kt b/src/main/kotlin/nbt/lang/colors/NbttColorSettingsPage.kt index 712fce2de..4695fec6f 100644 --- a/src/main/kotlin/nbt/lang/colors/NbttColorSettingsPage.kt +++ b/src/main/kotlin/nbt/lang/colors/NbttColorSettingsPage.kt @@ -88,21 +88,21 @@ class NbttColorSettingsPage : ColorSettingsPage { companion object { private val DESCRIPTORS = arrayOf( - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.keyword.display_name"), KEYWORD), - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.string.display_name"), STRING), - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.unquoted_string.display_name"), UNQUOTED_STRING), - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.name.display_name"), STRING_NAME), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.keyword.display_name"), KEYWORD), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.string.display_name"), STRING), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.unquoted_string.display_name"), UNQUOTED_STRING), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.name.display_name"), STRING_NAME), AttributesDescriptor( - MCDevBundle("nbt.lang.highlighting.unquoted_name.display_name"), + MCDevBundle.pointer("nbt.lang.highlighting.unquoted_name.display_name"), UNQUOTED_STRING_NAME ), - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.byte.display_name"), BYTE), - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.short.display_name"), SHORT), - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.int.display_name"), INT), - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.long.display_name"), LONG), - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.float.display_name"), FLOAT), - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.double.display_name"), DOUBLE), - AttributesDescriptor(MCDevBundle("nbt.lang.highlighting.material.display_name"), MATERIAL), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.byte.display_name"), BYTE), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.short.display_name"), SHORT), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.int.display_name"), INT), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.long.display_name"), LONG), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.float.display_name"), FLOAT), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.double.display_name"), DOUBLE), + AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.material.display_name"), MATERIAL), ) private val map = mapOf( diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt new file mode 100644 index 000000000..5c3e0c72a --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt @@ -0,0 +1,128 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.asset.MCDevBundle +import com.demonwav.mcdev.asset.PlatformAssets +import com.intellij.openapi.options.colors.AttributesDescriptor +import com.intellij.openapi.options.colors.ColorDescriptor +import com.intellij.openapi.options.colors.ColorSettingsPage + +class MEExpressionColorSettingsPage : ColorSettingsPage { + companion object { + private val DESCRIPTORS = arrayOf( + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.string.display_name"), + MEExpressionSyntaxHighlighter.STRING + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.string_escape.display_name"), + MEExpressionSyntaxHighlighter.STRING_ESCAPE + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.number.display_name"), + MEExpressionSyntaxHighlighter.NUMBER + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.keyword.display_name"), + MEExpressionSyntaxHighlighter.KEYWORD + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.operator.display_name"), + MEExpressionSyntaxHighlighter.OPERATOR + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.parens.display_name"), + MEExpressionSyntaxHighlighter.PARENS + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.brackets.display_name"), + MEExpressionSyntaxHighlighter.BRACKETS + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.dot.display_name"), + MEExpressionSyntaxHighlighter.DOT + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.comma.display_name"), + MEExpressionSyntaxHighlighter.COMMA + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.capture.display_name"), + MEExpressionSyntaxHighlighter.CAPTURE + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.wildcard.display_name"), + MEExpressionSyntaxHighlighter.WILDCARD + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.identifier.display_name"), + MEExpressionSyntaxHighlighter.IDENTIFIER + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.call_identifier.display_name"), + MEExpressionSyntaxHighlighter.IDENTIFIER_CALL + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.class_name_identifier.display_name"), + MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.member_name_identifier.display_name"), + MEExpressionSyntaxHighlighter.IDENTIFIER_MEMBER_NAME + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.variable_identifier.display_name"), + MEExpressionSyntaxHighlighter.IDENTIFIER_VARIABLE + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.bad_char.display_name"), + MEExpressionSyntaxHighlighter.BAD_CHAR + ), + ) + + private val TAGS = mapOf( + "call" to MEExpressionSyntaxHighlighter.IDENTIFIER_CALL, + "class_name" to MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME, + "member_name" to MEExpressionSyntaxHighlighter.IDENTIFIER_MEMBER_NAME, + "variable" to MEExpressionSyntaxHighlighter.IDENTIFIER_VARIABLE, + ) + } + + override fun getIcon() = PlatformAssets.MIXIN_ICON + override fun getHighlighter() = MEExpressionSyntaxHighlighter() + + override fun getDemoText() = """ + variable.function( + 'a string with \\ escapes', + 123 + @(45), + ?, + ClassName.class, + foo.bar, + 'a bad character: ' # other_identifier + )[0] + """.trimIndent() + + override fun getAdditionalHighlightingTagToDescriptorMap() = TAGS + override fun getAttributeDescriptors() = DESCRIPTORS + override fun getColorDescriptors(): Array = ColorDescriptor.EMPTY_ARRAY + override fun getDisplayName() = MCDevBundle("mixinextras.expression.lang.display_name") +} diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt index 8e7d4df59..3f709fda9 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt @@ -83,6 +83,22 @@ class MEExpressionSyntaxHighlighter : SyntaxHighlighterBase() { "MEEXPRESSION_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER ) + val IDENTIFIER_CALL = createTextAttributesKey( + "MEEXPRESSION_IDENTIFIER_CALL", + DefaultLanguageHighlighterColors.FUNCTION_CALL + ) + val IDENTIFIER_CLASS_NAME = createTextAttributesKey( + "MEEXPRESSION_IDENTIFIER_CLASS_NAME", + DefaultLanguageHighlighterColors.CLASS_REFERENCE + ) + val IDENTIFIER_MEMBER_NAME = createTextAttributesKey( + "MEEXPRESSION_IDENTIFIER_MEMBER_NAME", + DefaultLanguageHighlighterColors.INSTANCE_FIELD + ) + val IDENTIFIER_VARIABLE = createTextAttributesKey( + "MEEXPRESSION_IDENTIFIER_VARIABLE", + DefaultLanguageHighlighterColors.LOCAL_VARIABLE + ) val BAD_CHAR = createTextAttributesKey( "MEEXPRESSION_BAD_CHARACTER", HighlighterColors.BAD_CHARACTER diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index dcd053117..ff3278a9f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -468,6 +468,7 @@ implementationClass="com.demonwav.mcdev.platform.mixin.expression.MEExpressionParserDefinition"/> + diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 83d2ec790..79c2aea61 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -190,3 +190,22 @@ minecraft.settings.show_event_listener_gutter_icons=Show event listener gutter i minecraft.settings.show_chat_color_gutter_icons=Show chat color gutter icons minecraft.settings.show_chat_color_underlines=Show chat color underlines minecraft.settings.chat_color_underline_style=Chat color underline style: + +mixinextras.expression.lang.display_name=MixinExtras Expressions +mixinextras.expression.lang.highlighting.bad_char.display_name=Bad character +mixinextras.expression.lang.highlighting.brackets.display_name=Brackets +mixinextras.expression.lang.highlighting.call_identifier.display_name=Identifier//Method call +mixinextras.expression.lang.highlighting.capture.display_name=Capture +mixinextras.expression.lang.highlighting.class_name_identifier.display_name=Identifier//Class name +mixinextras.expression.lang.highlighting.comma.display_name=Comma +mixinextras.expression.lang.highlighting.dot.display_name=Dot +mixinextras.expression.lang.highlighting.identifier.display_name=Identifier +mixinextras.expression.lang.highlighting.keyword.display_name=Keyword +mixinextras.expression.lang.highlighting.member_name_identifier.display_name=Identifier//Member name +mixinextras.expression.lang.highlighting.number.display_name=Number +mixinextras.expression.lang.highlighting.operator.display_name=Operator +mixinextras.expression.lang.highlighting.parens.display_name=Parentheses +mixinextras.expression.lang.highlighting.string.display_name=String +mixinextras.expression.lang.highlighting.string_escape.display_name=String escape +mixinextras.expression.lang.highlighting.variable_identifier.display_name=Identifier//Variable +mixinextras.expression.lang.highlighting.wildcard.display_name=Wildcard From 3b4a2bf4605a8d1f6fedac97b6cf2a93b3af48d7 Mon Sep 17 00:00:00 2001 From: joe Date: Sat, 20 Jan 2024 17:45:09 +0000 Subject: [PATCH 003/100] MEExpression annotator --- .../mixin/expression/MEExpressionAnnotator.kt | 115 ++++++++++++++++++ .../psi/mixins/MELitExpressionMixin.kt | 3 + .../mixins/impl/MELitExpressionImplMixin.kt | 3 + src/main/resources/META-INF/plugin.xml | 2 + .../messages/MinecraftDevelopment.properties | 3 + 5 files changed, 126 insertions(+) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt new file mode 100644 index 000000000..a6a3dcbdc --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -0,0 +1,115 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.asset.MCDevBundle +import com.demonwav.mcdev.platform.mixin.expression.psi.MEBinaryExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.MECastExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.MEClassConstantExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.psi.MEIdentifierAssignmentStatement +import com.demonwav.mcdev.platform.mixin.expression.psi.MEInstantiationExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.MELitExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.MEMemberAccessExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.MEMemberAssignStatement +import com.demonwav.mcdev.platform.mixin.expression.psi.MEMethodCallExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.psi.MENameExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.MEStaticMethodCallExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.MESuperCallExpression +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.lang.annotation.Annotator +import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.psi.PsiElement + +class MEExpressionAnnotator : Annotator { + override fun annotate(element: PsiElement, holder: AnnotationHolder) { + when (element) { + is MEName -> { + if (!element.isWildcard) { + when (val parent = element.parent) { + is MECastExpression, + is MEClassConstantExpression, + is MEInstantiationExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(element) + .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) + .create() + is MEMemberAccessExpression, + is MEMemberAssignStatement -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(element) + .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_MEMBER_NAME) + .create() + is MESuperCallExpression, + is MEMethodCallExpression, + is MEStaticMethodCallExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(element) + .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CALL) + .create() + is MEIdentifierAssignmentStatement -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(element) + .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_VARIABLE) + .create() + is MENameExpression -> { + val grandparent = parent.parent + if (grandparent is MEBinaryExpression && + grandparent.operator == MEExpressionTypes.TOKEN_INSTANCEOF && + grandparent.rightExpr == parent + ) { + holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(element) + .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) + .create() + } else { + holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(element) + .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_VARIABLE) + .create() + } + } + } + } + } + is MELitExpression -> { + val minusToken = element.minusToken + if (minusToken != null) { + holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(minusToken) + .textAttributes(MEExpressionSyntaxHighlighter.NUMBER) + .create() + } + + if (!element.isNull && !element.isString && element.value == null) { + holder.newAnnotation(HighlightSeverity.ERROR, MCDevBundle("mixinextras.expression.lang.errors.invalid_number")) + .range(element) + .create() + } + } + is MEBinaryExpression -> { + val rightExpr = element.rightExpr + if (element.operator == MEExpressionTypes.TOKEN_INSTANCEOF && rightExpr !is MENameExpression && rightExpr != null) { + holder.newAnnotation(HighlightSeverity.ERROR, MCDevBundle("mixinextras.expression.lang.errors.instanceof_non_type")) + .range(rightExpr) + .create() + } + } + } + } +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt index ad44f46cc..bf2b2b815 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt @@ -21,8 +21,11 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression +import com.intellij.lang.ASTNode interface MELitExpressionMixin : MEExpression { val value: Any? val isNull: Boolean + val isString: Boolean + val minusToken: ASTNode? } \ No newline at end of file diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt index 443434b12..11f212ee5 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt @@ -66,4 +66,7 @@ abstract class MELitExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), } override val isNull get() = node.firstChildNode.elementType == MEExpressionTypes.TOKEN_NULL_LIT + override val isString get() = node.firstChildNode.elementType == MEExpressionTypes.TOKEN_STRING_TERMINATOR + + override val minusToken get() = node.firstChildNode.takeIf { it.elementType == MEExpressionTypes.TOKEN_MINUS } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index ff3278a9f..50a540e7c 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -469,6 +469,8 @@ + diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 79c2aea61..2fd0e692c 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -191,6 +191,9 @@ minecraft.settings.show_chat_color_gutter_icons=Show chat color gutter icons minecraft.settings.show_chat_color_underlines=Show chat color underlines minecraft.settings.chat_color_underline_style=Chat color underline style: +mixinextras.expression.lang.errors.instanceof_non_type=Expected type +mixinextras.expression.lang.errors.invalid_number=Invalid number + mixinextras.expression.lang.display_name=MixinExtras Expressions mixinextras.expression.lang.highlighting.bad_char.display_name=Bad character mixinextras.expression.lang.highlighting.brackets.display_name=Brackets From 445bc576c44218f5ca768f0f72fa5cc85753b6a7 Mon Sep 17 00:00:00 2001 From: joe Date: Sat, 20 Jan 2024 18:11:17 +0000 Subject: [PATCH 004/100] MEExpression brace matcher and quote handler --- .../expression/MEExpressionBraceMatcher.kt | 40 +++++++++++++++++++ .../expression/MEExpressionQuoteHandler.kt | 26 ++++++++++++ src/main/resources/META-INF/plugin.xml | 4 ++ 3 files changed, 70 insertions(+) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionQuoteHandler.kt diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt new file mode 100644 index 000000000..de85144a1 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt @@ -0,0 +1,40 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes +import com.intellij.lang.BracePair +import com.intellij.lang.PairedBraceMatcher +import com.intellij.psi.PsiFile +import com.intellij.psi.tree.IElementType + +class MEExpressionBraceMatcher : PairedBraceMatcher { + companion object { + private val PAIRS = arrayOf( + BracePair(MEExpressionTypes.TOKEN_LEFT_PAREN, MEExpressionTypes.TOKEN_RIGHT_PAREN, false), + BracePair(MEExpressionTypes.TOKEN_LEFT_BRACKET, MEExpressionTypes.TOKEN_RIGHT_BRACKET, false), + ) + } + + override fun getPairs() = PAIRS + override fun isPairedBracesAllowedBeforeType(lbraceType: IElementType, contextType: IElementType?) = true + override fun getCodeConstructStart(file: PsiFile?, openingBraceOffset: Int) = openingBraceOffset +} diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionQuoteHandler.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionQuoteHandler.kt new file mode 100644 index 000000000..dc1a1796d --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionQuoteHandler.kt @@ -0,0 +1,26 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTokenSets +import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler + +class MEExpressionQuoteHandler : SimpleTokenSetQuoteHandler(MEExpressionTokenSets.STRINGS) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 50a540e7c..aa8b4ad44 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -471,6 +471,10 @@ + + From 387f06ca49c08531af8f6b5ad41b561477f50d2a Mon Sep 17 00:00:00 2001 From: joe Date: Sat, 20 Jan 2024 21:04:41 +0000 Subject: [PATCH 005/100] Switch LHS of MEExpression assignmentExpression to themselves be certain types of expression --- src/main/grammars/MEExpressionParser.bnf | 37 +++++-------------- .../mixin/expression/MEExpressionAnnotator.kt | 9 +---- .../psi/mixins/MEAssignStatementMixin.kt | 28 ++++++++++++++ .../mixins/impl/MEAssignStatementImplMixin.kt | 30 +++++++++++++++ 4 files changed, 69 insertions(+), 35 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index 5d87791d4..fb90bf473 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -41,41 +41,24 @@ meExpressionFile ::= statement <> -statement ::= memberAssignStatement | - arrayStoreStatement | - identifierAssignmentStatement | +statement ::= assignStatement | returnStatement | throwStatement | expressionStatement { } -memberAssignStatement ::= expression TOKEN_DOT name TOKEN_ASSIGN expression { - pin = 4 - methods = [ - receiverExpr = "expression[0]" - memberName = "name" - valueExpr = "expression[1]" - ] -} - -arrayStoreStatement ::= expression TOKEN_LEFT_BRACKET expression TOKEN_RIGHT_BRACKET TOKEN_ASSIGN expression { - pin = 5 - methods = [ - arrayExpr = "expression[0]" - indexExpr = "expression[1]" - valueExpr = "expression[2]" - ] -} - -identifierAssignmentStatement ::= name TOKEN_ASSIGN expression { +assignStatement ::= assignableExpression TOKEN_ASSIGN expression { pin = 2 + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEAssignStatementMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEAssignStatementImplMixin" methods = [ - identifier = "name" - valueExpr = "expression" + rightExpr = "expression" ] } +private assignableExpression ::= arrayAccessExpression | memberAccessExpression | nameExpression + returnStatement ::= TOKEN_RETURN expression { pin = 1 methods = [ @@ -140,7 +123,7 @@ staticMethodCallExpression ::= name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN ] } -arrayAccessExpression ::= expression TOKEN_LEFT_BRACKET expression TOKEN_RIGHT_BRACKET !TOKEN_ASSIGN { +arrayAccessExpression ::= expression TOKEN_LEFT_BRACKET expression TOKEN_RIGHT_BRACKET { pin = 2 methods = [ arrayExpr = "expression[0]" @@ -155,7 +138,7 @@ classConstantExpression ::= name TOKEN_DOT TOKEN_CLASS { ] } -memberAccessExpression ::= expression TOKEN_DOT name !TOKEN_ASSIGN { +memberAccessExpression ::= expression TOKEN_DOT name { methods = [ receiverExpr = "expression" memberName = "name" @@ -238,7 +221,7 @@ private nulLLitExpression ::= TOKEN_NULL_LIT { thisExpression ::= TOKEN_THIS { } -nameExpression ::= name !TOKEN_ASSIGN { +nameExpression ::= name { methods = [ MEName = "name" ] diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index a6a3dcbdc..84b429a1e 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -25,11 +25,9 @@ import com.demonwav.mcdev.platform.mixin.expression.psi.MEBinaryExpression import com.demonwav.mcdev.platform.mixin.expression.psi.MECastExpression import com.demonwav.mcdev.platform.mixin.expression.psi.MEClassConstantExpression import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes -import com.demonwav.mcdev.platform.mixin.expression.psi.MEIdentifierAssignmentStatement import com.demonwav.mcdev.platform.mixin.expression.psi.MEInstantiationExpression import com.demonwav.mcdev.platform.mixin.expression.psi.MELitExpression import com.demonwav.mcdev.platform.mixin.expression.psi.MEMemberAccessExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MEMemberAssignStatement import com.demonwav.mcdev.platform.mixin.expression.psi.MEMethodCallExpression import com.demonwav.mcdev.platform.mixin.expression.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.psi.MENameExpression @@ -52,8 +50,7 @@ class MEExpressionAnnotator : Annotator { .range(element) .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) .create() - is MEMemberAccessExpression, - is MEMemberAssignStatement -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + is MEMemberAccessExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) .range(element) .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_MEMBER_NAME) .create() @@ -63,10 +60,6 @@ class MEExpressionAnnotator : Annotator { .range(element) .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CALL) .create() - is MEIdentifierAssignmentStatement -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) - .range(element) - .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_VARIABLE) - .create() is MENameExpression -> { val grandparent = parent.parent if (grandparent is MEBinaryExpression && diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt new file mode 100644 index 000000000..78c41fd01 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt @@ -0,0 +1,28 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.MEStatement + +interface MEAssignStatementMixin : MEStatement { + val targetExpr: MEExpression +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt new file mode 100644 index 000000000..9353c64e9 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt @@ -0,0 +1,30 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.impl.MEStatementImpl +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEAssignStatementMixin +import com.intellij.lang.ASTNode + +abstract class MEAssignStatementImplMixin(node: ASTNode) : MEStatementImpl(node), MEAssignStatementMixin { + override val targetExpr get() = firstChild as MEExpression +} From 8e6a686e99aa668935ae238b3496047eb0f900f2 Mon Sep 17 00:00:00 2001 From: joe Date: Sat, 20 Jan 2024 22:36:02 +0000 Subject: [PATCH 006/100] MEExpression language injection inside @Expression --- build.gradle.kts | 1 + .../resources/META-INF/mcdev-intellilang.xml | 25 +++++++++++++++++ src/main/resources/META-INF/plugin.xml | 1 + .../resources/languageInjections/java.xml | 28 +++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 src/main/resources/META-INF/mcdev-intellilang.xml create mode 100644 src/main/resources/languageInjections/java.xml diff --git a/build.gradle.kts b/build.gradle.kts index 49ff0ae36..0844c4436 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -176,6 +176,7 @@ intellij { "Kotlin", "org.toml.lang:$pluginTomlVersion", "ByteCodeViewer", + "org.intellij.intelliLang", "properties", // needed dependencies for unit tests "junit" diff --git a/src/main/resources/META-INF/mcdev-intellilang.xml b/src/main/resources/META-INF/mcdev-intellilang.xml new file mode 100644 index 000000000..27447cb73 --- /dev/null +++ b/src/main/resources/META-INF/mcdev-intellilang.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index aa8b4ad44..babb9d6c9 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -28,6 +28,7 @@ com.intellij.properties ByteCodeViewer org.toml.lang + org.intellij.intelliLang com.demonwav.minecraft-dev Minecraft Development diff --git a/src/main/resources/languageInjections/java.xml b/src/main/resources/languageInjections/java.xml new file mode 100644 index 000000000..9213220f4 --- /dev/null +++ b/src/main/resources/languageInjections/java.xml @@ -0,0 +1,28 @@ + + + + + MixinExtras @Expression + + + From 4c5e544717fa6cc7591da9fa669edb650ffde5d4 Mon Sep 17 00:00:00 2001 From: joe Date: Sat, 20 Jan 2024 22:49:54 +0000 Subject: [PATCH 007/100] Fix formatting and licenses --- src/main/grammars/MEExpressionLexer.flex | 20 +++++++++++++++++++ .../nbt/lang/colors/NbttColorSettingsPage.kt | 5 ++++- .../mixin/expression/MEExpressionAnnotator.kt | 15 +++++++++++--- .../expression/MEExpressionLexerAdapter.kt | 1 - .../MEExpressionParserDefinition.kt | 2 +- .../psi/mixins/MELitExpressionMixin.kt | 2 +- 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/main/grammars/MEExpressionLexer.flex b/src/main/grammars/MEExpressionLexer.flex index 6622013c3..3dca9b08b 100644 --- a/src/main/grammars/MEExpressionLexer.flex +++ b/src/main/grammars/MEExpressionLexer.flex @@ -1,3 +1,23 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package com.demonwav.mcdev.platform.mixin.expression; import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes; diff --git a/src/main/kotlin/nbt/lang/colors/NbttColorSettingsPage.kt b/src/main/kotlin/nbt/lang/colors/NbttColorSettingsPage.kt index 4695fec6f..dca1c1108 100644 --- a/src/main/kotlin/nbt/lang/colors/NbttColorSettingsPage.kt +++ b/src/main/kotlin/nbt/lang/colors/NbttColorSettingsPage.kt @@ -90,7 +90,10 @@ class NbttColorSettingsPage : ColorSettingsPage { private val DESCRIPTORS = arrayOf( AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.keyword.display_name"), KEYWORD), AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.string.display_name"), STRING), - AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.unquoted_string.display_name"), UNQUOTED_STRING), + AttributesDescriptor( + MCDevBundle.pointer("nbt.lang.highlighting.unquoted_string.display_name"), + UNQUOTED_STRING + ), AttributesDescriptor(MCDevBundle.pointer("nbt.lang.highlighting.name.display_name"), STRING_NAME), AttributesDescriptor( MCDevBundle.pointer("nbt.lang.highlighting.unquoted_name.display_name"), diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index 84b429a1e..b321ec046 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -90,15 +90,24 @@ class MEExpressionAnnotator : Annotator { } if (!element.isNull && !element.isString && element.value == null) { - holder.newAnnotation(HighlightSeverity.ERROR, MCDevBundle("mixinextras.expression.lang.errors.invalid_number")) + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.invalid_number") + ) .range(element) .create() } } is MEBinaryExpression -> { val rightExpr = element.rightExpr - if (element.operator == MEExpressionTypes.TOKEN_INSTANCEOF && rightExpr !is MENameExpression && rightExpr != null) { - holder.newAnnotation(HighlightSeverity.ERROR, MCDevBundle("mixinextras.expression.lang.errors.instanceof_non_type")) + if (element.operator == MEExpressionTypes.TOKEN_INSTANCEOF && + rightExpr !is MENameExpression && + rightExpr != null + ) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.instanceof_non_type") + ) .range(rightExpr) .create() } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionLexerAdapter.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionLexerAdapter.kt index f0879dc1d..8eb499188 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionLexerAdapter.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionLexerAdapter.kt @@ -20,7 +20,6 @@ package com.demonwav.mcdev.platform.mixin.expression -import com.demonwav.mcdev.platform.mixin.expression.MEExpressionLexer import com.intellij.lexer.FlexAdapter class MEExpressionLexerAdapter : FlexAdapter(MEExpressionLexer(null)) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt index 120db410a..7eac29301 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt @@ -42,4 +42,4 @@ class MEExpressionParserDefinition : ParserDefinition { override fun createElement(node: ASTNode): PsiElement = MEExpressionTypes.Factory.createElement(node) } -val FILE = IFileElementType(MEExpressionLanguage) \ No newline at end of file +val FILE = IFileElementType(MEExpressionLanguage) diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt index bf2b2b815..8f01aaa09 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt @@ -28,4 +28,4 @@ interface MELitExpressionMixin : MEExpression { val isNull: Boolean val isString: Boolean val minusToken: ASTNode? -} \ No newline at end of file +} From 73c065a3f050940f660701effa1bd5c7214e4fae Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 22 Jan 2024 21:09:02 +0000 Subject: [PATCH 008/100] Add MIXINEXTRAS:EXPRESSION injection point and add @Expression annotation on completion --- build.gradle.kts | 10 ++ .../mixinextras/ExpressionInjectionPoint.kt | 91 +++++++++++++++++++ .../platform/mixin/util/MixinConstants.kt | 1 + src/main/resources/META-INF/plugin.xml | 1 + 4 files changed, 103 insertions(+) create mode 100644 src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt diff --git a/build.gradle.kts b/build.gradle.kts index 0844c4436..9667b31fd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -86,12 +86,22 @@ repositories { } } mavenCentral() + + // TODO: temporary waiting for MixinExtras expression library + maven("https://jitpack.io/") { + content { + includeGroupByRegex("com\\.github\\..+") + } + } } dependencies { // Add tools.jar for the JDI API implementation(files(Jvm.current().toolsJar)) + // TODO: temporary waiting for MixinExtras expression library + implementation("com.github.LlamaLad7:MixinExtras:8aa79eb") + // Kotlin implementation(kotlin("stdlib-jdk8")) implementation(kotlin("reflect")) diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt new file mode 100644 index 000000000..c730daaa9 --- /dev/null +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -0,0 +1,91 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.handlers.mixinextras + +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InjectionPoint +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.NavigationVisitor +import com.demonwav.mcdev.platform.mixin.reference.MixinSelector +import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.util.findContainingModifierList +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.openapi.editor.Editor +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiLiteral +import com.intellij.psi.PsiModifierList +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.psi.codeStyle.JavaCodeStyleManager +import org.objectweb.asm.tree.ClassNode + +class ExpressionInjectionPoint : InjectionPoint() { + override fun onCompleted(editor: Editor, reference: PsiLiteral) { + val modifierList = reference.findContainingModifierList() ?: return + if (modifierList.hasAnnotation(MixinConstants.MixinExtras.EXPRESSION)) { + return + } + + val project = reference.project + + val exprAnnotation = modifierList.addAfter( + JavaPsiFacade.getElementFactory(project) + .createAnnotationFromText("@${MixinConstants.MixinExtras.EXPRESSION}(\"\")", reference), + null + ) + + // add imports and reformat + JavaCodeStyleManager.getInstance(project).shortenClassReferences(exprAnnotation) + JavaCodeStyleManager.getInstance(project).optimizeImports(modifierList.containingFile) + val formattedModifierList = CodeStyleManager.getInstance(project).reformat(modifierList) as PsiModifierList + + // move the caret to @Expression("") + val formattedExprAnnotation = formattedModifierList.findAnnotation(MixinConstants.MixinExtras.EXPRESSION) + ?: return + val exprLiteral = formattedExprAnnotation.findDeclaredAttributeValue(null) ?: return + editor.caretModel.moveToOffset(exprLiteral.textRange.startOffset + 1) + } + + override fun createNavigationVisitor( + at: PsiAnnotation, + target: MixinSelector?, + targetClass: PsiClass + ): NavigationVisitor? { + return null + } + + override fun doCreateCollectVisitor( + at: PsiAnnotation, + target: MixinSelector?, + targetClass: ClassNode, + mode: CollectVisitor.Mode + ): CollectVisitor? { + return null + } + + override fun createLookup( + targetClass: ClassNode, + result: CollectVisitor.Result + ): LookupElementBuilder? { + return null + } +} diff --git a/src/main/kotlin/platform/mixin/util/MixinConstants.kt b/src/main/kotlin/platform/mixin/util/MixinConstants.kt index 937cf44bc..7881d7454 100644 --- a/src/main/kotlin/platform/mixin/util/MixinConstants.kt +++ b/src/main/kotlin/platform/mixin/util/MixinConstants.kt @@ -85,6 +85,7 @@ object MixinConstants { const val WRAP_OPERATION = "com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation" const val LOCAL = "com.llamalad7.mixinextras.sugar.Local" const val LOCAL_REF_PACKAGE = "com.llamalad7.mixinextras.sugar.ref." + const val EXPRESSION = "com.llamalad7.mixinextras.expression.Expression" fun PsiType.unwrapLocalRef(): PsiType { if (this !is PsiClassType) { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index babb9d6c9..1a7e88ffe 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -155,6 +155,7 @@ + From acce47eddc78d90d54dcf2d2b81cd7f1ea7c87a5 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 24 Jan 2024 22:05:36 +0000 Subject: [PATCH 009/100] Fix licenser errors --- src/main/grammars/MEExpressionLexer.flex | 2 +- src/main/grammars/MEExpressionParser.bnf | 8 +++---- .../mixin/expression/MEExpressionAnnotator.kt | 24 +++++++++---------- .../expression/MEExpressionBraceMatcher.kt | 2 +- .../MEExpressionParserDefinition.kt | 3 ++- .../MEExpressionSyntaxHighlighter.kt | 2 +- .../expression/psi/MEExpressionTokenSets.kt | 1 + .../psi/mixins/MEAssignStatementMixin.kt | 4 ++-- .../psi/mixins/MEBinaryExpressionMixin.kt | 2 +- .../psi/mixins/MELitExpressionMixin.kt | 2 +- .../psi/mixins/MEUnaryExpressionMixin.kt | 2 +- .../mixins/impl/MEAssignStatementImplMixin.kt | 4 ++-- .../impl/MEBinaryExpressionImplMixin.kt | 4 ++-- .../mixins/impl/MELitExpressionImplMixin.kt | 4 ++-- .../psi/mixins/impl/MENameImplMixin.kt | 2 +- .../mixins/impl/MEUnaryExpressionImplMixin.kt | 2 +- 16 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/main/grammars/MEExpressionLexer.flex b/src/main/grammars/MEExpressionLexer.flex index 3dca9b08b..bebb7970e 100644 --- a/src/main/grammars/MEExpressionLexer.flex +++ b/src/main/grammars/MEExpressionLexer.flex @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression; -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes; +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes; import com.intellij.lexer.FlexLexer; import com.intellij.psi.tree.IElementType; import com.intellij.psi.TokenType; diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index fb90bf473..3100809bc 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -19,15 +19,15 @@ */ { - parserClass="com.demonwav.mcdev.platform.mixin.expression.MEExpressionParser" + parserClass="com.demonwav.mcdev.platform.mixin.expression.gen.MEExpressionParser" extends="com.intellij.extapi.psi.ASTWrapperPsiElement" psiClassPrefix="ME" psiImplClassSuffix="Impl" - psiPackage="com.demonwav.mcdev.platform.mixin.expression.psi" - psiImplPackage="com.demonwav.mcdev.platform.mixin.expression.psi.impl" + psiPackage="com.demonwav.mcdev.platform.mixin.expression.gen.psi" + psiImplPackage="com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl" - elementTypeHolderClass="com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes" + elementTypeHolderClass="com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes" elementTypeClass="com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionElementType" tokenTypeClass="com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTokenType" diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index b321ec046..dafc2b97d 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -21,18 +21,18 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.asset.MCDevBundle -import com.demonwav.mcdev.platform.mixin.expression.psi.MEBinaryExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MECastExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MEClassConstantExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes -import com.demonwav.mcdev.platform.mixin.expression.psi.MEInstantiationExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MELitExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MEMemberAccessExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MEMethodCallExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MEName -import com.demonwav.mcdev.platform.mixin.expression.psi.MENameExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MEStaticMethodCallExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MESuperCallExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEBinaryExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECastExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEInstantiationExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MELitExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMemberAccessExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMethodCallExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MESuperCallExpression import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.Annotator import com.intellij.lang.annotation.HighlightSeverity diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt index de85144a1..3b4a2b3f7 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.intellij.lang.BracePair import com.intellij.lang.PairedBraceMatcher import com.intellij.psi.PsiFile diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt index 7eac29301..7d16e8816 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionParserDefinition.kt @@ -20,9 +20,10 @@ package com.demonwav.mcdev.platform.mixin.expression +import com.demonwav.mcdev.platform.mixin.expression.gen.MEExpressionParser +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTokenSets -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes import com.intellij.lang.ASTNode import com.intellij.lang.ParserDefinition import com.intellij.openapi.project.Project diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt index 3f709fda9..d0fe7a238 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt @@ -20,8 +20,8 @@ package com.demonwav.mcdev.platform.mixin.expression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTokenSets -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes import com.intellij.openapi.editor.DefaultLanguageHighlighterColors import com.intellij.openapi.editor.HighlighterColors import com.intellij.openapi.editor.colors.TextAttributesKey diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt index 0b504bd10..d04496fe5 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.intellij.psi.tree.TokenSet object MEExpressionTokenSets { diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt index 78c41fd01..fce42a042 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt @@ -20,8 +20,8 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.MEStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement interface MEAssignStatementMixin : MEStatement { val targetExpr: MEExpression diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt index 1dd979a79..12efebb71 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.intellij.psi.tree.IElementType interface MEBinaryExpressionMixin : MEExpression { diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt index 8f01aaa09..e1a376343 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MELitExpressionMixin.kt @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.intellij.lang.ASTNode interface MELitExpressionMixin : MEExpression { diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEUnaryExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEUnaryExpressionMixin.kt index e60649d02..21cb6abf6 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEUnaryExpressionMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEUnaryExpressionMixin.kt @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.intellij.psi.tree.IElementType interface MEUnaryExpressionMixin : MEExpression { diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt index 9353c64e9..a8afb21dc 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt @@ -20,8 +20,8 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpression -import com.demonwav.mcdev.platform.mixin.expression.psi.impl.MEStatementImpl +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEStatementImpl import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEAssignStatementMixin import com.intellij.lang.ASTNode diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt index 311f10006..a697e838c 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt @@ -20,8 +20,8 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes -import com.demonwav.mcdev.platform.mixin.expression.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEBinaryExpressionMixin import com.intellij.lang.ASTNode import com.intellij.psi.tree.TokenSet diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt index 11f212ee5..48f3991dc 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt @@ -20,8 +20,8 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes -import com.demonwav.mcdev.platform.mixin.expression.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MELitExpressionMixin import com.intellij.lang.ASTNode import com.intellij.util.IncorrectOperationException diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt index 9bef628e5..23f446cc3 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl -import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameMixin import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt index d7a3b5be4..82223c3a4 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl -import com.demonwav.mcdev.platform.mixin.expression.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEUnaryExpressionMixin import com.intellij.lang.ASTNode From 2424c95dc0b4487c50661bd952dbbfd05e071352 Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 25 Jan 2024 02:14:50 +0000 Subject: [PATCH 010/100] Add new ME expression features --- build.gradle.kts | 2 +- src/main/grammars/MEExpressionLexer.flex | 4 + src/main/grammars/MEExpressionParser.bnf | 48 ++++++-- .../mixin/expression/MEExpressionAnnotator.kt | 113 ++++++++++++++++-- .../expression/MEExpressionBraceMatcher.kt | 1 + .../MEExpressionColorSettingsPage.kt | 5 + .../MEExpressionSyntaxHighlighter.kt | 8 ++ .../expression/psi/MEExpressionTokenSets.kt | 1 + .../mixins/MEArrayAccessExpressionMixin.kt | 28 +++++ .../psi/mixins/MENameWithDimsMixin.kt | 28 +++++ .../psi/mixins/MENewArrayExpressionMixin.kt | 31 +++++ .../impl/MEArrayAccessExpressionImplMixin.kt | 31 +++++ .../mixins/impl/MENameWithDimsImplMixin.kt | 32 +++++ .../impl/MENewArrayExpressionImplMixin.kt | 61 ++++++++++ .../messages/MinecraftDevelopment.properties | 7 ++ 15 files changed, 378 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MEArrayAccessExpressionMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MENameWithDimsMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MENewArrayExpressionMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameWithDimsImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt diff --git a/build.gradle.kts b/build.gradle.kts index 9667b31fd..1786dcbd1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -100,7 +100,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - implementation("com.github.LlamaLad7:MixinExtras:8aa79eb") + implementation("com.github.LlamaLad7:MixinExtras:e617eca") // Kotlin implementation(kotlin("stdlib-jdk8")) diff --git a/src/main/grammars/MEExpressionLexer.flex b/src/main/grammars/MEExpressionLexer.flex index bebb7970e..cc6f727c8 100644 --- a/src/main/grammars/MEExpressionLexer.flex +++ b/src/main/grammars/MEExpressionLexer.flex @@ -63,6 +63,8 @@ LEFT_PAREN="(" RIGHT_PAREN=")" LEFT_BRACKET="[" RIGHT_BRACKET="]" +LEFT_BRACE="{" +RIGHT_BRACE="}" AT=@ SHL=<< SHR=>> @@ -112,6 +114,8 @@ STRING_ESCAPE=\\'|\\\\ {RIGHT_PAREN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_PAREN; } {LEFT_BRACKET} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_BRACKET; } {RIGHT_BRACKET} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_BRACKET; } + {LEFT_BRACE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_BRACE; } + {RIGHT_BRACE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_BRACE; } {AT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_AT; } {SHL} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SHL; } {SHR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SHR; } diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index 3100809bc..14dca2bb1 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -76,21 +76,25 @@ throwStatement ::= TOKEN_THROW expression { expressionStatement ::= expression { } +private exprRecover ::= !( TOKEN_COMMA | TOKEN_RIGHT_PAREN | TOKEN_RIGHT_BRACKET | TOKEN_RIGHT_BRACE ) + expression ::= capturingExpression | superCallExpression | - methodCallExpression | - staticMethodCallExpression | - arrayAccessExpression | + staticMethodCallExpression | classConstantExpression | - memberAccessExpression | unaryExpression | + binaryExpression | castExpression | parenthesizedExpression | + methodCallExpression | + arrayAccessExpression | + memberAccessExpression | instantiationExpression | - binaryExpression | + newArrayExpression | litExpression | thisExpression | nameExpression { + recoverWhile = exprRecover } capturingExpression ::= TOKEN_AT TOKEN_LEFT_PAREN expression TOKEN_RIGHT_PAREN { @@ -123,15 +127,17 @@ staticMethodCallExpression ::= name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN ] } -arrayAccessExpression ::= expression TOKEN_LEFT_BRACKET expression TOKEN_RIGHT_BRACKET { +arrayAccessExpression ::= expression TOKEN_LEFT_BRACKET expression? TOKEN_RIGHT_BRACKET { pin = 2 + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEArrayAccessExpressionMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEArrayAccessExpressionImplMixin" methods = [ arrayExpr = "expression[0]" indexExpr = "expression[1]" ] } -classConstantExpression ::= name TOKEN_DOT TOKEN_CLASS { +classConstantExpression ::= nameWithDims TOKEN_DOT TOKEN_CLASS { pin = 3 methods = [ className = "name" @@ -145,13 +151,13 @@ memberAccessExpression ::= expression TOKEN_DOT name { ] } -unaryExpression ::= (TOKEN_MINUS | TOKEN_BITWISE_NOT) !(TOKEN_DEC_LIT | TOKEN_INT_LIT) expression { +unaryExpression ::= ((TOKEN_MINUS !(TOKEN_DEC_LIT | TOKEN_INT_LIT)) | TOKEN_BITWISE_NOT) expression { pin = 2 implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEUnaryExpressionMixin" mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEUnaryExpressionImplMixin" } -castExpression ::= TOKEN_LEFT_PAREN name TOKEN_RIGHT_PAREN expression { +castExpression ::= TOKEN_LEFT_PAREN nameWithDims TOKEN_RIGHT_PAREN expression { rightAssociative = true methods = [ type = "name" @@ -160,7 +166,7 @@ castExpression ::= TOKEN_LEFT_PAREN name TOKEN_RIGHT_PAREN expression { instantiationExpression ::= TOKEN_NEW name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { rightAssociative = true - pin = 1 + pin = 3 methods = [ type = "name" ] @@ -192,6 +198,20 @@ private shiftOp ::= TOKEN_SHL | TOKEN_SHR | TOKEN_USHR private comparisonOp ::= TOKEN_LT | TOKEN_LE | TOKEN_GT | TOKEN_GE private equalityOp ::= TOKEN_EQ | TOKEN_NE +newArrayExpression ::= TOKEN_NEW name + TOKEN_LEFT_BRACKET expression? TOKEN_RIGHT_BRACKET + ( TOKEN_LEFT_BRACKET expression? TOKEN_RIGHT_BRACKET )* + ( TOKEN_LEFT_BRACE arguments TOKEN_RIGHT_BRACE )? { + pin = 3 + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENewArrayExpressionMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MENewArrayExpressionImplMixin" + methods = [ + elementType = "name" + dimExprs = "expression" + arrayInitializer = "arguments" + ] +} + litExpression ::= decimalLitExpression | intLitExpression | stringLitExpression | boolLitExpression | nulLLitExpression { implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MELitExpressionMixin" mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MELitExpressionImplMixin" @@ -227,6 +247,14 @@ nameExpression ::= name { ] } +nameWithDims ::= name ( TOKEN_LEFT_BRACKET TOKEN_RIGHT_BRACKET )* { + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameWithDimsMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MENameWithDimsImplMixin" + methods = [ + MEName = "name" + ] +} + name ::= TOKEN_IDENTIFIER | TOKEN_WILDCARD { implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameMixin" mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MENameImplMixin" diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index dafc2b97d..342827ada 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -21,9 +21,8 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.asset.MCDevBundle +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEBinaryExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECastExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEInstantiationExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MELitExpression @@ -31,6 +30,8 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMemberAccessExpres import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMethodCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameWithDims +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENewArrayExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MESuperCallExpression import com.intellij.lang.annotation.AnnotationHolder @@ -43,10 +44,10 @@ class MEExpressionAnnotator : Annotator { when (element) { is MEName -> { if (!element.isWildcard) { - when (val parent = element.parent) { - is MECastExpression, - is MEClassConstantExpression, - is MEInstantiationExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + when (element.parent) { + is MENameWithDims, + is MEInstantiationExpression, + is MENewArrayExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) .range(element) .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) .create() @@ -61,11 +62,7 @@ class MEExpressionAnnotator : Annotator { .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CALL) .create() is MENameExpression -> { - val grandparent = parent.parent - if (grandparent is MEBinaryExpression && - grandparent.operator == MEExpressionTypes.TOKEN_INSTANCEOF && - grandparent.rightExpr == parent - ) { + if (element.isRightOfInstanceof()) { holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) .range(element) .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) @@ -77,6 +74,10 @@ class MEExpressionAnnotator : Annotator { .create() } } + else -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(element) + .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) + .create() } } } @@ -102,6 +103,7 @@ class MEExpressionAnnotator : Annotator { val rightExpr = element.rightExpr if (element.operator == MEExpressionTypes.TOKEN_INSTANCEOF && rightExpr !is MENameExpression && + rightExpr !is MEArrayAccessExpression && rightExpr != null ) { holder.newAnnotation( @@ -112,6 +114,95 @@ class MEExpressionAnnotator : Annotator { .create() } } + is MEArrayAccessExpression -> { + if (element.isRightOfInstanceof()) { + val indexExpr = element.indexExpr + if (indexExpr != null) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.index_not_expected_in_type"), + ) + .range(indexExpr) + .create() + } + val arrayExpr = element.arrayExpr + if (arrayExpr !is MEArrayAccessExpression && arrayExpr !is MENameExpression) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.instanceof_non_type"), + ) + .range(arrayExpr) + .create() + } + } else if (element.indexExpr == null) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.array_access_missing_index"), + ) + .range(element.leftBracketToken) + .create() + } + } + is MENewArrayExpression -> { + val initializer = element.arrayInitializer + if (initializer != null) { + if (element.dimExprs.isNotEmpty()) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.new_array_dim_expr_with_initializer"), + ) + .range(initializer) + .create() + } else if (initializer.expressionList.isEmpty()) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.empty_array_initializer"), + ) + .range(initializer) + .create() + } + } else { + if (element.dimExprs.isEmpty()) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.missing_array_length") + ) + .range(element.dimExprTokens[0].leftBracket) + .create() + } else { + element.dimExprTokens.asSequence().dropWhile { it.expr != null }.forEach { + if (it.expr != null) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.array_length_after_empty") + ) + .range(it.expr) + .create() + } + } + } + } + } } } + + private fun PsiElement.isRightOfInstanceof(): Boolean { + var elt: PsiElement? = this + while (elt != null) { + when (elt) { + is MEName, is MENameExpression, is MEArrayAccessExpression -> {} + else -> return false + } + val parent = elt.parent + if (parent is MEBinaryExpression && + parent.operator == MEExpressionTypes.TOKEN_INSTANCEOF && + elt == parent.rightExpr + ) { + return true + } + elt = parent + } + + return false + } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt index 3b4a2b3f7..323856006 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionBraceMatcher.kt @@ -31,6 +31,7 @@ class MEExpressionBraceMatcher : PairedBraceMatcher { private val PAIRS = arrayOf( BracePair(MEExpressionTypes.TOKEN_LEFT_PAREN, MEExpressionTypes.TOKEN_RIGHT_PAREN, false), BracePair(MEExpressionTypes.TOKEN_LEFT_BRACKET, MEExpressionTypes.TOKEN_RIGHT_BRACKET, false), + BracePair(MEExpressionTypes.TOKEN_LEFT_BRACE, MEExpressionTypes.TOKEN_RIGHT_BRACE, false), ) } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt index 5c3e0c72a..093076565 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt @@ -57,6 +57,10 @@ class MEExpressionColorSettingsPage : ColorSettingsPage { MCDevBundle.pointer("mixinextras.expression.lang.highlighting.brackets.display_name"), MEExpressionSyntaxHighlighter.BRACKETS ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.braces.display_name"), + MEExpressionSyntaxHighlighter.BRACES + ), AttributesDescriptor( MCDevBundle.pointer("mixinextras.expression.lang.highlighting.dot.display_name"), MEExpressionSyntaxHighlighter.DOT @@ -117,6 +121,7 @@ class MEExpressionColorSettingsPage : ColorSettingsPage { ?, ClassName.class, foo.bar, + new int[] { 1, 2, 3 }, 'a bad character: ' # other_identifier )[0] """.trimIndent() diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt index d0fe7a238..963d7fb6d 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt @@ -63,6 +63,10 @@ class MEExpressionSyntaxHighlighter : SyntaxHighlighterBase() { "MEEXPRESSION_BRACKETS", DefaultLanguageHighlighterColors.BRACKETS ) + val BRACES = createTextAttributesKey( + "MEEXPRESSION_BRACES", + DefaultLanguageHighlighterColors.BRACES + ) val DOT = createTextAttributesKey( "MEEXPRESSION_DOT", DefaultLanguageHighlighterColors.DOT @@ -111,6 +115,7 @@ class MEExpressionSyntaxHighlighter : SyntaxHighlighterBase() { val OPERATOR_KEYS = arrayOf(OPERATOR) val PARENS_KEYS = arrayOf(PARENS) val BRACKETS_KEYS = arrayOf(BRACKETS) + val BRACES_KEYS = arrayOf(BRACES) val DOT_KEYS = arrayOf(DOT) val COMMA_KEYS = arrayOf(COMMA) val CAPTURE_KEYS = arrayOf(CAPTURE) @@ -145,6 +150,9 @@ class MEExpressionSyntaxHighlighter : SyntaxHighlighterBase() { if (MEExpressionTokenSets.BRACKETS.contains(tokenType)) { return BRACKETS_KEYS } + if (MEExpressionTokenSets.BRACES.contains(tokenType)) { + return BRACES_KEYS + } if (tokenType == MEExpressionTypes.TOKEN_DOT) { return DOT_KEYS } diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt index d04496fe5..aac58bf43 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt @@ -68,4 +68,5 @@ object MEExpressionTokenSets { ) val PARENS = TokenSet.create(MEExpressionTypes.TOKEN_LEFT_PAREN, MEExpressionTypes.TOKEN_RIGHT_PAREN) val BRACKETS = TokenSet.create(MEExpressionTypes.TOKEN_LEFT_BRACKET, MEExpressionTypes.TOKEN_RIGHT_BRACKET) + val BRACES = TokenSet.create(MEExpressionTypes.TOKEN_LEFT_BRACE, MEExpressionTypes.TOKEN_RIGHT_BRACE) } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEArrayAccessExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEArrayAccessExpressionMixin.kt new file mode 100644 index 000000000..0ce35f25f --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEArrayAccessExpressionMixin.kt @@ -0,0 +1,28 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.intellij.psi.PsiElement + +interface MEArrayAccessExpressionMixin : MEExpression { + val leftBracketToken: PsiElement +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameWithDimsMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameWithDimsMixin.kt new file mode 100644 index 000000000..db2be7ece --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameWithDimsMixin.kt @@ -0,0 +1,28 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.intellij.psi.PsiElement + +interface MENameWithDimsMixin : PsiElement { + val isArray: Boolean + val dimensions: Int +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENewArrayExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENewArrayExpressionMixin.kt new file mode 100644 index 000000000..7f29c8eb2 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENewArrayExpressionMixin.kt @@ -0,0 +1,31 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.intellij.psi.PsiElement + +interface MENewArrayExpressionMixin : MEExpression { + val dimensions: Int + val dimExprTokens: List + + class DimExprTokens(val leftBracket: PsiElement, val expr: MEExpression?, val rightBracket: PsiElement?) +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt new file mode 100644 index 000000000..ba88ac42b --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt @@ -0,0 +1,31 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEArrayAccessExpressionMixin +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement + +abstract class MEArrayAccessExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MEArrayAccessExpressionMixin { + override val leftBracketToken get() = findNotNullChildByType(MEExpressionTypes.TOKEN_LEFT_BRACKET) +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameWithDimsImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameWithDimsImplMixin.kt new file mode 100644 index 000000000..998419830 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameWithDimsImplMixin.kt @@ -0,0 +1,32 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameWithDimsMixin +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement + +abstract class MENameWithDimsImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MENameWithDimsMixin { + override val isArray get() = findChildByType(MEExpressionTypes.TOKEN_LEFT_BRACKET) != null + override val dimensions get() = findChildrenByType(MEExpressionTypes.TOKEN_LEFT_BRACKET).size +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt new file mode 100644 index 000000000..df32ece01 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt @@ -0,0 +1,61 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENewArrayExpressionMixin +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.util.siblings + +abstract class MENewArrayExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MENewArrayExpressionMixin { + override val dimensions get() = findChildrenByType(MEExpressionTypes.TOKEN_LEFT_BRACKET).size + + override val dimExprTokens: List get() { + val result = mutableListOf() + + var leftBracket: PsiElement? = findNotNullChildByType(MEExpressionTypes.TOKEN_LEFT_BRACKET) + while (leftBracket != null) { + var expr: MEExpression? = null + var rightBracket: PsiElement? = null + var nextLeftBracket: PsiElement? = null + for (child in leftBracket.siblings(withSelf = false)) { + if (child is MEExpression) { + expr = child + } else { + when (child.node.elementType) { + MEExpressionTypes.TOKEN_RIGHT_BRACKET -> rightBracket = child + MEExpressionTypes.TOKEN_LEFT_BRACKET -> { + nextLeftBracket = child + break + } + } + } + } + result += MENewArrayExpressionMixin.DimExprTokens(leftBracket, expr, rightBracket) + leftBracket = nextLeftBracket + } + + return result + } +} diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 3bdf1005a..ae9d136bd 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -198,11 +198,18 @@ minecraft.settings.chat_color_underline_style=Chat color underline style: minecraft.settings.mixin=Mixin minecraft.settings.mixin.shadow_annotation_same_line=@Shadow annotations on same line +mixinextras.expression.lang.errors.array_access_missing_index=Missing index +mixinextras.expression.lang.errors.array_length_after_empty=Cannot specify array length after an unspecified array length +mixinextras.expression.lang.errors.empty_array_initializer=Array initializer cannot be empty +mixinextras.expression.lang.errors.index_not_expected_in_type=Index not expected in type mixinextras.expression.lang.errors.instanceof_non_type=Expected type mixinextras.expression.lang.errors.invalid_number=Invalid number +mixinextras.expression.lang.errors.missing_array_length=Array construction must contain a length +mixinextras.expression.lang.errors.new_array_dim_expr_with_initializer=Cannot use initializer for array with specified length mixinextras.expression.lang.display_name=MixinExtras Expressions mixinextras.expression.lang.highlighting.bad_char.display_name=Bad character +mixinextras.expression.lang.highlighting.braces.display_name=Braces mixinextras.expression.lang.highlighting.brackets.display_name=Brackets mixinextras.expression.lang.highlighting.call_identifier.display_name=Identifier//Method call mixinextras.expression.lang.highlighting.capture.display_name=Capture From 67b83654914c0783ddd465283e5cc0583a95e60b Mon Sep 17 00:00:00 2001 From: joe Date: Sat, 3 Feb 2024 19:29:12 +0000 Subject: [PATCH 011/100] Implement MixinExtras expression collect visitor --- build.gradle.kts | 5 +- .../expression/MEExpressionElementFactory.kt | 51 +++++ .../mixin/expression/psi/MEExpressionFile.kt | 3 + .../mixinextras/ExpressionInjectionPoint.kt | 205 +++++++++++++++++- .../platform/mixin/util/MixinConstants.kt | 1 + 5 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt diff --git a/build.gradle.kts b/build.gradle.kts index 1786dcbd1..f6afdd7a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -88,6 +88,7 @@ repositories { mavenCentral() // TODO: temporary waiting for MixinExtras expression library + maven("https://repo.spongepowered.org/") maven("https://jitpack.io/") { content { includeGroupByRegex("com\\.github\\..+") @@ -100,7 +101,9 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - implementation("com.github.LlamaLad7:MixinExtras:e617eca") + implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:6440263") + implementation("org.spongepowered:mixin:0.8.4") + implementation("org.ow2.asm:asm-util:9.3") // Kotlin implementation(kotlin("stdlib-jdk8")) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt new file mode 100644 index 000000000..532607c80 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt @@ -0,0 +1,51 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFileFactory +import com.intellij.util.IncorrectOperationException + +class MEExpressionElementFactory(private val project: Project) { + fun createFile(text: String): MEExpressionFile { + return PsiFileFactory.getInstance(project).createFileFromText( + "dummy.mixinextrasexpression", + MEExpressionFileType, + text + ) as MEExpressionFile + } + + fun createStatement(text: String): MEStatement { + return createFile(text).statement + ?: throw IncorrectOperationException("'$text' is not a statement") + } + + fun createExpression(text: String): MEExpression { + return (createStatement(text) as? MEExpressionStatement)?.expression + ?: throw IncorrectOperationException("'$text' is not an expression") + } +} + +val Project.meExpressionElementFactory get() = MEExpressionElementFactory(this) diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt index 66e12e7e9..bb0d1532f 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt @@ -22,10 +22,13 @@ package com.demonwav.mcdev.platform.mixin.expression.psi import com.demonwav.mcdev.platform.mixin.expression.MEExpressionFileType import com.demonwav.mcdev.platform.mixin.expression.MEExpressionLanguage +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement import com.intellij.extapi.psi.PsiFileBase import com.intellij.psi.FileViewProvider class MEExpressionFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, MEExpressionLanguage) { override fun getFileType() = MEExpressionFileType override fun toString() = "MixinExtras Expression File" + + val statement: MEStatement? get() = findChildByClass(MEStatement::class.java) } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index c730daaa9..deee994a2 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -20,14 +20,32 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression +import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InjectionPoint import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.NavigationVisitor import com.demonwav.mcdev.platform.mixin.reference.MixinSelector +import com.demonwav.mcdev.platform.mixin.util.LocalInfo import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.util.constantStringValue +import com.demonwav.mcdev.util.descriptor +import com.demonwav.mcdev.util.findAnnotations import com.demonwav.mcdev.util.findContainingModifierList +import com.demonwav.mcdev.util.findModule +import com.demonwav.mcdev.util.ifEmpty +import com.demonwav.mcdev.util.parseArray +import com.demonwav.mcdev.util.resolveType +import com.demonwav.mcdev.util.resolveTypeArray import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.lang.injection.InjectedLanguageManager import com.intellij.openapi.editor.Editor +import com.intellij.openapi.module.Module +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.RecursionManager import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiClass @@ -36,7 +54,23 @@ import com.intellij.psi.PsiLiteral import com.intellij.psi.PsiModifierList import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.psi.util.PsiTreeUtil +import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade +import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression +import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter +import com.llamalad7.mixinextras.expression.impl.flow.FlowValue +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext +import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool +import java.util.Collections +import java.util.IdentityHashMap +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.tree.VarInsnNode + +private typealias IdentifierPoolFactory = (MethodNode) -> IdentifierPool class ExpressionInjectionPoint : InjectionPoint() { override fun onCompleted(editor: Editor, reference: PsiLiteral) { @@ -79,7 +113,105 @@ class ExpressionInjectionPoint : InjectionPoint() { targetClass: ClassNode, mode: CollectVisitor.Mode ): CollectVisitor? { - return null + val project = at.project + + val atId = at.findDeclaredAttributeValue("id")?.constantStringValue ?: "" + + val injectorAnnotation = AtResolver.findInjectorAnnotation(at) ?: return null + val modifierList = injectorAnnotation.parent as? PsiModifierList ?: return null + val parsedExprs = parseExpressions(project, modifierList, atId) + parsedExprs.ifEmpty { return null } + + val module = at.findModule() ?: return null + + val poolFactory = createIdentifierPoolFactory(module, targetClass, modifierList) + + return MyCollectVisitor(mode, targetClass, parsedExprs, poolFactory) + } + + private fun parseExpressions( + project: Project, + modifierList: PsiModifierList, + atId: String + ): List> { + return modifierList.annotations.asSequence() + .filter { exprAnnotation -> + exprAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION) && + (exprAnnotation.findDeclaredAttributeValue("id")?.constantStringValue ?: "") == atId + } + .flatMap { exprAnnotation -> + val expressionElements = exprAnnotation.findDeclaredAttributeValue("value")?.parseArray { it } + ?: return@flatMap emptySequence>() + expressionElements.asSequence().mapNotNull { expressionElement -> + val text = expressionElement.constantStringValue ?: return@mapNotNull null + val rootStatementPsi = InjectedLanguageManager.getInstance(project) + .getInjectedPsiFiles(expressionElement)?.firstOrNull() + ?.let { (it.first as? MEExpressionFile)?.statement } + ?: project.meExpressionElementFactory.createFile(text).statement + ?: project.meExpressionElementFactory.createStatement("empty") + try { + ExpressionParserFacade.parse(text) to rootStatementPsi + } catch (e: Exception) { + null + } + } + } + .toList() + } + + private fun createIdentifierPoolFactory( + module: Module, + targetClass: ClassNode, + modifierList: PsiModifierList, + ): IdentifierPoolFactory = { targetMethod -> + val pool = IdentifierPool() + + for (annotation in modifierList.annotations) { + if (!annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { + continue + } + + val definitionId = annotation.findDeclaredAttributeValue("id")?.constantStringValue ?: "" + + val ats = annotation.findDeclaredAttributeValue("at")?.findAnnotations() ?: emptyList() + for (at in ats) { + val matchingInsns = RecursionManager.doPreventingRecursion(at, true) { + AtResolver(at, targetClass, targetMethod) + .resolveInstructions() + .mapTo(Collections.newSetFromMap(IdentityHashMap())) { it.insn } + } ?: emptySet() + pool.addMember(definitionId) { it in matchingInsns } + } + + val types = annotation.findDeclaredAttributeValue("type")?.resolveTypeArray() ?: emptyList() + for (type in types) { + val asmType = Type.getType(type.descriptor) + pool.addType(definitionId) { it == asmType } + } + + val locals = annotation.findDeclaredAttributeValue("local")?.findAnnotations() ?: emptyList() + for (localAnnotation in locals) { + val localType = annotation.findDeclaredAttributeValue("type")?.resolveType() + val localInfo = LocalInfo.fromAnnotation(localType, localAnnotation) + pool.addMember(definitionId) { insn -> + if (insn !is VarInsnNode) { + return@addMember false + } + val actualInsn = if (insn.opcode >= Opcodes.ISTORE && insn.opcode <= Opcodes.ASTORE) { + insn.next ?: return@addMember false + } else { + insn + } + + val unfilteredLocals = localInfo.getLocals(module, targetClass, targetMethod, actualInsn) + ?: return@addMember false + val filteredLocals = localInfo.matchLocals(unfilteredLocals, CollectVisitor.Mode.MATCH_ALL) + filteredLocals.any { it.index == insn.`var` } + } + } + } + + pool } override fun createLookup( @@ -88,4 +220,75 @@ class ExpressionInjectionPoint : InjectionPoint() { ): LookupElementBuilder? { return null } + + private class MyCollectVisitor( + mode: Mode, + private val targetClass: ClassNode, + private val expressions: List>, + private val poolFactory: IdentifierPoolFactory, + ) : CollectVisitor(mode) { + override fun accept(methodNode: MethodNode) { + val insns = methodNode.instructions ?: return + + val pool = poolFactory(methodNode) + val flows = try { + FlowInterpreter.analyze(targetClass, methodNode) + } catch (e: RuntimeException) { + return + } + + val result = IdentityHashMap() + + for ((expr, psiExpr) in expressions) { + insns.iterator().forEachRemaining { insn -> + val genericDecorations = IdentityHashMap>() + val injectorSpecificDecorations = IdentityHashMap>() + val captured = mutableListOf() + + val sink = object : Expression.OutputSink { + override fun capture(node: FlowValue) { + captured += node.insn + } + + override fun decorate(insn: AbstractInsnNode, key: String, value: Any?) { + genericDecorations.computeIfAbsent(insn) { mutableMapOf() }[key] = value + } + + override fun decorateInjectorSpecific(insn: AbstractInsnNode, key: String, value: Any?) { + injectorSpecificDecorations.computeIfAbsent(insn) { mutableMapOf() }[key] = value + } + } + + val flow = flows[insn] ?: return@forEachRemaining + try { + if (expr.matches(flow, ExpressionContext(pool, sink, targetClass, methodNode))) { + // TODO: for now we're assuming there's only one capture in the ME expression. + val capturedExpr = + PsiTreeUtil.findChildOfType(psiExpr, MECapturingExpression::class.java, false) + ?.expression ?: psiExpr + + for (capturedInsn in captured) { + result.putIfAbsent(capturedInsn, capturedExpr) + } + } + } catch (e: ProcessCanceledException) { + throw e + } catch (ignored: Exception) { + // MixinExtras throws lots of different exceptions + } + } + } + + if (result.isEmpty()) { + return + } + + insns.iterator().forEachRemaining { insn -> + val element = result[insn] + if (element != null) { + addResult(insn, element) + } + } + } + } } diff --git a/src/main/kotlin/platform/mixin/util/MixinConstants.kt b/src/main/kotlin/platform/mixin/util/MixinConstants.kt index 7881d7454..2031a9c4c 100644 --- a/src/main/kotlin/platform/mixin/util/MixinConstants.kt +++ b/src/main/kotlin/platform/mixin/util/MixinConstants.kt @@ -86,6 +86,7 @@ object MixinConstants { const val LOCAL = "com.llamalad7.mixinextras.sugar.Local" const val LOCAL_REF_PACKAGE = "com.llamalad7.mixinextras.sugar.ref." const val EXPRESSION = "com.llamalad7.mixinextras.expression.Expression" + const val DEFINITION = "com.llamalad7.mixinextras.expression.Definition" fun PsiType.unwrapLocalRef(): PsiType { if (this !is PsiClassType) { From 5f5bbd5ff3d5fa07c9ead245850c2251029d92f9 Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 4 Feb 2024 12:12:00 +0000 Subject: [PATCH 012/100] Fix cast expressions --- src/main/grammars/MEExpressionParser.bnf | 17 ++-- .../mixin/expression/MEExpressionAnnotator.kt | 31 ++------ .../expression/MEExpressionElementFactory.kt | 7 ++ .../mixin/expression/psi/METypeUtil.kt | 79 +++++++++++++++++++ .../mixins/MEArrayAccessExpressionMixin.kt | 1 + .../psi/mixins/MEBinaryExpressionMixin.kt | 2 + .../psi/mixins/MECastExpressionMixin.kt | 30 +++++++ ...{MENameWithDimsMixin.kt => METypeMixin.kt} | 2 +- .../impl/MEArrayAccessExpressionImplMixin.kt | 1 + .../impl/MEBinaryExpressionImplMixin.kt | 7 ++ .../mixins/impl/MECastExpressionImplMixin.kt | 37 +++++++++ ...ithDimsImplMixin.kt => METypeImplMixin.kt} | 4 +- 12 files changed, 181 insertions(+), 37 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MECastExpressionMixin.kt rename src/main/kotlin/platform/mixin/expression/psi/mixins/{MENameWithDimsMixin.kt => METypeMixin.kt} (95%) create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt rename src/main/kotlin/platform/mixin/expression/psi/mixins/impl/{MENameWithDimsImplMixin.kt => METypeImplMixin.kt} (91%) diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index 14dca2bb1..e8cb18705 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -106,7 +106,7 @@ parenthesizedExpression ::= TOKEN_LEFT_PAREN expression TOKEN_RIGHT_PAREN { } superCallExpression ::= TOKEN_SUPER TOKEN_DOT name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { - pin = 2 + pin = 1 methods = [ memberName = "name" ] @@ -137,7 +137,7 @@ arrayAccessExpression ::= expression TOKEN_LEFT_BRACKET expression? TOKEN_RIGHT_ ] } -classConstantExpression ::= nameWithDims TOKEN_DOT TOKEN_CLASS { +classConstantExpression ::= type TOKEN_DOT TOKEN_CLASS { pin = 3 methods = [ className = "name" @@ -157,11 +157,10 @@ unaryExpression ::= ((TOKEN_MINUS !(TOKEN_DEC_LIT | TOKEN_INT_LIT)) | TOKEN_BITW mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEUnaryExpressionImplMixin" } -castExpression ::= TOKEN_LEFT_PAREN nameWithDims TOKEN_RIGHT_PAREN expression { +castExpression ::= parenthesizedExpression expression { rightAssociative = true - methods = [ - type = "name" - ] + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MECastExpressionMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MECastExpressionImplMixin" } instantiationExpression ::= TOKEN_NEW name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { @@ -247,9 +246,9 @@ nameExpression ::= name { ] } -nameWithDims ::= name ( TOKEN_LEFT_BRACKET TOKEN_RIGHT_BRACKET )* { - implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameWithDimsMixin" - mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MENameWithDimsImplMixin" +type ::= name ( TOKEN_LEFT_BRACKET TOKEN_RIGHT_BRACKET )* { + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.METypeMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.METypeImplMixin" methods = [ MEName = "name" ] diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index 342827ada..1b43ab244 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -30,10 +30,11 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMemberAccessExpres import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMethodCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameWithDims import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENewArrayExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MESuperCallExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype +import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.Annotator import com.intellij.lang.annotation.HighlightSeverity @@ -44,8 +45,8 @@ class MEExpressionAnnotator : Annotator { when (element) { is MEName -> { if (!element.isWildcard) { - when (element.parent) { - is MENameWithDims, + when (val parent = element.parent) { + is METype, is MEInstantiationExpression, is MENewArrayExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) .range(element) @@ -62,7 +63,7 @@ class MEExpressionAnnotator : Annotator { .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CALL) .create() is MENameExpression -> { - if (element.isRightOfInstanceof()) { + if (METypeUtil.isExpressionInTypePosition(parent)) { holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) .range(element) .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) @@ -115,7 +116,7 @@ class MEExpressionAnnotator : Annotator { } } is MEArrayAccessExpression -> { - if (element.isRightOfInstanceof()) { + if (METypeUtil.isExpressionInTypePosition(element)) { val indexExpr = element.indexExpr if (indexExpr != null) { holder.newAnnotation( @@ -185,24 +186,4 @@ class MEExpressionAnnotator : Annotator { } } } - - private fun PsiElement.isRightOfInstanceof(): Boolean { - var elt: PsiElement? = this - while (elt != null) { - when (elt) { - is MEName, is MENameExpression, is MEArrayAccessExpression -> {} - else -> return false - } - val parent = elt.parent - if (parent is MEBinaryExpression && - parent.operator == MEExpressionTypes.TOKEN_INSTANCEOF && - elt == parent.rightExpr - ) { - return true - } - elt = parent - } - - return false - } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt index 532607c80..d3d4f9999 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt @@ -20,9 +20,11 @@ package com.demonwav.mcdev.platform.mixin.expression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile import com.intellij.openapi.project.Project import com.intellij.psi.PsiFileFactory @@ -46,6 +48,11 @@ class MEExpressionElementFactory(private val project: Project) { return (createStatement(text) as? MEExpressionStatement)?.expression ?: throw IncorrectOperationException("'$text' is not an expression") } + + fun createType(text: String): METype { + return (createExpression("$text.class") as? MEClassConstantExpression)?.type + ?: throw IncorrectOperationException("'$text' is not a type") + } } val Project.meExpressionElementFactory get() = MEExpressionElementFactory(this) diff --git a/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt b/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt new file mode 100644 index 000000000..4991c992c --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt @@ -0,0 +1,79 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEBinaryExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECastExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEParenthesizedExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype +import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory +import com.intellij.psi.PsiElement + +object METypeUtil { + fun convertExpressionToType(expr: MEExpression): METype? { + return if (isExpressionValidType(expr)) { + expr.project.meExpressionElementFactory.createType(expr.text) + } else { + null + } + } + + private fun isExpressionValidType(expr: MEExpression): Boolean { + var e = expr + while (true) { + when (e) { + is MEArrayAccessExpression -> { + if (e.indexExpr != null || e.rightBracketToken == null) { + return false + } + e = e.arrayExpr + } + is MENameExpression -> return true + else -> return false + } + } + } + + fun isExpressionInTypePosition(expr: MEExpression): Boolean { + var e: PsiElement? = expr + while (e != null) { + val parent = e.parent + when (parent) { + is MEArrayAccessExpression -> {} + is MEParenthesizedExpression -> { + val grandparent = parent.parent + return grandparent is MECastExpression && e == grandparent.castTypeExpr + } + is MEBinaryExpression -> { + return parent.operator == MEExpressionTypes.TOKEN_INSTANCEOF && e == parent.rightExpr + } + else -> return false + } + e = parent + } + + return false + } +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEArrayAccessExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEArrayAccessExpressionMixin.kt index 0ce35f25f..c026499db 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEArrayAccessExpressionMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEArrayAccessExpressionMixin.kt @@ -25,4 +25,5 @@ import com.intellij.psi.PsiElement interface MEArrayAccessExpressionMixin : MEExpression { val leftBracketToken: PsiElement + val rightBracketToken: PsiElement? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt index 12efebb71..1631852dd 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEBinaryExpressionMixin.kt @@ -21,8 +21,10 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.intellij.psi.tree.IElementType interface MEBinaryExpressionMixin : MEExpression { val operator: IElementType + val castType: METype? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MECastExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MECastExpressionMixin.kt new file mode 100644 index 000000000..3e61e8c13 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MECastExpressionMixin.kt @@ -0,0 +1,30 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype + +interface MECastExpressionMixin : MEExpression { + val castType: METype? + val castTypeExpr: MEExpression? + val castedExpr: MEExpression? +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameWithDimsMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/METypeMixin.kt similarity index 95% rename from src/main/kotlin/platform/mixin/expression/psi/mixins/MENameWithDimsMixin.kt rename to src/main/kotlin/platform/mixin/expression/psi/mixins/METypeMixin.kt index db2be7ece..7b304c0f0 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameWithDimsMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/METypeMixin.kt @@ -22,7 +22,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins import com.intellij.psi.PsiElement -interface MENameWithDimsMixin : PsiElement { +interface METypeMixin : PsiElement { val isArray: Boolean val dimensions: Int } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt index ba88ac42b..1b83f2394 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt @@ -28,4 +28,5 @@ import com.intellij.psi.PsiElement abstract class MEArrayAccessExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MEArrayAccessExpressionMixin { override val leftBracketToken get() = findNotNullChildByType(MEExpressionTypes.TOKEN_LEFT_BRACKET) + override val rightBracketToken get() = findChildByType(MEExpressionTypes.TOKEN_RIGHT_BRACKET) } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt index a697e838c..21d60c8b2 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt @@ -20,14 +20,21 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEBinaryExpressionMixin import com.intellij.lang.ASTNode import com.intellij.psi.tree.TokenSet abstract class MEBinaryExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MEBinaryExpressionMixin { override val operator get() = node.findChildByType(operatorTokens)!!.elementType + override val castType get() = rightExpr + ?.takeIf { operator == MEExpressionTypes.TOKEN_INSTANCEOF } + ?.let(METypeUtil::convertExpressionToType) + + protected abstract val rightExpr: MEExpression? companion object { private val operatorTokens = TokenSet.create( diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt new file mode 100644 index 000000000..ba977f357 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt @@ -0,0 +1,37 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEParenthesizedExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MECastExpressionMixin +import com.intellij.lang.ASTNode + +abstract class MECastExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MECastExpressionMixin { + override val castType get() = castTypeExpr?.let(METypeUtil::convertExpressionToType) + override val castTypeExpr get() = + (expressionList.let { it.getOrNull(it.size - 2) } as? MEParenthesizedExpression)?.expression + override val castedExpr get() = expressionList.lastOrNull() + + protected abstract val expressionList: List +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameWithDimsImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METypeImplMixin.kt similarity index 91% rename from src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameWithDimsImplMixin.kt rename to src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METypeImplMixin.kt index 998419830..85fe1546a 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameWithDimsImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METypeImplMixin.kt @@ -21,12 +21,12 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes -import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameWithDimsMixin +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.METypeMixin import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement -abstract class MENameWithDimsImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MENameWithDimsMixin { +abstract class METypeImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), METypeMixin { override val isArray get() = findChildByType(MEExpressionTypes.TOKEN_LEFT_BRACKET) != null override val dimensions get() = findChildrenByType(MEExpressionTypes.TOKEN_LEFT_BRACKET).size } From 4f753396d10680851f833732227ef45e603417cb Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 6 Feb 2024 19:36:13 +0000 Subject: [PATCH 013/100] Simple best-effort source matching for ME expressions --- src/main/grammars/MEExpressionParser.bnf | 23 ++++- .../expression/MEExpressionElementFactory.kt | 3 + .../mixin/expression/MESourceMatchContext.kt | 80 +++++++++++++++++ ...tatementMixin.kt => MEMatchableElement.kt} | 10 +-- .../expression/psi/mixins/MEArgumentsMixin.kt | 34 ++++++++ .../expression/psi/mixins/MENameMixin.kt | 3 + .../mixin/expression/psi/mixins/MEPsiUtil.kt | 50 +++++++++++ .../expression/psi/mixins/METypeMixin.kt | 4 + .../psi/mixins/impl/MEArgumentsImplMixin.kt | 44 ++++++++++ .../impl/MEArrayAccessExpressionImplMixin.kt | 24 +++++ .../mixins/impl/MEAssignStatementImplMixin.kt | 31 ++++++- .../impl/MEBinaryExpressionImplMixin.kt | 56 ++++++++++++ .../impl/MECapturingExpressionImplMixin.kt | 36 ++++++++ .../mixins/impl/MECastExpressionImplMixin.kt | 26 ++++++ .../MEClassConstantExpressionImplMixin.kt | 57 ++++++++++++ .../psi/mixins/impl/MEExpressionImplMixin.kt | 33 +++++++ .../impl/MEExpressionStatementImplMixin.kt | 35 ++++++++ .../MEInstantiationExpressionImplMixin.kt | 51 +++++++++++ .../mixins/impl/MELitExpressionImplMixin.kt | 37 ++++++++ .../impl/MEMemberAccessExpressionImplMixin.kt | 59 +++++++++++++ .../impl/MEMethodCallExpressionImplMixin.kt | 69 +++++++++++++++ .../mixins/impl/MENameExpressionImplMixin.kt | 47 ++++++++++ .../psi/mixins/impl/MENameImplMixin.kt | 45 ++++++++++ .../impl/MENewArrayExpressionImplMixin.kt | 53 +++++++++++ .../MEParenthesizedExpressionImplMixin.kt | 35 ++++++++ .../mixins/impl/MEReturnStatementImplMixin.kt | 41 +++++++++ .../MEStaticMethodCallExpressionImplMixin.kt | 52 +++++++++++ .../impl/MESuperCallExpressionImplMixin.kt | 50 +++++++++++ .../mixins/impl/METhisExpressionImplMixin.kt | 33 +++++++ .../mixins/impl/METhrowStatementImplMixin.kt | 41 +++++++++ .../psi/mixins/impl/METypeImplMixin.kt | 21 +++++ .../mixins/impl/MEUnaryExpressionImplMixin.kt | 34 ++++++++ .../handlers/injectionPoint/AtResolver.kt | 1 + .../handlers/injectionPoint/InjectionPoint.kt | 3 + .../injectionPoint/LoadInjectionPoint.kt | 45 ++-------- .../mixinextras/ExpressionInjectionPoint.kt | 87 ++++++++++++++++++- .../MixinClassCastInspectionSuppressor.kt | 4 +- .../kotlin/platform/mixin/util/LocalInfo.kt | 23 +++++ .../platform/mixin/util/LocalVariables.kt | 16 +++- 39 files changed, 1340 insertions(+), 56 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt rename src/main/kotlin/platform/mixin/expression/psi/{mixins/MEAssignStatementMixin.kt => MEMatchableElement.kt} (71%) create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MEArgumentsMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MEPsiUtil.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArgumentsImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECapturingExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEClassConstantExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionStatementImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEParenthesizedExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEReturnStatementImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhisExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index e8cb18705..c5044c1bf 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -45,15 +45,15 @@ statement ::= assignStatement | returnStatement | throwStatement | expressionStatement { - + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.MEMatchableElement" } assignStatement ::= assignableExpression TOKEN_ASSIGN expression { pin = 2 - implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEAssignStatementMixin" mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEAssignStatementImplMixin" methods = [ - rightExpr = "expression" + targetExpr = "expression[0]" + rightExpr = "expression[1]" ] } @@ -61,6 +61,7 @@ private assignableExpression ::= arrayAccessExpression | memberAccessExpression returnStatement ::= TOKEN_RETURN expression { pin = 1 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEReturnStatementImplMixin" methods = [ valueExpr = "expression" ] @@ -68,12 +69,14 @@ returnStatement ::= TOKEN_RETURN expression { throwStatement ::= TOKEN_THROW expression { pin = 1 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.METhrowStatementImplMixin" methods = [ valueExpr = "expression" ] } expressionStatement ::= expression { + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEExpressionStatementImplMixin" } private exprRecover ::= !( TOKEN_COMMA | TOKEN_RIGHT_PAREN | TOKEN_RIGHT_BRACKET | TOKEN_RIGHT_BRACE ) @@ -94,19 +97,24 @@ expression ::= capturingExpression | litExpression | thisExpression | nameExpression { + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.MEMatchableElement" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEExpressionImplMixin" recoverWhile = exprRecover } capturingExpression ::= TOKEN_AT TOKEN_LEFT_PAREN expression TOKEN_RIGHT_PAREN { pin = 1 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MECapturingExpressionImplMixin" } parenthesizedExpression ::= TOKEN_LEFT_PAREN expression TOKEN_RIGHT_PAREN { pin = 1 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEParenthesizedExpressionImplMixin" } superCallExpression ::= TOKEN_SUPER TOKEN_DOT name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { pin = 1 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MESuperCallExpressionImplMixin" methods = [ memberName = "name" ] @@ -114,6 +122,7 @@ superCallExpression ::= TOKEN_SUPER TOKEN_DOT name TOKEN_LEFT_PAREN arguments TO methodCallExpression ::= expression TOKEN_DOT name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { pin = 4 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEMethodCallExpressionImplMixin" methods = [ receiverExpr = "expression" memberName = "name" @@ -122,6 +131,7 @@ methodCallExpression ::= expression TOKEN_DOT name TOKEN_LEFT_PAREN arguments TO staticMethodCallExpression ::= name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { pin = 2 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEStaticMethodCallExpressionImplMixin" methods = [ memberName = "name" ] @@ -139,12 +149,14 @@ arrayAccessExpression ::= expression TOKEN_LEFT_BRACKET expression? TOKEN_RIGHT_ classConstantExpression ::= type TOKEN_DOT TOKEN_CLASS { pin = 3 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEClassConstantExpressionImplMixin" methods = [ className = "name" ] } memberAccessExpression ::= expression TOKEN_DOT name { + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEMemberAccessExpressionImplMixin" methods = [ receiverExpr = "expression" memberName = "name" @@ -166,6 +178,7 @@ castExpression ::= parenthesizedExpression expression { instantiationExpression ::= TOKEN_NEW name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { rightAssociative = true pin = 3 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEInstantiationExpressionImplMixin" methods = [ type = "name" ] @@ -238,9 +251,11 @@ private nulLLitExpression ::= TOKEN_NULL_LIT { } thisExpression ::= TOKEN_THIS { + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.METhisExpressionImplMixin" } nameExpression ::= name { + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MENameExpressionImplMixin" methods = [ MEName = "name" ] @@ -260,4 +275,6 @@ name ::= TOKEN_IDENTIFIER | TOKEN_WILDCARD { } arguments ::= (expression (TOKEN_COMMA expression)*)? { + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEArgumentsMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEArgumentsImplMixin" } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt index d3d4f9999..7c6e688d2 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt @@ -23,6 +23,7 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile @@ -53,6 +54,8 @@ class MEExpressionElementFactory(private val project: Project) { return (createExpression("$text.class") as? MEClassConstantExpression)?.type ?: throw IncorrectOperationException("'$text' is not a type") } + + fun createType(name: MEName) = createType(name.text) } val Project.meExpressionElementFactory get() = MEExpressionElementFactory(this) diff --git a/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt b/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt new file mode 100644 index 000000000..82b7de47e --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt @@ -0,0 +1,80 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.util.LocalInfo +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement + +class MESourceMatchContext(val project: Project) { + @PublishedApi + internal var realElement: PsiElement? = null + private val capturesInternal = mutableListOf() + val captures: List get() = capturesInternal + + private val types = mutableMapOf>() + private val targetedElements = mutableMapOf>() + private val localInfos = mutableMapOf>() + + fun addCapture(capturedElement: PsiElement) { + val element = realElement ?: capturedElement + capturesInternal += element + } + + fun getTypes(key: String): List = types[key] ?: emptyList() + + fun addType(key: String, desc: String) { + types.computeIfAbsent(key) { mutableListOf() } += desc + } + + fun getTargetedElements(key: String): List = targetedElements[key] ?: emptyList() + + fun addTargetedElement(key: String, element: PsiElement) { + targetedElements.computeIfAbsent(key) { mutableListOf() } += element + } + + fun getLocalInfos(key: String): List = localInfos[key] ?: emptyList() + + fun addLocalInfo(key: String, localInfo: LocalInfo) { + localInfos.computeIfAbsent(key) { mutableListOf() } += localInfo + } + + fun reset() { + capturesInternal.clear() + } + + inline fun fakeElementScope( + isFake: Boolean, + realElement: PsiElement, + action: () -> T + ): T { + if (this.realElement != null || !isFake) { + return action() + } + + this.realElement = realElement + try { + return action() + } finally { + this.realElement = null + } + } +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/MEMatchableElement.kt similarity index 71% rename from src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt rename to src/main/kotlin/platform/mixin/expression/psi/MEMatchableElement.kt index fce42a042..46c2d7523 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEAssignStatementMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEMatchableElement.kt @@ -18,11 +18,11 @@ * along with this program. If not, see . */ -package com.demonwav.mcdev.platform.mixin.expression.psi.mixins +package com.demonwav.mcdev.platform.mixin.expression.psi -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.intellij.psi.PsiElement -interface MEAssignStatementMixin : MEStatement { - val targetExpr: MEExpression +interface MEMatchableElement : PsiElement { + fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEArgumentsMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEArgumentsMixin.kt new file mode 100644 index 000000000..2ef6ed9f3 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEArgumentsMixin.kt @@ -0,0 +1,34 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiExpression +import com.intellij.psi.PsiExpressionList + +interface MEArgumentsMixin : PsiElement { + fun matchesJava(java: PsiExpressionList, context: MESourceMatchContext): Boolean { + return matchesJava(java.expressions, context) + } + + fun matchesJava(java: Array, context: MESourceMatchContext): Boolean +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt index e63f0e403..e29b5bb9d 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt @@ -20,8 +20,11 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.intellij.psi.PsiElement interface MENameMixin : PsiElement { val isWildcard: Boolean + + fun matchesJavaExpr(javaExpr: PsiElement, context: MESourceMatchContext): Boolean } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEPsiUtil.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEPsiUtil.kt new file mode 100644 index 000000000..f0698dc33 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEPsiUtil.kt @@ -0,0 +1,50 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEAssignStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEParenthesizedExpression + +object MEPsiUtil { + fun isAccessedForReading(expr: MEExpression): Boolean { + return !isAccessedForWriting(expr) + } + + fun isAccessedForWriting(expr: MEExpression): Boolean { + val parent = expr.parent + return parent is MEAssignStatement && expr == parent.targetExpr + } + + fun skipParenthesizedExprDown(expr: MEExpression): MEExpression? { + var e: MEExpression? = expr + while (e is MEParenthesizedExpression) { + e = e.expression + } + return e + } + + fun isWildcardExpression(expr: MEExpression): Boolean { + val actualExpr = skipParenthesizedExprDown(expr) ?: return false + return actualExpr is MENameExpression && actualExpr.meName.isWildcard + } +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/METypeMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/METypeMixin.kt index 7b304c0f0..30d404f6c 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/METypeMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/METypeMixin.kt @@ -20,9 +20,13 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.intellij.psi.PsiElement +import com.intellij.psi.PsiType interface METypeMixin : PsiElement { val isArray: Boolean val dimensions: Int + + fun matchesJava(java: PsiType, context: MESourceMatchContext): Boolean } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArgumentsImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArgumentsImplMixin.kt new file mode 100644 index 000000000..a83224344 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArgumentsImplMixin.kt @@ -0,0 +1,44 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEArgumentsMixin +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiExpression +import com.intellij.psi.util.PsiUtil + +abstract class MEArgumentsImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MEArgumentsMixin { + override fun matchesJava(java: Array, context: MESourceMatchContext): Boolean { + val exprs = expressionList + if (exprs.size != java.size) { + return false + } + return exprs.asSequence().zip(java.asSequence()).all { (expr, javaExpr) -> + val actualJavaExpr = PsiUtil.skipParenthesizedExprDown(javaExpr) ?: return@all false + expr.matchesJava(actualJavaExpr, context) + } + } + + protected abstract val expressionList: List +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt index 1b83f2394..c934a3af2 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt @@ -20,13 +20,37 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEArrayAccessExpressionMixin +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEPsiUtil import com.intellij.lang.ASTNode +import com.intellij.psi.PsiArrayAccessExpression import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiUtil abstract class MEArrayAccessExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MEArrayAccessExpressionMixin { override val leftBracketToken get() = findNotNullChildByType(MEExpressionTypes.TOKEN_LEFT_BRACKET) override val rightBracketToken get() = findChildByType(MEExpressionTypes.TOKEN_RIGHT_BRACKET) + + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiArrayAccessExpression) { + return false + } + + val readMatch = MEPsiUtil.isAccessedForReading(this) && PsiUtil.isAccessedForReading(java) + val writeMatch = MEPsiUtil.isAccessedForWriting(this) && PsiUtil.isAccessedForWriting(java) + if (!readMatch && !writeMatch) { + return false + } + + val javaArray = java.arrayExpression + val javaIndex = java.indexExpression ?: return false + return arrayExpr.matchesJava(javaArray, context) && indexExpr?.matchesJava(javaIndex, context) == true + } + + protected abstract val arrayExpr: MEExpression + protected abstract val indexExpr: MEExpression? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt index a8afb21dc..0e9d5fec3 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt @@ -20,11 +20,36 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEStatementImpl -import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEAssignStatementMixin import com.intellij.lang.ASTNode +import com.intellij.psi.JavaTokenType +import com.intellij.psi.PsiAssignmentExpression +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiUtil +import com.siyeh.ig.PsiReplacementUtil -abstract class MEAssignStatementImplMixin(node: ASTNode) : MEStatementImpl(node), MEAssignStatementMixin { - override val targetExpr get() = firstChild as MEExpression +abstract class MEAssignStatementImplMixin(node: ASTNode) : MEStatementImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiAssignmentExpression) { + return false + } + val isOperatorAssignment = java.operationTokenType != JavaTokenType.EQ + val expandedJava = if (isOperatorAssignment) { + PsiReplacementUtil.replaceOperatorAssignmentWithAssignmentExpression(java.copy() as PsiAssignmentExpression) + as PsiAssignmentExpression + } else { + java + } + + val leftJava = PsiUtil.skipParenthesizedExprDown(expandedJava.lExpression) ?: return false + val rightJava = PsiUtil.skipParenthesizedExprDown(expandedJava.rExpression) ?: return false + context.fakeElementScope(isOperatorAssignment, java) { + return targetExpr.matchesJava(leftJava, context) && rightExpr?.matchesJava(rightJava, context) == true + } + } + + protected abstract val targetExpr: MEExpression + protected abstract val rightExpr: MEExpression? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt index 21d60c8b2..6490324be 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt @@ -20,13 +20,20 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEBinaryExpressionMixin import com.intellij.lang.ASTNode +import com.intellij.psi.JavaTokenType +import com.intellij.psi.PsiBinaryExpression +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiInstanceOfExpression +import com.intellij.psi.PsiTypeTestPattern import com.intellij.psi.tree.TokenSet +import com.intellij.psi.util.JavaPsiPatternUtil abstract class MEBinaryExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MEBinaryExpressionMixin { override val operator get() = node.findChildByType(operatorTokens)!!.elementType @@ -34,6 +41,55 @@ abstract class MEBinaryExpressionImplMixin(node: ASTNode) : MEExpressionImpl(nod ?.takeIf { operator == MEExpressionTypes.TOKEN_INSTANCEOF } ?.let(METypeUtil::convertExpressionToType) + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (operator == MEExpressionTypes.TOKEN_INSTANCEOF) { + if (java !is PsiInstanceOfExpression) { + return false + } + if (!leftExpr.matchesJava(java.operand, context)) { + return false + } + val javaType = java.checkType?.type + ?: (JavaPsiPatternUtil.skipParenthesizedPatternDown(java.pattern) as? PsiTypeTestPattern) + ?.checkType?.type + ?: return false + return castType?.matchesJava(javaType, context) == true + } else { + if (java !is PsiBinaryExpression) { + return false + } + + val operatorMatches = when (java.operationTokenType) { + JavaTokenType.ASTERISK -> operator == MEExpressionTypes.TOKEN_MULT + JavaTokenType.DIV -> operator == MEExpressionTypes.TOKEN_DIV + JavaTokenType.PERC -> operator == MEExpressionTypes.TOKEN_MOD + JavaTokenType.PLUS -> operator == MEExpressionTypes.TOKEN_PLUS + JavaTokenType.MINUS -> operator == MEExpressionTypes.TOKEN_MINUS + JavaTokenType.LTLT -> operator == MEExpressionTypes.TOKEN_SHL + JavaTokenType.GTGT -> operator == MEExpressionTypes.TOKEN_SHR + JavaTokenType.GTGTGT -> operator == MEExpressionTypes.TOKEN_USHR + JavaTokenType.LT -> operator == MEExpressionTypes.TOKEN_LT + JavaTokenType.LE -> operator == MEExpressionTypes.TOKEN_LE + JavaTokenType.GT -> operator == MEExpressionTypes.TOKEN_GT + JavaTokenType.GE -> operator == MEExpressionTypes.TOKEN_GE + JavaTokenType.EQEQ -> operator == MEExpressionTypes.TOKEN_EQ + JavaTokenType.NE -> operator == MEExpressionTypes.TOKEN_NE + JavaTokenType.AND -> operator == MEExpressionTypes.TOKEN_BITWISE_AND + JavaTokenType.XOR -> operator == MEExpressionTypes.TOKEN_BITWISE_XOR + JavaTokenType.OR -> operator == MEExpressionTypes.TOKEN_BITWISE_OR + else -> false + } + if (!operatorMatches) { + return false + } + + val javaLeft = java.lOperand + val javaRight = java.rOperand ?: return false + return leftExpr.matchesJava(javaLeft, context) && rightExpr?.matchesJava(javaRight, context) == true + } + } + + protected abstract val leftExpr: MEExpression protected abstract val rightExpr: MEExpression? companion object { diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECapturingExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECapturingExpressionImplMixin.kt new file mode 100644 index 000000000..4fefc8ad3 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECapturingExpressionImplMixin.kt @@ -0,0 +1,36 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement + +abstract class MECapturingExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + context.addCapture(java) + return expression?.matchesJava(java, context) == true + } + + protected abstract val expression: MEExpression? +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt index ba977f357..1207e94f3 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt @@ -20,12 +20,19 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEParenthesizedExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MECastExpressionMixin +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEPsiUtil import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiInstanceOfExpression +import com.intellij.psi.PsiTypeCastExpression +import com.intellij.psi.PsiTypeTestPattern +import com.intellij.psi.util.JavaPsiPatternUtil abstract class MECastExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MECastExpressionMixin { override val castType get() = castTypeExpr?.let(METypeUtil::convertExpressionToType) @@ -33,5 +40,24 @@ abstract class MECastExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) (expressionList.let { it.getOrNull(it.size - 2) } as? MEParenthesizedExpression)?.expression override val castedExpr get() = expressionList.lastOrNull() + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + return when (java) { + is PsiTypeCastExpression -> { + val javaType = java.castType?.type ?: return false + val javaOperand = java.operand ?: return false + castType?.matchesJava(javaType, context) == true && + castedExpr?.matchesJava(javaOperand, context) == true + } + is PsiInstanceOfExpression -> { + val pattern = JavaPsiPatternUtil.skipParenthesizedPatternDown(java.pattern) as? PsiTypeTestPattern + ?: return false + val javaType = pattern.checkType?.type ?: return false + val castedExpr = this.castedExpr ?: return false + return MEPsiUtil.isWildcardExpression(castedExpr) && castType?.matchesJava(javaType, context) == true + } + else -> false + } + } + protected abstract val expressionList: List } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEClassConstantExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEClassConstantExpressionImplMixin.kt new file mode 100644 index 000000000..5cf7bf337 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEClassConstantExpressionImplMixin.kt @@ -0,0 +1,57 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiClassObjectAccessExpression +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiField +import com.intellij.psi.PsiReferenceExpression +import com.intellij.psi.util.PsiTypesUtil + +abstract class MEClassConstantExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { + + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + return when (java) { + is PsiClassObjectAccessExpression -> type.matchesJava(java.operand.type, context) + is PsiReferenceExpression -> { + if (java.referenceName != "TYPE") { + return false + } + val field = java.resolve() as? PsiField ?: return false + val containingClass = field.containingClass?.qualifiedName ?: return false + val unboxedType = PsiTypesUtil.unboxIfPossible(containingClass) + if (unboxedType == null || unboxedType == containingClass) { + return false + } + val javaType = JavaPsiFacade.getElementFactory(context.project).createPrimitiveTypeFromText(unboxedType) + type.matchesJava(javaType, context) + } + else -> false + } + } + + protected abstract val type: METype +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionImplMixin.kt new file mode 100644 index 000000000..eb641a520 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionImplMixin.kt @@ -0,0 +1,33 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.psi.MEMatchableElement +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement + +abstract class MEExpressionImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MEMatchableElement { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + throw UnsupportedOperationException("Please implement matchesJava for your expression type") + } +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionStatementImplMixin.kt new file mode 100644 index 000000000..f19b58369 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionStatementImplMixin.kt @@ -0,0 +1,35 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEStatementImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement + +abstract class MEExpressionStatementImplMixin(node: ASTNode) : MEStatementImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + return expression.matchesJava(java, context) + } + + protected abstract val expression: MEExpression +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt new file mode 100644 index 000000000..d1448e3e9 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt @@ -0,0 +1,51 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArguments +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNewExpression + +abstract class MEInstantiationExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiNewExpression) { + return false + } + + if (java.isArrayCreation) { + return false + } + + val javaType = java.type ?: return false + val javaArgs = java.argumentList ?: return false + + return context.project.meExpressionElementFactory.createType(type).matchesJava(javaType, context) && + arguments?.matchesJava(javaArgs, context) == true + } + + protected abstract val type: MEName + protected abstract val arguments: MEArguments? +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt index 48f3991dc..3998d2f11 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt @@ -20,10 +20,16 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MELitExpressionMixin import com.intellij.lang.ASTNode +import com.intellij.psi.JavaTokenType +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiLiteral +import com.intellij.psi.PsiUnaryExpression +import com.intellij.psi.util.PsiUtil import com.intellij.util.IncorrectOperationException abstract class MELitExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MELitExpressionMixin { @@ -69,4 +75,35 @@ abstract class MELitExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), override val isString get() = node.firstChildNode.elementType == MEExpressionTypes.TOKEN_STRING_TERMINATOR override val minusToken get() = node.firstChildNode.takeIf { it.elementType == MEExpressionTypes.TOKEN_MINUS } + + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + return when (java) { + is PsiLiteral -> { + val value = this.value + val javaValue = java.value + // MixinExtras compares floats as strings + when (value) { + is Double -> javaValue is Double && value.toString() == javaValue.toString() + else -> value == javaValue + } + } + is PsiUnaryExpression -> { + if (java.operationSign != JavaTokenType.MINUS) { + return false + } + val javaOperand = PsiUtil.skipParenthesizedExprDown(java.operand) ?: return false + if (javaOperand !is PsiLiteral) { + return false + } + val value = this.value + val javaValue = javaOperand.value + when (value) { + is Long -> javaValue == -value + is Double -> javaValue is Double && javaValue.toString() == (-value).toString() + else -> false + } + } + else -> false + } + } } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt new file mode 100644 index 000000000..e8c22d54a --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt @@ -0,0 +1,59 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiField +import com.intellij.psi.PsiModifier +import com.intellij.psi.PsiReferenceExpression +import com.intellij.psi.util.PsiUtil + +abstract class MEMemberAccessExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiReferenceExpression) { + return false + } + + val resolved = java.resolve() as? PsiField ?: return false + if (resolved.hasModifierProperty(PsiModifier.STATIC)) { + return false + } + + val javaReceiver = PsiUtil.skipParenthesizedExprDown(java.qualifierExpression) + ?: JavaPsiFacade.getElementFactory(context.project).createExpressionFromText("this", null) + context.fakeElementScope(java.qualifierExpression == null, java) { + if (!receiverExpr.matchesJava(javaReceiver, context)) { + return false + } + } + + return memberName.matchesJavaExpr(java, context) + } + + protected abstract val receiverExpr: MEExpression + protected abstract val memberName: MEName +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt new file mode 100644 index 000000000..0ef980012 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt @@ -0,0 +1,69 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArguments +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethodCallExpression +import com.intellij.psi.PsiModifier +import com.intellij.psi.util.PsiUtil +import com.siyeh.ig.psiutils.MethodCallUtils + +abstract class MEMethodCallExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiMethodCallExpression) { + return false + } + + if (MethodCallUtils.hasSuperQualifier(java)) { + return false + } + + if (!memberName.matchesJavaExpr(java, context)) { + return false + } + + val method = java.resolveMethod() ?: return false + if (method.hasModifierProperty(PsiModifier.STATIC)) { + return false + } + + val javaReceiver = PsiUtil.skipParenthesizedExprDown(java.methodExpression.qualifierExpression) + ?: JavaPsiFacade.getElementFactory(context.project).createExpressionFromText("this", null) + context.fakeElementScope(java.methodExpression.qualifierExpression == null, java.methodExpression) { + if (!receiverExpr.matchesJava(javaReceiver, context)) { + return false + } + } + + return arguments?.matchesJava(java.argumentList, context) == true + } + + protected abstract val receiverExpr: MEExpression + protected abstract val memberName: MEName + protected abstract val arguments: MEArguments? +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt new file mode 100644 index 000000000..0c66dd00b --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt @@ -0,0 +1,47 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiField +import com.intellij.psi.PsiModifier +import com.intellij.psi.PsiReferenceExpression + +abstract class MENameExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiReferenceExpression) { + return false + } + val resolved = java.resolve() ?: return false + if (resolved is PsiField && !resolved.hasModifierProperty(PsiModifier.STATIC)) { + return false + } + + return MEName.matchesJavaExpr(java, context) + } + + @Suppress("PropertyName") + protected abstract val MEName: MEName +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt index 23f446cc3..255a2175e 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt @@ -20,11 +20,56 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameMixin +import com.demonwav.mcdev.platform.mixin.util.LocalVariables import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiField +import com.intellij.psi.PsiReferenceExpression +import com.intellij.psi.PsiVariable +import com.intellij.psi.util.PsiUtil abstract class MENameImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MENameMixin { override val isWildcard get() = node.elementType == MEExpressionTypes.TOKEN_WILDCARD + + override fun matchesJavaExpr(javaExpr: PsiElement, context: MESourceMatchContext): Boolean { + if (isWildcard) { + return true + } + + // match against elements targeted with @At + val name = text + if (javaExpr in context.getTargetedElements(name)) { + return true + } + + // match against local variables + if (javaExpr !is PsiReferenceExpression) { + return false + } + val variable = javaExpr.resolve() as? PsiVariable ?: return false + if (variable is PsiField) { + return false + } + + val sourceArgs by lazy { + LocalVariables.guessLocalsAt(javaExpr, true, !PsiUtil.isAccessedForWriting(javaExpr)) + } + val sourceVariables by lazy { + LocalVariables.guessLocalsAt(javaExpr, false, !PsiUtil.isAccessedForWriting(javaExpr)) + } + for (localInfo in context.getLocalInfos(name)) { + val sourceLocals = if (localInfo.argsOnly) sourceArgs else sourceVariables + for (local in localInfo.matchSourceLocals(sourceLocals)) { + if (local.variable == variable) { + return true + } + } + } + + return false + } } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt index df32ece01..b45ba7828 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt @@ -20,12 +20,19 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArguments import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENewArrayExpressionMixin import com.intellij.lang.ASTNode +import com.intellij.psi.PsiArrayType import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNewExpression +import com.intellij.psi.util.PsiUtil import com.intellij.psi.util.siblings abstract class MENewArrayExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MENewArrayExpressionMixin { @@ -58,4 +65,50 @@ abstract class MENewArrayExpressionImplMixin(node: ASTNode) : MEExpressionImpl(n return result } + + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiNewExpression) { + return false + } + + if (!java.isArrayCreation) { + return false + } + + val javaArrayType = java.type as? PsiArrayType ?: return false + if (javaArrayType.arrayDimensions != dimensions) { + return false + } + + val matchesType = context.project.meExpressionElementFactory.createType(elementType) + .matchesJava(javaArrayType.deepComponentType, context) + if (!matchesType) { + return false + } + + val javaArrayDims = java.arrayDimensions + val arrayDims = dimExprs + if (javaArrayDims.size != arrayDims.size) { + return false + } + if (!javaArrayDims.asSequence().zip(arrayDims.asSequence()).all { (javaArrayDim, arrayDim) -> + val actualJavaDim = PsiUtil.skipParenthesizedExprDown(javaArrayDim) ?: return@all false + arrayDim.matchesJava(actualJavaDim, context) + } + ) { + return false + } + + val javaArrayInitializer = java.arrayInitializer + val arrayInitializer = this.arrayInitializer + return if (javaArrayInitializer == null) { + arrayInitializer == null + } else { + arrayInitializer?.matchesJava(javaArrayInitializer.initializers, context) == true + } + } + + protected abstract val elementType: MEName + protected abstract val dimExprs: List + protected abstract val arrayInitializer: MEArguments? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEParenthesizedExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEParenthesizedExpressionImplMixin.kt new file mode 100644 index 000000000..0bc457722 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEParenthesizedExpressionImplMixin.kt @@ -0,0 +1,35 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement + +abstract class MEParenthesizedExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + return expression?.matchesJava(java, context) == true + } + + protected abstract val expression: MEExpression? +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEReturnStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEReturnStatementImplMixin.kt new file mode 100644 index 000000000..4de931e27 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEReturnStatementImplMixin.kt @@ -0,0 +1,41 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEStatementImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReturnStatement +import com.intellij.psi.util.PsiUtil + +abstract class MEReturnStatementImplMixin(node: ASTNode) : MEStatementImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiReturnStatement) { + return false + } + val javaReturnValue = PsiUtil.skipParenthesizedExprDown(java.returnValue) ?: return false + return valueExpr?.matchesJava(javaReturnValue, context) == true + } + + protected abstract val valueExpr: MEExpression? +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt new file mode 100644 index 000000000..1a0cd50a2 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt @@ -0,0 +1,52 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArguments +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethodCallExpression +import com.intellij.psi.PsiModifier + +abstract class MEStaticMethodCallExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiMethodCallExpression) { + return false + } + + if (!memberName.matchesJavaExpr(java, context)) { + return false + } + + val method = java.resolveMethod() ?: return false + if (!method.hasModifierProperty(PsiModifier.STATIC)) { + return false + } + + return arguments?.matchesJava(java.argumentList, context) == true + } + + protected abstract val memberName: MEName + protected abstract val arguments: MEArguments? +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt new file mode 100644 index 000000000..1d5770ce6 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt @@ -0,0 +1,50 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArguments +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethodCallExpression +import com.siyeh.ig.psiutils.MethodCallUtils + +abstract class MESuperCallExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiMethodCallExpression) { + return false + } + if (!MethodCallUtils.hasSuperQualifier(java)) { + return false + } + + if (memberName?.matchesJavaExpr(java, context) != true) { + return false + } + + return arguments?.matchesJava(java.argumentList, context) == true + } + + protected abstract val memberName: MEName? + protected abstract val arguments: MEArguments? +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhisExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhisExpressionImplMixin.kt new file mode 100644 index 000000000..2e979ce38 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhisExpressionImplMixin.kt @@ -0,0 +1,33 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiThisExpression + +abstract class METhisExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + return java is PsiThisExpression && java.qualifier == null + } +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt new file mode 100644 index 000000000..e5bccde10 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt @@ -0,0 +1,41 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEStatementImpl +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiThrowStatement + +abstract class METhrowStatementImplMixin(node: ASTNode) : MEStatementImpl(node) { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiThrowStatement) { + return false + } + + val javaException = java.exception ?: return false + return valueExpr?.matchesJava(javaException, context) == true + } + + protected abstract val valueExpr: MEExpression? +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METypeImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METypeImplMixin.kt index 85fe1546a..f41550915 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METypeImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METypeImplMixin.kt @@ -20,13 +20,34 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.METypeMixin +import com.demonwav.mcdev.util.descriptor import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode +import com.intellij.psi.PsiArrayType import com.intellij.psi.PsiElement +import com.intellij.psi.PsiType abstract class METypeImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), METypeMixin { override val isArray get() = findChildByType(MEExpressionTypes.TOKEN_LEFT_BRACKET) != null override val dimensions get() = findChildrenByType(MEExpressionTypes.TOKEN_LEFT_BRACKET).size + + override fun matchesJava(java: PsiType, context: MESourceMatchContext): Boolean { + if (MEName.isWildcard) { + return java.arrayDimensions >= dimensions + } else { + var unwrappedElementType = java + repeat(dimensions) { + unwrappedElementType = (unwrappedElementType as? PsiArrayType)?.componentType ?: return false + } + val descriptor = unwrappedElementType.descriptor + return context.getTypes(MEName.text).any { it == descriptor } + } + } + + @Suppress("PropertyName") + protected abstract val MEName: MEName } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt index 82223c3a4..3c8533c2b 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt @@ -20,10 +20,44 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEUnaryExpressionMixin import com.intellij.lang.ASTNode +import com.intellij.psi.JavaTokenType +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiLiteral +import com.intellij.psi.PsiUnaryExpression +import com.intellij.psi.util.PsiUtil abstract class MEUnaryExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MEUnaryExpressionMixin { override val operator get() = node.firstChildNode.elementType + + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiUnaryExpression) { + return false + } + + val operatorMatches = when (java.operationTokenType) { + JavaTokenType.MINUS -> operator == MEExpressionTypes.TOKEN_MINUS + JavaTokenType.TILDE -> operator == MEExpressionTypes.TOKEN_BITWISE_NOT + else -> false + } + if (!operatorMatches) { + return false + } + + val javaOperand = PsiUtil.skipParenthesizedExprDown(java.operand) ?: return false + + if (operator == MEExpressionTypes.TOKEN_MINUS && javaOperand is PsiLiteral) { + // avoid matching "-1" etc + return false + } + + return expression?.matchesJava(javaOperand, context) == true + } + + protected abstract val expression: MEExpression? } diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt index 15b2ab3e7..2f4a5596b 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt @@ -209,6 +209,7 @@ class AtResolver( val targetPsiClass = targetElement.parentOfType() ?: return emptyList() val navigationVisitor = injectionPoint.createNavigationVisitor(at, target, targetPsiClass) ?: return emptyList() + navigationVisitor.configureBytecodeTarget(targetClass, targetMethod) targetElement.accept(navigationVisitor) return bytecodeResults.mapNotNull { bytecodeResult -> diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt index 8939f36ae..a4d5ccb19 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt @@ -321,6 +321,9 @@ abstract class NavigationVisitor : JavaRecursiveElementVisitor() { result += element } + open fun configureBytecodeTarget(classNode: ClassNode, methodNode: MethodNode) { + } + open fun visitStart(executableElement: PsiElement) { } diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt index 4e7378045..7ad828179 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt @@ -28,7 +28,6 @@ import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.MODIFY_ import com.demonwav.mcdev.util.constantValue import com.demonwav.mcdev.util.findContainingMethod import com.demonwav.mcdev.util.findModule -import com.demonwav.mcdev.util.isErasureEquivalentTo import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.openapi.module.Module import com.intellij.psi.JavaPsiFacade @@ -166,13 +165,13 @@ abstract class AbstractLoadInjectionPoint(private val store: Boolean) : Injectio val parentExpr = PsiUtil.skipParenthesizedExprUp(expression.parent) val isIincUnary = parentExpr is PsiUnaryExpression && ( - parentExpr.operationSign.tokenType == JavaTokenType.PLUSPLUS || - parentExpr.operationSign.tokenType == JavaTokenType.MINUSMINUS + parentExpr.operationTokenType == JavaTokenType.PLUSPLUS || + parentExpr.operationTokenType == JavaTokenType.MINUSMINUS ) val isIincAssignment = parentExpr is PsiAssignmentExpression && ( - parentExpr.operationSign.tokenType == JavaTokenType.PLUSEQ || - parentExpr.operationSign.tokenType == JavaTokenType.MINUSEQ + parentExpr.operationTokenType == JavaTokenType.PLUSEQ || + parentExpr.operationTokenType == JavaTokenType.MINUSEQ ) && PsiUtil.isConstantExpression(parentExpr.rExpression) && (parentExpr.rExpression?.constantValue as? Number)?.toInt() @@ -239,42 +238,10 @@ abstract class AbstractLoadInjectionPoint(private val store: Boolean) : Injectio name: String, localsHere: List, ) { - if (info.ordinal != null) { - val local = localsHere.asSequence().filter { - it.type.isErasureEquivalentTo(info.type) - }.drop(info.ordinal).firstOrNull() - if (name == local?.name) { + for (local in info.matchSourceLocals(localsHere)) { + if (name == local.name) { addResult(location) } - return - } - - if (info.index != null) { - val local = localsHere.getOrNull(info.index) - if (name == local?.name) { - addResult(location) - } - return - } - - if (info.names.isNotEmpty()) { - val matchingLocals = localsHere.filter { - info.names.contains(it.mixinName) - } - for (local in matchingLocals) { - if (local.name == name) { - addResult(location) - } - } - return - } - - // implicit mode - val local = localsHere.singleOrNull { - it.type.isErasureEquivalentTo(info.type) - } - if (local != null && local.name == name) { - addResult(location) } } } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index deee994a2..5e72d91e9 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -20,7 +20,9 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver @@ -71,6 +73,7 @@ import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.VarInsnNode private typealias IdentifierPoolFactory = (MethodNode) -> IdentifierPool +private typealias SourceMatchContextFactory = (ClassNode, MethodNode) -> MESourceMatchContext class ExpressionInjectionPoint : InjectionPoint() { override fun onCompleted(editor: Editor, reference: PsiLiteral) { @@ -104,7 +107,57 @@ class ExpressionInjectionPoint : InjectionPoint() { target: MixinSelector?, targetClass: PsiClass ): NavigationVisitor? { - return null + val project = at.project + + val atId = at.findDeclaredAttributeValue("id")?.constantStringValue ?: "" + + val injectorAnnotation = AtResolver.findInjectorAnnotation(at) ?: return null + val modifierList = injectorAnnotation.parent as? PsiModifierList ?: return null + val parsedExprs = parseExpressions(project, modifierList, atId) + parsedExprs.ifEmpty { return null } + + val sourceMatchContextFactory = createSourceMatchContextFactory(project, modifierList) + + return MyNavigationVisitor(parsedExprs.map { it.second }, sourceMatchContextFactory) + } + + private fun createSourceMatchContextFactory( + project: Project, + modifierList: PsiModifierList + ): SourceMatchContextFactory = { targetClass, targetMethod -> + val matchContext = MESourceMatchContext(project) + + for (annotation in modifierList.annotations) { + if (!annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { + continue + } + + val definitionId = annotation.findDeclaredAttributeValue("id")?.constantStringValue ?: "" + + val ats = annotation.findDeclaredAttributeValue("at")?.findAnnotations() ?: emptyList() + for (at in ats) { + val matches = RecursionManager.doPreventingRecursion(at, true) { + AtResolver(at, targetClass, targetMethod).resolveNavigationTargets() + } ?: continue + for (target in matches) { + matchContext.addTargetedElement(definitionId, target) + } + } + + val types = annotation.findDeclaredAttributeValue("type")?.resolveTypeArray() ?: emptyList() + for (type in types) { + matchContext.addType(definitionId, type.descriptor) + } + + val locals = annotation.findDeclaredAttributeValue("local")?.findAnnotations() ?: emptyList() + for (localAnnotation in locals) { + val localType = annotation.findDeclaredAttributeValue("type")?.resolveType() + val localInfo = LocalInfo.fromAnnotation(localType, localAnnotation) + matchContext.addLocalInfo(definitionId, localInfo) + } + } + + matchContext } override fun doCreateCollectVisitor( @@ -133,7 +186,7 @@ class ExpressionInjectionPoint : InjectionPoint() { project: Project, modifierList: PsiModifierList, atId: String - ): List> { + ): List> { return modifierList.annotations.asSequence() .filter { exprAnnotation -> exprAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION) && @@ -141,7 +194,7 @@ class ExpressionInjectionPoint : InjectionPoint() { } .flatMap { exprAnnotation -> val expressionElements = exprAnnotation.findDeclaredAttributeValue("value")?.parseArray { it } - ?: return@flatMap emptySequence>() + ?: return@flatMap emptySequence>() expressionElements.asSequence().mapNotNull { expressionElement -> val text = expressionElement.constantStringValue ?: return@mapNotNull null val rootStatementPsi = InjectedLanguageManager.getInstance(project) @@ -291,4 +344,32 @@ class ExpressionInjectionPoint : InjectionPoint() { } } } + + private class MyNavigationVisitor( + private val statements: List, + private val matchContextFactory: SourceMatchContextFactory + ) : NavigationVisitor() { + private lateinit var matchContext: MESourceMatchContext + + override fun configureBytecodeTarget(classNode: ClassNode, methodNode: MethodNode) { + matchContext = matchContextFactory(classNode, methodNode) + } + + override fun visitElement(element: PsiElement) { + for (statement in statements) { + if (statement.matchesJava(element, matchContext)) { + if (matchContext.captures.isNotEmpty()) { + for (capture in matchContext.captures) { + addResult(capture) + } + } else { + addResult(element) + } + } + matchContext.reset() + } + + super.visitElement(element) + } + } } diff --git a/src/main/kotlin/platform/mixin/inspection/suppress/MixinClassCastInspectionSuppressor.kt b/src/main/kotlin/platform/mixin/inspection/suppress/MixinClassCastInspectionSuppressor.kt index 720a4084a..79ea61bfb 100644 --- a/src/main/kotlin/platform/mixin/inspection/suppress/MixinClassCastInspectionSuppressor.kt +++ b/src/main/kotlin/platform/mixin/inspection/suppress/MixinClassCastInspectionSuppressor.kt @@ -38,6 +38,7 @@ import com.intellij.psi.PsiInstanceOfExpression import com.intellij.psi.PsiType import com.intellij.psi.PsiTypeCastExpression import com.intellij.psi.PsiTypeTestPattern +import com.intellij.psi.util.JavaPsiPatternUtil import com.intellij.psi.util.PsiUtil /** @@ -54,7 +55,8 @@ class MixinClassCastInspectionSuppressor : InspectionSuppressor { // check instanceof if (element is PsiInstanceOfExpression) { val castType = element.checkType?.type - ?: (element.pattern as? PsiTypeTestPattern)?.checkType?.type + ?: (JavaPsiPatternUtil.skipParenthesizedPatternDown(element.pattern) as? PsiTypeTestPattern) + ?.checkType?.type ?: return false var operand = PsiUtil.skipParenthesizedExprDown(element.operand) ?: return false while (operand is PsiTypeCastExpression) { diff --git a/src/main/kotlin/platform/mixin/util/LocalInfo.kt b/src/main/kotlin/platform/mixin/util/LocalInfo.kt index 710e7834e..fc684799d 100644 --- a/src/main/kotlin/platform/mixin/util/LocalInfo.kt +++ b/src/main/kotlin/platform/mixin/util/LocalInfo.kt @@ -24,9 +24,11 @@ import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.util.computeStringArray import com.demonwav.mcdev.util.constantValue import com.demonwav.mcdev.util.descriptor +import com.demonwav.mcdev.util.isErasureEquivalentTo import com.intellij.openapi.module.Module import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType +import com.intellij.util.containers.sequenceOfNotNull import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode @@ -128,6 +130,27 @@ class LocalInfo( } } + fun matchSourceLocals( + sourceLocals: List + ): Sequence { + if (ordinal != null) { + return sequenceOfNotNull( + sourceLocals.asSequence().filter { it.type.isErasureEquivalentTo(type) }.drop(ordinal).firstOrNull() + ) + } + if (index != null) { + return sequenceOfNotNull(sourceLocals.getOrNull(index)) + } + if (names.isNotEmpty()) { + return sourceLocals.asSequence().filter { it.mixinName in names } + } + + // implicit mode + return sequenceOfNotNull( + sourceLocals.singleOrNull { it.type.isErasureEquivalentTo(type) } + ) + } + companion object { /** * Gets a [LocalInfo] from an annotation which declares the following attributes: diff --git a/src/main/kotlin/platform/mixin/util/LocalVariables.kt b/src/main/kotlin/platform/mixin/util/LocalVariables.kt index 59ee23686..d153ebd5c 100644 --- a/src/main/kotlin/platform/mixin/util/LocalVariables.kt +++ b/src/main/kotlin/platform/mixin/util/LocalVariables.kt @@ -119,7 +119,13 @@ object LocalVariables { for (parameter in method.parameterList.parameters) { val mixinName = if (argsOnly) "var$argsIndex" else parameter.name - args += SourceLocalVariable(parameter.name, parameter.type, argsIndex, mixinName = mixinName) + args += SourceLocalVariable( + parameter.name, + parameter.type, + argsIndex, + mixinName = mixinName, + variable = parameter + ) argsIndex++ if (parameter.isDoubleSlot) { argsIndex++ @@ -207,7 +213,12 @@ object LocalVariables { localsHere = localsHere.copyOf(localIndex + 1) } val name = instruction.variable.name ?: return - localsHere[localIndex] = SourceLocalVariable(name, instruction.variable.type, localIndex) + localsHere[localIndex] = SourceLocalVariable( + name, + instruction.variable.type, + localIndex, + variable = instruction.variable + ) if (instruction.variable.isDoubleSlot && localIndex + 1 < localsHere.size) { localsHere[localIndex + 1] = null } @@ -855,6 +866,7 @@ object LocalVariables { val type: PsiType, val index: Int, val mixinName: String = name, + val variable: PsiVariable? = null, val implicitLoadCountBefore: Int = 0, val implicitLoadCountAfter: Int = 0, val implicitStoreCountBefore: Int = 0, From 36c7ff9cc8ba00b6c631c78eb1442f5d64eab386 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 6 Feb 2024 20:14:53 +0000 Subject: [PATCH 014/100] Fix name expression source matching --- .../psi/mixins/impl/MENameExpressionImplMixin.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt index 0c66dd00b..f21499655 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt @@ -25,20 +25,9 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement -import com.intellij.psi.PsiField -import com.intellij.psi.PsiModifier -import com.intellij.psi.PsiReferenceExpression abstract class MENameExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { - if (java !is PsiReferenceExpression) { - return false - } - val resolved = java.resolve() ?: return false - if (resolved is PsiField && !resolved.hasModifierProperty(PsiModifier.STATIC)) { - return false - } - return MEName.matchesJavaExpr(java, context) } From 0ff7c7490a25e8f8360f43d527134a9e09c798df Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 6 Feb 2024 20:20:27 +0000 Subject: [PATCH 015/100] Fix MEName.isWildcard --- .../mixin/expression/psi/mixins/impl/MENameImplMixin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt index 255a2175e..b3fbffced 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt @@ -33,7 +33,7 @@ import com.intellij.psi.PsiVariable import com.intellij.psi.util.PsiUtil abstract class MENameImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MENameMixin { - override val isWildcard get() = node.elementType == MEExpressionTypes.TOKEN_WILDCARD + override val isWildcard get() = node.firstChildNode.elementType == MEExpressionTypes.TOKEN_WILDCARD override fun matchesJavaExpr(javaExpr: PsiElement, context: MESourceMatchContext): Boolean { if (isWildcard) { From d0312b473f3deb7bb59ecc9393584bc2e25c6170 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 6 Feb 2024 20:37:28 +0000 Subject: [PATCH 016/100] Fix MELitExpression source matching --- .../psi/mixins/impl/MELitExpressionImplMixin.kt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt index 3998d2f11..91e9aaeab 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt @@ -80,10 +80,15 @@ abstract class MELitExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), return when (java) { is PsiLiteral -> { val value = this.value - val javaValue = java.value + val javaValue = java.value.widened // MixinExtras compares floats as strings when (value) { is Double -> javaValue is Double && value.toString() == javaValue.toString() + is String -> { + val matchesChar = + value.length == 1 && javaValue is Long && value.firstOrNull()?.code?.toLong() == javaValue + matchesChar || value == javaValue + } else -> value == javaValue } } @@ -96,7 +101,7 @@ abstract class MELitExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), return false } val value = this.value - val javaValue = javaOperand.value + val javaValue = javaOperand.value.widened when (value) { is Long -> javaValue == -value is Double -> javaValue is Double && javaValue.toString() == (-value).toString() @@ -106,4 +111,11 @@ abstract class MELitExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), else -> false } } + + private val Any?.widened: Any? get() = when (this) { + is Int -> toLong() + is Float -> toDouble() + is Char -> code.toLong() + else -> this + } } From ff6cdbc9c3381e1f73f16bf5d32a4bb9918c24b3 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 6 Feb 2024 20:38:42 +0000 Subject: [PATCH 017/100] operationSign - operationTokenType --- .../expression/psi/mixins/impl/MELitExpressionImplMixin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt index 91e9aaeab..30523d0ba 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt @@ -93,7 +93,7 @@ abstract class MELitExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), } } is PsiUnaryExpression -> { - if (java.operationSign != JavaTokenType.MINUS) { + if (java.operationTokenType != JavaTokenType.MINUS) { return false } val javaOperand = PsiUtil.skipParenthesizedExprDown(java.operand) ?: return false From cfe911ababe388ee44453caa75f36e997d06d00a Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 7 Feb 2024 00:00:10 +0000 Subject: [PATCH 018/100] Add built-in definitions --- .../platform/mixin/expression/MESourceMatchContext.kt | 10 ++++++++++ .../mixins/impl/MEMemberAccessExpressionImplMixin.kt | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt b/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt index 82b7de47e..857f87b50 100644 --- a/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt +++ b/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt @@ -34,6 +34,16 @@ class MESourceMatchContext(val project: Project) { private val targetedElements = mutableMapOf>() private val localInfos = mutableMapOf>() + init { + addType("byte", "B") + addType("char", "C") + addType("double", "D") + addType("float", "F") + addType("int", "I") + addType("long", "J") + addType("short", "S") + } + fun addCapture(capturedElement: PsiElement) { val element = realElement ?: capturedElement capturesInternal += element diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt index e8c22d54a..16cc07763 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt @@ -31,6 +31,7 @@ import com.intellij.psi.PsiField import com.intellij.psi.PsiModifier import com.intellij.psi.PsiReferenceExpression import com.intellij.psi.util.PsiUtil +import com.siyeh.ig.psiutils.ExpressionUtils abstract class MEMemberAccessExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { @@ -38,6 +39,13 @@ abstract class MEMemberAccessExpressionImplMixin(node: ASTNode) : MEExpressionIm return false } + val arrayFromLength = ExpressionUtils.getArrayFromLengthExpression(java) + if (arrayFromLength != null) { + if (memberName.isWildcard || memberName.text == "length") { + return true + } + } + val resolved = java.resolve() as? PsiField ?: return false if (resolved.hasModifierProperty(PsiModifier.STATIC)) { return false From 226fdaac50ae37693b282920cf7020e78c1bdf5e Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 7 Feb 2024 21:40:07 +0000 Subject: [PATCH 019/100] Update MixinExtras --- build.gradle.kts | 2 +- .../mixinextras/ExpressionInjectionPoint.kt | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 8fb664662..8145738b5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:6440263") + implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:fe1e2b9") implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index 5e72d91e9..21f2a1a49 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -56,7 +56,7 @@ import com.intellij.psi.PsiLiteral import com.intellij.psi.PsiModifierList import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager -import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.parentOfType import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter @@ -296,11 +296,11 @@ class ExpressionInjectionPoint : InjectionPoint() { insns.iterator().forEachRemaining { insn -> val genericDecorations = IdentityHashMap>() val injectorSpecificDecorations = IdentityHashMap>() - val captured = mutableListOf() + val captured = mutableListOf>() val sink = object : Expression.OutputSink { - override fun capture(node: FlowValue) { - captured += node.insn + override fun capture(node: FlowValue, expr: Expression?) { + captured += node.insn to (expr?.src?.startIndex ?: 0) } override fun decorate(insn: AbstractInsnNode, key: String, value: Any?) { @@ -315,12 +315,11 @@ class ExpressionInjectionPoint : InjectionPoint() { val flow = flows[insn] ?: return@forEachRemaining try { if (expr.matches(flow, ExpressionContext(pool, sink, targetClass, methodNode))) { - // TODO: for now we're assuming there's only one capture in the ME expression. - val capturedExpr = - PsiTreeUtil.findChildOfType(psiExpr, MECapturingExpression::class.java, false) - ?.expression ?: psiExpr - - for (capturedInsn in captured) { + for ((capturedInsn, startOffset) in captured) { + val capturedExpr = psiExpr.findElementAt(startOffset) + ?.parentOfType(withSelf = true) + ?.expression + ?: psiExpr result.putIfAbsent(capturedInsn, capturedExpr) } } From 35102d2691719d49fc2bfb6efc1d503c807839c1 Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 11 Feb 2024 18:02:16 +0000 Subject: [PATCH 020/100] Start with ME definition references --- .../expression/MEExpressionElementFactory.kt | 6 ++ .../expression/MENameElementManipulator.kt | 33 ++++++++ .../expression/psi/{mixins => }/MEPsiUtil.kt | 2 +- .../impl/MEArrayAccessExpressionImplMixin.kt | 2 +- .../mixins/impl/MECastExpressionImplMixin.kt | 2 +- .../psi/mixins/impl/MENameImplMixin.kt | 10 +++ .../MEDefinitionFindUsagesProvider.kt | 36 +++++++++ .../reference/MEDefinitionReference.kt | 49 ++++++++++++ .../MEDefinitionReferencesSearcher.kt | 76 +++++++++++++++++++ .../MEDefinitionUsageTargetProvider.kt | 56 ++++++++++++++ .../expression/reference/MEReferenceUtil.kt | 36 +++++++++ src/main/kotlin/util/psi-utils.kt | 6 ++ src/main/resources/META-INF/plugin.xml | 6 ++ 13 files changed, 317 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MENameElementManipulator.kt rename src/main/kotlin/platform/mixin/expression/psi/{mixins => }/MEPsiUtil.kt (96%) create mode 100644 src/main/kotlin/platform/mixin/expression/reference/MEDefinitionFindUsagesProvider.kt create mode 100644 src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt create mode 100644 src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReferencesSearcher.kt create mode 100644 src/main/kotlin/platform/mixin/expression/reference/MEDefinitionUsageTargetProvider.kt create mode 100644 src/main/kotlin/platform/mixin/expression/reference/MEReferenceUtil.kt diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt index 7c6e688d2..fff931c56 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt @@ -24,6 +24,7 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpre import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile @@ -50,6 +51,11 @@ class MEExpressionElementFactory(private val project: Project) { ?: throw IncorrectOperationException("'$text' is not an expression") } + fun createName(text: String): MEName { + return (createExpression(text) as? MENameExpression)?.meName + ?: throw IncorrectOperationException("'$text' is not a name") + } + fun createType(text: String): METype { return (createExpression("$text.class") as? MEClassConstantExpression)?.type ?: throw IncorrectOperationException("'$text' is not a type") diff --git a/src/main/kotlin/platform/mixin/expression/MENameElementManipulator.kt b/src/main/kotlin/platform/mixin/expression/MENameElementManipulator.kt new file mode 100644 index 000000000..dc853beb0 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MENameElementManipulator.kt @@ -0,0 +1,33 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.intellij.openapi.util.TextRange +import com.intellij.psi.AbstractElementManipulator + +class MENameElementManipulator : AbstractElementManipulator() { + override fun handleContentChange(element: MEName, range: TextRange, newContent: String): MEName { + val text = element.text + val newText = text.substring(0, range.startOffset) + newContent + text.substring(range.endOffset) + return element.project.meExpressionElementFactory.createName(newText) + } +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEPsiUtil.kt b/src/main/kotlin/platform/mixin/expression/psi/MEPsiUtil.kt similarity index 96% rename from src/main/kotlin/platform/mixin/expression/psi/mixins/MEPsiUtil.kt rename to src/main/kotlin/platform/mixin/expression/psi/MEPsiUtil.kt index f0698dc33..417bcca06 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEPsiUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEPsiUtil.kt @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -package com.demonwav.mcdev.platform.mixin.expression.psi.mixins +package com.demonwav.mcdev.platform.mixin.expression.psi import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEAssignStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt index c934a3af2..70d2dbf6c 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt @@ -24,8 +24,8 @@ import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.psi.MEPsiUtil import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEArrayAccessExpressionMixin -import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEPsiUtil import com.intellij.lang.ASTNode import com.intellij.psi.PsiArrayAccessExpression import com.intellij.psi.PsiElement diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt index 1207e94f3..68d071951 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt @@ -24,9 +24,9 @@ import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEParenthesizedExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.expression.psi.MEPsiUtil import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MECastExpressionMixin -import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEPsiUtil import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement import com.intellij.psi.PsiInstanceOfExpression diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt index b3fbffced..d4c6c7d97 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt @@ -22,12 +22,15 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameMixin +import com.demonwav.mcdev.platform.mixin.expression.reference.MEDefinitionReference import com.demonwav.mcdev.platform.mixin.util.LocalVariables import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement import com.intellij.psi.PsiField +import com.intellij.psi.PsiReference import com.intellij.psi.PsiReferenceExpression import com.intellij.psi.PsiVariable import com.intellij.psi.util.PsiUtil @@ -72,4 +75,11 @@ abstract class MENameImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MENa return false } + + override fun getReference(): PsiReference? { + if (isWildcard) { + return null + } + return MEDefinitionReference(this as MEName) + } } diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionFindUsagesProvider.kt b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionFindUsagesProvider.kt new file mode 100644 index 000000000..639a4aabb --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionFindUsagesProvider.kt @@ -0,0 +1,36 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.reference + +import com.intellij.lang.findUsages.FindUsagesProvider +import com.intellij.psi.PsiElement + +class MEDefinitionFindUsagesProvider : FindUsagesProvider { + override fun canFindUsagesFor(element: PsiElement) = MEReferenceUtil.isDefinitionId(element) + + override fun getHelpId(psiElement: PsiElement): String? = null + + override fun getType(element: PsiElement) = "definition id" + + override fun getDescriptiveName(element: PsiElement) = "Definition ID" + + override fun getNodeText(element: PsiElement, useFullName: Boolean): String = element.text +} diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt new file mode 100644 index 000000000..28ad34bb3 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt @@ -0,0 +1,49 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.reference + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.util.constantStringValue +import com.intellij.lang.injection.InjectedLanguageManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiModifierList +import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.util.parentOfType + +class MEDefinitionReference(name: MEName) : PsiReferenceBase(name) { + override fun resolve(): PsiElement? { + val injectionHost = InjectedLanguageManager.getInstance(element.project).getInjectionHost(element) + ?: return null + val modifierList = injectionHost.parentOfType() ?: return null + for (annotation in modifierList.annotations) { + if (!annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { + continue + } + val definitionId = annotation.findDeclaredAttributeValue("id") ?: return null + if (definitionId.constantStringValue == element.text) { + return definitionId + } + } + + return null + } +} diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReferencesSearcher.kt b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReferencesSearcher.kt new file mode 100644 index 000000000..a3c2007ea --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReferencesSearcher.kt @@ -0,0 +1,76 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.reference + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.util.constantStringValue +import com.intellij.lang.injection.InjectedLanguageManager +import com.intellij.openapi.application.QueryExecutorBase +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReference +import com.intellij.psi.search.RequestResultProcessor +import com.intellij.psi.search.UsageSearchContext +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.psi.util.parentOfType +import com.intellij.util.Processor + +class MEDefinitionReferencesSearcher : QueryExecutorBase(true) { + override fun processQuery( + queryParameters: ReferencesSearch.SearchParameters, + consumer: Processor + ) { + val targetElement = queryParameters.elementToSearch + + if (!MEReferenceUtil.isDefinitionId(targetElement)) { + return + } + + val strValue = targetElement.constantStringValue ?: return + queryParameters.optimizer.searchWord( + strValue, + queryParameters.effectiveSearchScope, + UsageSearchContext.IN_STRINGS, + true, + targetElement, + object : RequestResultProcessor() { + override fun processTextOccurrence( + element: PsiElement, + offsetInElement: Int, + consumer: Processor + ): Boolean { + val meElement = InjectedLanguageManager.getInstance(queryParameters.project).findInjectedElementAt( + element.containingFile, + element.textOffset + offsetInElement + ) ?: return false + val meName = meElement.parentOfType(withSelf = true) ?: return false + for (reference in meName.references) { + if (reference.isReferenceTo(targetElement)) { + if (!consumer.process(reference)) { + return true + } + } + } + return false + } + }, + ) + } +} diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionUsageTargetProvider.kt b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionUsageTargetProvider.kt new file mode 100644 index 000000000..f6544afe1 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionUsageTargetProvider.kt @@ -0,0 +1,56 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.reference + +import com.demonwav.mcdev.util.findContainingNameValuePair +import com.intellij.codeInsight.TargetElementUtil +import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.usages.UsageTarget +import com.intellij.usages.UsageTargetProvider + +class MEDefinitionUsageTargetProvider : UsageTargetProvider { + override fun getTargets(element: PsiElement): Array? { + return if (MEReferenceUtil.isDefinitionId(element)) { + arrayOf(PsiElement2UsageTargetAdapter(element, true)) + } else { + null + } + } + + override fun getTargets(editor: Editor, file: PsiFile): Array? { + val offset = editor.caretModel.offset + val element = file.findElementAt(TargetElementUtil.adjustOffset(file, editor.document, offset)) ?: return null + val nameValuePair = element.findContainingNameValuePair() ?: return null + val value = nameValuePair.value ?: return null + if (!PsiTreeUtil.isAncestor(value, element, false)) { + return null + } + return if (MEReferenceUtil.isDefinitionId(value)) { + arrayOf(PsiElement2UsageTargetAdapter(value, true)) + } else { + null + } + } +} diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEReferenceUtil.kt b/src/main/kotlin/platform/mixin/expression/reference/MEReferenceUtil.kt new file mode 100644 index 000000000..5f292d475 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/reference/MEReferenceUtil.kt @@ -0,0 +1,36 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.reference + +import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNameValuePair +import com.intellij.psi.util.parentOfType + +object MEReferenceUtil { + fun isDefinitionId(element: PsiElement): Boolean { + val parent = element.parent + return parent is PsiNameValuePair && + parent.name == "id" && + parent.parentOfType()?.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) == true + } +} diff --git a/src/main/kotlin/util/psi-utils.kt b/src/main/kotlin/util/psi-utils.kt index af3363228..b6e8a8bbb 100644 --- a/src/main/kotlin/util/psi-utils.kt +++ b/src/main/kotlin/util/psi-utils.kt @@ -36,6 +36,7 @@ import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.ElementManipulator import com.intellij.psi.ElementManipulators import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiClass import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiElement @@ -51,6 +52,7 @@ import com.intellij.psi.PsiMethodReferenceExpression import com.intellij.psi.PsiModifier import com.intellij.psi.PsiModifier.ModifierConstant import com.intellij.psi.PsiModifierList +import com.intellij.psi.PsiNameValuePair import com.intellij.psi.PsiParameter import com.intellij.psi.PsiParameterList import com.intellij.psi.PsiReference @@ -82,6 +84,10 @@ fun PsiElement.findContainingMethod(): PsiMethod? = findParent(resolveReferences fun PsiElement.findContainingModifierList(): PsiModifierList? = findParent(resolveReferences = false) { it is PsiClass } +fun PsiElement.findContainingNameValuePair(): PsiNameValuePair? = findParent(resolveReferences = false) { + it is PsiClass || it is PsiMethod || it is PsiAnnotation +} + private val PsiElement.ancestors: Sequence get() = generateSequence(this) { if (it is PsiFile) null else it.parent } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 2106cc09e..1ee3fccd1 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -497,6 +497,12 @@ implementationClass="com.demonwav.mcdev.platform.mixin.expression.MEExpressionBraceMatcher"/> + + + + From 86d8b34354448b61f4f3b3723beaa7a353602c96 Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 12 Feb 2024 23:49:05 +0000 Subject: [PATCH 021/100] Attempt to overhaul ME expression injection --- src/main/grammars/MEExpressionLexer.flex | 4 +- src/main/grammars/MEExpressionParser.bnf | 19 +- .../mixin/expression/MEExpressionAnnotator.kt | 24 +++ .../MEExpressionColorSettingsPage.kt | 8 + .../expression/MEExpressionElementFactory.kt | 14 +- .../mixin/expression/MEExpressionInjector.kt | 182 ++++++++++++++++++ .../MEExpressionSyntaxHighlighter.kt | 8 + .../mixin/expression/psi/MEExpressionFile.kt | 7 +- .../expression/psi/MEExpressionTokenSets.kt | 1 + .../mixins/MEDeclarationMixin.kt} | 15 +- .../expression/psi/mixins/MENameMixin.kt | 1 + .../psi/mixins/impl/MEDeclarationImplMixin.kt | 46 +++++ .../psi/mixins/impl/MENameImplMixin.kt | 1 + .../reference/MEDefinitionReference.kt | 20 +- .../MEDefinitionReferencesSearcher.kt | 76 -------- .../MEDefinitionUsageTargetProvider.kt | 56 ------ .../resources/META-INF/mcdev-intellilang.xml | 25 --- src/main/resources/META-INF/plugin.xml | 10 +- .../resources/languageInjections/java.xml | 28 --- .../messages/MinecraftDevelopment.properties | 2 + 20 files changed, 327 insertions(+), 220 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt rename src/main/kotlin/platform/mixin/expression/{reference/MEDefinitionFindUsagesProvider.kt => psi/mixins/MEDeclarationMixin.kt} (56%) create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt delete mode 100644 src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReferencesSearcher.kt delete mode 100644 src/main/kotlin/platform/mixin/expression/reference/MEDefinitionUsageTargetProvider.kt delete mode 100644 src/main/resources/META-INF/mcdev-intellilang.xml delete mode 100644 src/main/resources/languageInjections/java.xml diff --git a/src/main/grammars/MEExpressionLexer.flex b/src/main/grammars/MEExpressionLexer.flex index cc6f727c8..08f39f526 100644 --- a/src/main/grammars/MEExpressionLexer.flex +++ b/src/main/grammars/MEExpressionLexer.flex @@ -37,12 +37,13 @@ import com.intellij.psi.TokenType; %eof} WHITE_SPACE=[\ \n\t\r] -RESERVED=assert|break|case|catch|const|continue|default|do|else|finally|for|goto|if|switch|synchronized|try|while|yield|_ +RESERVED=assert|break|case|catch|const|continue|default|else|finally|for|goto|if|switch|synchronized|try|while|yield|_ WILDCARD="?" NEW=new INSTANCEOF=instanceof BOOL_LIT=true|false NULL_LIT=null +DO=do RETURN=return THROW=throw THIS=this @@ -94,6 +95,7 @@ STRING_ESCAPE=\\'|\\\\ {INSTANCEOF} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_INSTANCEOF; } {BOOL_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BOOL_LIT; } {NULL_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NULL_LIT; } + {DO} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DO; } {RETURN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RETURN; } {THROW} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_THROW; } {THIS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_THIS; } diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index c5044c1bf..2b8a47fb1 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -39,7 +39,24 @@ extends(".+Statement") = statement } -meExpressionFile ::= statement <> +meExpressionFile ::= item* <> + +item ::= declaration | statementItem + +declaration ::= TOKEN_CLASS TOKEN_IDENTIFIER { + pin = 1 + extends = item + implements = [ + "com.intellij.psi.PsiNamedElement" + "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEDeclarationMixin" + ] + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEDeclarationImplMixin" +} + +statementItem ::= TOKEN_DO TOKEN_LEFT_BRACE statement TOKEN_RIGHT_BRACE { + pin = 1 + extends = item +} statement ::= assignStatement | returnStatement | diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index 1b43ab244..328d38045 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -23,6 +23,7 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEBinaryExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclaration import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEInstantiationExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MELitExpression @@ -35,14 +36,37 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallEx import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MESuperCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil +import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.Annotator import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.lang.injection.InjectedLanguageManager +import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiElement +import com.intellij.psi.util.parentOfType class MEExpressionAnnotator : Annotator { override fun annotate(element: PsiElement, holder: AnnotationHolder) { when (element) { + is MEDeclaration -> { + val injectionHost = InjectedLanguageManager.getInstance(element.project).getInjectionHost(element) + ?: return + val declarationAnnotation = injectionHost.parentOfType() ?: return + if (!declarationAnnotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { + return + } + if (declarationAnnotation.findDeclaredAttributeValue("type") != null) { + holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(element) + .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_TYPE_DECLARATION) + .create() + } else { + holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(element) + .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_DECLARATION) + .create() + } + } is MEName -> { if (!element.isWildcard) { when (val parent = element.parent) { diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt index 093076565..f0e289a77 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt @@ -97,6 +97,14 @@ class MEExpressionColorSettingsPage : ColorSettingsPage { MCDevBundle.pointer("mixinextras.expression.lang.highlighting.variable_identifier.display_name"), MEExpressionSyntaxHighlighter.IDENTIFIER_VARIABLE ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.type_declaration_identifier.display_name"), + MEExpressionSyntaxHighlighter.IDENTIFIER_TYPE_DECLARATION + ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.declaration_identifier.display_name"), + MEExpressionSyntaxHighlighter.IDENTIFIER_DECLARATION + ), AttributesDescriptor( MCDevBundle.pointer("mixinextras.expression.lang.highlighting.bad_char.display_name"), MEExpressionSyntaxHighlighter.BAD_CHAR diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt index fff931c56..98718a082 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclaration import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName @@ -29,6 +30,7 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement import com.intellij.psi.PsiFileFactory import com.intellij.util.IncorrectOperationException @@ -41,8 +43,13 @@ class MEExpressionElementFactory(private val project: Project) { ) as MEExpressionFile } + fun createDeclaration(name: String): MEDeclaration { + return createFile("class $name").declarations.firstOrNull() + ?: throw IncorrectOperationException("'$name' is not a declaration") + } + fun createStatement(text: String): MEStatement { - return createFile(text).statement + return createFile("do {$text}").statement ?: throw IncorrectOperationException("'$text' is not a statement") } @@ -56,6 +63,11 @@ class MEExpressionElementFactory(private val project: Project) { ?: throw IncorrectOperationException("'$text' is not a name") } + fun createIdentifier(text: String): PsiElement { + return createName(text).identifierElement + ?: throw IncorrectOperationException("'$text' is not an identifier") + } + fun createType(text: String): METype { return (createExpression("$text.class") as? MEClassConstantExpression)?.type ?: throw IncorrectOperationException("'$text' is not a type") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt new file mode 100644 index 000000000..609254ce6 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt @@ -0,0 +1,182 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.util.findContainingModifierList +import com.demonwav.mcdev.util.findContainingNameValuePair +import com.intellij.lang.injection.InjectedLanguageManager +import com.intellij.lang.injection.MultiHostInjector +import com.intellij.lang.injection.MultiHostRegistrar +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.TextRange +import com.intellij.openapi.util.UserDataHolderEx +import com.intellij.openapi.util.component1 +import com.intellij.openapi.util.component2 +import com.intellij.psi.ElementManipulators +import com.intellij.psi.JavaTokenType +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiLanguageInjectionHost +import com.intellij.psi.PsiLiteralExpression +import com.intellij.psi.PsiParenthesizedExpression +import com.intellij.psi.PsiPolyadicExpression +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil +import com.intellij.psi.impl.source.tree.injected.JavaConcatenationToInjectorAdapter +import com.intellij.psi.util.PsiLiteralUtil +import com.intellij.psi.util.PsiModificationTracker +import com.intellij.psi.util.PsiUtil +import com.intellij.psi.util.parentOfType +import com.intellij.util.SmartList + +class MEExpressionInjector : MultiHostInjector { + companion object { + private val ELEMENTS = listOf(PsiLiteralExpression::class.java) + private val PRIMARY_ELEMENT_KEY = Key.create("mcdev.anchorModCount") + } + + private data class PrimaryElement(val modCount: Long, val element: PsiElement) + + override fun getLanguagesToInject(registrar: MultiHostRegistrar, context: PsiElement) { + val project = context.project + val (anchor, operands) = JavaConcatenationToInjectorAdapter(project).computeAnchorAndOperands(context) + + val nameValuePair = anchor.findContainingNameValuePair() ?: return + if (nameValuePair.name != "value" && nameValuePair.name != null) { + return + } + val expressionAnnotation = nameValuePair.parentOfType() ?: return + if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { + return + } + + val modCount = PsiModificationTracker.getInstance(project).modificationCount + val primaryElement = PrimaryElement(modCount, context) + val existingElement = (anchor as UserDataHolderEx).putUserDataIfAbsent(PRIMARY_ELEMENT_KEY, primaryElement) + if (existingElement !== primaryElement && existingElement.modCount == modCount && context != existingElement.element) { + return + } + + val modifierList = expressionAnnotation.findContainingModifierList() ?: return + + var isFrankenstein = false + registrar.startInjecting(MEExpressionLanguage) + + for (annotation in modifierList.annotations) { + if (annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { + val idExpr = annotation.findDeclaredAttributeValue("id") ?: continue + var needsPrefix = true + iterateConcatenation(idExpr) { op -> + if (op is PsiLanguageInjectionHost) { + for (textRange in getTextRanges(op)) { + val prefix = " class ".takeIf { needsPrefix } + needsPrefix = false + registrar.addPlace(prefix, null, op, textRange) + } + } else { + isFrankenstein = true + } + } + } else if (annotation == expressionAnnotation) { + val places = mutableListOf>() + for (operand in operands) { + iterateConcatenation(operand) { op -> + if (op is PsiLanguageInjectionHost) { + for (textRange in getTextRanges(op)) { + places += op to textRange + } + } else { + isFrankenstein = true + } + } + } + if (places.isNotEmpty()) { + for ((i, place) in places.withIndex()) { + val (host, range) = place + val prefix = " do { ".takeIf { i == 0 } + val suffix = " }".takeIf { i == places.size - 1 } + registrar.addPlace(prefix, suffix, host, range) + } + } + } + } + + registrar.doneInjecting() + + if (isFrankenstein) { + InjectedLanguageUtil.putInjectedFileUserData( + context, + MEExpressionLanguage, + InjectedLanguageManager.FRANKENSTEIN_INJECTION, + true + ) + } + } + + private fun iterateConcatenation(element: PsiElement, consumer: (PsiElement) -> Unit) { + when (element) { + is PsiParenthesizedExpression -> { + val inner = PsiUtil.skipParenthesizedExprDown(element) ?: return + iterateConcatenation(inner, consumer) + } + is PsiPolyadicExpression -> { + if (element.operationTokenType == JavaTokenType.PLUS) { + for (operand in element.operands) { + iterateConcatenation(operand, consumer) + } + } else { + consumer(element) + } + } + else -> consumer(element) + } + } + + private fun getTextRanges(host: PsiLanguageInjectionHost): List { + if (host is PsiLiteralExpression && host.isTextBlock) { + val textRange = ElementManipulators.getValueTextRange(host) + val indent = PsiLiteralUtil.getTextBlockIndent(host) + if (indent <= 0) { + return listOf(textRange) + } + + val text = (host as PsiElement).text + var startOffset = textRange.startOffset + indent + var endOffset = text.indexOf('\n', startOffset) + val result = SmartList() + while (endOffset > 0) { + endOffset++ + result.add(TextRange(startOffset, endOffset)) + startOffset = endOffset + indent + endOffset = text.indexOf('\n', startOffset) + } + endOffset = textRange.endOffset + if (startOffset < endOffset) { + result.add(TextRange(startOffset, endOffset)) + } + return result + } else { + return listOf(ElementManipulators.getValueTextRange(host)) + } + } + + override fun elementsToInjectIn() = ELEMENTS +} diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt index 963d7fb6d..e1aee7ef6 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt @@ -103,6 +103,14 @@ class MEExpressionSyntaxHighlighter : SyntaxHighlighterBase() { "MEEXPRESSION_IDENTIFIER_VARIABLE", DefaultLanguageHighlighterColors.LOCAL_VARIABLE ) + val IDENTIFIER_TYPE_DECLARATION = createTextAttributesKey( + "MEEXPRESSION_IDENTIFIER_TYPE_DECLARATION", + DefaultLanguageHighlighterColors.CLASS_NAME + ) + val IDENTIFIER_DECLARATION = createTextAttributesKey( + "MEEXPRESSION_IDENTIFIER_DECLARATION", + DefaultLanguageHighlighterColors.FUNCTION_DECLARATION + ) val BAD_CHAR = createTextAttributesKey( "MEEXPRESSION_BAD_CHARACTER", HighlighterColors.BAD_CHARACTER diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt index bb0d1532f..35d3d3948 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt @@ -22,7 +22,10 @@ package com.demonwav.mcdev.platform.mixin.expression.psi import com.demonwav.mcdev.platform.mixin.expression.MEExpressionFileType import com.demonwav.mcdev.platform.mixin.expression.MEExpressionLanguage +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclaration +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEItem import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatementItem import com.intellij.extapi.psi.PsiFileBase import com.intellij.psi.FileViewProvider @@ -30,5 +33,7 @@ class MEExpressionFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvide override fun getFileType() = MEExpressionFileType override fun toString() = "MixinExtras Expression File" - val statement: MEStatement? get() = findChildByClass(MEStatement::class.java) + val items: Array get() = findChildrenByClass(MEItem::class.java) + val declarations: List get() = items.filterIsInstance() + val statement: MEStatement? get() = items.asSequence().filterIsInstance().singleOrNull()?.statement } diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt index aac58bf43..cd4a1842e 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionTokenSets.kt @@ -36,6 +36,7 @@ object MEExpressionTokenSets { val KEYWORDS = TokenSet.create( MEExpressionTypes.TOKEN_BOOL_LIT, MEExpressionTypes.TOKEN_NULL_LIT, + MEExpressionTypes.TOKEN_DO, MEExpressionTypes.TOKEN_INSTANCEOF, MEExpressionTypes.TOKEN_NEW, MEExpressionTypes.TOKEN_RETURN, diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionFindUsagesProvider.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationMixin.kt similarity index 56% rename from src/main/kotlin/platform/mixin/expression/reference/MEDefinitionFindUsagesProvider.kt rename to src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationMixin.kt index 639a4aabb..adb2b8023 100644 --- a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionFindUsagesProvider.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationMixin.kt @@ -18,19 +18,10 @@ * along with this program. If not, see . */ -package com.demonwav.mcdev.platform.mixin.expression.reference +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins -import com.intellij.lang.findUsages.FindUsagesProvider import com.intellij.psi.PsiElement -class MEDefinitionFindUsagesProvider : FindUsagesProvider { - override fun canFindUsagesFor(element: PsiElement) = MEReferenceUtil.isDefinitionId(element) - - override fun getHelpId(psiElement: PsiElement): String? = null - - override fun getType(element: PsiElement) = "definition id" - - override fun getDescriptiveName(element: PsiElement) = "Definition ID" - - override fun getNodeText(element: PsiElement, useFullName: Boolean): String = element.text +interface MEDeclarationMixin : PsiElement { + val nameIdentifier: PsiElement? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt index e29b5bb9d..d6e68c99f 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt @@ -25,6 +25,7 @@ import com.intellij.psi.PsiElement interface MENameMixin : PsiElement { val isWildcard: Boolean + val identifierElement: PsiElement? fun matchesJavaExpr(javaExpr: PsiElement, context: MESourceMatchContext): Boolean } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt new file mode 100644 index 000000000..4b5ea3210 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt @@ -0,0 +1,46 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEItemImpl +import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEDeclarationMixin +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNamedElement + +abstract class MEDeclarationImplMixin(node: ASTNode) : MEItemImpl(node), MEDeclarationMixin, PsiNamedElement { + override fun getName() = nameIdentifier?.text + + override fun setName(name: String): PsiElement { + val nameIdentifier = this.nameIdentifier + if (nameIdentifier != null) { + nameIdentifier.replace(project.meExpressionElementFactory.createIdentifier(name)) + return this + } else { + return replace(project.meExpressionElementFactory.createDeclaration(name)) + } + } + + override val nameIdentifier: PsiElement? + get() = node.findChildByType(MEExpressionTypes.TOKEN_IDENTIFIER)?.psi +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt index d4c6c7d97..2dc41f9ab 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt @@ -37,6 +37,7 @@ import com.intellij.psi.util.PsiUtil abstract class MENameImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MENameMixin { override val isWildcard get() = node.firstChildNode.elementType == MEExpressionTypes.TOKEN_WILDCARD + override val identifierElement get() = if (isWildcard) null else firstChild override fun matchesJavaExpr(javaExpr: PsiElement, context: MESourceMatchContext): Boolean { if (isWildcard) { diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt index 28ad34bb3..336e65301 100644 --- a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt +++ b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt @@ -21,26 +21,18 @@ package com.demonwav.mcdev.platform.mixin.expression.reference import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName -import com.demonwav.mcdev.platform.mixin.util.MixinConstants -import com.demonwav.mcdev.util.constantStringValue -import com.intellij.lang.injection.InjectedLanguageManager +import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile import com.intellij.psi.PsiElement -import com.intellij.psi.PsiModifierList import com.intellij.psi.PsiReferenceBase import com.intellij.psi.util.parentOfType class MEDefinitionReference(name: MEName) : PsiReferenceBase(name) { override fun resolve(): PsiElement? { - val injectionHost = InjectedLanguageManager.getInstance(element.project).getInjectionHost(element) - ?: return null - val modifierList = injectionHost.parentOfType() ?: return null - for (annotation in modifierList.annotations) { - if (!annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { - continue - } - val definitionId = annotation.findDeclaredAttributeValue("id") ?: return null - if (definitionId.constantStringValue == element.text) { - return definitionId + val file = element.parentOfType() ?: return null + val name = element.text + for (declaration in file.declarations) { + if (declaration.name == name) { + return declaration } } diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReferencesSearcher.kt b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReferencesSearcher.kt deleted file mode 100644 index a3c2007ea..000000000 --- a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReferencesSearcher.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Minecraft Development for IntelliJ - * - * https://mcdev.io/ - * - * Copyright (C) 2024 minecraft-dev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, version 3.0 only. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.demonwav.mcdev.platform.mixin.expression.reference - -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName -import com.demonwav.mcdev.util.constantStringValue -import com.intellij.lang.injection.InjectedLanguageManager -import com.intellij.openapi.application.QueryExecutorBase -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiReference -import com.intellij.psi.search.RequestResultProcessor -import com.intellij.psi.search.UsageSearchContext -import com.intellij.psi.search.searches.ReferencesSearch -import com.intellij.psi.util.parentOfType -import com.intellij.util.Processor - -class MEDefinitionReferencesSearcher : QueryExecutorBase(true) { - override fun processQuery( - queryParameters: ReferencesSearch.SearchParameters, - consumer: Processor - ) { - val targetElement = queryParameters.elementToSearch - - if (!MEReferenceUtil.isDefinitionId(targetElement)) { - return - } - - val strValue = targetElement.constantStringValue ?: return - queryParameters.optimizer.searchWord( - strValue, - queryParameters.effectiveSearchScope, - UsageSearchContext.IN_STRINGS, - true, - targetElement, - object : RequestResultProcessor() { - override fun processTextOccurrence( - element: PsiElement, - offsetInElement: Int, - consumer: Processor - ): Boolean { - val meElement = InjectedLanguageManager.getInstance(queryParameters.project).findInjectedElementAt( - element.containingFile, - element.textOffset + offsetInElement - ) ?: return false - val meName = meElement.parentOfType(withSelf = true) ?: return false - for (reference in meName.references) { - if (reference.isReferenceTo(targetElement)) { - if (!consumer.process(reference)) { - return true - } - } - } - return false - } - }, - ) - } -} diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionUsageTargetProvider.kt b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionUsageTargetProvider.kt deleted file mode 100644 index f6544afe1..000000000 --- a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionUsageTargetProvider.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Minecraft Development for IntelliJ - * - * https://mcdev.io/ - * - * Copyright (C) 2024 minecraft-dev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, version 3.0 only. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.demonwav.mcdev.platform.mixin.expression.reference - -import com.demonwav.mcdev.util.findContainingNameValuePair -import com.intellij.codeInsight.TargetElementUtil -import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter -import com.intellij.openapi.editor.Editor -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.util.PsiTreeUtil -import com.intellij.usages.UsageTarget -import com.intellij.usages.UsageTargetProvider - -class MEDefinitionUsageTargetProvider : UsageTargetProvider { - override fun getTargets(element: PsiElement): Array? { - return if (MEReferenceUtil.isDefinitionId(element)) { - arrayOf(PsiElement2UsageTargetAdapter(element, true)) - } else { - null - } - } - - override fun getTargets(editor: Editor, file: PsiFile): Array? { - val offset = editor.caretModel.offset - val element = file.findElementAt(TargetElementUtil.adjustOffset(file, editor.document, offset)) ?: return null - val nameValuePair = element.findContainingNameValuePair() ?: return null - val value = nameValuePair.value ?: return null - if (!PsiTreeUtil.isAncestor(value, element, false)) { - return null - } - return if (MEReferenceUtil.isDefinitionId(value)) { - arrayOf(PsiElement2UsageTargetAdapter(value, true)) - } else { - null - } - } -} diff --git a/src/main/resources/META-INF/mcdev-intellilang.xml b/src/main/resources/META-INF/mcdev-intellilang.xml deleted file mode 100644 index 27447cb73..000000000 --- a/src/main/resources/META-INF/mcdev-intellilang.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 1ee3fccd1..3ef001a51 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -28,7 +28,6 @@ com.intellij.properties ByteCodeViewer org.toml.lang - org.intellij.intelliLang com.demonwav.minecraft-dev Minecraft Development @@ -499,10 +498,11 @@ implementationClass="com.demonwav.mcdev.platform.mixin.expression.MEExpressionQuoteHandler"/> - - - + + + + + diff --git a/src/main/resources/languageInjections/java.xml b/src/main/resources/languageInjections/java.xml deleted file mode 100644 index 9213220f4..000000000 --- a/src/main/resources/languageInjections/java.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - MixinExtras @Expression - - - diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index ae9d136bd..385b8528d 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -215,6 +215,7 @@ mixinextras.expression.lang.highlighting.call_identifier.display_name=Identifier mixinextras.expression.lang.highlighting.capture.display_name=Capture mixinextras.expression.lang.highlighting.class_name_identifier.display_name=Identifier//Class name mixinextras.expression.lang.highlighting.comma.display_name=Comma +mixinextras.expression.lang.highlighting.declaration_identifier.display_name=Identifier//Declaration mixinextras.expression.lang.highlighting.dot.display_name=Dot mixinextras.expression.lang.highlighting.identifier.display_name=Identifier mixinextras.expression.lang.highlighting.keyword.display_name=Keyword @@ -224,5 +225,6 @@ mixinextras.expression.lang.highlighting.operator.display_name=Operator mixinextras.expression.lang.highlighting.parens.display_name=Parentheses mixinextras.expression.lang.highlighting.string.display_name=String mixinextras.expression.lang.highlighting.string_escape.display_name=String escape +mixinextras.expression.lang.highlighting.type_declaration_identifier.display_name=Identifier//Type declaration mixinextras.expression.lang.highlighting.variable_identifier.display_name=Identifier//Variable mixinextras.expression.lang.highlighting.wildcard.display_name=Wildcard From fd06864fedd149f0dbe73bc160accf6a5831b250 Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 13 Feb 2024 10:38:01 +0000 Subject: [PATCH 022/100] Some fixes to the new injection + navigation --- .../platform/mixin/expression/MEExpressionAnnotator.kt | 6 +++++- .../mixin/expression/MEExpressionColorSettingsPage.kt | 4 +++- .../platform/mixin/expression/MEExpressionInjector.kt | 5 ++++- .../expression/psi/mixins/impl/MEDeclarationImplMixin.kt | 4 ++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index 328d38045..f159e7232 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -49,7 +49,11 @@ class MEExpressionAnnotator : Annotator { override fun annotate(element: PsiElement, holder: AnnotationHolder) { when (element) { is MEDeclaration -> { - val injectionHost = InjectedLanguageManager.getInstance(element.project).getInjectionHost(element) + val nameIdentifier = element.nameIdentifier ?: return + val injectManager = InjectedLanguageManager.getInstance(element.project) + val hostFile = injectManager.getInjectionHost(element)?.containingFile ?: return + val injectionHost = hostFile + .findElementAt(injectManager.injectedToHost(element, nameIdentifier.textOffset, false)) ?: return val declarationAnnotation = injectionHost.parentOfType() ?: return if (!declarationAnnotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt index f0e289a77..c1abe4a30 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt @@ -98,7 +98,9 @@ class MEExpressionColorSettingsPage : ColorSettingsPage { MEExpressionSyntaxHighlighter.IDENTIFIER_VARIABLE ), AttributesDescriptor( - MCDevBundle.pointer("mixinextras.expression.lang.highlighting.type_declaration_identifier.display_name"), + MCDevBundle.pointer( + "mixinextras.expression.lang.highlighting.type_declaration_identifier.display_name" + ), MEExpressionSyntaxHighlighter.IDENTIFIER_TYPE_DECLARATION ), AttributesDescriptor( diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt index 609254ce6..54a0f6a02 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt @@ -71,7 +71,10 @@ class MEExpressionInjector : MultiHostInjector { val modCount = PsiModificationTracker.getInstance(project).modificationCount val primaryElement = PrimaryElement(modCount, context) val existingElement = (anchor as UserDataHolderEx).putUserDataIfAbsent(PRIMARY_ELEMENT_KEY, primaryElement) - if (existingElement !== primaryElement && existingElement.modCount == modCount && context != existingElement.element) { + if (existingElement !== primaryElement && + existingElement.modCount == modCount && + context != existingElement.element + ) { return } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt index 4b5ea3210..c23bf2e11 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt @@ -43,4 +43,8 @@ abstract class MEDeclarationImplMixin(node: ASTNode) : MEItemImpl(node), MEDecla override val nameIdentifier: PsiElement? get() = node.findChildByType(MEExpressionTypes.TOKEN_IDENTIFIER)?.psi + + override fun getNavigationElement() = nameIdentifier ?: this + + override fun getTextOffset() = nameIdentifier?.textOffset ?: super.getTextOffset() } From ba6b6b53a74d99145349a665f3409119f1fea952 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 7 Mar 2024 20:18:58 +0000 Subject: [PATCH 023/100] MixinExtras: Add handler signature support for expressions. (#2244) --- build.gradle.kts | 2 +- .../handlers/injectionPoint/InjectionPoint.kt | 11 ++++- .../mixinextras/ExpressionInjectionPoint.kt | 28 ++++++------ .../MixinExtrasInjectorAnnotationHandler.kt | 45 +++++++++++++------ .../ModifyExpressionValueHandler.kt | 24 ++++++++-- .../mixinextras/ModifyReceiverHandler.kt | 4 +- .../mixinextras/ModifyReturnValueHandler.kt | 5 +-- .../mixin/handlers/mixinextras/TargetInsn.kt | 30 +++++++++++++ .../mixinextras/WrapOperationHandler.kt | 31 ++++++++++--- .../mixinextras/WrapWithConditionHandler.kt | 4 +- 10 files changed, 138 insertions(+), 46 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/handlers/mixinextras/TargetInsn.kt diff --git a/build.gradle.kts b/build.gradle.kts index 8a71d990e..71c6d89bd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:fe1e2b9") + implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:debfdc8") implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt index a4d5ccb19..17a917eef 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt @@ -410,6 +410,7 @@ abstract class CollectVisitor(protected val mode: Mode) { insn: AbstractInsnNode, element: T, qualifier: String? = null, + decorations: Map = emptyMap(), ) { // apply shift. // being able to break out of the shift loops is important to prevent IDE freezes in case of large shift bys. @@ -430,7 +431,14 @@ abstract class CollectVisitor(protected val mode: Mode) { } } - val result = Result(nextIndex++, insn, shiftedInsn ?: return, element, qualifier) + val result = Result( + nextIndex++, + insn, + shiftedInsn ?: return, + element, + qualifier, + if (insn === shiftedInsn) decorations else emptyMap() + ) var isFiltered = false for ((name, filter) in resultFilters) { if (!filter(result, method)) { @@ -466,6 +474,7 @@ abstract class CollectVisitor(protected val mode: Mode) { val insn: AbstractInsnNode, val target: T, val qualifier: String? = null, + val decorations: Map ) enum class Mode { MATCH_ALL, MATCH_FIRST, COMPLETION } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index 21f2a1a49..9a44038a4 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -290,29 +290,29 @@ class ExpressionInjectionPoint : InjectionPoint() { return } - val result = IdentityHashMap() + val result = IdentityHashMap>>() for ((expr, psiExpr) in expressions) { - insns.iterator().forEachRemaining { insn -> - val genericDecorations = IdentityHashMap>() - val injectorSpecificDecorations = IdentityHashMap>() - val captured = mutableListOf>() - + val decorations = IdentityHashMap>() + val captured = mutableListOf>() + for (insn in insns) { val sink = object : Expression.OutputSink { override fun capture(node: FlowValue, expr: Expression?) { captured += node.insn to (expr?.src?.startIndex ?: 0) + decorations.getOrPut(insn, ::mutableMapOf).putAll(node.decorations) } override fun decorate(insn: AbstractInsnNode, key: String, value: Any?) { - genericDecorations.computeIfAbsent(insn) { mutableMapOf() }[key] = value + decorations.getOrPut(insn, ::mutableMapOf)[key] = value } override fun decorateInjectorSpecific(insn: AbstractInsnNode, key: String, value: Any?) { - injectorSpecificDecorations.computeIfAbsent(insn) { mutableMapOf() }[key] = value + // Our maps are per-injector anyway, so this is just a normal decoration. + decorations.getOrPut(insn, ::mutableMapOf)[key] = value } } - val flow = flows[insn] ?: return@forEachRemaining + val flow = flows[insn] ?: continue try { if (expr.matches(flow, ExpressionContext(pool, sink, targetClass, methodNode))) { for ((capturedInsn, startOffset) in captured) { @@ -320,7 +320,7 @@ class ExpressionInjectionPoint : InjectionPoint() { ?.parentOfType(withSelf = true) ?.expression ?: psiExpr - result.putIfAbsent(capturedInsn, capturedExpr) + result.putIfAbsent(capturedInsn, capturedExpr to decorations[capturedInsn].orEmpty()) } } } catch (e: ProcessCanceledException) { @@ -335,11 +335,9 @@ class ExpressionInjectionPoint : InjectionPoint() { return } - insns.iterator().forEachRemaining { insn -> - val element = result[insn] - if (element != null) { - addResult(insn, element) - } + for (insn in insns) { + val (element, decorations) = result[insn] ?: continue + addResult(insn, element, decorations = decorations) } } } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt index a0cb1ba40..fedb33055 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt @@ -35,8 +35,10 @@ import com.demonwav.mcdev.util.Parameter import com.demonwav.mcdev.util.toJavaIdentifier import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiElement import com.intellij.psi.PsiType import com.intellij.psi.PsiTypes +import com.llamalad7.mixinextras.utils.Decorations import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode @@ -52,30 +54,38 @@ abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler( enum class InstructionType { METHOD_CALL { - override fun matches(insn: AbstractInsnNode) = insn is MethodInsnNode && insn.name != "" + override fun matches(target: TargetInsn) = target.insn is MethodInsnNode && target.insn.name != "" }, FIELD_GET { - override fun matches(insn: AbstractInsnNode) = - insn.opcode == Opcodes.GETFIELD || insn.opcode == Opcodes.GETSTATIC + override fun matches(target: TargetInsn) = + target.insn.opcode == Opcodes.GETFIELD || target.insn.opcode == Opcodes.GETSTATIC }, FIELD_SET { - override fun matches(insn: AbstractInsnNode) = - insn.opcode == Opcodes.PUTFIELD || insn.opcode == Opcodes.PUTSTATIC + override fun matches(target: TargetInsn) = + target.insn.opcode == Opcodes.PUTFIELD || target.insn.opcode == Opcodes.PUTSTATIC }, INSTANTIATION { - override fun matches(insn: AbstractInsnNode) = insn.opcode == Opcodes.NEW + override fun matches(target: TargetInsn) = target.insn.opcode == Opcodes.NEW }, INSTANCEOF { - override fun matches(insn: AbstractInsnNode) = insn.opcode == Opcodes.INSTANCEOF + override fun matches(target: TargetInsn) = target.insn.opcode == Opcodes.INSTANCEOF }, CONSTANT { - override fun matches(insn: AbstractInsnNode) = isConstant(insn) + override fun matches(target: TargetInsn) = isConstant(target.insn) }, RETURN { - override fun matches(insn: AbstractInsnNode) = insn.opcode in Opcodes.IRETURN..Opcodes.ARETURN + override fun matches(target: TargetInsn) = target.insn.opcode in Opcodes.IRETURN..Opcodes.ARETURN + }, + SIMPLE_OPERATION { + override fun matches(target: TargetInsn) = + target.hasDecoration(Decorations.SIMPLE_OPERATION_ARGS) && + target.hasDecoration(Decorations.SIMPLE_OPERATION_RETURN_TYPE) + }, + SIMPLE_EXPRESSION { + override fun matches(target: TargetInsn) = target.hasDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) }; - abstract fun matches(insn: AbstractInsnNode): Boolean + abstract fun matches(target: TargetInsn): Boolean } abstract val supportedInstructionTypes: Collection @@ -86,7 +96,7 @@ abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insn: AbstractInsnNode + target: TargetInsn, ): Pair? override val allowCoerce = true @@ -98,9 +108,11 @@ abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler( ): List? { val insns = resolveInstructions(annotation, targetClass, targetMethod) .ifEmpty { return emptyList() } - .map { it.insn } + .map { TargetInsn(it.insn, it.decorations) } if (insns.any { insn -> supportedInstructionTypes.none { it.matches(insn) } }) return emptyList() - val signatures = insns.map { expectedMethodSignature(annotation, targetClass, targetMethod, it) } + val signatures = insns.map { insn -> + expectedMethodSignature(annotation, targetClass, targetMethod, insn) + } val firstMatch = signatures[0] ?: return emptyList() if (signatures.drop(1).any { it != firstMatch }) return emptyList() return listOf( @@ -287,7 +299,12 @@ abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler( } else -> null - } ?: getInsnArgTypes(insn, targetClass)?.map { Parameter(null, it.toPsiType(elementFactory)) } + } ?: getInsnArgTypes(insn, targetClass)?.toParameters(annotation) + } + + protected fun List.toParameters(context: PsiElement): List { + val elementFactory = JavaPsiFacade.getElementFactory(context.project) + return map { Parameter(null, it.toPsiType(elementFactory)) } } } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt index 791423584..a712dfcea 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt @@ -21,9 +21,12 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup +import com.demonwav.mcdev.platform.mixin.util.toPsiType import com.demonwav.mcdev.util.Parameter +import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType +import com.llamalad7.mixinextras.utils.Decorations import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode @@ -31,7 +34,8 @@ import org.objectweb.asm.tree.MethodNode class ModifyExpressionValueHandler : MixinExtrasInjectorAnnotationHandler() { override val supportedInstructionTypes = listOf( - InstructionType.METHOD_CALL, InstructionType.FIELD_GET, InstructionType.INSTANTIATION, InstructionType.CONSTANT + InstructionType.METHOD_CALL, InstructionType.FIELD_GET, InstructionType.INSTANTIATION, InstructionType.CONSTANT, + InstructionType.SIMPLE_EXPRESSION ) override fun extraTargetRestrictions(insn: AbstractInsnNode): Boolean { @@ -43,9 +47,23 @@ class ModifyExpressionValueHandler : MixinExtrasInjectorAnnotationHandler() { annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insn: AbstractInsnNode + target: TargetInsn ): Pair? { - val psiType = getPsiReturnType(insn, annotation) ?: return null + val psiType = getReturnType(target, annotation) ?: return null return ParameterGroup(listOf(Parameter("original", psiType))) to psiType } + + private fun getReturnType( + target: TargetInsn, + annotation: PsiAnnotation + ): PsiType? { + val psiReturnType = getPsiReturnType(target.insn, annotation) + val rawReturnType = getInsnReturnType(target.insn) + val exprType = target.getDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) + if (exprType != null && rawReturnType != exprType) { + // The expression knows more than the standard logic does. + return exprType.toPsiType(JavaPsiFacade.getElementFactory(annotation.project)) + } + return psiReturnType + } } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt index 0c3c3c564..1680f4541 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt @@ -44,9 +44,9 @@ class ModifyReceiverHandler : MixinExtrasInjectorAnnotationHandler() { annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insn: AbstractInsnNode + target: TargetInsn ): Pair? { - val params = getPsiParameters(insn, targetClass, annotation) ?: return null + val params = getPsiParameters(target.insn, targetClass, annotation) ?: return null return ParameterGroup(params) to params[0].type } } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt index 8c2706c33..2537fb8eb 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt @@ -25,7 +25,6 @@ import com.demonwav.mcdev.platform.mixin.util.getGenericReturnType import com.demonwav.mcdev.util.Parameter import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType -import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode @@ -36,8 +35,8 @@ class ModifyReturnValueHandler : MixinExtrasInjectorAnnotationHandler() { annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insn: AbstractInsnNode - ): Pair? { + target: TargetInsn + ): Pair { val returnType = targetMethod.getGenericReturnType(targetClass, annotation.project) return ParameterGroup(listOf(Parameter("original", returnType))) to returnType } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/TargetInsn.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/TargetInsn.kt new file mode 100644 index 000000000..1a03ff012 --- /dev/null +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/TargetInsn.kt @@ -0,0 +1,30 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.handlers.mixinextras + +import org.objectweb.asm.tree.AbstractInsnNode + +class TargetInsn(val insn: AbstractInsnNode, private val decorations: Map) { + fun hasDecoration(key: String) = key in decorations + + @Suppress("UNCHECKED_CAST") + fun getDecoration(key: String): T? = decorations[key] as T +} diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt index 516256102..c28fca7aa 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt @@ -22,20 +22,22 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup import com.demonwav.mcdev.platform.mixin.util.MixinConstants.MixinExtras.OPERATION +import com.demonwav.mcdev.platform.mixin.util.toPsiType import com.demonwav.mcdev.util.Parameter import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiElement import com.intellij.psi.PsiPrimitiveType import com.intellij.psi.PsiType -import org.objectweb.asm.tree.AbstractInsnNode +import com.llamalad7.mixinextras.utils.Decorations +import org.objectweb.asm.Type import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { override val supportedInstructionTypes = listOf( InstructionType.METHOD_CALL, InstructionType.FIELD_GET, InstructionType.FIELD_SET, InstructionType.INSTANCEOF, - InstructionType.INSTANTIATION + InstructionType.INSTANTIATION, InstructionType.SIMPLE_OPERATION ) override fun getAtKey(annotation: PsiAnnotation): String { @@ -46,16 +48,35 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insn: AbstractInsnNode + target: TargetInsn ): Pair? { - val params = getPsiParameters(insn, targetClass, annotation) ?: return null - val returnType = getPsiReturnType(insn, annotation) ?: return null + val params = getParameterTypes(target, targetClass, annotation) ?: return null + val returnType = getReturnType(target, annotation) ?: return null val operationType = getOperationType(annotation, returnType) ?: return null return ParameterGroup( params + Parameter("original", operationType) ) to returnType } + private fun getParameterTypes( + target: TargetInsn, + targetClass: ClassNode, + annotation: PsiAnnotation + ): List? { + getPsiParameters(target.insn, targetClass, annotation)?.let { return it } + val args = target.getDecoration>(Decorations.SIMPLE_OPERATION_ARGS) ?: return null + return args.toList().toParameters(annotation) + } + + private fun getReturnType( + target: TargetInsn, + annotation: PsiAnnotation + ): PsiType? { + getPsiReturnType(target.insn, annotation)?.let { return it } + val type = target.getDecoration(Decorations.SIMPLE_OPERATION_RETURN_TYPE) ?: return null + return type.toPsiType(JavaPsiFacade.getElementFactory(annotation.project)) + } + private fun getOperationType(context: PsiElement, type: PsiType): PsiType? { val project = context.project val boxedType = if (type is PsiPrimitiveType) { diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt index 8a92d6bc6..6edf8784d 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt @@ -42,9 +42,9 @@ class WrapWithConditionHandler : MixinExtrasInjectorAnnotationHandler() { annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insn: AbstractInsnNode + target: TargetInsn ): Pair? { - val params = getPsiParameters(insn, targetClass, annotation) ?: return null + val params = getPsiParameters(target.insn, targetClass, annotation) ?: return null return ParameterGroup(params) to PsiTypes.booleanType() } } From c5fdbd29d8389cb9e1011a80d50db23899c2cd23 Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 8 Mar 2024 17:12:20 +0000 Subject: [PATCH 024/100] Partially fix ME definition renaming --- src/main/grammars/MEExpressionParser.bnf | 7 +++-- .../expression/MEExpressionElementFactory.kt | 6 ---- .../mixin/expression/psi/MEExpressionFile.kt | 4 +-- .../psi/mixins/MEDeclarationMixin.kt | 2 +- .../psi/mixins/impl/MEDeclarationImplMixin.kt | 20 +++--------- .../reference/MEDefinitionReference.kt | 31 ++++++++++++++++--- 6 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index 2b8a47fb1..e15c35342 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -41,11 +41,14 @@ meExpressionFile ::= item* <> -item ::= declaration | statementItem +item ::= declarationItem | statementItem -declaration ::= TOKEN_CLASS TOKEN_IDENTIFIER { +declarationItem ::= TOKEN_CLASS declaration { pin = 1 extends = item +} + +declaration ::= TOKEN_IDENTIFIER { implements = [ "com.intellij.psi.PsiNamedElement" "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEDeclarationMixin" diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt index 98718a082..8a90e2fdc 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt @@ -21,7 +21,6 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclaration import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName @@ -43,11 +42,6 @@ class MEExpressionElementFactory(private val project: Project) { ) as MEExpressionFile } - fun createDeclaration(name: String): MEDeclaration { - return createFile("class $name").declarations.firstOrNull() - ?: throw IncorrectOperationException("'$name' is not a declaration") - } - fun createStatement(text: String): MEStatement { return createFile("do {$text}").statement ?: throw IncorrectOperationException("'$text' is not a statement") diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt index 35d3d3948..31e603712 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt @@ -22,7 +22,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi import com.demonwav.mcdev.platform.mixin.expression.MEExpressionFileType import com.demonwav.mcdev.platform.mixin.expression.MEExpressionLanguage -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclaration +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclarationItem import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEItem import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatementItem @@ -34,6 +34,6 @@ class MEExpressionFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvide override fun toString() = "MixinExtras Expression File" val items: Array get() = findChildrenByClass(MEItem::class.java) - val declarations: List get() = items.filterIsInstance() + val declarations: List get() = items.filterIsInstance() val statement: MEStatement? get() = items.asSequence().filterIsInstance().singleOrNull()?.statement } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationMixin.kt index adb2b8023..62c0f8bec 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationMixin.kt @@ -23,5 +23,5 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins import com.intellij.psi.PsiElement interface MEDeclarationMixin : PsiElement { - val nameIdentifier: PsiElement? + val nameIdentifier: PsiElement } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt index c23bf2e11..0f589ccc4 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt @@ -20,7 +20,6 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEItemImpl import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEDeclarationMixin @@ -29,22 +28,13 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiNamedElement abstract class MEDeclarationImplMixin(node: ASTNode) : MEItemImpl(node), MEDeclarationMixin, PsiNamedElement { - override fun getName() = nameIdentifier?.text + override fun getName(): String = nameIdentifier.text override fun setName(name: String): PsiElement { - val nameIdentifier = this.nameIdentifier - if (nameIdentifier != null) { - nameIdentifier.replace(project.meExpressionElementFactory.createIdentifier(name)) - return this - } else { - return replace(project.meExpressionElementFactory.createDeclaration(name)) - } + this.nameIdentifier.replace(project.meExpressionElementFactory.createIdentifier(name)) + return this } - override val nameIdentifier: PsiElement? - get() = node.findChildByType(MEExpressionTypes.TOKEN_IDENTIFIER)?.psi - - override fun getNavigationElement() = nameIdentifier ?: this - - override fun getTextOffset() = nameIdentifier?.textOffset ?: super.getTextOffset() + override val nameIdentifier: PsiElement + get() = firstChild } diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt index 336e65301..2d3da3648 100644 --- a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt +++ b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt @@ -21,21 +21,44 @@ package com.demonwav.mcdev.platform.mixin.expression.reference import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory import com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionFile +import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement -import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.PsiReference import com.intellij.psi.util.parentOfType +import com.intellij.util.IncorrectOperationException + +class MEDefinitionReference(private var name: MEName) : PsiReference { + override fun getElement() = name + + override fun getRangeInElement() = TextRange(0, name.textLength) -class MEDefinitionReference(name: MEName) : PsiReferenceBase(name) { override fun resolve(): PsiElement? { val file = element.parentOfType() ?: return null val name = element.text - for (declaration in file.declarations) { - if (declaration.name == name) { + for (declItem in file.declarations) { + val declaration = declItem.declaration + if (declaration?.name == name) { return declaration } } return null } + + override fun getCanonicalText(): String = name.text + + override fun handleElementRename(newElementName: String): PsiElement { + name = name.replace(name.project.meExpressionElementFactory.createName(newElementName)) as MEName + return name + } + + override fun bindToElement(element: PsiElement): PsiElement { + throw IncorrectOperationException() + } + + override fun isReferenceTo(element: PsiElement) = element.manager.areElementsEquivalent(element, resolve()) + + override fun isSoft() = false } From bdb3e9f961d7a018378a0c120fcb67b8b2ff51f1 Mon Sep 17 00:00:00 2001 From: joe Date: Sat, 9 Mar 2024 13:14:23 +0000 Subject: [PATCH 025/100] Attempt to get inplace rename refactoring to work (it doesn't) --- src/main/grammars/MEExpressionParser.bnf | 2 +- ...ationMixin.kt => MEExpressionRefactoringSupport.kt} | 8 +++++--- .../psi/mixins/impl/MEDeclarationImplMixin.kt | 10 ++++++---- src/main/resources/META-INF/plugin.xml | 1 + 4 files changed, 13 insertions(+), 8 deletions(-) rename src/main/kotlin/platform/mixin/expression/{psi/mixins/MEDeclarationMixin.kt => MEExpressionRefactoringSupport.kt} (69%) diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index e15c35342..91b000620 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -51,7 +51,7 @@ declarationItem ::= TOKEN_CLASS declaration { declaration ::= TOKEN_IDENTIFIER { implements = [ "com.intellij.psi.PsiNamedElement" - "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEDeclarationMixin" + "com.intellij.psi.PsiNameIdentifierOwner" ] mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEDeclarationImplMixin" } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationMixin.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionRefactoringSupport.kt similarity index 69% rename from src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationMixin.kt rename to src/main/kotlin/platform/mixin/expression/MEExpressionRefactoringSupport.kt index 62c0f8bec..234ef4f75 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionRefactoringSupport.kt @@ -18,10 +18,12 @@ * along with this program. If not, see . */ -package com.demonwav.mcdev.platform.mixin.expression.psi.mixins +package com.demonwav.mcdev.platform.mixin.expression +import com.intellij.lang.refactoring.RefactoringSupportProvider import com.intellij.psi.PsiElement -interface MEDeclarationMixin : PsiElement { - val nameIdentifier: PsiElement +class MEExpressionRefactoringSupport : RefactoringSupportProvider() { + // TODO inplace refactoring doesn't seem to work + override fun isInplaceRenameAvailable(element: PsiElement, context: PsiElement?) = false } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt index 0f589ccc4..b48e173ae 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt @@ -22,12 +22,13 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEItemImpl import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory -import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEDeclarationMixin import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiNamedElement +import com.intellij.psi.search.LocalSearchScope -abstract class MEDeclarationImplMixin(node: ASTNode) : MEItemImpl(node), MEDeclarationMixin, PsiNamedElement { +abstract class MEDeclarationImplMixin(node: ASTNode) : MEItemImpl(node), PsiNamedElement, PsiNameIdentifierOwner { override fun getName(): String = nameIdentifier.text override fun setName(name: String): PsiElement { @@ -35,6 +36,7 @@ abstract class MEDeclarationImplMixin(node: ASTNode) : MEItemImpl(node), MEDecla return this } - override val nameIdentifier: PsiElement - get() = firstChild + override fun getNameIdentifier(): PsiElement = firstChild + + override fun getUseScope() = containingFile?.let(::LocalSearchScope) ?: super.getUseScope() } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 864c4279f..eb00818eb 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -502,6 +502,7 @@ + From d8a45c8b5892a8457a5cdff98ef53bc3a0b5e30a Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 10 Mar 2024 17:39:33 +0000 Subject: [PATCH 026/100] MixinExtras: Use expression-suggested parameter names if they're present. (#2257) --- build.gradle.kts | 2 +- .../mixinextras/MixinExtrasInjectorAnnotationHandler.kt | 4 ++-- .../mixin/handlers/mixinextras/WrapOperationHandler.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 71c6d89bd..8e9fc4dfe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:debfdc8") + implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:e6450a2") implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt index fedb33055..86702813b 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt @@ -302,9 +302,9 @@ abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler( } ?: getInsnArgTypes(insn, targetClass)?.toParameters(annotation) } - protected fun List.toParameters(context: PsiElement): List { + protected fun List.toParameters(context: PsiElement, names: Array? = null): List { val elementFactory = JavaPsiFacade.getElementFactory(context.project) - return map { Parameter(null, it.toPsiType(elementFactory)) } + return mapIndexed { i, it -> Parameter(names?.getOrNull(i), it.toPsiType(elementFactory)) } } } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt index c28fca7aa..32e45ab3b 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt @@ -65,7 +65,7 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { ): List? { getPsiParameters(target.insn, targetClass, annotation)?.let { return it } val args = target.getDecoration>(Decorations.SIMPLE_OPERATION_ARGS) ?: return null - return args.toList().toParameters(annotation) + return args.toList().toParameters(annotation, target.getDecoration(Decorations.SIMPLE_OPERATION_PARAM_NAMES)) } private fun getReturnType( From 69ff93ff0b13f9687a7afcfd0a1e1f486bd827b4 Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 10 Mar 2024 19:36:05 +0000 Subject: [PATCH 027/100] Fix MEExpressionInjector. Rename refactoring works! --- .../mixin/expression/MEExpressionInjector.kt | 73 ++++++++++++------- .../MEExpressionRefactoringSupport.kt | 2 +- .../{ => psi}/MENameElementManipulator.kt | 3 +- .../expression/reference/MEReferenceUtil.kt | 36 --------- src/main/resources/META-INF/plugin.xml | 2 +- 5 files changed, 50 insertions(+), 66 deletions(-) rename src/main/kotlin/platform/mixin/expression/{ => psi}/MENameElementManipulator.kt (90%) delete mode 100644 src/main/kotlin/platform/mixin/expression/reference/MEReferenceUtil.kt diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt index 54a0f6a02..9914765f2 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt @@ -28,7 +28,6 @@ import com.intellij.lang.injection.MultiHostInjector import com.intellij.lang.injection.MultiHostRegistrar import com.intellij.openapi.util.Key import com.intellij.openapi.util.TextRange -import com.intellij.openapi.util.UserDataHolderEx import com.intellij.openapi.util.component1 import com.intellij.openapi.util.component2 import com.intellij.psi.ElementManipulators @@ -50,36 +49,52 @@ import com.intellij.util.SmartList class MEExpressionInjector : MultiHostInjector { companion object { private val ELEMENTS = listOf(PsiLiteralExpression::class.java) - private val PRIMARY_ELEMENT_KEY = Key.create("mcdev.anchorModCount") + private val ME_EXPRESSION_INJECTION = Key.create("mcdev.meExpressionInjection") + + private val CLASS_INJECTION_RESULT = + Class.forName("com.intellij.psi.impl.source.tree.injected.InjectionResult") + private val CLASS_INJECTION_REGISTRAR_IMPL = + Class.forName("com.intellij.psi.impl.source.tree.injected.InjectionRegistrarImpl") + @JvmStatic + private val METHOD_ADD_TO_RESULTS = + CLASS_INJECTION_REGISTRAR_IMPL.getDeclaredMethod("addToResults", CLASS_INJECTION_RESULT) + .also { it.isAccessible = true } + @JvmStatic + private val METHOD_GET_INJECTED_RESULT = + CLASS_INJECTION_REGISTRAR_IMPL.getDeclaredMethod("getInjectedResult") + .also { it.isAccessible = true } } - private data class PrimaryElement(val modCount: Long, val element: PsiElement) + private data class MEExpressionInjection(val modCount: Long, val injectionResult: Any) + + private fun shouldInjectIn(anchor: PsiElement): Boolean { + val nameValuePair = anchor.findContainingNameValuePair() ?: return false + return when (nameValuePair.name) { + "value", null -> nameValuePair.parentOfType() + ?.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION) == true + "id" -> nameValuePair.parentOfType() + ?.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) == true + else -> false + } + } override fun getLanguagesToInject(registrar: MultiHostRegistrar, context: PsiElement) { val project = context.project - val (anchor, operands) = JavaConcatenationToInjectorAdapter(project).computeAnchorAndOperands(context) + val (anchor, _) = JavaConcatenationToInjectorAdapter(project).computeAnchorAndOperands(context) - val nameValuePair = anchor.findContainingNameValuePair() ?: return - if (nameValuePair.name != "value" && nameValuePair.name != null) { - return - } - val expressionAnnotation = nameValuePair.parentOfType() ?: return - if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { + if (!shouldInjectIn(anchor)) { return } + val modifierList = anchor.findContainingModifierList() ?: return + val modCount = PsiModificationTracker.getInstance(project).modificationCount - val primaryElement = PrimaryElement(modCount, context) - val existingElement = (anchor as UserDataHolderEx).putUserDataIfAbsent(PRIMARY_ELEMENT_KEY, primaryElement) - if (existingElement !== primaryElement && - existingElement.modCount == modCount && - context != existingElement.element - ) { + val primaryElement = modifierList.getUserData(ME_EXPRESSION_INJECTION) + if (primaryElement != null && primaryElement.modCount == modCount) { + METHOD_ADD_TO_RESULTS.invoke(registrar, primaryElement.injectionResult) return } - val modifierList = expressionAnnotation.findContainingModifierList() ?: return - var isFrankenstein = false registrar.startInjecting(MEExpressionLanguage) @@ -98,17 +113,16 @@ class MEExpressionInjector : MultiHostInjector { isFrankenstein = true } } - } else if (annotation == expressionAnnotation) { + } else if (annotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { + val valueExpr = annotation.findDeclaredAttributeValue("value") ?: continue val places = mutableListOf>() - for (operand in operands) { - iterateConcatenation(operand) { op -> - if (op is PsiLanguageInjectionHost) { - for (textRange in getTextRanges(op)) { - places += op to textRange - } - } else { - isFrankenstein = true + iterateConcatenation(valueExpr) { op -> + if (op is PsiLanguageInjectionHost) { + for (textRange in getTextRanges(op)) { + places += op to textRange } + } else { + isFrankenstein = true } } if (places.isNotEmpty()) { @@ -124,6 +138,11 @@ class MEExpressionInjector : MultiHostInjector { registrar.doneInjecting() + modifierList.putUserData( + ME_EXPRESSION_INJECTION, + MEExpressionInjection(modCount, METHOD_GET_INJECTED_RESULT.invoke(registrar)) + ) + if (isFrankenstein) { InjectedLanguageUtil.putInjectedFileUserData( context, diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionRefactoringSupport.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionRefactoringSupport.kt index 234ef4f75..2294b4bdd 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionRefactoringSupport.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionRefactoringSupport.kt @@ -24,6 +24,6 @@ import com.intellij.lang.refactoring.RefactoringSupportProvider import com.intellij.psi.PsiElement class MEExpressionRefactoringSupport : RefactoringSupportProvider() { - // TODO inplace refactoring doesn't seem to work + // Inplace renaming doesn't work due to IDEA-348784 override fun isInplaceRenameAvailable(element: PsiElement, context: PsiElement?) = false } diff --git a/src/main/kotlin/platform/mixin/expression/MENameElementManipulator.kt b/src/main/kotlin/platform/mixin/expression/psi/MENameElementManipulator.kt similarity index 90% rename from src/main/kotlin/platform/mixin/expression/MENameElementManipulator.kt rename to src/main/kotlin/platform/mixin/expression/psi/MENameElementManipulator.kt index dc853beb0..ba971b0e2 100644 --- a/src/main/kotlin/platform/mixin/expression/MENameElementManipulator.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MENameElementManipulator.kt @@ -18,9 +18,10 @@ * along with this program. If not, see . */ -package com.demonwav.mcdev.platform.mixin.expression +package com.demonwav.mcdev.platform.mixin.expression.psi import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory import com.intellij.openapi.util.TextRange import com.intellij.psi.AbstractElementManipulator diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEReferenceUtil.kt b/src/main/kotlin/platform/mixin/expression/reference/MEReferenceUtil.kt deleted file mode 100644 index 5f292d475..000000000 --- a/src/main/kotlin/platform/mixin/expression/reference/MEReferenceUtil.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Minecraft Development for IntelliJ - * - * https://mcdev.io/ - * - * Copyright (C) 2024 minecraft-dev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, version 3.0 only. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.demonwav.mcdev.platform.mixin.expression.reference - -import com.demonwav.mcdev.platform.mixin.util.MixinConstants -import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiNameValuePair -import com.intellij.psi.util.parentOfType - -object MEReferenceUtil { - fun isDefinitionId(element: PsiElement): Boolean { - val parent = element.parent - return parent is PsiNameValuePair && - parent.name == "id" && - parent.parentOfType()?.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) == true - } -} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index eb00818eb..2807987ea 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -497,7 +497,7 @@ + implementationClass="com.demonwav.mcdev.platform.mixin.expression.psi.MENameElementManipulator"/> From 1838363cc83841ee4522f4e5731e1deb9498f6a6 Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 10 Mar 2024 21:39:54 +0000 Subject: [PATCH 028/100] Suppress deprecation warning --- .../kotlin/platform/mixin/expression/MEExpressionInjector.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt index 9914765f2..3ab9ddeef 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt @@ -38,7 +38,6 @@ import com.intellij.psi.PsiLanguageInjectionHost import com.intellij.psi.PsiLiteralExpression import com.intellij.psi.PsiParenthesizedExpression import com.intellij.psi.PsiPolyadicExpression -import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil import com.intellij.psi.impl.source.tree.injected.JavaConcatenationToInjectorAdapter import com.intellij.psi.util.PsiLiteralUtil import com.intellij.psi.util.PsiModificationTracker @@ -144,7 +143,8 @@ class MEExpressionInjector : MultiHostInjector { ) if (isFrankenstein) { - InjectedLanguageUtil.putInjectedFileUserData( + @Suppress("DEPRECATION") // no replacement for this method + com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil.putInjectedFileUserData( context, MEExpressionLanguage, InjectedLanguageManager.FRANKENSTEIN_INJECTION, From daffb7266ea3ab643abb674c510ce781742a76c5 Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 10 Mar 2024 22:46:03 +0000 Subject: [PATCH 029/100] ME expression `@Definition` find usages --- src/main/grammars/MEExpressionParser.bnf | 1 + .../mixin/expression/MEExpressionInjector.kt | 4 +- .../psi/mixins/impl/MEDeclarationImplMixin.kt | 13 ++++++- .../MEExpressionFindUsagesProvider.kt | 38 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 6 +-- 5 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/reference/MEExpressionFindUsagesProvider.kt diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index 91b000620..953e2fbff 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -52,6 +52,7 @@ declaration ::= TOKEN_IDENTIFIER { implements = [ "com.intellij.psi.PsiNamedElement" "com.intellij.psi.PsiNameIdentifierOwner" + "com.intellij.psi.NavigatablePsiElement" ] mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEDeclarationImplMixin" } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt index 3ab9ddeef..2d42b8c2e 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt @@ -104,7 +104,7 @@ class MEExpressionInjector : MultiHostInjector { iterateConcatenation(idExpr) { op -> if (op is PsiLanguageInjectionHost) { for (textRange in getTextRanges(op)) { - val prefix = " class ".takeIf { needsPrefix } + val prefix = "\nclass ".takeIf { needsPrefix } needsPrefix = false registrar.addPlace(prefix, null, op, textRange) } @@ -127,7 +127,7 @@ class MEExpressionInjector : MultiHostInjector { if (places.isNotEmpty()) { for ((i, place) in places.withIndex()) { val (host, range) = place - val prefix = " do { ".takeIf { i == 0 } + val prefix = "\ndo { ".takeIf { i == 0 } val suffix = " }".takeIf { i == places.size - 1 } registrar.addPlace(prefix, suffix, host, range) } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt index b48e173ae..ce2c9eb1d 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt @@ -20,15 +20,20 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl +import com.demonwav.mcdev.asset.PlatformAssets import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEItemImpl import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory import com.intellij.lang.ASTNode +import com.intellij.navigation.ItemPresentation +import com.intellij.psi.NavigatablePsiElement import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiNamedElement import com.intellij.psi.search.LocalSearchScope -abstract class MEDeclarationImplMixin(node: ASTNode) : MEItemImpl(node), PsiNamedElement, PsiNameIdentifierOwner { +abstract class MEDeclarationImplMixin( + node: ASTNode +) : MEItemImpl(node), PsiNamedElement, PsiNameIdentifierOwner, NavigatablePsiElement { override fun getName(): String = nameIdentifier.text override fun setName(name: String): PsiElement { @@ -39,4 +44,10 @@ abstract class MEDeclarationImplMixin(node: ASTNode) : MEItemImpl(node), PsiName override fun getNameIdentifier(): PsiElement = firstChild override fun getUseScope() = containingFile?.let(::LocalSearchScope) ?: super.getUseScope() + + override fun getPresentation() = object : ItemPresentation { + override fun getPresentableText() = name + + override fun getIcon(unused: Boolean) = PlatformAssets.MIXIN_ICON + } } diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEExpressionFindUsagesProvider.kt b/src/main/kotlin/platform/mixin/expression/reference/MEExpressionFindUsagesProvider.kt new file mode 100644 index 000000000..c20296b7c --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/reference/MEExpressionFindUsagesProvider.kt @@ -0,0 +1,38 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.reference + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclaration +import com.intellij.lang.findUsages.FindUsagesProvider +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNamedElement + +class MEExpressionFindUsagesProvider : FindUsagesProvider { + override fun canFindUsagesFor(psiElement: PsiElement) = psiElement is MEDeclaration + + override fun getHelpId(psiElement: PsiElement) = null + + override fun getType(element: PsiElement) = "Definition" + + override fun getDescriptiveName(element: PsiElement) = (element as? PsiNamedElement)?.name ?: "null" + + override fun getNodeText(element: PsiElement, useFullName: Boolean) = getDescriptiveName(element) +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 2807987ea..f5f1951e4 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -498,10 +498,8 @@ implementationClass="com.demonwav.mcdev.platform.mixin.expression.MEExpressionQuoteHandler"/> - - - - + From 37ef16c9757311233a5ec5bc4e59ea37830e8aff Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 10 Mar 2024 23:00:44 +0000 Subject: [PATCH 030/100] Fix/expressions int like types (#2261) * Mixin: Combine parameter and return type inspections. * MixinExtras: Offer a choice between all valid int-like types. * Mixin: Fix tests for handler signature inspection. --- .../MixinExtrasInjectorAnnotationHandler.kt | 86 +++++++- .../ModifyExpressionValueHandler.kt | 10 + .../mixinextras/WrapOperationHandler.kt | 13 ++ ...nvalidInjectorMethodSignatureInspection.kt | 205 +++++++++++++----- .../inspection/injector/MethodSignature.kt | 20 +- .../kotlin/platform/mixin/util/AsmUtil.kt | 5 + .../InvalidInjectorMethodSignatureFixTest.kt | 2 +- ...idInjectorMethodSignatureInspectionTest.kt | 6 +- 8 files changed, 280 insertions(+), 67 deletions(-) diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt index 86702813b..dfaf12b53 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt @@ -99,6 +99,10 @@ abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler( target: TargetInsn, ): Pair? + open fun intLikeTypePositions( + target: TargetInsn + ): List = emptyList() + override val allowCoerce = true override fun expectedMethodSignature( @@ -115,21 +119,75 @@ abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler( } val firstMatch = signatures[0] ?: return emptyList() if (signatures.drop(1).any { it != firstMatch }) return emptyList() - return listOf( - MethodSignature( - listOf( - firstMatch.first, - ParameterGroup( - collectTargetMethodParameters(annotation.project, targetClass, targetMethod), - required = ParameterGroup.RequiredLevel.OPTIONAL, - isVarargs = true, - ), - ), - firstMatch.second - ) + val intLikeTypePositions = insns.map { intLikeTypePositions(it) }.distinct().singleOrNull().orEmpty() + return allPossibleSignatures( + annotation, + targetClass, + targetMethod, + firstMatch.first, + firstMatch.second, + intLikeTypePositions ) } + private fun allPossibleSignatures( + annotation: PsiAnnotation, + targetClass: ClassNode, + targetMethod: MethodNode, + params: ParameterGroup, + returnType: PsiType, + intLikeTypePositions: List + ): List { + if (intLikeTypePositions.isEmpty()) { + return listOf( + makeSignature(annotation, targetClass, targetMethod, params, returnType, intLikeTypePositions) + ) + } + return buildList { + for (actualType in intLikePsiTypes) { + val newParams = params.parameters.toMutableList() + var newReturnType = returnType + for (pos in intLikeTypePositions) { + when (pos) { + is MethodSignature.TypePosition.Return -> newReturnType = actualType + is MethodSignature.TypePosition.Param -> + newParams[pos.index] = newParams[pos.index].copy(type = actualType) + } + } + add( + makeSignature( + annotation, + targetClass, + targetMethod, + ParameterGroup(newParams), + newReturnType, + intLikeTypePositions + ) + ) + } + } + } + + private fun makeSignature( + annotation: PsiAnnotation, + targetClass: ClassNode, + targetMethod: MethodNode, + params: ParameterGroup, + returnType: PsiType, + intLikeTypePositions: List + ) = MethodSignature( + listOf( + params, + ParameterGroup( + collectTargetMethodParameters(annotation.project, targetClass, targetMethod), + required = ParameterGroup.RequiredLevel.OPTIONAL, + isVarargs = true, + ), + ), + returnType, + intLikeTypePositions + ) + protected fun getInsnReturnType(insn: AbstractInsnNode): Type? { return when { insn is MethodInsnNode -> Type.getReturnType(insn.desc) @@ -365,3 +423,7 @@ private fun getConstantType(insn: AbstractInsnNode?): Type? { } } } + +private val intLikePsiTypes = listOf( + PsiTypes.intType(), PsiTypes.booleanType(), PsiTypes.charType(), PsiTypes.byteType(), PsiTypes.shortType() +) diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt index a712dfcea..ce56b3f4b 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras +import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup import com.demonwav.mcdev.platform.mixin.util.toPsiType import com.demonwav.mcdev.util.Parameter @@ -27,6 +28,7 @@ import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType import com.llamalad7.mixinextras.utils.Decorations +import com.llamalad7.mixinextras.utils.TypeUtils import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode @@ -53,6 +55,14 @@ class ModifyExpressionValueHandler : MixinExtrasInjectorAnnotationHandler() { return ParameterGroup(listOf(Parameter("original", psiType))) to psiType } + override fun intLikeTypePositions(target: TargetInsn): List { + val expressionType = target.getDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) + if (expressionType == TypeUtils.INTLIKE_TYPE) { + return listOf(MethodSignature.TypePosition.Return, MethodSignature.TypePosition.Param(0)) + } + return emptyList() + } + private fun getReturnType( target: TargetInsn, annotation: PsiAnnotation diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt index 32e45ab3b..27f889991 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras +import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup import com.demonwav.mcdev.platform.mixin.util.MixinConstants.MixinExtras.OPERATION import com.demonwav.mcdev.platform.mixin.util.toPsiType @@ -30,6 +31,7 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiPrimitiveType import com.intellij.psi.PsiType import com.llamalad7.mixinextras.utils.Decorations +import com.llamalad7.mixinextras.utils.TypeUtils import org.objectweb.asm.Type import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode @@ -58,6 +60,17 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { ) to returnType } + override fun intLikeTypePositions(target: TargetInsn) = buildList { + if (target.getDecoration(Decorations.SIMPLE_OPERATION_RETURN_TYPE) == TypeUtils.INTLIKE_TYPE) { + add(MethodSignature.TypePosition.Return) + } + target.getDecoration>(Decorations.SIMPLE_OPERATION_ARGS)?.forEachIndexed { i, it -> + if (it == TypeUtils.INTLIKE_TYPE) { + add(MethodSignature.TypePosition.Param(i)) + } + } + } + private fun getParameterTypes( target: TargetInsn, targetClass: ClassNode, diff --git a/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt b/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt index 5d636de7f..cd25a23a3 100644 --- a/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt +++ b/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt @@ -20,7 +20,6 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector -import com.demonwav.mcdev.platform.mixin.handlers.InjectAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection @@ -34,18 +33,34 @@ import com.demonwav.mcdev.platform.mixin.util.isConstructor import com.demonwav.mcdev.platform.mixin.util.isMixinExtrasSugar import com.demonwav.mcdev.util.Parameter import com.demonwav.mcdev.util.fullQualifiedName +import com.demonwav.mcdev.util.invokeLater import com.demonwav.mcdev.util.synchronize +import com.intellij.codeInsight.FileModificationService import com.intellij.codeInsight.intention.FileModifier.SafeFieldForPreview import com.intellij.codeInsight.intention.QuickFixFactory -import com.intellij.codeInspection.LocalQuickFix -import com.intellij.codeInspection.ProblemDescriptor +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.Template +import com.intellij.codeInsight.template.TemplateBuilderImpl +import com.intellij.codeInsight.template.TemplateManager +import com.intellij.codeInsight.template.TextResult +import com.intellij.codeInsight.template.impl.VariableNode +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange import com.intellij.psi.JavaElementVisitor import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.PsiFile import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifier import com.intellij.psi.PsiNameHelper @@ -56,6 +71,8 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.codeStyle.VariableKind import com.intellij.psi.util.PsiUtil import com.intellij.psi.util.TypeConversionUtil +import com.intellij.psi.util.parentOfType +import com.intellij.refactoring.suggested.startOffset import org.objectweb.asm.Opcodes class InvalidInjectorMethodSignatureInspection : MixinInspection() { @@ -165,45 +182,38 @@ class InvalidInjectorMethodSignatureInspection : MixinInspection() { } if (!isValid) { - val (expectedParameters, expectedReturnType) = possibleSignatures[0] + val (expectedParameters, expectedReturnType, intLikeTypePositions) = possibleSignatures[0] - val checkResult = checkParameters(parameters, expectedParameters, handler.allowCoerce) - if (checkResult != CheckResult.OK) { + val paramsCheck = checkParameters(parameters, expectedParameters, handler.allowCoerce) + val isWarning = paramsCheck == CheckResult.WARNING + val methodReturnType = method.returnType + val returnTypeOk = methodReturnType != null && + checkReturnType(expectedReturnType, methodReturnType, method, handler.allowCoerce) + val isError = paramsCheck == CheckResult.ERROR || !returnTypeOk + if (isWarning || isError) { reportedSignature = true val description = - "Method parameters do not match expected parameters for $annotationName" - val quickFix = ParametersQuickFix( - expectedParameters, - handler is InjectAnnotationHandler, + "Method signature does not match expected signature for $annotationName" + val quickFix = SignatureQuickFix( + method, + expectedParameters.takeUnless { paramsCheck == CheckResult.OK }, + expectedReturnType.takeUnless { returnTypeOk }, + intLikeTypePositions ) - if (checkResult == CheckResult.ERROR) { - holder.registerProblem(parameters, description, quickFix) - } else { - holder.registerProblem( - parameters, - description, - ProblemHighlightType.WARNING, - quickFix, - ) - } - } - - val methodReturnType = method.returnType - if (methodReturnType == null || - !checkReturnType(expectedReturnType, methodReturnType, method, handler.allowCoerce) - ) { - reportedSignature = true - + val highlightType = + if (isError) + ProblemHighlightType.GENERIC_ERROR_OR_WARNING + else + ProblemHighlightType.WARNING + val declarationStart = (method.returnTypeElement ?: identifier).startOffsetInParent + val declarationEnd = method.parameterList.textRangeInParent.endOffset holder.registerProblem( - method.returnTypeElement ?: identifier, - "Expected return type '${expectedReturnType.presentableText}' " + - "for $annotationName method", - QuickFixFactory.getInstance().createMethodReturnFix( - method, - expectedReturnType, - false, - ), + method, + description, + highlightType, + TextRange.create(declarationStart, declarationEnd), + quickFix ) } } @@ -283,22 +293,43 @@ class InvalidInjectorMethodSignatureInspection : MixinInspection() { OK, WARNING, ERROR } - private class ParametersQuickFix( + private class SignatureQuickFix( + method: PsiMethod, @SafeFieldForPreview - private val expected: List, - isInject: Boolean, - ) : LocalQuickFix { - - private val fixName = if (isInject) { - "Fix method parameters" - } else { - "Fix method parameters (won't keep captured locals)" - } + private val expectedParams: List?, + @SafeFieldForPreview + private val expectedReturnType: PsiType?, + private val intLikeTypePositions: List + ) : LocalQuickFixAndIntentionActionOnPsiElement(method) { + + private val fixName = "Fix method signature" override fun getFamilyName() = fixName - override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - val parameters = descriptor.psiElement as PsiParameterList + override fun getText() = familyName + + override fun startInWriteAction() = false + + override fun invoke( + project: Project, + file: PsiFile, + editor: Editor?, + startElement: PsiElement, + endElement: PsiElement, + ) { + if (!FileModificationService.getInstance().preparePsiElementForWrite(startElement)) { + return + } + val method = startElement as PsiMethod + fixParameters(project, method.parameterList) + fixReturnType(method) + fixIntLikeTypes(method, editor ?: return) + } + + private fun fixParameters(project: Project, parameters: PsiParameterList) { + if (expectedParams == null) { + return + } // We want to preserve captured locals val locals = parameters.parameters.dropWhile { val fqname = (it.type as? PsiClassType)?.fullQualifiedName ?: return@dropWhile true @@ -310,7 +341,7 @@ class InvalidInjectorMethodSignatureInspection : MixinInspection() { // We want to preserve sugars, and while we're at it, we might as well move them all to the end val sugars = parameters.parameters.filter { it.isMixinExtrasSugar } - val newParams = expected.flatMapTo(mutableListOf()) { + val newParams = expectedParams.flatMapTo(mutableListOf()) { if (it.default) { val nameHelper = PsiNameHelper.getInstance(project) val languageLevel = PsiUtil.getLanguageLevel(parameters) @@ -329,7 +360,81 @@ class InvalidInjectorMethodSignatureInspection : MixinInspection() { // Restore the captured locals and sugars before applying the fix newParams.addAll(locals) newParams.addAll(sugars) - parameters.synchronize(newParams) + runWriteAction { + parameters.synchronize(newParams) + } + } + + private fun fixReturnType(method: PsiMethod) { + if (expectedReturnType == null) { + return + } + QuickFixFactory.getInstance() + .createMethodReturnFix(method, expectedReturnType, false) + .applyFix() + } + + private fun fixIntLikeTypes(method: PsiMethod, editor: Editor) { + if (intLikeTypePositions.isEmpty()) { + return + } + invokeLater { + WriteCommandAction.runWriteCommandAction( + method.project, + "Choose Int-Like Type", + null, + { + val template = makeIntLikeTypeTemplate(method, intLikeTypePositions) + if (template != null) { + editor.caretModel.moveToOffset(method.startOffset) + TemplateManager.getInstance(method.project) + .startTemplate(editor, template) + } + }, + method.parentOfType()!! + ) + } + } + + private fun makeIntLikeTypeTemplate( + method: PsiMethod, + positions: List + ): Template? { + val builder = TemplateBuilderImpl(method) + builder.replaceElement( + positions.first().getElement(method) ?: return null, + "intliketype", + ChooseIntLikeTypeExpression(), + true + ) + for (pos in positions.drop(1)) { + builder.replaceElement( + pos.getElement(method) ?: return null, + VariableNode("intliketype", null), + false + ) + } + return builder.buildInlineTemplate() } } } + +private class ChooseIntLikeTypeExpression : Expression() { + private val lookupItems: Array = intLikeTypes.map(LookupElementBuilder::create).toTypedArray() + + override fun calculateLookupItems(context: ExpressionContext) = if (lookupItems.size > 1) lookupItems else null + + override fun calculateQuickResult(context: ExpressionContext) = calculateResult(context) + + override fun calculateResult(context: ExpressionContext) = TextResult("int") + + private companion object { + private val intLikeTypes = listOf( + "int", + "char", + "boolean", + "byte", + "short" + ) + } +} diff --git a/src/main/kotlin/platform/mixin/inspection/injector/MethodSignature.kt b/src/main/kotlin/platform/mixin/inspection/injector/MethodSignature.kt index 167782cbe..631e1acf9 100644 --- a/src/main/kotlin/platform/mixin/inspection/injector/MethodSignature.kt +++ b/src/main/kotlin/platform/mixin/inspection/injector/MethodSignature.kt @@ -20,6 +20,24 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector +import com.intellij.psi.PsiMethod import com.intellij.psi.PsiType +import com.intellij.psi.PsiTypeElement -data class MethodSignature(val parameters: List, val returnType: PsiType) +data class MethodSignature( + val parameters: List, + val returnType: PsiType, + val intLikeTypes: List = emptyList() +) { + sealed interface TypePosition { + fun getElement(method: PsiMethod): PsiTypeElement? + + data object Return : TypePosition { + override fun getElement(method: PsiMethod) = method.returnTypeElement + } + + data class Param(val index: Int) : TypePosition { + override fun getElement(method: PsiMethod) = method.parameterList.parameters[index].typeElement + } + } +} diff --git a/src/main/kotlin/platform/mixin/util/AsmUtil.kt b/src/main/kotlin/platform/mixin/util/AsmUtil.kt index da1de2c4a..8d2633831 100644 --- a/src/main/kotlin/platform/mixin/util/AsmUtil.kt +++ b/src/main/kotlin/platform/mixin/util/AsmUtil.kt @@ -67,11 +67,13 @@ import com.intellij.psi.PsiModifierList import com.intellij.psi.PsiParameter import com.intellij.psi.PsiParameterList import com.intellij.psi.PsiType +import com.intellij.psi.PsiTypes import com.intellij.psi.impl.compiled.ClsElementImpl import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiUtil import com.intellij.refactoring.util.LambdaRefactoringUtil import com.intellij.util.CommonJavaRefactoringUtil +import com.llamalad7.mixinextras.utils.TypeUtils import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import org.objectweb.asm.ClassReader @@ -129,6 +131,9 @@ private fun hasModifier(access: Int, @PsiModifier.ModifierConstant modifier: Str } fun Type.toPsiType(elementFactory: PsiElementFactory, context: PsiElement? = null): PsiType { + if (this == TypeUtils.INTLIKE_TYPE) { + return PsiTypes.intType() + } val javaClassName = className.replace("(\\$)(\\D)".toRegex()) { "." + it.groupValues[2] } return elementFactory.createTypeFromText(javaClassName, context) } diff --git a/src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureFixTest.kt b/src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureFixTest.kt index ae88d95a5..dfa58058f 100644 --- a/src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureFixTest.kt +++ b/src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureFixTest.kt @@ -33,7 +33,7 @@ class InvalidInjectorMethodSignatureFixTest : BaseMixinTest() { private fun doTest(testName: String) { fixture.enableInspections(InvalidInjectorMethodSignatureInspection::class) - testInspectionFix(fixture, "invalidInjectorMethodSignature/$testName", "Fix method parameters") + testInspectionFix(fixture, "invalidInjectorMethodSignature/$testName", "Fix method signature") } @Test diff --git a/src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureInspectionTest.kt b/src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureInspectionTest.kt index 769b3894c..ae97fb26e 100644 --- a/src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureInspectionTest.kt +++ b/src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureInspectionTest.kt @@ -98,7 +98,7 @@ class InvalidInjectorMethodSignatureInspectionTest : BaseMixinTest() { } @Inject(method = "(Lcom/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureInspection/MixedInOuter;Ljava/lang/String;)V", at = @At("RETURN")) - private void injectCtor(String string, CallbackInfo ci) { + private void injectCtor(String string, CallbackInfo ci) { } } """, @@ -122,7 +122,7 @@ class InvalidInjectorMethodSignatureInspectionTest : BaseMixinTest() { public class TestMixin { @Inject(method = "()V", at = @At("RETURN")) - private void injectCtorWrong(MixedInOuter outer, CallbackInfo ci) { + private void injectCtorWrong(MixedInOuter outer, CallbackInfo ci) { } @Inject(method = "", at = @At("RETURN")) @@ -130,7 +130,7 @@ class InvalidInjectorMethodSignatureInspectionTest : BaseMixinTest() { } @Inject(method = "(Ljava/lang/String;)V", at = @At("RETURN")) - private void injectCtor(MixedInOuter outer, String string, CallbackInfo ci) { + private void injectCtor(MixedInOuter outer, String string, CallbackInfo ci) { } @Inject(method = "(Ljava/lang/String;)V", at = @At("RETURN")) From 8c2eea10a989a97c366cf3f2eb1034df87fde540 Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 11 Mar 2024 14:39:42 +0000 Subject: [PATCH 031/100] Add simple keyword completion to ME expressions --- src/main/grammars/MEExpressionParser.bnf | 29 ++-- .../mixin/expression/MEExpressionAnnotator.kt | 4 +- .../MEExpressionCompletionContributor.kt | 152 ++++++++++++++++++ .../expression/psi/MEExpressionParserUtil.kt | 45 ++++++ .../mixin/expression/psi/METypeUtil.kt | 48 +++++- .../psi/mixins/impl/MEStatementImplMixin.kt | 33 ++++ src/main/resources/META-INF/plugin.xml | 2 + 7 files changed, 300 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/MEExpressionParserUtil.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStatementImplMixin.kt diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index 953e2fbff..8bb954645 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -21,6 +21,7 @@ { parserClass="com.demonwav.mcdev.platform.mixin.expression.gen.MEExpressionParser" extends="com.intellij.extapi.psi.ASTWrapperPsiElement" + parserImports = ["static com.demonwav.mcdev.platform.mixin.expression.psi.MEExpressionParserUtil.*"] psiClassPrefix="ME" psiImplClassSuffix="Impl" @@ -62,11 +63,15 @@ statementItem ::= TOKEN_DO TOKEN_LEFT_BRACE statement TOKEN_RIGHT_BRACE { extends = item } +private statementRecover ::= !TOKEN_RIGHT_BRACE + statement ::= assignStatement | returnStatement | throwStatement | expressionStatement { implements = "com.demonwav.mcdev.platform.mixin.expression.psi.MEMatchableElement" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEStatementImplMixin" + recoverWhile = statementRecover } assignStatement ::= assignableExpression TOKEN_ASSIGN expression { @@ -123,17 +128,21 @@ expression ::= capturingExpression | recoverWhile = exprRecover } -capturingExpression ::= TOKEN_AT TOKEN_LEFT_PAREN expression TOKEN_RIGHT_PAREN { +external rightParen ::= parseToRightBracket exprRecover TOKEN_RIGHT_PAREN +external rightBracket ::= parseToRightBracket exprRecover TOKEN_RIGHT_BRACKET +external rightBrace ::= parseToRightBracket exprRecover TOKEN_RIGHT_BRACE + +capturingExpression ::= TOKEN_AT TOKEN_LEFT_PAREN expression rightParen { pin = 1 mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MECapturingExpressionImplMixin" } -parenthesizedExpression ::= TOKEN_LEFT_PAREN expression TOKEN_RIGHT_PAREN { +parenthesizedExpression ::= TOKEN_LEFT_PAREN expression rightParen { pin = 1 mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEParenthesizedExpressionImplMixin" } -superCallExpression ::= TOKEN_SUPER TOKEN_DOT name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { +superCallExpression ::= TOKEN_SUPER TOKEN_DOT name TOKEN_LEFT_PAREN arguments rightParen { pin = 1 mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MESuperCallExpressionImplMixin" methods = [ @@ -141,7 +150,7 @@ superCallExpression ::= TOKEN_SUPER TOKEN_DOT name TOKEN_LEFT_PAREN arguments TO ] } -methodCallExpression ::= expression TOKEN_DOT name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { +methodCallExpression ::= expression TOKEN_DOT name TOKEN_LEFT_PAREN arguments rightParen { pin = 4 mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEMethodCallExpressionImplMixin" methods = [ @@ -150,7 +159,7 @@ methodCallExpression ::= expression TOKEN_DOT name TOKEN_LEFT_PAREN arguments TO ] } -staticMethodCallExpression ::= name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { +staticMethodCallExpression ::= name TOKEN_LEFT_PAREN arguments rightParen { pin = 2 mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEStaticMethodCallExpressionImplMixin" methods = [ @@ -158,7 +167,7 @@ staticMethodCallExpression ::= name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN ] } -arrayAccessExpression ::= expression TOKEN_LEFT_BRACKET expression? TOKEN_RIGHT_BRACKET { +arrayAccessExpression ::= expression TOKEN_LEFT_BRACKET expression? rightBracket { pin = 2 implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEArrayAccessExpressionMixin" mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEArrayAccessExpressionImplMixin" @@ -196,7 +205,7 @@ castExpression ::= parenthesizedExpression expression { mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MECastExpressionImplMixin" } -instantiationExpression ::= TOKEN_NEW name TOKEN_LEFT_PAREN arguments TOKEN_RIGHT_PAREN { +instantiationExpression ::= TOKEN_NEW name TOKEN_LEFT_PAREN arguments rightParen { rightAssociative = true pin = 3 mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEInstantiationExpressionImplMixin" @@ -232,9 +241,9 @@ private comparisonOp ::= TOKEN_LT | TOKEN_LE | TOKEN_GT | TOKEN_GE private equalityOp ::= TOKEN_EQ | TOKEN_NE newArrayExpression ::= TOKEN_NEW name - TOKEN_LEFT_BRACKET expression? TOKEN_RIGHT_BRACKET - ( TOKEN_LEFT_BRACKET expression? TOKEN_RIGHT_BRACKET )* - ( TOKEN_LEFT_BRACE arguments TOKEN_RIGHT_BRACE )? { + TOKEN_LEFT_BRACKET expression? rightBracket + ( TOKEN_LEFT_BRACKET expression? rightBracket )* + ( TOKEN_LEFT_BRACE arguments rightBrace )? { pin = 3 implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENewArrayExpressionMixin" mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MENewArrayExpressionImplMixin" diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index f159e7232..ffb7ed25c 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -91,7 +91,7 @@ class MEExpressionAnnotator : Annotator { .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CALL) .create() is MENameExpression -> { - if (METypeUtil.isExpressionInTypePosition(parent)) { + if (METypeUtil.isExpressionDirectlyInTypePosition(parent)) { holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) .range(element) .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) @@ -144,7 +144,7 @@ class MEExpressionAnnotator : Annotator { } } is MEArrayAccessExpression -> { - if (METypeUtil.isExpressionInTypePosition(element)) { + if (METypeUtil.isExpressionDirectlyInTypePosition(element)) { val indexExpr = element.indexExpr if (indexExpr != null) { holder.newAnnotation( diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt new file mode 100644 index 000000000..600b8796b --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt @@ -0,0 +1,152 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MELitExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatementItem +import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil.notInTypePosition +import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil.validType +import com.intellij.codeInsight.TailType +import com.intellij.codeInsight.completion.BasicExpressionCompletionContributor +import com.intellij.codeInsight.completion.CompletionContributor +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionProvider +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.codeInsight.completion.CompletionType +import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.TailTypeDecorator +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.patterns.StandardPatterns.and +import com.intellij.patterns.StandardPatterns.not +import com.intellij.psi.tree.TokenSet +import com.intellij.util.ProcessingContext + +class MEExpressionCompletionContributor : CompletionContributor() { + companion object { + private val NORMAL_ELEMENT = psiElement() + .inside(MEStatement::class.java) + .andNot(psiElement().inside(MELitExpression::class.java)) + .notInTypePosition() + private val TYPE_PATTERN = psiElement() + .inside(MEStatement::class.java) + .validType() + private val AFTER_END_EXPRESSION_PATTERN = psiElement().afterLeaf( + psiElement().withElementType( + TokenSet.create( + MEExpressionTypes.TOKEN_IDENTIFIER, + MEExpressionTypes.TOKEN_WILDCARD, + MEExpressionTypes.TOKEN_RIGHT_PAREN, + MEExpressionTypes.TOKEN_RIGHT_BRACKET, + MEExpressionTypes.TOKEN_RIGHT_BRACE, + MEExpressionTypes.TOKEN_BOOL_LIT, + MEExpressionTypes.TOKEN_CLASS, + MEExpressionTypes.TOKEN_INT_LIT, + MEExpressionTypes.TOKEN_DEC_LIT, + MEExpressionTypes.TOKEN_NULL_LIT, + MEExpressionTypes.TOKEN_STRING_TERMINATOR, + ) + ) + ) + + private val STATEMENT_KEYWORD_PLACE = psiElement().afterLeaf( + psiElement().withText("{").withParent(MEStatementItem::class.java) + ) + private val VALUE_KEYWORD_PLACE = and( + NORMAL_ELEMENT, + not(AFTER_END_EXPRESSION_PATTERN), + not(psiElement().afterLeaf(".")), + ) + private val CLASS_PLACE = and( + NORMAL_ELEMENT, + psiElement() + .afterLeaf(psiElement().withText(".").withParent(psiElement().withFirstChild(TYPE_PATTERN))), + ) + private val INSTANCEOF_PLACE = and( + NORMAL_ELEMENT, + AFTER_END_EXPRESSION_PATTERN, + ) + } + + init { + extend( + CompletionType.BASIC, + STATEMENT_KEYWORD_PLACE, + KeywordCompletionProvider( + Keyword("return", TailType.INSERT_SPACE), + Keyword("throw", TailType.INSERT_SPACE), + ) + ) + extend( + CompletionType.BASIC, + VALUE_KEYWORD_PLACE, + KeywordCompletionProvider( + Keyword("this"), + Keyword("super"), + Keyword("true"), + Keyword("false"), + Keyword("null"), + Keyword("new", TailType.INSERT_SPACE), + ) + ) + extend( + CompletionType.BASIC, + CLASS_PLACE, + KeywordCompletionProvider( + Keyword("class") + ) + ) + extend( + CompletionType.BASIC, + INSTANCEOF_PLACE, + KeywordCompletionProvider( + Keyword("instanceof", TailType.INSERT_SPACE) + ) + ) + } + + private class KeywordCompletionProvider( + private vararg val keywords: Keyword, + ) : CompletionProvider() { + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + result.addAllElements( + keywords.map { keyword -> + var lookupItem = + BasicExpressionCompletionContributor.createKeywordLookupItem(parameters.position, keyword.name) + if (keyword.tailType != TailType.NONE) { + lookupItem = object : TailTypeDecorator(lookupItem) { + override fun computeTailType(context: InsertionContext?) = keyword.tailType + } + } + lookupItem + } + ) + } + } + + private class Keyword(val name: String, val tailType: TailType = TailType.NONE) +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionParserUtil.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionParserUtil.kt new file mode 100644 index 000000000..2b6c41702 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionParserUtil.kt @@ -0,0 +1,45 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +@file:JvmName("MEExpressionParserUtil") + +package com.demonwav.mcdev.platform.mixin.expression.psi + +import com.intellij.lang.PsiBuilder +import com.intellij.lang.parser.GeneratedParserUtilBase.* // ktlint-disable no-wildcard-imports + +fun parseToRightBracket( + builder: PsiBuilder, + level: Int, + recoverParser: Parser, + rightBracketParser: Parser +): Boolean { + recursion_guard_(builder, level, "parseToRightBracket") + + // continue over any stuff inside the brackets as error elements. We need to find our precious right bracket. + var marker = enter_section_(builder, level, _NONE_) + exit_section_(builder, level, marker, false, false, recoverParser) + + // consume our right bracket. + marker = enter_section_(builder) + val result = rightBracketParser.parse(builder, level) + exit_section_(builder, marker, null, result) + return result +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt b/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt index 4991c992c..7da2e02ee 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt @@ -27,9 +27,14 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEParenthesizedExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory +import com.intellij.patterns.ObjectPattern +import com.intellij.patterns.PatternCondition import com.intellij.psi.PsiElement +import com.intellij.psi.util.parentOfType +import com.intellij.util.ProcessingContext object METypeUtil { fun convertExpressionToType(expr: MEExpression): METype? { @@ -56,7 +61,7 @@ object METypeUtil { } } - fun isExpressionInTypePosition(expr: MEExpression): Boolean { + fun isExpressionDirectlyInTypePosition(expr: MEExpression): Boolean { var e: PsiElement? = expr while (e != null) { val parent = e.parent @@ -76,4 +81,45 @@ object METypeUtil { return false } + + fun isExpressionInTypePosition(expr: MEExpression): Boolean { + var e: PsiElement? = expr + while (e != null) { + val parent = e.parent + when (parent) { + is MEParenthesizedExpression -> { + val grandparent = parent.parent + if (grandparent is MECastExpression && e == grandparent.castTypeExpr) { + return true + } + } + is MEBinaryExpression -> { + if (parent.operator == MEExpressionTypes.TOKEN_INSTANCEOF && e == parent.rightExpr) { + return true + } + } + is MEStatement -> return false + } + e = parent + } + + return false + } + + fun > ObjectPattern.inTypePosition(): Self = + with(InTypePositionCondition) + fun > ObjectPattern.notInTypePosition(): Self = + without(InTypePositionCondition) + fun > ObjectPattern.validType(): Self = + with(ValidTypeCondition) + + private object InTypePositionCondition : PatternCondition("inTypePosition") { + override fun accepts(t: PsiElement, context: ProcessingContext?) = + t.parentOfType()?.let(::isExpressionInTypePosition) == true + } + + private object ValidTypeCondition : PatternCondition("validType") { + override fun accepts(t: PsiElement, context: ProcessingContext?) = + t.parentOfType(withSelf = true)?.let(::isExpressionValidType) == true + } } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStatementImplMixin.kt new file mode 100644 index 000000000..97e49fe34 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStatementImplMixin.kt @@ -0,0 +1,33 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.psi.MEMatchableElement +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement + +abstract class MEStatementImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MEMatchableElement { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + throw UnsupportedOperationException("Please implement matchesJava for your statement type") + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f5f1951e4..b4963aa69 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -501,6 +501,8 @@ + From 6b83a7c9cb41a07456ee0ea058d5cb84cc2d4aea Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 11 Mar 2024 14:45:11 +0000 Subject: [PATCH 032/100] Why didn't my local ktlint tell me about these --- src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt b/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt index 7da2e02ee..95c047703 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/METypeUtil.kt @@ -106,11 +106,11 @@ object METypeUtil { return false } - fun > ObjectPattern.inTypePosition(): Self = + fun > ObjectPattern.inTypePosition(): Self = with(InTypePositionCondition) - fun > ObjectPattern.notInTypePosition(): Self = + fun > ObjectPattern.notInTypePosition(): Self = without(InTypePositionCondition) - fun > ObjectPattern.validType(): Self = + fun > ObjectPattern.validType(): Self = with(ValidTypeCondition) private object InTypePositionCondition : PatternCondition("inTypePosition") { From 8665190962c5a4446a274fafc18503c9b21198dc Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 11 Mar 2024 16:22:27 +0000 Subject: [PATCH 033/100] Store whether a declaration is a type in the ME PSI --- src/main/grammars/MEExpressionParser.bnf | 6 +++- .../mixin/expression/MEExpressionAnnotator.kt | 18 ++--------- .../mixin/expression/MEExpressionInjector.kt | 3 +- .../psi/mixins/MEDeclarationItemMixin.kt | 27 ++++++++++++++++ .../mixins/impl/MEDeclarationItemImplMixin.kt | 32 +++++++++++++++++++ 5 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationItemMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationItemImplMixin.kt diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index 8bb954645..903b2599d 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -44,9 +44,13 @@ meExpressionFile ::= item* <> item ::= declarationItem | statementItem -declarationItem ::= TOKEN_CLASS declaration { +declarationItem ::= TOKEN_CLASS TOKEN_BOOL_LIT declaration { pin = 1 extends = item + implements = [ + "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEDeclarationItemMixin" + ] + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEDeclarationItemImplMixin" } declaration ::= TOKEN_IDENTIFIER { diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index ffb7ed25c..2ff10c913 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -24,6 +24,7 @@ import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEBinaryExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclaration +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclarationItem import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEInstantiationExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MELitExpression @@ -36,30 +37,17 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallEx import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MESuperCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil -import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.Annotator import com.intellij.lang.annotation.HighlightSeverity -import com.intellij.lang.injection.InjectedLanguageManager -import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiElement -import com.intellij.psi.util.parentOfType class MEExpressionAnnotator : Annotator { override fun annotate(element: PsiElement, holder: AnnotationHolder) { when (element) { is MEDeclaration -> { - val nameIdentifier = element.nameIdentifier ?: return - val injectManager = InjectedLanguageManager.getInstance(element.project) - val hostFile = injectManager.getInjectionHost(element)?.containingFile ?: return - val injectionHost = hostFile - .findElementAt(injectManager.injectedToHost(element, nameIdentifier.textOffset, false)) - ?: return - val declarationAnnotation = injectionHost.parentOfType() ?: return - if (!declarationAnnotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { - return - } - if (declarationAnnotation.findDeclaredAttributeValue("type") != null) { + val parent = element.parent as? MEDeclarationItem ?: return + if (parent.isType) { holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) .range(element) .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_TYPE_DECLARATION) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt index 2d42b8c2e..b832e932e 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt @@ -100,11 +100,12 @@ class MEExpressionInjector : MultiHostInjector { for (annotation in modifierList.annotations) { if (annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { val idExpr = annotation.findDeclaredAttributeValue("id") ?: continue + val isType = annotation.findDeclaredAttributeValue("type") != null var needsPrefix = true iterateConcatenation(idExpr) { op -> if (op is PsiLanguageInjectionHost) { for (textRange in getTextRanges(op)) { - val prefix = "\nclass ".takeIf { needsPrefix } + val prefix = "\nclass $isType ".takeIf { needsPrefix } needsPrefix = false registrar.addPlace(prefix, null, op, textRange) } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationItemMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationItemMixin.kt new file mode 100644 index 000000000..cb8d52136 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MEDeclarationItemMixin.kt @@ -0,0 +1,27 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins + +import com.intellij.psi.PsiElement + +interface MEDeclarationItemMixin : PsiElement { + val isType: Boolean +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationItemImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationItemImplMixin.kt new file mode 100644 index 000000000..fbd21db66 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationItemImplMixin.kt @@ -0,0 +1,32 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEDeclarationItemMixin +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement + +abstract class MEDeclarationItemImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MEDeclarationItemMixin { + override val isType: Boolean + get() = findChildByType(MEExpressionTypes.TOKEN_BOOL_LIT)?.text == "true" +} From 946a2ad2b1828182c9777f23c8d11bf6345a7755 Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 11 Mar 2024 22:53:34 +0000 Subject: [PATCH 034/100] Add completions for items that already have a definition --- .../mixin/expression/psi/MEExpressionFile.kt | 2 ++ .../psi/mixins/impl/MEDeclarationImplMixin.kt | 12 +++++++++++- .../expression/reference/MEDefinitionReference.kt | 6 ++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt index 31e603712..07f1363c4 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi +import com.demonwav.mcdev.asset.PlatformAssets import com.demonwav.mcdev.platform.mixin.expression.MEExpressionFileType import com.demonwav.mcdev.platform.mixin.expression.MEExpressionLanguage import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclarationItem @@ -32,6 +33,7 @@ import com.intellij.psi.FileViewProvider class MEExpressionFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, MEExpressionLanguage) { override fun getFileType() = MEExpressionFileType override fun toString() = "MixinExtras Expression File" + override fun getIcon(flags: Int) = PlatformAssets.MIXIN_ICON val items: Array get() = findChildrenByClass(MEItem::class.java) val declarations: List get() = items.filterIsInstance() diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt index ce2c9eb1d..6201fbb9c 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEDeclarationImplMixin.kt @@ -21,15 +21,19 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl import com.demonwav.mcdev.asset.PlatformAssets +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclarationItem import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEItemImpl import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory import com.intellij.lang.ASTNode import com.intellij.navigation.ItemPresentation +import com.intellij.openapi.util.Iconable import com.intellij.psi.NavigatablePsiElement import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiNamedElement import com.intellij.psi.search.LocalSearchScope +import com.intellij.util.PlatformIcons +import javax.swing.Icon abstract class MEDeclarationImplMixin( node: ASTNode @@ -48,6 +52,12 @@ abstract class MEDeclarationImplMixin( override fun getPresentation() = object : ItemPresentation { override fun getPresentableText() = name - override fun getIcon(unused: Boolean) = PlatformAssets.MIXIN_ICON + override fun getIcon(unused: Boolean) = this@MEDeclarationImplMixin.getIcon(Iconable.ICON_FLAG_VISIBILITY) + } + + override fun getIcon(flags: Int): Icon = if ((parent as? MEDeclarationItem)?.isType == true) { + PlatformIcons.CLASS_ICON + } else { + PlatformAssets.MIXIN_ICON } } diff --git a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt index 2d3da3648..2b281f093 100644 --- a/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt +++ b/src/main/kotlin/platform/mixin/expression/reference/MEDefinitionReference.kt @@ -27,6 +27,7 @@ import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import com.intellij.psi.PsiReference import com.intellij.psi.util.parentOfType +import com.intellij.util.ArrayUtilRt import com.intellij.util.IncorrectOperationException class MEDefinitionReference(private var name: MEName) : PsiReference { @@ -61,4 +62,9 @@ class MEDefinitionReference(private var name: MEName) : PsiReference { override fun isReferenceTo(element: PsiElement) = element.manager.areElementsEquivalent(element, resolve()) override fun isSoft() = false + + override fun getVariants(): Array { + return (name.containingFile as? MEExpressionFile)?.declarations?.mapNotNull { it.declaration }?.toTypedArray() + ?: ArrayUtilRt.EMPTY_OBJECT_ARRAY + } } From 43cc4c9195c484c3e37c0d072c09ddbb1d460a71 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 12 Mar 2024 17:45:24 +0000 Subject: [PATCH 035/100] Extract some ME expression matching into its own class, and cache some more things --- build.gradle.kts | 2 +- .../expression/MEExpressionElementFactory.kt | 2 +- .../mixin/expression/MEExpressionMatchUtil.kt | 242 ++++++++++++++++++ .../mixin/expression/psi/MEExpressionFile.kt | 2 +- .../mixinextras/ExpressionInjectionPoint.kt | 133 ++-------- .../insight/MixinTargetLineMarkerProvider.kt | 7 +- .../kotlin/platform/mixin/util/AsmDfaUtil.kt | 4 +- .../kotlin/platform/mixin/util/AsmUtil.kt | 133 ++++++---- .../mixin/util/UnsafeCachedValueCapture.kt | 28 ++ src/main/kotlin/util/psi-utils.kt | 42 +++ 10 files changed, 428 insertions(+), 167 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt create mode 100644 src/main/kotlin/platform/mixin/util/UnsafeCachedValueCapture.kt diff --git a/build.gradle.kts b/build.gradle.kts index 8e9fc4dfe..71d858a2a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:e6450a2") + implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:2e9ca3f") implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt index 8a90e2fdc..1f93df64b 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionElementFactory.kt @@ -43,7 +43,7 @@ class MEExpressionElementFactory(private val project: Project) { } fun createStatement(text: String): MEStatement { - return createFile("do {$text}").statement + return createFile("do {$text}").statements.singleOrNull() ?: throw IncorrectOperationException("'$text' is not a statement") } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt new file mode 100644 index 000000000..3b8565ddd --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -0,0 +1,242 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor +import com.demonwav.mcdev.platform.mixin.util.LocalInfo +import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.platform.mixin.util.cached +import com.demonwav.mcdev.util.constantStringValue +import com.demonwav.mcdev.util.descriptor +import com.demonwav.mcdev.util.findAnnotations +import com.demonwav.mcdev.util.resolveType +import com.demonwav.mcdev.util.resolveTypeArray +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.module.Module +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.RecursionManager +import com.intellij.psi.PsiModifierList +import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression +import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter +import com.llamalad7.mixinextras.expression.impl.flow.FlowValue +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext +import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool +import java.util.Collections +import java.util.IdentityHashMap +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.tree.VarInsnNode +import org.objectweb.asm.tree.analysis.Analyzer + +typealias IdentifierPoolFactory = (MethodNode) -> IdentifierPool +typealias FlowMap = Map + +object MEExpressionMatchUtil { + private val LOGGER = logger() + + fun getFlowMap(project: Project, classIn: ClassNode, methodIn: MethodNode): FlowMap? { + if (methodIn.instructions == null) { + return null + } + + return methodIn.cached(classIn, project) { classNode, methodNode -> + val interpreter = object : FlowInterpreter(classNode, methodNode) { + override fun newValue(type: Type?): FlowValue? { + ProgressManager.checkCanceled() + return super.newValue(type) + } + + override fun newOperation(insn: AbstractInsnNode?): FlowValue? { + ProgressManager.checkCanceled() + return super.newOperation(insn) + } + + override fun copyOperation(insn: AbstractInsnNode?, value: FlowValue?): FlowValue? { + ProgressManager.checkCanceled() + return super.copyOperation(insn, value) + } + + override fun unaryOperation(insn: AbstractInsnNode?, value: FlowValue?): FlowValue? { + ProgressManager.checkCanceled() + return super.unaryOperation(insn, value) + } + + override fun binaryOperation( + insn: AbstractInsnNode?, + value1: FlowValue?, + value2: FlowValue? + ): FlowValue? { + ProgressManager.checkCanceled() + return super.binaryOperation(insn, value1, value2) + } + + override fun ternaryOperation( + insn: AbstractInsnNode?, + value1: FlowValue?, + value2: FlowValue?, + value3: FlowValue? + ): FlowValue? { + ProgressManager.checkCanceled() + return super.ternaryOperation(insn, value1, value2, value3) + } + + override fun naryOperation(insn: AbstractInsnNode?, values: MutableList?): FlowValue? { + ProgressManager.checkCanceled() + return super.naryOperation(insn, values) + } + + override fun returnOperation(insn: AbstractInsnNode?, value: FlowValue?, expected: FlowValue?) { + ProgressManager.checkCanceled() + super.returnOperation(insn, value, expected) + } + + override fun merge(value1: FlowValue?, value2: FlowValue?): FlowValue? { + ProgressManager.checkCanceled() + return super.merge(value1, value2) + } + } + + try { + Analyzer(interpreter).analyze(classNode.name, methodNode) + } catch (e: RuntimeException) { + if (e is ProcessCanceledException) { + throw e + } + LOGGER.warn("MEExpressionMatchUtil.getFlowMap failed", e) + return@cached null + } + + interpreter.finish() + } + } + + fun createIdentifierPoolFactory( + module: Module, + targetClass: ClassNode, + modifierList: PsiModifierList, + ): IdentifierPoolFactory = { targetMethod -> + val pool = IdentifierPool() + + for (annotation in modifierList.annotations) { + if (!annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { + continue + } + + val definitionId = annotation.findDeclaredAttributeValue("id")?.constantStringValue ?: "" + + val ats = annotation.findDeclaredAttributeValue("at")?.findAnnotations() ?: emptyList() + for (at in ats) { + val matchingInsns = RecursionManager.doPreventingRecursion(at, true) { + AtResolver(at, targetClass, targetMethod) + .resolveInstructions() + .mapTo(Collections.newSetFromMap(IdentityHashMap())) { it.insn } + } ?: emptySet() + pool.addMember(definitionId) { it in matchingInsns } + } + + val types = annotation.findDeclaredAttributeValue("type")?.resolveTypeArray() ?: emptyList() + for (type in types) { + val asmType = Type.getType(type.descriptor) + pool.addType(definitionId) { it == asmType } + } + + val locals = annotation.findDeclaredAttributeValue("local")?.findAnnotations() ?: emptyList() + for (localAnnotation in locals) { + val localType = annotation.findDeclaredAttributeValue("type")?.resolveType() + val localInfo = LocalInfo.fromAnnotation(localType, localAnnotation) + pool.addMember(definitionId) { insn -> + if (insn !is VarInsnNode) { + return@addMember false + } + val actualInsn = if (insn.opcode >= Opcodes.ISTORE && insn.opcode <= Opcodes.ASTORE) { + insn.next ?: return@addMember false + } else { + insn + } + + val unfilteredLocals = localInfo.getLocals(module, targetClass, targetMethod, actualInsn) + ?: return@addMember false + val filteredLocals = localInfo.matchLocals(unfilteredLocals, CollectVisitor.Mode.MATCH_ALL) + filteredLocals.any { it.index == insn.`var` } + } + } + } + + pool + } + + inline fun findMatchingInstructions( + targetClass: ClassNode, + targetMethod: MethodNode, + pool: IdentifierPool, + flows: FlowMap, + expr: Expression, + insns: Iterable, + forCompletion: Boolean, + callback: (ExpressionMatch) -> Unit + ) { + for (insn in insns) { + val decorations = IdentityHashMap>() + val captured = mutableListOf>() + + val sink = object : Expression.OutputSink { + override fun capture(node: FlowValue, expr: Expression?) { + captured += node to (expr?.src?.startIndex ?: 0) + decorations.getOrPut(insn, ::mutableMapOf).putAll(node.decorations) + } + + override fun decorate(insn: AbstractInsnNode, key: String, value: Any?) { + decorations.getOrPut(insn, ::mutableMapOf)[key] = value + } + + override fun decorateInjectorSpecific(insn: AbstractInsnNode, key: String, value: Any?) { + // Our maps are per-injector anyway, so this is just a normal decoration. + decorations.getOrPut(insn, ::mutableMapOf)[key] = value + } + } + + val flow = flows[insn] ?: continue + try { + if (expr.matches(flow, ExpressionContext(pool, sink, targetClass, targetMethod, forCompletion))) { + for ((capturedFlow, startOffset) in captured) { + callback(ExpressionMatch(flow, startOffset, decorations[capturedFlow.insn].orEmpty())) + } + } + } catch (e: ProcessCanceledException) { + throw e + } catch (ignored: Exception) { + // MixinExtras throws lots of different exceptions + } + } + } + + class ExpressionMatch @PublishedApi internal constructor( + val flow: FlowValue, + val startOffset: Int, + val decorations: Map, + ) +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt index 07f1363c4..1f9002488 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEExpressionFile.kt @@ -37,5 +37,5 @@ class MEExpressionFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvide val items: Array get() = findChildrenByClass(MEItem::class.java) val declarations: List get() = items.filterIsInstance() - val statement: MEStatement? get() = items.asSequence().filterIsInstance().singleOrNull()?.statement + val statements: List get() = items.mapNotNull { (it as? MEStatementItem)?.statement } } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index 9a44038a4..5a3a2a31a 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -20,6 +20,8 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras +import com.demonwav.mcdev.platform.mixin.expression.IdentifierPoolFactory +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement @@ -44,8 +46,6 @@ import com.demonwav.mcdev.util.resolveTypeArray import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.lang.injection.InjectedLanguageManager import com.intellij.openapi.editor.Editor -import com.intellij.openapi.module.Module -import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.project.Project import com.intellij.openapi.util.RecursionManager import com.intellij.psi.JavaPsiFacade @@ -59,20 +59,11 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.util.parentOfType import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression -import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter -import com.llamalad7.mixinextras.expression.impl.flow.FlowValue -import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext -import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool -import java.util.Collections import java.util.IdentityHashMap -import org.objectweb.asm.Opcodes -import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode -import org.objectweb.asm.tree.VarInsnNode -private typealias IdentifierPoolFactory = (MethodNode) -> IdentifierPool private typealias SourceMatchContextFactory = (ClassNode, MethodNode) -> MESourceMatchContext class ExpressionInjectionPoint : InjectionPoint() { @@ -177,9 +168,9 @@ class ExpressionInjectionPoint : InjectionPoint() { val module = at.findModule() ?: return null - val poolFactory = createIdentifierPoolFactory(module, targetClass, modifierList) + val poolFactory = MEExpressionMatchUtil.createIdentifierPoolFactory(module, targetClass, modifierList) - return MyCollectVisitor(mode, targetClass, parsedExprs, poolFactory) + return MyCollectVisitor(mode, project, targetClass, parsedExprs, poolFactory) } private fun parseExpressions( @@ -197,10 +188,11 @@ class ExpressionInjectionPoint : InjectionPoint() { ?: return@flatMap emptySequence>() expressionElements.asSequence().mapNotNull { expressionElement -> val text = expressionElement.constantStringValue ?: return@mapNotNull null + // TODO: get the right statement from the injected file val rootStatementPsi = InjectedLanguageManager.getInstance(project) .getInjectedPsiFiles(expressionElement)?.firstOrNull() - ?.let { (it.first as? MEExpressionFile)?.statement } - ?: project.meExpressionElementFactory.createFile(text).statement + ?.let { (it.first as? MEExpressionFile)?.statements?.singleOrNull() } + ?: project.meExpressionElementFactory.createFile("do {$text}").statements.singleOrNull() ?: project.meExpressionElementFactory.createStatement("empty") try { ExpressionParserFacade.parse(text) to rootStatementPsi @@ -212,61 +204,6 @@ class ExpressionInjectionPoint : InjectionPoint() { .toList() } - private fun createIdentifierPoolFactory( - module: Module, - targetClass: ClassNode, - modifierList: PsiModifierList, - ): IdentifierPoolFactory = { targetMethod -> - val pool = IdentifierPool() - - for (annotation in modifierList.annotations) { - if (!annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { - continue - } - - val definitionId = annotation.findDeclaredAttributeValue("id")?.constantStringValue ?: "" - - val ats = annotation.findDeclaredAttributeValue("at")?.findAnnotations() ?: emptyList() - for (at in ats) { - val matchingInsns = RecursionManager.doPreventingRecursion(at, true) { - AtResolver(at, targetClass, targetMethod) - .resolveInstructions() - .mapTo(Collections.newSetFromMap(IdentityHashMap())) { it.insn } - } ?: emptySet() - pool.addMember(definitionId) { it in matchingInsns } - } - - val types = annotation.findDeclaredAttributeValue("type")?.resolveTypeArray() ?: emptyList() - for (type in types) { - val asmType = Type.getType(type.descriptor) - pool.addType(definitionId) { it == asmType } - } - - val locals = annotation.findDeclaredAttributeValue("local")?.findAnnotations() ?: emptyList() - for (localAnnotation in locals) { - val localType = annotation.findDeclaredAttributeValue("type")?.resolveType() - val localInfo = LocalInfo.fromAnnotation(localType, localAnnotation) - pool.addMember(definitionId) { insn -> - if (insn !is VarInsnNode) { - return@addMember false - } - val actualInsn = if (insn.opcode >= Opcodes.ISTORE && insn.opcode <= Opcodes.ASTORE) { - insn.next ?: return@addMember false - } else { - insn - } - - val unfilteredLocals = localInfo.getLocals(module, targetClass, targetMethod, actualInsn) - ?: return@addMember false - val filteredLocals = localInfo.matchLocals(unfilteredLocals, CollectVisitor.Mode.MATCH_ALL) - filteredLocals.any { it.index == insn.`var` } - } - } - } - - pool - } - override fun createLookup( targetClass: ClassNode, result: CollectVisitor.Result @@ -276,6 +213,7 @@ class ExpressionInjectionPoint : InjectionPoint() { private class MyCollectVisitor( mode: Mode, + private val project: Project, private val targetClass: ClassNode, private val expressions: List>, private val poolFactory: IdentifierPoolFactory, @@ -284,50 +222,25 @@ class ExpressionInjectionPoint : InjectionPoint() { val insns = methodNode.instructions ?: return val pool = poolFactory(methodNode) - val flows = try { - FlowInterpreter.analyze(targetClass, methodNode) - } catch (e: RuntimeException) { - return - } + val flows = MEExpressionMatchUtil.getFlowMap(project, targetClass, methodNode) ?: return val result = IdentityHashMap>>() for ((expr, psiExpr) in expressions) { - val decorations = IdentityHashMap>() - val captured = mutableListOf>() - for (insn in insns) { - val sink = object : Expression.OutputSink { - override fun capture(node: FlowValue, expr: Expression?) { - captured += node.insn to (expr?.src?.startIndex ?: 0) - decorations.getOrPut(insn, ::mutableMapOf).putAll(node.decorations) - } - - override fun decorate(insn: AbstractInsnNode, key: String, value: Any?) { - decorations.getOrPut(insn, ::mutableMapOf)[key] = value - } - - override fun decorateInjectorSpecific(insn: AbstractInsnNode, key: String, value: Any?) { - // Our maps are per-injector anyway, so this is just a normal decoration. - decorations.getOrPut(insn, ::mutableMapOf)[key] = value - } - } - - val flow = flows[insn] ?: continue - try { - if (expr.matches(flow, ExpressionContext(pool, sink, targetClass, methodNode))) { - for ((capturedInsn, startOffset) in captured) { - val capturedExpr = psiExpr.findElementAt(startOffset) - ?.parentOfType(withSelf = true) - ?.expression - ?: psiExpr - result.putIfAbsent(capturedInsn, capturedExpr to decorations[capturedInsn].orEmpty()) - } - } - } catch (e: ProcessCanceledException) { - throw e - } catch (ignored: Exception) { - // MixinExtras throws lots of different exceptions - } + MEExpressionMatchUtil.findMatchingInstructions( + targetClass, + methodNode, + pool, + flows, + expr, + insns, + false + ) { match -> + val capturedExpr = psiExpr.findElementAt(match.startOffset) + ?.parentOfType(withSelf = true) + ?.expression + ?: psiExpr + result.putIfAbsent(match.flow.insn, capturedExpr to match.decorations) } } diff --git a/src/main/kotlin/platform/mixin/insight/MixinTargetLineMarkerProvider.kt b/src/main/kotlin/platform/mixin/insight/MixinTargetLineMarkerProvider.kt index 58a5e5e06..55a6e788c 100644 --- a/src/main/kotlin/platform/mixin/insight/MixinTargetLineMarkerProvider.kt +++ b/src/main/kotlin/platform/mixin/insight/MixinTargetLineMarkerProvider.kt @@ -68,7 +68,12 @@ class MixinTargetLineMarkerProvider : LineMarkerProviderDescriptor() { MixinAnnotationHandler.forMixinAnnotation(qName, annotation.project)?.let { it to annotation } } } ?: return null - if (handler.isUnresolved(annotation) != null) { + try { + if (handler.isUnresolved(annotation) != null) { + return null + } + } catch (e: Exception) { + e.printStackTrace() return null } val simpleName = annotation.qualifiedName?.substringAfterLast('.') ?: return null diff --git a/src/main/kotlin/platform/mixin/util/AsmDfaUtil.kt b/src/main/kotlin/platform/mixin/util/AsmDfaUtil.kt index 3e800c643..842be4863 100644 --- a/src/main/kotlin/platform/mixin/util/AsmDfaUtil.kt +++ b/src/main/kotlin/platform/mixin/util/AsmDfaUtil.kt @@ -41,8 +41,8 @@ import org.objectweb.asm.tree.analysis.SimpleVerifier object AsmDfaUtil { private val LOGGER = thisLogger() - fun analyzeMethod(project: Project, clazz: ClassNode, method: MethodNode): Array?>? { - return method.cached(clazz, project) { + fun analyzeMethod(project: Project, classIn: ClassNode, methodIn: MethodNode): Array?>? { + return methodIn.cached(classIn, project) { clazz, method -> try { Analyzer( PsiBytecodeInterpreter( diff --git a/src/main/kotlin/platform/mixin/util/AsmUtil.kt b/src/main/kotlin/platform/mixin/util/AsmUtil.kt index 8d2633831..f0c80420f 100644 --- a/src/main/kotlin/platform/mixin/util/AsmUtil.kt +++ b/src/main/kotlin/platform/mixin/util/AsmUtil.kt @@ -32,6 +32,7 @@ import com.demonwav.mcdev.util.findQualifiedClass import com.demonwav.mcdev.util.fullQualifiedName import com.demonwav.mcdev.util.hasSyntheticMethod import com.demonwav.mcdev.util.isErasureEquivalentTo +import com.demonwav.mcdev.util.lockedCached import com.demonwav.mcdev.util.loggerForTopLevel import com.demonwav.mcdev.util.mapToArray import com.demonwav.mcdev.util.realName @@ -42,6 +43,7 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.project.Project import com.intellij.openapi.roots.CompilerModuleExtension +import com.intellij.openapi.util.Key import com.intellij.openapi.util.RecursionManager import com.intellij.psi.JavaPsiFacade import com.intellij.psi.JavaRecursiveElementWalkingVisitor @@ -70,12 +72,15 @@ import com.intellij.psi.PsiType import com.intellij.psi.PsiTypes import com.intellij.psi.impl.compiled.ClsElementImpl import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.CachedValue import com.intellij.psi.util.PsiUtil import com.intellij.refactoring.util.LambdaRefactoringUtil import com.intellij.util.CommonJavaRefactoringUtil import com.llamalad7.mixinextras.utils.TypeUtils import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap import org.objectweb.asm.ClassReader import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes @@ -157,13 +162,10 @@ private val LOAD_CLASS_FILE_BYTES: Method? = runCatching { .let { it.isAccessible = true; it } }.getOrNull() +private val INNER_CLASS_NODES_KEY = Key.create>>("mcdev.innerClassNodes") + /** * Tries to find the bytecode for the class for the given qualified name. - * - * ### Implementation note: - * First attempts to resolve the class using [findQualifiedClass]. This may fail in the case of anonymous classes, which - * don't exist inside `PsiCompiledElement`s, so it then creates a fake `PsiClass` based on the qualified name and - * attempts to resolve it from that. */ fun findClassNodeByQualifiedName(project: Project, module: Module?, fqn: String): ClassNode? { val psiClass = findQualifiedClass(project, fqn) @@ -171,52 +173,70 @@ fun findClassNodeByQualifiedName(project: Project, module: Module?, fqn: String) return findClassNodeByPsiClass(psiClass, module) } - // try to find it by a fake one - val fakeClassNode = ClassNode() - fakeClassNode.name = fqn.replace('.', '/') - val fakePsiClass = fakeClassNode.constructClass(project, "") ?: return null - return findClassNodeByPsiClass(fakePsiClass, module) + fun resolveViaFakeClass(): ClassNode? { + val fakeClassNode = ClassNode() + fakeClassNode.name = fqn.replace('.', '/') + val fakePsiClass = fakeClassNode.constructClass(project, "") ?: return null + return findClassNodeByPsiClass(fakePsiClass, module) + } + + val outerClass = findQualifiedClass(project, fqn.substringBefore('$')) + if (outerClass != null) { + val innerClasses = outerClass.lockedCached( + INNER_CLASS_NODES_KEY, + compute = ::ConcurrentHashMap + ) + return innerClasses.computeIfAbsent(fqn) { resolveViaFakeClass() } + } + + return resolveViaFakeClass() } +private val NODE_BY_PSI_CLASS_KEY = Key.create>("mcdev.nodeByPsiClass") + fun findClassNodeByPsiClass(psiClass: PsiClass, module: Module? = psiClass.findModule()): ClassNode? { - return try { - val bytes = LOAD_CLASS_FILE_BYTES?.invoke(null, psiClass) as? ByteArray - if (bytes == null) { - // find compiler output - if (module == null) return null - val fqn = psiClass.fullQualifiedName ?: return null - var parentDir = CompilerModuleExtension.getInstance(module)?.compilerOutputPath ?: return null - val packageName = fqn.substringBeforeLast('.', "") - if (packageName.isNotEmpty()) { - for (dir in packageName.split('.')) { - parentDir = parentDir.findChild(dir) ?: return null + return psiClass.lockedCached(NODE_BY_PSI_CLASS_KEY) { + try { + val bytes = LOAD_CLASS_FILE_BYTES?.invoke(null, psiClass) as? ByteArray + if (bytes == null) { + // find compiler output + if (module == null) return@lockedCached null + val fqn = psiClass.fullQualifiedName ?: return@lockedCached null + var parentDir = CompilerModuleExtension.getInstance(module)?.compilerOutputPath + ?: return@lockedCached null + val packageName = fqn.substringBeforeLast('.', "") + if (packageName.isNotEmpty()) { + for (dir in packageName.split('.')) { + parentDir = parentDir.findChild(dir) ?: return@lockedCached null + } } + val classFile = parentDir.findChild("${fqn.substringAfterLast('.')}.class") + ?: return@lockedCached null + val node = ClassNode() + classFile.inputStream.use { ClassReader(it).accept(node, 0) } + node + } else { + val node = ClassNode() + ClassReader(bytes).accept(node, 0) + node + } + } catch (e: Throwable) { + val actualThrowable = if (e is InvocationTargetException) e.cause ?: e else e + if (actualThrowable is ProcessCanceledException) { + throw actualThrowable } - val classFile = parentDir.findChild("${fqn.substringAfterLast('.')}.class") ?: return null - val node = ClassNode() - classFile.inputStream.use { ClassReader(it).accept(node, 0) } - node - } else { - val node = ClassNode() - ClassReader(bytes).accept(node, 0) - node - } - } catch (e: Throwable) { - val actualThrowable = if (e is InvocationTargetException) e.cause ?: e else e - if (actualThrowable is ProcessCanceledException) { - throw actualThrowable - } - if (actualThrowable is NoSuchFileException) { - return null - } + if (actualThrowable is NoSuchFileException) { + return@lockedCached null + } - val message = actualThrowable.message - // TODO: display an error to the user? - if (message == null || !message.contains("Unsupported class file major version")) { - LOGGER.error(actualThrowable) + val message = actualThrowable.message + // TODO: display an error to the user? + if (message == null || !message.contains("Unsupported class file major version")) { + LOGGER.error(actualThrowable) + } + null } - null } } @@ -330,8 +350,11 @@ private fun ClassNode.constructClass(project: Project, body: String): PsiClass? return clazz } -inline fun ClassNode.cached(project: Project, vararg dependencies: Any, crossinline compute: () -> T): T { - return findStubClass(project)?.cached(*dependencies, compute = compute) ?: compute() +fun ClassNode.cached(project: Project, vararg dependencies: Any, compute: (ClassNode) -> T): T { + val unsafeClass = UnsafeCachedValueCapture(this) + return findStubClass(project)?.cached(*dependencies) { + compute(unsafeClass.value) + } ?: compute(this) } /** @@ -457,13 +480,17 @@ fun FieldNode.getGenericType( return Type.getType(this.desc).toPsiType(elementFactory) } -inline fun FieldNode.cached( +fun FieldNode.cached( clazz: ClassNode, project: Project, vararg dependencies: Any, - crossinline compute: () -> T, + compute: (ClassNode, FieldNode) -> T, ): T { - return findStubField(clazz, project)?.cached(*dependencies, compute = compute) ?: compute() + val unsafeClass = UnsafeCachedValueCapture(clazz) + val unsafeField = UnsafeCachedValueCapture(this) + return findStubField(clazz, project)?.cached(*dependencies) { + compute(unsafeClass.value, unsafeField.value) + } ?: compute(clazz, this) } fun FieldNode.findStubField(clazz: ClassNode, project: Project): PsiField? { @@ -698,13 +725,17 @@ private fun findAssociatedLambda(psiClass: PsiClass, clazz: ClassNode, lambdaMet } } -inline fun MethodNode.cached( +fun MethodNode.cached( clazz: ClassNode, project: Project, vararg dependencies: Array, - crossinline compute: () -> T, + compute: (ClassNode, MethodNode) -> T, ): T { - return findStubMethod(clazz, project)?.cached(*dependencies, compute = compute) ?: compute() + val unsafeClass = UnsafeCachedValueCapture(clazz) + val unsafeMethod = UnsafeCachedValueCapture(this) + return findStubMethod(clazz, project)?.cached(*dependencies) { + compute(unsafeClass.value, unsafeMethod.value) + } ?: compute(clazz, this) } fun MethodNode.findStubMethod(clazz: ClassNode, project: Project): PsiMethod? { diff --git a/src/main/kotlin/platform/mixin/util/UnsafeCachedValueCapture.kt b/src/main/kotlin/platform/mixin/util/UnsafeCachedValueCapture.kt new file mode 100644 index 000000000..bcc1c15d4 --- /dev/null +++ b/src/main/kotlin/platform/mixin/util/UnsafeCachedValueCapture.kt @@ -0,0 +1,28 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.util + +// See CachedValueStabilityChecker +class UnsafeCachedValueCapture(val value: T) { + override fun hashCode() = 0 + override fun equals(other: Any?) = other is UnsafeCachedValueCapture<*> + override fun toString() = value.toString() +} diff --git a/src/main/kotlin/util/psi-utils.kt b/src/main/kotlin/util/psi-utils.kt index b6e8a8bbb..9a7ce1609 100644 --- a/src/main/kotlin/util/psi-utils.kt +++ b/src/main/kotlin/util/psi-utils.kt @@ -32,6 +32,7 @@ import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.roots.impl.OrderEntryUtil import com.intellij.openapi.util.Key +import com.intellij.openapi.util.UserDataHolderEx import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.ElementManipulator import com.intellij.psi.ElementManipulators @@ -60,6 +61,7 @@ import com.intellij.psi.PsiReferenceExpression import com.intellij.psi.PsiType import com.intellij.psi.ResolveResult import com.intellij.psi.filters.ElementFilter +import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.PsiTreeUtil @@ -68,6 +70,10 @@ import com.intellij.psi.util.TypeConversionUtil import com.intellij.refactoring.changeSignature.ChangeSignatureUtil import com.intellij.util.IncorrectOperationException import com.siyeh.ig.psiutils.ImportUtils +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap +import java.util.concurrent.locks.ReadWriteLock +import java.util.concurrent.locks.ReentrantReadWriteLock // Parent fun PsiElement.findModule(): Module? = ModuleUtilCore.findModuleForPsiElement(this) @@ -232,6 +238,42 @@ inline fun PsiElement.cached(vararg dependencies: Any, crossinline compute: } } +@PublishedApi +internal val CACHE_LOCKS_KEY = Key.create, ReadWriteLock>>("mcdev.cacheLock") + +inline fun PsiElement.lockedCached( + key: Key>, + vararg dependencies: Any, + crossinline compute: () -> T, +): T { + val cacheLocks = (this as UserDataHolderEx).putUserDataIfAbsent(CACHE_LOCKS_KEY, ConcurrentHashMap()) + val cacheLock = cacheLocks.computeIfAbsent(key) { ReentrantReadWriteLock() } + + cacheLock.readLock().lock() + try { + val value = getUserData(key)?.upToDateOrNull + if (value != null) { + return value.get() + } + } finally { + cacheLock.readLock().unlock() + } + + cacheLock.writeLock().lock() + try { + val value = getUserData(key)?.upToDateOrNull + if (value != null) { + return value.get() + } + + return CachedValuesManager.getCachedValue(this, key) { + CachedValueProvider.Result.create(compute(), *(dependencies.toList() + this).toTypedArray()) + } + } finally { + cacheLock.writeLock().unlock() + } +} + fun LookupElementBuilder.withImportInsertion(toImport: List): LookupElementBuilder = this.withInsertHandler { insertionContext, _ -> toImport.forEach { ImportUtils.addImportIfNeeded(it, insertionContext.file) } From df388fe12e1deff70b1f0753a1395a9662207783 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 12 Mar 2024 17:48:03 +0000 Subject: [PATCH 036/100] Remove some debug code --- .../mixin/insight/MixinTargetLineMarkerProvider.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/kotlin/platform/mixin/insight/MixinTargetLineMarkerProvider.kt b/src/main/kotlin/platform/mixin/insight/MixinTargetLineMarkerProvider.kt index 55a6e788c..58a5e5e06 100644 --- a/src/main/kotlin/platform/mixin/insight/MixinTargetLineMarkerProvider.kt +++ b/src/main/kotlin/platform/mixin/insight/MixinTargetLineMarkerProvider.kt @@ -68,12 +68,7 @@ class MixinTargetLineMarkerProvider : LineMarkerProviderDescriptor() { MixinAnnotationHandler.forMixinAnnotation(qName, annotation.project)?.let { it to annotation } } } ?: return null - try { - if (handler.isUnresolved(annotation) != null) { - return null - } - } catch (e: Exception) { - e.printStackTrace() + if (handler.isUnresolved(annotation) != null) { return null } val simpleName = annotation.qualifiedName?.substringAfterLast('.') ?: return null From e520269e3a70a7a82ba3841ed9c467e5122fb9fa Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 14 Mar 2024 02:21:30 +0000 Subject: [PATCH 037/100] First attempt at bytecode-based completion (it's broken) --- build.gradle.kts | 2 +- .../MEExpressionCompletionContributor.kt | 62 ++ .../mixin/expression/MEExpressionMatchUtil.kt | 559 ++++++++++++++++++ .../expression/psi/MEMatchableElement.kt | 3 + .../mixin/expression/psi/MEPsiUtil.kt | 8 + .../psi/MERecursiveWalkingVisitor.kt | 45 ++ .../impl/MEArrayAccessExpressionImplMixin.kt | 2 + .../mixins/impl/MEAssignStatementImplMixin.kt | 2 + .../impl/MEBinaryExpressionImplMixin.kt | 6 + .../impl/MECapturingExpressionImplMixin.kt | 2 + .../mixins/impl/MECastExpressionImplMixin.kt | 2 + .../MEClassConstantExpressionImplMixin.kt | 3 + .../psi/mixins/impl/MEExpressionImplMixin.kt | 5 + .../impl/MEExpressionStatementImplMixin.kt | 2 + .../MEInstantiationExpressionImplMixin.kt | 2 + .../mixins/impl/MELitExpressionImplMixin.kt | 3 + .../impl/MEMemberAccessExpressionImplMixin.kt | 2 + .../impl/MEMethodCallExpressionImplMixin.kt | 2 + .../mixins/impl/MENameExpressionImplMixin.kt | 3 + .../impl/MENewArrayExpressionImplMixin.kt | 2 + .../MEParenthesizedExpressionImplMixin.kt | 2 + .../mixins/impl/MEReturnStatementImplMixin.kt | 2 + .../psi/mixins/impl/MEStatementImplMixin.kt | 5 + .../MEStaticMethodCallExpressionImplMixin.kt | 2 + .../impl/MESuperCallExpressionImplMixin.kt | 2 + .../mixins/impl/METhisExpressionImplMixin.kt | 3 + .../mixins/impl/METhrowStatementImplMixin.kt | 2 + .../mixins/impl/MEUnaryExpressionImplMixin.kt | 2 + .../mixinextras/ExpressionInjectionPoint.kt | 7 +- .../kotlin/platform/mixin/util/AsmUtil.kt | 12 + 30 files changed, 749 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/psi/MERecursiveWalkingVisitor.kt diff --git a/build.gradle.kts b/build.gradle.kts index 71d858a2a..386e22e43 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:2e9ca3f") + implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:1d1aefa") implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt index 600b8796b..bef98c8d2 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt @@ -36,11 +36,13 @@ import com.intellij.codeInsight.completion.CompletionType import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.TailTypeDecorator +import com.intellij.openapi.editor.Editor import com.intellij.patterns.PlatformPatterns.psiElement import com.intellij.patterns.StandardPatterns.and import com.intellij.patterns.StandardPatterns.not import com.intellij.psi.tree.TokenSet import com.intellij.util.ProcessingContext +import com.intellij.util.text.CharArrayUtil class MEExpressionCompletionContributor : CompletionContributor() { companion object { @@ -86,6 +88,26 @@ class MEExpressionCompletionContributor : CompletionContributor() { NORMAL_ELEMENT, AFTER_END_EXPRESSION_PATTERN, ) + private val FROM_BYTECODE_PLACE = psiElement() + .inside(MEStatement::class.java) + .andNot(psiElement().inside(MELitExpression::class.java)) + + val DOT_CLASS_TAIL = object : TailType() { + override fun processTail(editor: Editor, tailOffset: Int): Int { + editor.document.insertString(tailOffset, ".class") + return moveCaret(editor, tailOffset, 6) + } + + override fun isApplicable(context: InsertionContext): Boolean { + val chars = context.document.charsSequence + val dotOffset = CharArrayUtil.shiftForward(chars, context.tailOffset, " \n\t") + if (!CharArrayUtil.regionMatches(chars, dotOffset, ".")) { + return true + } + val classOffset = CharArrayUtil.shiftForward(chars, dotOffset + 1, " \n\t") + return !CharArrayUtil.regionMatches(chars, classOffset, "class") + } + } } init { @@ -123,6 +145,22 @@ class MEExpressionCompletionContributor : CompletionContributor() { Keyword("instanceof", TailType.INSERT_SPACE) ) ) + extend( + CompletionType.BASIC, + FROM_BYTECODE_PLACE, + object : CompletionProvider() { + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + val project = parameters.originalFile.project + result.addAllElements( + MEExpressionMatchUtil.getCompletionVariantsFromBytecode(project, parameters.position) + ) + } + } + ) } private class KeywordCompletionProvider( @@ -149,4 +187,28 @@ class MEExpressionCompletionContributor : CompletionContributor() { } private class Keyword(val name: String, val tailType: TailType = TailType.NONE) + + class BracketsTailType(private val dimensions: Int) : TailType() { + override fun processTail(editor: Editor, tailOffset: Int): Int { + editor.document.insertString(tailOffset, "[]".repeat(dimensions)) + return moveCaret(editor, tailOffset, 2 * dimensions) + } + + override fun isApplicable(context: InsertionContext): Boolean { + val chars = context.document.charsSequence + var offset = context.tailOffset + repeat(dimensions) { + offset = CharArrayUtil.shiftForward(chars, offset, " \n\t") + if (!CharArrayUtil.regionMatches(chars, offset, "[")) { + return true + } + offset = CharArrayUtil.shiftForward(chars, offset, " \n\t") + if (!CharArrayUtil.regionMatches(chars, offset, "]")) { + return true + } + } + + return false + } + } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 3b8565ddd..a32e724ca 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -20,35 +20,93 @@ package com.demonwav.mcdev.platform.mixin.expression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEInstantiationExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMemberAccessExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMethodCallExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENewArrayExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEParenthesizedExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MESuperCallExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype +import com.demonwav.mcdev.platform.mixin.expression.psi.MEMatchableElement +import com.demonwav.mcdev.platform.mixin.expression.psi.MEPsiUtil +import com.demonwav.mcdev.platform.mixin.expression.psi.MERecursiveWalkingVisitor +import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil +import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.util.LocalInfo +import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.platform.mixin.util.cached +import com.demonwav.mcdev.platform.mixin.util.canonicalName +import com.demonwav.mcdev.platform.mixin.util.isPrimitive +import com.demonwav.mcdev.platform.mixin.util.mixinTargets import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.findAnnotations +import com.demonwav.mcdev.util.findContainingClass +import com.demonwav.mcdev.util.findContainingModifierList +import com.demonwav.mcdev.util.findModule +import com.demonwav.mcdev.util.mapFirstNotNull +import com.demonwav.mcdev.util.packageName import com.demonwav.mcdev.util.resolveType import com.demonwav.mcdev.util.resolveTypeArray +import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.codeInsight.lookup.TailTypeDecorator +import com.intellij.lang.injection.InjectedLanguageManager import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.module.Module import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.RecursionManager +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiAnonymousClass +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement import com.intellij.psi.PsiModifierList +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.PsiUtil +import com.intellij.psi.util.parentOfType +import com.intellij.psi.util.parents +import com.intellij.util.PlatformIcons +import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression +import com.llamalad7.mixinextras.expression.impl.flow.ComplexFlowValue +import com.llamalad7.mixinextras.expression.impl.flow.DummyFlowValue import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter import com.llamalad7.mixinextras.expression.impl.flow.FlowValue import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool import java.util.Collections import java.util.IdentityHashMap +import org.apache.commons.lang3.mutable.MutableInt import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.IincInsnNode +import org.objectweb.asm.tree.InsnNode +import org.objectweb.asm.tree.IntInsnNode +import org.objectweb.asm.tree.LdcInsnNode +import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.tree.MultiANewArrayInsnNode +import org.objectweb.asm.tree.TypeInsnNode import org.objectweb.asm.tree.VarInsnNode import org.objectweb.asm.tree.analysis.Analyzer @@ -189,6 +247,16 @@ object MEExpressionMatchUtil { pool } + fun createExpression(text: String): Expression? { + return try { + ExpressionParserFacade.parse(text) + } catch (e: Exception) { + null + } catch (e: StackOverflowError) { + null + } + } + inline fun findMatchingInstructions( targetClass: ClassNode, targetMethod: MethodNode, @@ -234,6 +302,497 @@ object MEExpressionMatchUtil { } } + fun getCompletionVariantsFromBytecode(project: Project, contextElement: PsiElement): List { + val statement = contextElement.parentOfType() ?: return emptyList() + + val expressionAnnotation = + InjectedLanguageManager.getInstance(project).getInjectionHost(statement)?.parentOfType() + ?: return emptyList() + if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { + return emptyList() + } + + val modifierList = expressionAnnotation.findContainingModifierList() ?: return emptyList() + val module = modifierList.findModule() ?: return emptyList() + + val mixinClass = modifierList.findContainingClass() ?: return emptyList() + + val (handler, handlerAnnotation) = modifierList.annotations.mapFirstNotNull { annotation -> + val qName = annotation.qualifiedName ?: return@mapFirstNotNull null + val handler = MixinAnnotationHandler.forMixinAnnotation(qName, project) ?: return@mapFirstNotNull null + handler to annotation + } ?: return emptyList() + + val cursorOffset = contextElement.textRange.startOffset + + return mixinClass.mixinTargets.flatMap { targetClass -> + val poolFactory = createIdentifierPoolFactory(module, targetClass, modifierList) + handler.resolveTarget(handlerAnnotation, targetClass) + .filterIsInstance() + .flatMap { methodTarget -> + + getCompletionVariantsFromBytecode( + project, + mixinClass, + cursorOffset, + statement.copy() as MEStatement, + targetClass, + methodTarget.classAndMethod.method, + poolFactory, + ) + } + } + } + + private fun getCompletionVariantsFromBytecode( + project: Project, + mixinClass: PsiClass, + cursorOffsetIn: Int, + statement: MEStatement, + targetClass: ClassNode, + targetMethod: MethodNode, + poolFactory: IdentifierPoolFactory, + ): List { + if (targetMethod.instructions == null) { + return emptyList() + } + + val cursorOffset = MutableInt(cursorOffsetIn) + val pool = poolFactory(targetMethod) + val flows = getFlowMap(project, targetClass, targetMethod) ?: return emptyList() + + removeExplicitCaptures(statement, cursorOffset) + replaceUnknownNamesWithWildcards(project, statement, cursorOffset, pool) + + val statementToMatch = statement.copy() as MEStatement + replaceCursorInputWithWildcard(project, statementToMatch, cursorOffset.toInt()) + + val meStatement = createExpression(statementToMatch.text) ?: return emptyList() + val matchingFlows = mutableListOf() + findMatchingInstructions( + targetClass, + targetMethod, + pool, + flows, + meStatement, + targetMethod.instructions, + true + ) { match -> + matchingFlows += match.flow + } + if (matchingFlows.isEmpty()) { + return emptyList() + } + + var subExpr: MEMatchableElement = statement + while (true) { + val inputExprOnCursor = subExpr.getInputExprs().firstOrNull { it.textRange.contains(cursorOffset.toInt()) } + ?: break + val exprToMatch = inputExprOnCursor.copy() as MEExpression + replaceCursorInputWithWildcard(project, exprToMatch, cursorOffset.toInt()) + val meExpression = createExpression(exprToMatch.text) ?: return emptyList() + + val flattenedInstructions = mutableSetOf() + for (flow in matchingFlows) { + getInstructionsInFlowTree( + flow, + flattenedInstructions, + subExpr !is MEExpressionStatement && subExpr !is MEParenthesizedExpression + ) + } + + matchingFlows.clear() + findMatchingInstructions( + targetClass, + targetMethod, + pool, + flows, + meExpression, + flattenedInstructions, + true + ) { match -> + matchingFlows += match.flow + } + if (matchingFlows.isEmpty()) { + return emptyList() + } + + subExpr = inputExprOnCursor + } + + val cursorInstructions = mutableSetOf() + for (flow in matchingFlows) { + getInstructionsInFlowTree(flow, cursorInstructions, false) + } + + val elementAtCursor = statement.findElementAt(cursorOffset.toInt()) ?: return emptyList() + + val isInsideMeType = PsiTreeUtil.getParentOfType( + elementAtCursor, + METype::class.java, + false, + MEExpression::class.java + ) != null + val cursorExprInTypePosition = !isInsideMeType && + elementAtCursor.parentOfType()?.let(METypeUtil::isExpressionInTypePosition) == true + val inTypePosition = isInsideMeType || cursorExprInTypePosition + val isPossiblyIncompleteCast = !inTypePosition && + elementAtCursor.parentOfType() + ?.parents(false) + ?.dropWhile { it is MEArrayAccessExpression && it.indexExpr == null } is MEParenthesizedExpression + val canCompleteExprs = !inTypePosition + val canCompleteTypes = inTypePosition || isPossiblyIncompleteCast + + return cursorInstructions.mapNotNull { insn -> + getCompletionForInstruction(insn, mixinClass, canCompleteExprs, canCompleteTypes) + } + } + + private fun replaceUnknownNamesWithWildcards( + project: Project, + statement: MEStatement, + cursorOffset: MutableInt, + pool: IdentifierPool, + ) { + val unknownNames = mutableListOf() + statement.accept(object : MERecursiveWalkingVisitor() { + override fun visitType(o: METype) { + val name = o.meName + if (!name.isWildcard && !pool.typeExists(name.text)) { + unknownNames += name + } + } + + override fun visitNameExpression(o: MENameExpression) { + val name = o.meName + if (!name.isWildcard) { + if (METypeUtil.isExpressionDirectlyInTypePosition(o)) { + if (!pool.typeExists(name.text)) { + unknownNames += name + } + } else { + if (!pool.memberExists(name.text)) { + unknownNames += name + } + } + } + } + + override fun visitSuperCallExpression(o: MESuperCallExpression) { + val name = o.memberName + if (name != null && !name.isWildcard && !pool.memberExists(name.text)) { + unknownNames += name + } + super.visitSuperCallExpression(o) + } + + override fun visitMethodCallExpression(o: MEMethodCallExpression) { + val name = o.memberName + if (!name.isWildcard && !pool.memberExists(name.text)) { + unknownNames += name + } + super.visitMethodCallExpression(o) + } + + override fun visitStaticMethodCallExpression(o: MEStaticMethodCallExpression) { + val name = o.memberName + if (!name.isWildcard && !pool.memberExists(name.text)) { + unknownNames += name + } + super.visitStaticMethodCallExpression(o) + } + + override fun visitMemberAccessExpression(o: MEMemberAccessExpression) { + val name = o.memberName + if (!name.isWildcard && !pool.memberExists(name.text)) { + unknownNames += name + } + super.visitMemberAccessExpression(o) + } + + override fun visitInstantiationExpression(o: MEInstantiationExpression) { + val name = o.type + if (!name.isWildcard && !pool.typeExists(name.text)) { + unknownNames += name + } + super.visitInstantiationExpression(o) + } + + override fun visitNewArrayExpression(o: MENewArrayExpression) { + val name = o.elementType + if (!name.isWildcard && !pool.typeExists(name.text)) { + unknownNames += name + } + super.visitNewArrayExpression(o) + } + }) + + for (unknownName in unknownNames) { + val startOffset = unknownName.textRange.startOffset + if (cursorOffset.toInt() > startOffset) { + cursorOffset.setValue(cursorOffset.toInt() - unknownName.textLength + 1) + } + + unknownName.replace(project.meExpressionElementFactory.createName("?")) + } + } + + private fun removeExplicitCaptures(statement: MEStatement, cursorOffset: MutableInt) { + val captures = mutableListOf() + + statement.accept(object : MERecursiveWalkingVisitor() { + override fun elementFinished(element: PsiElement) { + // do this on elementFinished to ensure that inner captures are replaced before outer captures + if (element is MECapturingExpression) { + captures += element + } + } + }) + + for (capture in captures) { + val innerExpr = capture.expression ?: continue + val textRange = capture.textRange + + if (cursorOffset.toInt() > textRange.startOffset) { + cursorOffset.setValue(cursorOffset.toInt() - if (cursorOffset.toInt() >= textRange.endOffset) 3 else 2) + } + + capture.replace(innerExpr) + } + } + + private fun replaceCursorInputWithWildcard(project: Project, element: MEMatchableElement, cursorOffset: Int) { + for (input in element.getInputExprs()) { + if (input.textRange.contains(cursorOffset)) { + input.replace(project.meExpressionElementFactory.createExpression("?")) + return + } + } + } + + private fun getInstructionsInFlowTree( + flow: FlowValue, + outInstructions: MutableSet, + strict: Boolean + ) { + if (flow is DummyFlowValue || flow is ComplexFlowValue) { + return + } + + if (!strict) { + outInstructions += flow.insn + } + for (i in 0 until flow.inputCount()) { + getInstructionsInFlowTree(flow.getInput(i), outInstructions, false) + } + } + + private fun getCompletionForInstruction( + insn: AbstractInsnNode, + mixinClass: PsiClass, + canCompleteExprs: Boolean, + canCompleteTypes: Boolean + ): LookupElement? { + when (insn) { + is LdcInsnNode -> { + when (val cst = insn.cst) { + is Type -> { + if (canCompleteTypes && cst.isAccessibleFrom(mixinClass)) { + return object : TailTypeDecorator(createTypeLookup(cst)) { + override fun computeTailType(context: InsertionContext?) = + MEExpressionCompletionContributor.DOT_CLASS_TAIL + } + } + } + // TODO: string literals? + } + } + is VarInsnNode -> { + // TODO: local variables + } + is IincInsnNode -> { + // TODO: local variables + } + is FieldInsnNode -> { + if (canCompleteExprs) { + val at = "at = @${MixinConstants.Annotations.AT}(value = \"FIELD\"," + + " target = \"L${insn.owner};${insn.name}:${insn.desc}\")" + var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) + .withIcon(PlatformIcons.FIELD_ICON) + .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) + .withDefinition(insn.name.toValidIdentifier(), at) + if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.PUTSTATIC) { + lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) + } + return lookup + } + } + is MethodInsnNode -> { + if (canCompleteExprs) { + val at = "at = @${MixinConstants.Annotations.AT}(value = \"INVOKE\"," + + " target = \"L${insn.owner};${insn.name}${insn.desc}\")" + var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) + .withIcon(PlatformIcons.METHOD_ICON) + .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) + .withDefinition(insn.name.toValidIdentifier(), at) + if (insn.opcode == Opcodes.INVOKESTATIC) { + lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) + } + return lookup + } + } + is TypeInsnNode -> { + // TODO: put cursor in right position for array lengths for array creation + val type = Type.getObjectType(insn.desc) + if (canCompleteTypes && type.isAccessibleFrom(mixinClass)) { + val lookup = createTypeLookup(type) + if (insn.opcode == Opcodes.ANEWARRAY) { + return object : TailTypeDecorator(lookup) { + override fun computeTailType(context: InsertionContext?) = + MEExpressionCompletionContributor.BracketsTailType(1) + } + } else { + return lookup + } + } + } + is IntInsnNode -> { + if (insn.opcode == Opcodes.NEWARRAY) { + val type = when (insn.operand) { + Opcodes.T_BOOLEAN -> "boolean" + Opcodes.T_CHAR -> "char" + Opcodes.T_FLOAT -> "float" + Opcodes.T_DOUBLE -> "double" + Opcodes.T_BYTE -> "byte" + Opcodes.T_SHORT -> "short" + Opcodes.T_INT -> "int" + Opcodes.T_LONG -> "long" + else -> "unknown" // wtf? + } + return object : TailTypeDecorator( + LookupElementBuilder.create(type).withIcon(PlatformIcons.CLASS_ICON) + ) { + override fun computeTailType(context: InsertionContext?) = + MEExpressionCompletionContributor.BracketsTailType(1) + } + } + } + is MultiANewArrayInsnNode -> { + val type = Type.getType(insn.desc) + return object : TailTypeDecorator( + createTypeLookup(type.elementType) + ) { + override fun computeTailType(context: InsertionContext?) = + MEExpressionCompletionContributor.BracketsTailType(type.dimensions) + } + } + is InsnNode -> { + when (insn.opcode) { + Opcodes.ARRAYLENGTH -> { + if (canCompleteExprs) { + return LookupElementBuilder.create("length") + .withIcon(PlatformIcons.FIELD_ICON) + } + } + } + } + } + + return null + } + + private fun Type.typeNameToInsert(): String { + if (sort == Type.ARRAY) { + return elementType.typeNameToInsert() + "[]".repeat(dimensions) + } + if (sort != Type.OBJECT) { + return className + } + + val simpleName = internalName.substringAfterLast('/') + val lastValidCharIndex = (simpleName.length - 1 downTo 0).firstOrNull { + MEPsiUtil.isIdentifierStart(simpleName[it]) + } ?: return "_" + simpleName.filterInvalidIdentifierChars() + + return simpleName.substring(simpleName.lastIndexOf('$', lastValidCharIndex) + 1).toValidIdentifier() + } + + private fun String.toValidIdentifier(): String { + return when { + isEmpty() -> "_" + !MEPsiUtil.isIdentifierStart(this[0]) -> "_" + filterInvalidIdentifierChars() + else -> this[0] + substring(1).filterInvalidIdentifierChars() + } + } + + private fun String.filterInvalidIdentifierChars(): String { + return asSequence().map { if (MEPsiUtil.isIdentifierPart(it)) it else '_' }.joinToString() + } + + private fun Type.presentableName(): String = when (sort) { + Type.ARRAY -> elementType.presentableName() + "[]".repeat(dimensions) + Type.OBJECT -> internalName.substringAfterLast('/') + else -> className + } + + private fun Type.isAccessibleFrom(fromClass: PsiClass): Boolean { + return when (sort) { + Type.ARRAY -> elementType.isAccessibleFrom(fromClass) + Type.OBJECT -> { + val facade = JavaPsiFacade.getInstance(fromClass.project) + val clazz = facade.findClass(canonicalName, fromClass.resolveScope) ?: return false + val pkg = fromClass.packageName?.let(facade::findPackage) ?: return false + clazz !is PsiAnonymousClass && PsiUtil.isAccessibleFromPackage(clazz, pkg) + } + else -> true + } + } + + private fun createTypeLookup(type: Type): LookupElement { + val definitionId = type.typeNameToInsert() + + val lookupElement = LookupElementBuilder.create(definitionId) + .withIcon(PlatformIcons.CLASS_ICON) + .withPresentableText(type.presentableName()) + + return if (type.isPrimitive) { + lookupElement + } else { + lookupElement.withDefinition(definitionId, "type = ${type.canonicalName}.class") + } + } + + private fun LookupElementBuilder.withDefinition(id: String, at: String) = withInsertHandler { context, _ -> + val injectionHost = InjectedLanguageManager.getInstance(context.project).getInjectionHost(context.file) + ?: return@withInsertHandler + val expressionAnnotation = injectionHost.parentOfType() ?: return@withInsertHandler + if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { + return@withInsertHandler + } + val modifierList = expressionAnnotation.findContainingModifierList() ?: return@withInsertHandler + + // look for an existing definition with this id, skip if it exists + for (annotation in modifierList.annotations) { + if (annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) && + annotation.findDeclaredAttributeValue("id")?.constantStringValue == id + ) { + return@withInsertHandler + } + } + + // create and add the new @Definition annotation + val newAnnotation = JavaPsiFacade.getElementFactory(context.project).createAnnotationFromText( + "@${MixinConstants.MixinExtras.DEFINITION}(id = \"$id\", $at)", + modifierList, + ) + val addedAnnotation = modifierList.add(newAnnotation) + + // add imports and reformat + JavaCodeStyleManager.getInstance(context.project).shortenClassReferences(addedAnnotation) + JavaCodeStyleManager.getInstance(context.project).optimizeImports(modifierList.containingFile) + CodeStyleManager.getInstance(context.project).reformat(modifierList) + } + class ExpressionMatch @PublishedApi internal constructor( val flow: FlowValue, val startOffset: Int, diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEMatchableElement.kt b/src/main/kotlin/platform/mixin/expression/psi/MEMatchableElement.kt index 46c2d7523..3474a3067 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/MEMatchableElement.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEMatchableElement.kt @@ -21,8 +21,11 @@ package com.demonwav.mcdev.platform.mixin.expression.psi import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.intellij.psi.PsiElement interface MEMatchableElement : PsiElement { fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean + + fun getInputExprs(): List } diff --git a/src/main/kotlin/platform/mixin/expression/psi/MEPsiUtil.kt b/src/main/kotlin/platform/mixin/expression/psi/MEPsiUtil.kt index 417bcca06..b9856a13e 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/MEPsiUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/MEPsiUtil.kt @@ -47,4 +47,12 @@ object MEPsiUtil { val actualExpr = skipParenthesizedExprDown(expr) ?: return false return actualExpr is MENameExpression && actualExpr.meName.isWildcard } + + fun isIdentifierStart(char: Char): Boolean { + return char in 'a'..'z' || char in 'A'..'Z' || char == '_' + } + + fun isIdentifierPart(char: Char): Boolean { + return isIdentifierStart(char) || char in '0'..'9' + } } diff --git a/src/main/kotlin/platform/mixin/expression/psi/MERecursiveWalkingVisitor.kt b/src/main/kotlin/platform/mixin/expression/psi/MERecursiveWalkingVisitor.kt new file mode 100644 index 000000000..40f6beab5 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/MERecursiveWalkingVisitor.kt @@ -0,0 +1,45 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEVisitor +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiRecursiveVisitor +import com.intellij.psi.PsiWalkingState + +abstract class MERecursiveWalkingVisitor : MEVisitor(), PsiRecursiveVisitor { + private val walkingState = object : PsiWalkingState(this) { + override fun elementFinished(element: PsiElement) { + this@MERecursiveWalkingVisitor.elementFinished(element) + } + } + + override fun visitElement(element: PsiElement) { + walkingState.elementStarted(element) + } + + open fun elementFinished(element: PsiElement) { + } + + fun stopWalking() { + walkingState.stopWalking() + } +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt index 70d2dbf6c..5bdeca1fb 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt @@ -51,6 +51,8 @@ abstract class MEArrayAccessExpressionImplMixin(node: ASTNode) : MEExpressionImp return arrayExpr.matchesJava(javaArray, context) && indexExpr?.matchesJava(javaIndex, context) == true } + override fun getInputExprs() = listOfNotNull(arrayExpr, indexExpr) + protected abstract val arrayExpr: MEExpression protected abstract val indexExpr: MEExpression? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt index 0e9d5fec3..fba4db160 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt @@ -50,6 +50,8 @@ abstract class MEAssignStatementImplMixin(node: ASTNode) : MEStatementImpl(node) } } + override fun getInputExprs() = listOfNotNull(targetExpr, rightExpr) + protected abstract val targetExpr: MEExpression protected abstract val rightExpr: MEExpression? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt index 6490324be..5b492107d 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt @@ -89,6 +89,12 @@ abstract class MEBinaryExpressionImplMixin(node: ASTNode) : MEExpressionImpl(nod } } + override fun getInputExprs() = if (operator == MEExpressionTypes.TOKEN_INSTANCEOF) { + listOf(leftExpr) + } else { + listOfNotNull(leftExpr, rightExpr) + } + protected abstract val leftExpr: MEExpression protected abstract val rightExpr: MEExpression? diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECapturingExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECapturingExpressionImplMixin.kt index 4fefc8ad3..3401aab82 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECapturingExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECapturingExpressionImplMixin.kt @@ -32,5 +32,7 @@ abstract class MECapturingExpressionImplMixin(node: ASTNode) : MEExpressionImpl( return expression?.matchesJava(java, context) == true } + override fun getInputExprs() = listOfNotNull(expression) + protected abstract val expression: MEExpression? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt index 68d071951..119590f1f 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt @@ -59,5 +59,7 @@ abstract class MECastExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) } } + override fun getInputExprs() = listOfNotNull(castedExpr) + protected abstract val expressionList: List } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEClassConstantExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEClassConstantExpressionImplMixin.kt index 5cf7bf337..8415f7cd0 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEClassConstantExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEClassConstantExpressionImplMixin.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.intellij.lang.ASTNode @@ -53,5 +54,7 @@ abstract class MEClassConstantExpressionImplMixin(node: ASTNode) : MEExpressionI } } + override fun getInputExprs() = emptyList() + protected abstract val type: METype } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionImplMixin.kt index eb641a520..9cc9b64a6 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionImplMixin.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.psi.MEMatchableElement import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode @@ -30,4 +31,8 @@ abstract class MEExpressionImplMixin(node: ASTNode) : ASTWrapperPsiElement(node) override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { throw UnsupportedOperationException("Please implement matchesJava for your expression type") } + + override fun getInputExprs(): List { + throw UnsupportedOperationException("Please implement getInputExprs for your expression type") + } } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionStatementImplMixin.kt index f19b58369..e895d32ba 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionStatementImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEExpressionStatementImplMixin.kt @@ -31,5 +31,7 @@ abstract class MEExpressionStatementImplMixin(node: ASTNode) : MEStatementImpl(n return expression.matchesJava(java, context) } + override fun getInputExprs() = listOf(expression) + protected abstract val expression: MEExpression } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt index d1448e3e9..561c8c8b9 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt @@ -46,6 +46,8 @@ abstract class MEInstantiationExpressionImplMixin(node: ASTNode) : MEExpressionI arguments?.matchesJava(javaArgs, context) == true } + override fun getInputExprs() = arguments?.expressionList ?: emptyList() + protected abstract val type: MEName protected abstract val arguments: MEArguments? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt index 30523d0ba..733f689f8 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MELitExpressionImplMixin.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MELitExpressionMixin @@ -112,6 +113,8 @@ abstract class MELitExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), } } + override fun getInputExprs() = emptyList() + private val Any?.widened: Any? get() = when (this) { is Int -> toLong() is Float -> toDouble() diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt index 16cc07763..dd3300c11 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt @@ -62,6 +62,8 @@ abstract class MEMemberAccessExpressionImplMixin(node: ASTNode) : MEExpressionIm return memberName.matchesJavaExpr(java, context) } + override fun getInputExprs() = listOf(receiverExpr) + protected abstract val receiverExpr: MEExpression protected abstract val memberName: MEName } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt index 0ef980012..0541cf8e3 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt @@ -63,6 +63,8 @@ abstract class MEMethodCallExpressionImplMixin(node: ASTNode) : MEExpressionImpl return arguments?.matchesJava(java.argumentList, context) == true } + override fun getInputExprs() = listOf(receiverExpr) + (arguments?.expressionList ?: emptyList()) + protected abstract val receiverExpr: MEExpression protected abstract val memberName: MEName protected abstract val arguments: MEArguments? diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt index f21499655..674f511cb 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.intellij.lang.ASTNode @@ -31,6 +32,8 @@ abstract class MENameExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) return MEName.matchesJavaExpr(java, context) } + override fun getInputExprs() = emptyList() + @Suppress("PropertyName") protected abstract val MEName: MEName } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt index b45ba7828..63a0b92c6 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt @@ -108,6 +108,8 @@ abstract class MENewArrayExpressionImplMixin(node: ASTNode) : MEExpressionImpl(n } } + override fun getInputExprs() = dimExprs + (arrayInitializer?.expressionList ?: emptyList()) + protected abstract val elementType: MEName protected abstract val dimExprs: List protected abstract val arrayInitializer: MEArguments? diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEParenthesizedExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEParenthesizedExpressionImplMixin.kt index 0bc457722..4061c6c6a 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEParenthesizedExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEParenthesizedExpressionImplMixin.kt @@ -31,5 +31,7 @@ abstract class MEParenthesizedExpressionImplMixin(node: ASTNode) : MEExpressionI return expression?.matchesJava(java, context) == true } + override fun getInputExprs() = listOfNotNull(expression) + protected abstract val expression: MEExpression? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEReturnStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEReturnStatementImplMixin.kt index 4de931e27..0113a7b86 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEReturnStatementImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEReturnStatementImplMixin.kt @@ -37,5 +37,7 @@ abstract class MEReturnStatementImplMixin(node: ASTNode) : MEStatementImpl(node) return valueExpr?.matchesJava(javaReturnValue, context) == true } + override fun getInputExprs() = listOfNotNull(valueExpr) + protected abstract val valueExpr: MEExpression? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStatementImplMixin.kt index 97e49fe34..39f8ad153 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStatementImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStatementImplMixin.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.psi.MEMatchableElement import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode @@ -30,4 +31,8 @@ abstract class MEStatementImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { throw UnsupportedOperationException("Please implement matchesJava for your statement type") } + + override fun getInputExprs(): List { + throw UnsupportedOperationException("Please implement getInputExprs for your statement type") + } } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt index 1a0cd50a2..ab068e3a9 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt @@ -47,6 +47,8 @@ abstract class MEStaticMethodCallExpressionImplMixin(node: ASTNode) : MEExpressi return arguments?.matchesJava(java.argumentList, context) == true } + override fun getInputExprs() = arguments?.expressionList ?: emptyList() + protected abstract val memberName: MEName protected abstract val arguments: MEArguments? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt index 1d5770ce6..c389c8418 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt @@ -45,6 +45,8 @@ abstract class MESuperCallExpressionImplMixin(node: ASTNode) : MEExpressionImpl( return arguments?.matchesJava(java.argumentList, context) == true } + override fun getInputExprs() = arguments?.expressionList ?: emptyList() + protected abstract val memberName: MEName? protected abstract val arguments: MEArguments? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhisExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhisExpressionImplMixin.kt index 2e979ce38..6a4d25339 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhisExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhisExpressionImplMixin.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement @@ -30,4 +31,6 @@ abstract class METhisExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { return java is PsiThisExpression && java.qualifier == null } + + override fun getInputExprs() = emptyList() } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt index e5bccde10..e18fa1bed 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt @@ -37,5 +37,7 @@ abstract class METhrowStatementImplMixin(node: ASTNode) : MEStatementImpl(node) return valueExpr?.matchesJava(javaException, context) == true } + override fun getInputExprs() = listOfNotNull(valueExpr) + protected abstract val valueExpr: MEExpression? } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt index 3c8533c2b..8238e13d8 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEUnaryExpressionImplMixin.kt @@ -59,5 +59,7 @@ abstract class MEUnaryExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node return expression?.matchesJava(javaOperand, context) == true } + override fun getInputExprs() = listOfNotNull(expression) + protected abstract val expression: MEExpression? } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index 5a3a2a31a..d8edcb81d 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -57,7 +57,6 @@ import com.intellij.psi.PsiModifierList import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.util.parentOfType -import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression import java.util.IdentityHashMap import org.objectweb.asm.tree.AbstractInsnNode @@ -194,11 +193,7 @@ class ExpressionInjectionPoint : InjectionPoint() { ?.let { (it.first as? MEExpressionFile)?.statements?.singleOrNull() } ?: project.meExpressionElementFactory.createFile("do {$text}").statements.singleOrNull() ?: project.meExpressionElementFactory.createStatement("empty") - try { - ExpressionParserFacade.parse(text) to rootStatementPsi - } catch (e: Exception) { - null - } + MEExpressionMatchUtil.createExpression(text)?.let { it to rootStatementPsi } } } .toList() diff --git a/src/main/kotlin/platform/mixin/util/AsmUtil.kt b/src/main/kotlin/platform/mixin/util/AsmUtil.kt index f0c80420f..d5c2ed693 100644 --- a/src/main/kotlin/platform/mixin/util/AsmUtil.kt +++ b/src/main/kotlin/platform/mixin/util/AsmUtil.kt @@ -143,6 +143,18 @@ fun Type.toPsiType(elementFactory: PsiElementFactory, context: PsiElement? = nul return elementFactory.createTypeFromText(javaClassName, context) } +val Type.canonicalName get() = computeCanonicalName(this) + +private fun computeCanonicalName(type: Type): String { + return when (type.sort) { + Type.ARRAY -> computeCanonicalName(type.elementType) + "[]".repeat(type.dimensions) + Type.OBJECT -> type.className.replace('$', '.') + else -> type.className + } +} + +val Type.isPrimitive get() = sort != Type.ARRAY && sort != Type.OBJECT && sort != Type.METHOD + private fun hasAccess(access: Int, flag: Int) = (access and flag) != 0 // ClassNode From 02c8ba450a850d88881efcbda2c29b5f1e8c5608 Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 14 Mar 2024 12:14:24 +0000 Subject: [PATCH 038/100] Bytecode-based completion fixes --- .../mixin/expression/MEExpressionMatchUtil.kt | 17 +++++++++-------- src/main/kotlin/util/psi-utils.kt | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index a32e724ca..e07566466 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -55,6 +55,7 @@ import com.demonwav.mcdev.util.findAnnotations import com.demonwav.mcdev.util.findContainingClass import com.demonwav.mcdev.util.findContainingModifierList import com.demonwav.mcdev.util.findModule +import com.demonwav.mcdev.util.findMultiInjectionHost import com.demonwav.mcdev.util.mapFirstNotNull import com.demonwav.mcdev.util.packageName import com.demonwav.mcdev.util.resolveType @@ -63,7 +64,6 @@ import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.lookup.TailTypeDecorator -import com.intellij.lang.injection.InjectedLanguageManager import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.module.Module import com.intellij.openapi.progress.ProcessCanceledException @@ -305,9 +305,8 @@ object MEExpressionMatchUtil { fun getCompletionVariantsFromBytecode(project: Project, contextElement: PsiElement): List { val statement = contextElement.parentOfType() ?: return emptyList() - val expressionAnnotation = - InjectedLanguageManager.getInstance(project).getInjectionHost(statement)?.parentOfType() - ?: return emptyList() + val expressionAnnotation = contextElement.findMultiInjectionHost()?.parentOfType() + ?: return emptyList() if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { return emptyList() } @@ -726,7 +725,9 @@ object MEExpressionMatchUtil { } private fun String.filterInvalidIdentifierChars(): String { - return asSequence().map { if (MEPsiUtil.isIdentifierPart(it)) it else '_' }.joinToString() + return asSequence().joinToString("") { + if (MEPsiUtil.isIdentifierPart(it)) it.toString() else "_" + } } private fun Type.presentableName(): String = when (sort) { @@ -763,8 +764,8 @@ object MEExpressionMatchUtil { } private fun LookupElementBuilder.withDefinition(id: String, at: String) = withInsertHandler { context, _ -> - val injectionHost = InjectedLanguageManager.getInstance(context.project).getInjectionHost(context.file) - ?: return@withInsertHandler + val contextElement = context.file.findElementAt(context.startOffset) ?: return@withInsertHandler + val injectionHost = contextElement.findMultiInjectionHost() ?: return@withInsertHandler val expressionAnnotation = injectionHost.parentOfType() ?: return@withInsertHandler if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { return@withInsertHandler @@ -785,7 +786,7 @@ object MEExpressionMatchUtil { "@${MixinConstants.MixinExtras.DEFINITION}(id = \"$id\", $at)", modifierList, ) - val addedAnnotation = modifierList.add(newAnnotation) + val addedAnnotation = modifierList.addAfter(newAnnotation, modifierList.annotations.lastOrNull()) // add imports and reformat JavaCodeStyleManager.getInstance(context.project).shortenClassReferences(addedAnnotation) diff --git a/src/main/kotlin/util/psi-utils.kt b/src/main/kotlin/util/psi-utils.kt index 9a7ce1609..be6bd764d 100644 --- a/src/main/kotlin/util/psi-utils.kt +++ b/src/main/kotlin/util/psi-utils.kt @@ -24,6 +24,7 @@ import com.demonwav.mcdev.facet.MinecraftFacet import com.demonwav.mcdev.platform.mcp.McpModule import com.demonwav.mcdev.platform.mcp.McpModuleType import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.lang.injection.InjectedLanguageManager import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.ModuleUtilCore @@ -47,6 +48,7 @@ import com.intellij.psi.PsiEllipsisType import com.intellij.psi.PsiExpression import com.intellij.psi.PsiFile import com.intellij.psi.PsiKeyword +import com.intellij.psi.PsiLanguageInjectionHost import com.intellij.psi.PsiMember import com.intellij.psi.PsiMethod import com.intellij.psi.PsiMethodReferenceExpression @@ -67,6 +69,7 @@ import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTypesUtil import com.intellij.psi.util.TypeConversionUtil +import com.intellij.psi.util.parentOfType import com.intellij.refactoring.changeSignature.ChangeSignatureUtil import com.intellij.util.IncorrectOperationException import com.siyeh.ig.psiutils.ImportUtils @@ -186,6 +189,18 @@ inline fun PsiElement.childrenOfType(): Collection = inline fun PsiElement.childOfType(): T? = PsiTreeUtil.findChildOfType(this, T::class.java) +/** + * [InjectedLanguageManager.getInjectionHost] returns the first host of a multi-host injection for some reason. + * Use this method as a workaround. + */ +fun PsiElement.findMultiInjectionHost(): PsiLanguageInjectionHost? { + val injectedLanguageManager = InjectedLanguageManager.getInstance(project) + val hostFile = injectedLanguageManager.getInjectionHost(this)?.containingFile ?: return null + val hostOffset = injectedLanguageManager.injectedToHost(this, textRange.startOffset) + val hostElement = hostFile.findElementAt(hostOffset) ?: return null + return hostElement.parentOfType(withSelf = true) +} + fun Sequence.filter(filter: ElementFilter?, context: PsiElement): Sequence { filter ?: return this return filter { filter.isAcceptable(it, context) } From ee82630908d24a916e523d7bcffa33ed67a611fc Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 14 Mar 2024 12:24:48 +0000 Subject: [PATCH 039/100] Add new definition annotations below existing definition annotations, or at the top --- .../platform/mixin/expression/MEExpressionMatchUtil.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index e07566466..539f1a628 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -786,7 +786,10 @@ object MEExpressionMatchUtil { "@${MixinConstants.MixinExtras.DEFINITION}(id = \"$id\", $at)", modifierList, ) - val addedAnnotation = modifierList.addAfter(newAnnotation, modifierList.annotations.lastOrNull()) + val addedAnnotation = modifierList.addAfter( + newAnnotation, + modifierList.annotations.lastOrNull { it.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) } + ) // add imports and reformat JavaCodeStyleManager.getInstance(context.project).shortenClassReferences(addedAnnotation) From 21cd38c33fb5dcd1a0c1b1c008e6d2aeee595836 Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 15 Mar 2024 00:42:27 +0000 Subject: [PATCH 040/100] Fix cursor offset --- .../platform/mixin/expression/MEExpressionMatchUtil.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 539f1a628..4d4fd05ab 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -322,7 +322,7 @@ object MEExpressionMatchUtil { handler to annotation } ?: return emptyList() - val cursorOffset = contextElement.textRange.startOffset + val cursorOffset = contextElement.textRange.startOffset - statement.textRange.startOffset return mixinClass.mixinTargets.flatMap { targetClass -> val poolFactory = createIdentifierPoolFactory(module, targetClass, modifierList) @@ -363,6 +363,8 @@ object MEExpressionMatchUtil { removeExplicitCaptures(statement, cursorOffset) replaceUnknownNamesWithWildcards(project, statement, cursorOffset, pool) + val elementAtCursor = statement.findElementAt(cursorOffset.toInt()) ?: return emptyList() + val statementToMatch = statement.copy() as MEStatement replaceCursorInputWithWildcard(project, statementToMatch, cursorOffset.toInt()) @@ -388,6 +390,7 @@ object MEExpressionMatchUtil { val inputExprOnCursor = subExpr.getInputExprs().firstOrNull { it.textRange.contains(cursorOffset.toInt()) } ?: break val exprToMatch = inputExprOnCursor.copy() as MEExpression + cursorOffset.setValue(cursorOffset.toInt() - inputExprOnCursor.textRange.startOffset) replaceCursorInputWithWildcard(project, exprToMatch, cursorOffset.toInt()) val meExpression = createExpression(exprToMatch.text) ?: return emptyList() @@ -424,8 +427,6 @@ object MEExpressionMatchUtil { getInstructionsInFlowTree(flow, cursorInstructions, false) } - val elementAtCursor = statement.findElementAt(cursorOffset.toInt()) ?: return emptyList() - val isInsideMeType = PsiTreeUtil.getParentOfType( elementAtCursor, METype::class.java, From 11ecf75738409ae7e8977f3a431408a27a8c1cb7 Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 18 Mar 2024 13:29:56 +0000 Subject: [PATCH 041/100] Add utilities to textify various ASM nodes --- .../kotlin/platform/mixin/util/AsmUtil.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/main/kotlin/platform/mixin/util/AsmUtil.kt b/src/main/kotlin/platform/mixin/util/AsmUtil.kt index d5c2ed693..161ad6a56 100644 --- a/src/main/kotlin/platform/mixin/util/AsmUtil.kt +++ b/src/main/kotlin/platform/mixin/util/AsmUtil.kt @@ -77,6 +77,8 @@ import com.intellij.psi.util.PsiUtil import com.intellij.refactoring.util.LambdaRefactoringUtil import com.intellij.util.CommonJavaRefactoringUtil import com.llamalad7.mixinextras.utils.TypeUtils +import java.io.PrintWriter +import java.io.StringWriter import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.util.concurrent.ConcurrentHashMap @@ -87,6 +89,7 @@ import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.signature.SignatureReader import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.AnnotationNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.FieldNode @@ -96,6 +99,10 @@ import org.objectweb.asm.tree.InvokeDynamicInsnNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.VarInsnNode +import org.objectweb.asm.util.Textifier +import org.objectweb.asm.util.TraceAnnotationVisitor +import org.objectweb.asm.util.TraceClassVisitor +import org.objectweb.asm.util.TraceMethodVisitor private val LOGGER = loggerForTopLevel() @@ -980,3 +987,43 @@ fun MethodInsnNode.fakeResolve(): ClassAndMethodNode { addConstructorToFakeClass(clazz) return ClassAndMethodNode(clazz, method) } + +// Textifier + +fun ClassNode.textify(): String { + val sw = StringWriter() + accept(TraceClassVisitor(PrintWriter(sw))) + return sw.toString().replaceIndent().trimEnd() +} + +fun FieldNode.textify(): String { + val cv = TraceClassVisitor(null) + accept(cv) + val sw = StringWriter() + cv.p.print(PrintWriter(sw)) + return sw.toString().replaceIndent().trimEnd() +} + +fun MethodNode.textify(): String { + val cv = TraceClassVisitor(null) + accept(cv) + val sw = StringWriter() + cv.p.print(PrintWriter(sw)) + return sw.toString().replaceIndent().trimEnd() +} + +fun AnnotationNode.textify(): String { + val textifier = Textifier() + accept(TraceAnnotationVisitor(textifier)) + val sw = StringWriter() + textifier.print(PrintWriter(sw)) + return sw.toString().replaceIndent().trimEnd() +} + +fun AbstractInsnNode.textify(): String { + val mv = TraceMethodVisitor(Textifier()) + accept(mv) + val sw = StringWriter() + mv.p.print(PrintWriter(sw)) + return sw.toString().replaceIndent().trimEnd() +} From a603be151452ecb4736efcf9be463146e896d8fe Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 18 Mar 2024 14:28:55 +0000 Subject: [PATCH 042/100] Add expression variants, to allow MixinExtras to match expressions as if they were other types of expressions --- .../mixin/expression/MEExpressionMatchUtil.kt | 106 ++++++++++++------ 1 file changed, 69 insertions(+), 37 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 4d4fd05ab..a0c363b6f 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEAssignStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement @@ -365,21 +366,23 @@ object MEExpressionMatchUtil { val elementAtCursor = statement.findElementAt(cursorOffset.toInt()) ?: return emptyList() - val statementToMatch = statement.copy() as MEStatement - replaceCursorInputWithWildcard(project, statementToMatch, cursorOffset.toInt()) - - val meStatement = createExpression(statementToMatch.text) ?: return emptyList() - val matchingFlows = mutableListOf() - findMatchingInstructions( - targetClass, - targetMethod, - pool, - flows, - meStatement, - targetMethod.instructions, - true - ) { match -> - matchingFlows += match.flow + val wildcardReplacedStatement = statement.copy() as MEStatement + replaceCursorInputWithWildcard(project, wildcardReplacedStatement, cursorOffset.toInt()) + + var matchingFlows = mutableListOf() + for (statementToMatch in getStatementVariants(project.meExpressionElementFactory, wildcardReplacedStatement)) { + val meStatement = createExpression(statementToMatch.text) ?: continue + findMatchingInstructions( + targetClass, + targetMethod, + pool, + flows, + meStatement, + targetMethod.instructions, + true + ) { match -> + matchingFlows += match.flow + } } if (matchingFlows.isEmpty()) { return emptyList() @@ -389,35 +392,40 @@ object MEExpressionMatchUtil { while (true) { val inputExprOnCursor = subExpr.getInputExprs().firstOrNull { it.textRange.contains(cursorOffset.toInt()) } ?: break - val exprToMatch = inputExprOnCursor.copy() as MEExpression + val wildcardReplacedExpr = inputExprOnCursor.copy() as MEExpression cursorOffset.setValue(cursorOffset.toInt() - inputExprOnCursor.textRange.startOffset) - replaceCursorInputWithWildcard(project, exprToMatch, cursorOffset.toInt()) - val meExpression = createExpression(exprToMatch.text) ?: return emptyList() + replaceCursorInputWithWildcard(project, wildcardReplacedExpr, cursorOffset.toInt()) + + val newMatchingFlows = mutableSetOf() + for (exprToMatch in getExpressionVariants(project.meExpressionElementFactory, wildcardReplacedExpr)) { + val meExpression = createExpression(exprToMatch.text) ?: continue + + val flattenedInstructions = mutableSetOf() + for (flow in matchingFlows) { + getInstructionsInFlowTree( + flow, + flattenedInstructions, + subExpr !is MEExpressionStatement && subExpr !is MEParenthesizedExpression + ) + } - val flattenedInstructions = mutableSetOf() - for (flow in matchingFlows) { - getInstructionsInFlowTree( - flow, + findMatchingInstructions( + targetClass, + targetMethod, + pool, + flows, + meExpression, flattenedInstructions, - subExpr !is MEExpressionStatement && subExpr !is MEParenthesizedExpression - ) + true + ) { match -> + newMatchingFlows += match.flow + } } - matchingFlows.clear() - findMatchingInstructions( - targetClass, - targetMethod, - pool, - flows, - meExpression, - flattenedInstructions, - true - ) { match -> - matchingFlows += match.flow - } - if (matchingFlows.isEmpty()) { + if (newMatchingFlows.isEmpty()) { return emptyList() } + matchingFlows = newMatchingFlows.toMutableList() subExpr = inputExprOnCursor } @@ -798,6 +806,30 @@ object MEExpressionMatchUtil { CodeStyleManager.getInstance(context.project).reformat(modifierList) } + private fun getStatementVariants( + factory: MEExpressionElementFactory, + statement: MEStatement + ): List { + return if (statement is MEExpressionStatement) { + getExpressionVariants(factory, statement.expression) + } else { + listOf(statement) + } + } + + private fun getExpressionVariants( + factory: MEExpressionElementFactory, + expression: MEExpression + ): List { + val variants = mutableListOf(expression) + + val assignmentStatement = factory.createStatement("? = ?") as MEAssignStatement + assignmentStatement.targetExpr.replace(expression.copy()) + variants += assignmentStatement + + return variants + } + class ExpressionMatch @PublishedApi internal constructor( val flow: FlowValue, val startOffset: Int, From 2323f54068acb4da370a9cc2da8327a649337202 Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 18 Mar 2024 22:12:27 +0000 Subject: [PATCH 043/100] Merge two expression types into a single MENewExpression, improve ME expression completion --- src/main/grammars/MEExpressionParser.bnf | 33 +++---- .../mixin/expression/MEExpressionAnnotator.kt | 82 +++++++++------- .../mixin/expression/MEExpressionMatchUtil.kt | 88 ++++++++++++++--- ...essionMixin.kt => MENewExpressionMixin.kt} | 6 +- .../MEInstantiationExpressionImplMixin.kt | 53 ---------- ...plMixin.kt => MENewExpressionImplMixin.kt} | 98 ++++++++++++------- .../messages/MinecraftDevelopment.properties | 1 + 7 files changed, 199 insertions(+), 162 deletions(-) rename src/main/kotlin/platform/mixin/expression/psi/mixins/{MENewArrayExpressionMixin.kt => MENewExpressionMixin.kt} (82%) delete mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt rename src/main/kotlin/platform/mixin/expression/psi/mixins/impl/{MENewArrayExpressionImplMixin.kt => MENewExpressionImplMixin.kt} (51%) diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index 903b2599d..75b2907c3 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -122,8 +122,7 @@ expression ::= capturingExpression | methodCallExpression | arrayAccessExpression | memberAccessExpression | - instantiationExpression | - newArrayExpression | + newExpression | litExpression | thisExpression | nameExpression { @@ -209,15 +208,6 @@ castExpression ::= parenthesizedExpression expression { mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MECastExpressionImplMixin" } -instantiationExpression ::= TOKEN_NEW name TOKEN_LEFT_PAREN arguments rightParen { - rightAssociative = true - pin = 3 - mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEInstantiationExpressionImplMixin" - methods = [ - type = "name" - ] -} - binaryExpression ::= expression binaryOp expression { pin = 2 implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEBinaryExpressionMixin" @@ -244,17 +234,20 @@ private shiftOp ::= TOKEN_SHL | TOKEN_SHR | TOKEN_USHR private comparisonOp ::= TOKEN_LT | TOKEN_LE | TOKEN_GT | TOKEN_GE private equalityOp ::= TOKEN_EQ | TOKEN_NE -newArrayExpression ::= TOKEN_NEW name - TOKEN_LEFT_BRACKET expression? rightBracket - ( TOKEN_LEFT_BRACKET expression? rightBracket )* - ( TOKEN_LEFT_BRACE arguments rightBrace )? { - pin = 3 - implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENewArrayExpressionMixin" - mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MENewArrayExpressionImplMixin" +newExpression ::= TOKEN_NEW name ( + (TOKEN_LEFT_PAREN arguments rightParen) | + ( + TOKEN_LEFT_BRACKET expression? rightBracket + ( TOKEN_LEFT_BRACKET expression? rightBracket )* + ( TOKEN_LEFT_BRACE arguments rightBrace )? + ) +) { + pin = 1 + implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENewExpressionMixin" + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MENewExpressionImplMixin" methods = [ - elementType = "name" + type = "name" dimExprs = "expression" - arrayInitializer = "arguments" ] } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index 2ff10c913..cbb2dd5b2 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -26,13 +26,12 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEBinaryExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclaration import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclarationItem import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEInstantiationExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MELitExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMemberAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMethodCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENewArrayExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENewExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MESuperCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype @@ -63,8 +62,7 @@ class MEExpressionAnnotator : Annotator { if (!element.isWildcard) { when (val parent = element.parent) { is METype, - is MEInstantiationExpression, - is MENewArrayExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + is MENewExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) .range(element) .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) .create() @@ -160,43 +158,55 @@ class MEExpressionAnnotator : Annotator { .create() } } - is MENewArrayExpression -> { - val initializer = element.arrayInitializer - if (initializer != null) { - if (element.dimExprs.isNotEmpty()) { - holder.newAnnotation( - HighlightSeverity.ERROR, - MCDevBundle("mixinextras.expression.lang.errors.new_array_dim_expr_with_initializer"), - ) - .range(initializer) - .create() - } else if (initializer.expressionList.isEmpty()) { - holder.newAnnotation( - HighlightSeverity.ERROR, - MCDevBundle("mixinextras.expression.lang.errors.empty_array_initializer"), - ) - .range(initializer) - .create() + is MENewExpression -> { + if (element.isArrayCreation) { + val initializer = element.arrayInitializer + if (initializer != null) { + if (element.dimExprs.isNotEmpty()) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.new_array_dim_expr_with_initializer"), + ) + .range(initializer) + .create() + } else if (initializer.expressionList.isEmpty()) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.empty_array_initializer"), + ) + .range(initializer) + .create() + } + } else { + if (element.dimExprs.isEmpty()) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.missing_array_length") + ) + .range(element.dimExprTokens[0].leftBracket) + .create() + } else { + element.dimExprTokens.asSequence().dropWhile { it.expr != null }.forEach { + if (it.expr != null) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.array_length_after_empty") + ) + .range(it.expr) + .create() + } + } + } } - } else { - if (element.dimExprs.isEmpty()) { + } else if (!element.hasConstructorArguments) { + val type = element.type + if (type != null) { holder.newAnnotation( HighlightSeverity.ERROR, - MCDevBundle("mixinextras.expression.lang.errors.missing_array_length") + MCDevBundle("mixinextras.expression.lang.errors.new_no_constructor_args_or_array"), ) - .range(element.dimExprTokens[0].leftBracket) + .range(type) .create() - } else { - element.dimExprTokens.asSequence().dropWhile { it.expr != null }.forEach { - if (it.expr != null) { - holder.newAnnotation( - HighlightSeverity.ERROR, - MCDevBundle("mixinextras.expression.lang.errors.array_length_after_empty") - ) - .range(it.expr) - .create() - } - } } } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index a0c363b6f..368d014ea 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -23,14 +23,15 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEAssignStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECastExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEInstantiationExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMemberAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMethodCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENewArrayExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENewExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEParenthesizedExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallExpression @@ -50,6 +51,7 @@ import com.demonwav.mcdev.platform.mixin.util.cached import com.demonwav.mcdev.platform.mixin.util.canonicalName import com.demonwav.mcdev.platform.mixin.util.isPrimitive import com.demonwav.mcdev.platform.mixin.util.mixinTargets +import com.demonwav.mcdev.platform.mixin.util.textify import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.findAnnotations @@ -116,6 +118,7 @@ typealias FlowMap = Map object MEExpressionMatchUtil { private val LOGGER = logger() + private const val DEBUG_COMPLETION = false fun getFlowMap(project: Project, classIn: ClassNode, methodIn: MethodNode): FlowMap? { if (methodIn.instructions == null) { @@ -353,6 +356,10 @@ object MEExpressionMatchUtil { targetMethod: MethodNode, poolFactory: IdentifierPoolFactory, ): List { + if (DEBUG_COMPLETION) { + println("======") + } + if (targetMethod.instructions == null) { return emptyList() } @@ -371,6 +378,10 @@ object MEExpressionMatchUtil { var matchingFlows = mutableListOf() for (statementToMatch in getStatementVariants(project.meExpressionElementFactory, wildcardReplacedStatement)) { + if (DEBUG_COMPLETION) { + println("Matching against statement ${statementToMatch.text}") + } + val meStatement = createExpression(statementToMatch.text) ?: continue findMatchingInstructions( targetClass, @@ -382,6 +393,9 @@ object MEExpressionMatchUtil { true ) { match -> matchingFlows += match.flow + if (DEBUG_COMPLETION) { + println("Matched ${match.flow.insn.textify()}") + } } } if (matchingFlows.isEmpty()) { @@ -398,6 +412,10 @@ object MEExpressionMatchUtil { val newMatchingFlows = mutableSetOf() for (exprToMatch in getExpressionVariants(project.meExpressionElementFactory, wildcardReplacedExpr)) { + if (DEBUG_COMPLETION) { + println("Matching against expression ${exprToMatch.text}") + } + val meExpression = createExpression(exprToMatch.text) ?: continue val flattenedInstructions = mutableSetOf() @@ -419,6 +437,9 @@ object MEExpressionMatchUtil { true ) { match -> newMatchingFlows += match.flow + if (DEBUG_COMPLETION) { + println("Matched ${match.flow.insn.textify()}") + } } } @@ -435,15 +456,28 @@ object MEExpressionMatchUtil { getInstructionsInFlowTree(flow, cursorInstructions, false) } + if (DEBUG_COMPLETION) { + println("Found ${cursorInstructions.size} matching instructions:") + for (insn in cursorInstructions) { + println("- ${insn.textify()}") + } + } + val isInsideMeType = PsiTreeUtil.getParentOfType( elementAtCursor, METype::class.java, false, MEExpression::class.java ) != null + val isInsideNewExpr = PsiTreeUtil.getParentOfType( + elementAtCursor, + MENewExpression::class.java, + false, + MEExpression::class.java + ) != null val cursorExprInTypePosition = !isInsideMeType && elementAtCursor.parentOfType()?.let(METypeUtil::isExpressionInTypePosition) == true - val inTypePosition = isInsideMeType || cursorExprInTypePosition + val inTypePosition = isInsideMeType || isInsideNewExpr || cursorExprInTypePosition val isPossiblyIncompleteCast = !inTypePosition && elementAtCursor.parentOfType() ?.parents(false) @@ -518,20 +552,12 @@ object MEExpressionMatchUtil { super.visitMemberAccessExpression(o) } - override fun visitInstantiationExpression(o: MEInstantiationExpression) { + override fun visitNewExpression(o: MENewExpression) { val name = o.type - if (!name.isWildcard && !pool.typeExists(name.text)) { - unknownNames += name - } - super.visitInstantiationExpression(o) - } - - override fun visitNewArrayExpression(o: MENewArrayExpression) { - val name = o.elementType - if (!name.isWildcard && !pool.typeExists(name.text)) { + if (name != null && !name.isWildcard && !pool.typeExists(name.text)) { unknownNames += name } - super.visitNewArrayExpression(o) + super.visitNewExpression(o) } }) @@ -827,6 +853,40 @@ object MEExpressionMatchUtil { assignmentStatement.targetExpr.replace(expression.copy()) variants += assignmentStatement + when (expression) { + is MEParenthesizedExpression -> { + val castExpr = factory.createExpression("(?) ?") as MECastExpression + castExpr.castTypeExpr!!.replace(expression.copy()) + variants += castExpr + } + is MENameExpression -> { + val callExpr = factory.createExpression("?()") as MEStaticMethodCallExpression + callExpr.memberName.replace(expression.meName) + variants += callExpr + + val classExpr = factory.createExpression("${expression.text}.class") as MEClassConstantExpression + variants += classExpr + } + is MEMemberAccessExpression -> { + val callExpr = factory.createExpression("?.?()") as MEMethodCallExpression + callExpr.receiverExpr.replace(expression.receiverExpr) + callExpr.memberName.replace(expression.memberName) + variants += callExpr + } + is MENewExpression -> { + val type = expression.type + if (type != null && !expression.hasConstructorArguments && !expression.isArrayCreation) { + val fixedNewExpr = factory.createExpression("new ?()") as MENewExpression + fixedNewExpr.type!!.replace(type) + variants += fixedNewExpr + + val fixedNewArrayExpr = factory.createExpression("new ?[?]") as MENewExpression + fixedNewArrayExpr.type!!.replace(type) + variants += fixedNewArrayExpr + } + } + } + return variants } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENewArrayExpressionMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENewExpressionMixin.kt similarity index 82% rename from src/main/kotlin/platform/mixin/expression/psi/mixins/MENewArrayExpressionMixin.kt rename to src/main/kotlin/platform/mixin/expression/psi/mixins/MENewExpressionMixin.kt index 7f29c8eb2..31b29b4a9 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENewArrayExpressionMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENewExpressionMixin.kt @@ -20,12 +20,16 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArguments import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.intellij.psi.PsiElement -interface MENewArrayExpressionMixin : MEExpression { +interface MENewExpressionMixin : PsiElement { + val isArrayCreation: Boolean + val hasConstructorArguments: Boolean val dimensions: Int val dimExprTokens: List + val arrayInitializer: MEArguments? class DimExprTokens(val leftBracket: PsiElement, val expr: MEExpression?, val rightBracket: PsiElement?) } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt deleted file mode 100644 index 561c8c8b9..000000000 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEInstantiationExpressionImplMixin.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Minecraft Development for IntelliJ - * - * https://mcdev.io/ - * - * Copyright (C) 2024 minecraft-dev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, version 3.0 only. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl - -import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArguments -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl -import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory -import com.intellij.lang.ASTNode -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiNewExpression - -abstract class MEInstantiationExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { - override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { - if (java !is PsiNewExpression) { - return false - } - - if (java.isArrayCreation) { - return false - } - - val javaType = java.type ?: return false - val javaArgs = java.argumentList ?: return false - - return context.project.meExpressionElementFactory.createType(type).matchesJava(javaType, context) && - arguments?.matchesJava(javaArgs, context) == true - } - - override fun getInputExprs() = arguments?.expressionList ?: emptyList() - - protected abstract val type: MEName - protected abstract val arguments: MEArguments? -} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewExpressionImplMixin.kt similarity index 51% rename from src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt rename to src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewExpressionImplMixin.kt index 63a0b92c6..1f72ada47 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewArrayExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENewExpressionImplMixin.kt @@ -27,7 +27,7 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl import com.demonwav.mcdev.platform.mixin.expression.meExpressionElementFactory -import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENewArrayExpressionMixin +import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENewExpressionMixin import com.intellij.lang.ASTNode import com.intellij.psi.PsiArrayType import com.intellij.psi.PsiElement @@ -35,11 +35,15 @@ import com.intellij.psi.PsiNewExpression import com.intellij.psi.util.PsiUtil import com.intellij.psi.util.siblings -abstract class MENewArrayExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MENewArrayExpressionMixin { +abstract class MENewExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MENewExpressionMixin { + override val isArrayCreation get() = findChildByType(MEExpressionTypes.TOKEN_LEFT_BRACKET) != null + + override val hasConstructorArguments get() = findChildByType(MEExpressionTypes.TOKEN_LEFT_PAREN) != null + override val dimensions get() = findChildrenByType(MEExpressionTypes.TOKEN_LEFT_BRACKET).size - override val dimExprTokens: List get() { - val result = mutableListOf() + override val dimExprTokens: List get() { + val result = mutableListOf() var leftBracket: PsiElement? = findNotNullChildByType(MEExpressionTypes.TOKEN_LEFT_BRACKET) while (leftBracket != null) { @@ -59,58 +63,76 @@ abstract class MENewArrayExpressionImplMixin(node: ASTNode) : MEExpressionImpl(n } } } - result += MENewArrayExpressionMixin.DimExprTokens(leftBracket, expr, rightBracket) + result += MENewExpressionMixin.DimExprTokens(leftBracket, expr, rightBracket) leftBracket = nextLeftBracket } return result } + override val arrayInitializer get() = if (isArrayCreation) { + arguments + } else { + null + } + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { if (java !is PsiNewExpression) { return false } - if (!java.isArrayCreation) { - return false - } + if (isArrayCreation) { + if (!java.isArrayCreation) { + return false + } - val javaArrayType = java.type as? PsiArrayType ?: return false - if (javaArrayType.arrayDimensions != dimensions) { - return false - } + val javaArrayType = java.type as? PsiArrayType ?: return false + if (javaArrayType.arrayDimensions != dimensions) { + return false + } - val matchesType = context.project.meExpressionElementFactory.createType(elementType) - .matchesJava(javaArrayType.deepComponentType, context) - if (!matchesType) { - return false - } + val matchesType = context.project.meExpressionElementFactory.createType(type) + .matchesJava(javaArrayType.deepComponentType, context) + if (!matchesType) { + return false + } - val javaArrayDims = java.arrayDimensions - val arrayDims = dimExprs - if (javaArrayDims.size != arrayDims.size) { - return false - } - if (!javaArrayDims.asSequence().zip(arrayDims.asSequence()).all { (javaArrayDim, arrayDim) -> - val actualJavaDim = PsiUtil.skipParenthesizedExprDown(javaArrayDim) ?: return@all false - arrayDim.matchesJava(actualJavaDim, context) - } - ) { - return false - } + val javaArrayDims = java.arrayDimensions + val arrayDims = dimExprs + if (javaArrayDims.size != arrayDims.size) { + return false + } + if (!javaArrayDims.asSequence().zip(arrayDims.asSequence()).all { (javaArrayDim, arrayDim) -> + val actualJavaDim = PsiUtil.skipParenthesizedExprDown(javaArrayDim) ?: return@all false + arrayDim.matchesJava(actualJavaDim, context) + } + ) { + return false + } + + val javaArrayInitializer = java.arrayInitializer + val arrayInitializer = this.arrayInitializer + return if (javaArrayInitializer == null) { + arrayInitializer == null + } else { + arrayInitializer?.matchesJava(javaArrayInitializer.initializers, context) == true + } + } else { // !isArrayCreation + if (java.isArrayCreation) { + return false + } + + val javaType = java.type ?: return false + val javaArgs = java.argumentList ?: return false - val javaArrayInitializer = java.arrayInitializer - val arrayInitializer = this.arrayInitializer - return if (javaArrayInitializer == null) { - arrayInitializer == null - } else { - arrayInitializer?.matchesJava(javaArrayInitializer.initializers, context) == true + return context.project.meExpressionElementFactory.createType(type).matchesJava(javaType, context) && + arguments?.matchesJava(javaArgs, context) == true } } - override fun getInputExprs() = dimExprs + (arrayInitializer?.expressionList ?: emptyList()) + override fun getInputExprs() = dimExprs + (arguments?.expressionList ?: emptyList()) - protected abstract val elementType: MEName + protected abstract val type: MEName protected abstract val dimExprs: List - protected abstract val arrayInitializer: MEArguments? + protected abstract val arguments: MEArguments? } diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 01c9ce2d1..007062d58 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -209,6 +209,7 @@ mixinextras.expression.lang.errors.instanceof_non_type=Expected type mixinextras.expression.lang.errors.invalid_number=Invalid number mixinextras.expression.lang.errors.missing_array_length=Array construction must contain a length mixinextras.expression.lang.errors.new_array_dim_expr_with_initializer=Cannot use initializer for array with specified length +mixinextras.expression.lang.errors.new_no_constructor_args_or_array=Expected constructor arguments or array creation mixinextras.expression.lang.display_name=MixinExtras Expressions mixinextras.expression.lang.highlighting.bad_char.display_name=Bad character From 6f12b1dc3dae7ea8f08794ce499c9fbf251cf2e8 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 19 Mar 2024 20:29:02 +0000 Subject: [PATCH 044/100] Add better completion tail types --- .../MEExpressionCompletionContributor.kt | 32 +++--- .../mixin/expression/MEExpressionMatchUtil.kt | 106 ++++++++++++------ .../mixinextras/ExpressionInjectionPoint.kt | 6 +- 3 files changed, 95 insertions(+), 49 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt index bef98c8d2..8ce7a38f8 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt @@ -188,27 +188,29 @@ class MEExpressionCompletionContributor : CompletionContributor() { private class Keyword(val name: String, val tailType: TailType = TailType.NONE) - class BracketsTailType(private val dimensions: Int) : TailType() { + class ParenthesesTailType(private val hasParameters: Boolean) : TailType() { override fun processTail(editor: Editor, tailOffset: Int): Int { - editor.document.insertString(tailOffset, "[]".repeat(dimensions)) - return moveCaret(editor, tailOffset, 2 * dimensions) + editor.document.insertString(tailOffset, "()") + return moveCaret(editor, tailOffset, if (hasParameters) 1 else 2) } override fun isApplicable(context: InsertionContext): Boolean { val chars = context.document.charsSequence - var offset = context.tailOffset - repeat(dimensions) { - offset = CharArrayUtil.shiftForward(chars, offset, " \n\t") - if (!CharArrayUtil.regionMatches(chars, offset, "[")) { - return true - } - offset = CharArrayUtil.shiftForward(chars, offset, " \n\t") - if (!CharArrayUtil.regionMatches(chars, offset, "]")) { - return true - } - } + val offset = CharArrayUtil.shiftForward(chars, context.tailOffset, " \n\t") + return !CharArrayUtil.regionMatches(chars, offset, "(") + } + } + + class BracketsTailType(private val dimensions: Int, private val hasInitializer: Boolean) : TailType() { + override fun processTail(editor: Editor, tailOffset: Int): Int { + editor.document.insertString(tailOffset, "[]".repeat(dimensions) + if (hasInitializer) "{}" else "") + return moveCaret(editor, tailOffset, if (hasInitializer) 2 * dimensions + 1 else 1) + } - return false + override fun isApplicable(context: InsertionContext): Boolean { + val chars = context.document.charsSequence + val offset = CharArrayUtil.shiftForward(chars, context.tailOffset, " \n\t") + return !CharArrayUtil.regionMatches(chars, offset, "[") } } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 368d014ea..758abc0fa 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -88,12 +88,14 @@ import com.intellij.psi.util.parents import com.intellij.util.PlatformIcons import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression +import com.llamalad7.mixinextras.expression.impl.flow.ComplexDataException import com.llamalad7.mixinextras.expression.impl.flow.ComplexFlowValue import com.llamalad7.mixinextras.expression.impl.flow.DummyFlowValue import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter import com.llamalad7.mixinextras.expression.impl.flow.FlowValue import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool +import com.llamalad7.mixinextras.utils.Decorations import java.util.Collections import java.util.IdentityHashMap import org.apache.commons.lang3.mutable.MutableInt @@ -295,7 +297,8 @@ object MEExpressionMatchUtil { try { if (expr.matches(flow, ExpressionContext(pool, sink, targetClass, targetMethod, forCompletion))) { for ((capturedFlow, startOffset) in captured) { - callback(ExpressionMatch(flow, startOffset, decorations[capturedFlow.insn].orEmpty())) + val capturedInsn = capturedFlow.insnOrNull ?: continue + callback(ExpressionMatch(flow, startOffset, decorations[capturedInsn].orEmpty())) } } } catch (e: ProcessCanceledException) { @@ -394,7 +397,7 @@ object MEExpressionMatchUtil { ) { match -> matchingFlows += match.flow if (DEBUG_COMPLETION) { - println("Matched ${match.flow.insn.textify()}") + println("Matched ${match.flow.insnOrNull?.textify()}") } } } @@ -438,7 +441,7 @@ object MEExpressionMatchUtil { ) { match -> newMatchingFlows += match.flow if (DEBUG_COMPLETION) { - println("Matched ${match.flow.insn.textify()}") + println("Matched ${match.flow.insnOrNull?.textify()}") } } } @@ -486,7 +489,7 @@ object MEExpressionMatchUtil { val canCompleteTypes = inTypePosition || isPossiblyIncompleteCast return cursorInstructions.mapNotNull { insn -> - getCompletionForInstruction(insn, mixinClass, canCompleteExprs, canCompleteTypes) + getCompletionForInstruction(insn, flows, mixinClass, canCompleteExprs, canCompleteTypes) } } @@ -623,6 +626,7 @@ object MEExpressionMatchUtil { private fun getCompletionForInstruction( insn: AbstractInsnNode, + flows: FlowMap, mixinClass: PsiClass, canCompleteExprs: Boolean, canCompleteTypes: Boolean @@ -672,54 +676,84 @@ object MEExpressionMatchUtil { if (insn.opcode == Opcodes.INVOKESTATIC) { lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) } - return lookup + return object : TailTypeDecorator(lookup) { + override fun computeTailType(context: InsertionContext?) = + MEExpressionCompletionContributor.ParenthesesTailType(!insn.desc.startsWith("()")) + } } } is TypeInsnNode -> { - // TODO: put cursor in right position for array lengths for array creation val type = Type.getObjectType(insn.desc) if (canCompleteTypes && type.isAccessibleFrom(mixinClass)) { val lookup = createTypeLookup(type) - if (insn.opcode == Opcodes.ANEWARRAY) { - return object : TailTypeDecorator(lookup) { - override fun computeTailType(context: InsertionContext?) = - MEExpressionCompletionContributor.BracketsTailType(1) + when (insn.opcode) { + Opcodes.ANEWARRAY -> { + return object : TailTypeDecorator(lookup) { + override fun computeTailType(context: InsertionContext?) = + MEExpressionCompletionContributor.BracketsTailType( + 1, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + ) + } } - } else { - return lookup + Opcodes.NEW -> { + val initCall = flows[insn]?.next?.firstOrNull { + val nextInsn = it.left.insnOrNull ?: return@firstOrNull false + it.right == 0 && + nextInsn.opcode == Opcodes.INVOKESPECIAL && + (nextInsn as MethodInsnNode).name == "" + }?.left?.insn as MethodInsnNode? + return object : TailTypeDecorator(lookup) { + override fun computeTailType(context: InsertionContext?) = + MEExpressionCompletionContributor.ParenthesesTailType( + initCall?.desc?.startsWith("()") == false + ) + } + } + else -> return lookup } } } is IntInsnNode -> { if (insn.opcode == Opcodes.NEWARRAY) { - val type = when (insn.operand) { - Opcodes.T_BOOLEAN -> "boolean" - Opcodes.T_CHAR -> "char" - Opcodes.T_FLOAT -> "float" - Opcodes.T_DOUBLE -> "double" - Opcodes.T_BYTE -> "byte" - Opcodes.T_SHORT -> "short" - Opcodes.T_INT -> "int" - Opcodes.T_LONG -> "long" - else -> "unknown" // wtf? + if (canCompleteTypes) { + val type = when (insn.operand) { + Opcodes.T_BOOLEAN -> "boolean" + Opcodes.T_CHAR -> "char" + Opcodes.T_FLOAT -> "float" + Opcodes.T_DOUBLE -> "double" + Opcodes.T_BYTE -> "byte" + Opcodes.T_SHORT -> "short" + Opcodes.T_INT -> "int" + Opcodes.T_LONG -> "long" + else -> "unknown" // wtf? + } + return object : TailTypeDecorator( + LookupElementBuilder.create(type).withIcon(PlatformIcons.CLASS_ICON) + ) { + override fun computeTailType(context: InsertionContext?) = + MEExpressionCompletionContributor.BracketsTailType( + 1, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + ) + } } + } + } + is MultiANewArrayInsnNode -> { + if (canCompleteTypes) { + val type = Type.getType(insn.desc) return object : TailTypeDecorator( - LookupElementBuilder.create(type).withIcon(PlatformIcons.CLASS_ICON) + createTypeLookup(type.elementType) ) { override fun computeTailType(context: InsertionContext?) = - MEExpressionCompletionContributor.BracketsTailType(1) + MEExpressionCompletionContributor.BracketsTailType( + type.dimensions, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + ) } } } - is MultiANewArrayInsnNode -> { - val type = Type.getType(insn.desc) - return object : TailTypeDecorator( - createTypeLookup(type.elementType) - ) { - override fun computeTailType(context: InsertionContext?) = - MEExpressionCompletionContributor.BracketsTailType(type.dimensions) - } - } is InsnNode -> { when (insn.opcode) { Opcodes.ARRAYLENGTH -> { @@ -890,6 +924,12 @@ object MEExpressionMatchUtil { return variants } + val FlowValue.insnOrNull: AbstractInsnNode? get() = try { + insn + } catch (e: ComplexDataException) { + null + } + class ExpressionMatch @PublishedApi internal constructor( val flow: FlowValue, val startOffset: Int, diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index d8edcb81d..5abfd4d67 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -22,6 +22,7 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras import com.demonwav.mcdev.platform.mixin.expression.IdentifierPoolFactory import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.insnOrNull import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement @@ -235,7 +236,10 @@ class ExpressionInjectionPoint : InjectionPoint() { ?.parentOfType(withSelf = true) ?.expression ?: psiExpr - result.putIfAbsent(match.flow.insn, capturedExpr to match.decorations) + val insn = match.flow.insnOrNull + if (insn != null) { + result.putIfAbsent(insn, capturedExpr to match.decorations) + } } } From a6aac8c45dd5a111a06c3f5c649bb0c9d51edaa9 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 19 Mar 2024 20:39:36 +0000 Subject: [PATCH 045/100] Fix completion not working inside of constructors --- .../mixin/expression/MEExpressionMatchUtil.kt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 758abc0fa..37d5331a9 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -424,7 +424,7 @@ object MEExpressionMatchUtil { val flattenedInstructions = mutableSetOf() for (flow in matchingFlows) { getInstructionsInFlowTree( - flow, + findFlowTreeRoot(flow), flattenedInstructions, subExpr !is MEExpressionStatement && subExpr !is MEParenthesizedExpression ) @@ -456,7 +456,7 @@ object MEExpressionMatchUtil { val cursorInstructions = mutableSetOf() for (flow in matchingFlows) { - getInstructionsInFlowTree(flow, cursorInstructions, false) + getInstructionsInFlowTree(findFlowTreeRoot(flow), cursorInstructions, false) } if (DEBUG_COMPLETION) { @@ -607,6 +607,20 @@ object MEExpressionMatchUtil { } } + private fun findFlowTreeRoot(flow: FlowValue): FlowValue { + val insn = flow.insnOrNull ?: return flow + return if (insn.opcode == Opcodes.NEW) { + flow.next.firstOrNull { + val nextInsn = it.left.insnOrNull ?: return@firstOrNull false + it.right == 0 && + nextInsn.opcode == Opcodes.INVOKESPECIAL && + (nextInsn as MethodInsnNode).name == "" + }?.left ?: flow + } else { + flow + } + } + private fun getInstructionsInFlowTree( flow: FlowValue, outInstructions: MutableSet, From c5ee3d0c031a2d78dc769223005cc701dc0024ab Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 19 Mar 2024 22:10:01 +0000 Subject: [PATCH 046/100] Add errors/warnings for unused and unresolved definitions, and highlight primitive types specially --- .../mixin/expression/MEExpressionAnnotator.kt | 162 ++++++++++++++---- .../MEExpressionColorSettingsPage.kt | 7 +- .../MEExpressionSyntaxHighlighter.kt | 4 + .../messages/MinecraftDevelopment.properties | 4 + 4 files changed, 144 insertions(+), 33 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index cbb2dd5b2..8d534e591 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -36,10 +36,21 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallEx import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MESuperCallExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil +import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.util.findMultiInjectionHost +import com.intellij.codeInspection.InspectionManager +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.RemoveAnnotationQuickFix import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.Annotator import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiElement +import com.intellij.psi.PsiModifierListOwner +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.psi.util.TypeConversionUtil +import com.intellij.psi.util.parentOfType class MEExpressionAnnotator : Annotator { override fun annotate(element: PsiElement, holder: AnnotationHolder) { @@ -47,52 +58,43 @@ class MEExpressionAnnotator : Annotator { is MEDeclaration -> { val parent = element.parent as? MEDeclarationItem ?: return if (parent.isType) { - holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) - .range(element) - .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_TYPE_DECLARATION) - .create() + highlightDeclaration(holder, element, MEExpressionSyntaxHighlighter.IDENTIFIER_TYPE_DECLARATION) } else { - holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) - .range(element) - .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_DECLARATION) - .create() + highlightDeclaration(holder, element, MEExpressionSyntaxHighlighter.IDENTIFIER_DECLARATION) } } is MEName -> { if (!element.isWildcard) { when (val parent = element.parent) { is METype, - is MENewExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) - .range(element) - .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) - .create() - is MEMemberAccessExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) - .range(element) - .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_MEMBER_NAME) - .create() + is MENewExpression -> highlightType(holder, element) + is MEMemberAccessExpression -> highlightVariable( + holder, + element, + MEExpressionSyntaxHighlighter.IDENTIFIER_MEMBER_NAME, + true, + ) is MESuperCallExpression, is MEMethodCallExpression, - is MEStaticMethodCallExpression -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) - .range(element) - .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CALL) - .create() + is MEStaticMethodCallExpression -> highlightVariable( + holder, + element, + MEExpressionSyntaxHighlighter.IDENTIFIER_CALL, + false, + ) is MENameExpression -> { if (METypeUtil.isExpressionDirectlyInTypePosition(parent)) { - holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) - .range(element) - .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) - .create() + highlightType(holder, element) } else { - holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) - .range(element) - .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_VARIABLE) - .create() + highlightVariable( + holder, + element, + MEExpressionSyntaxHighlighter.IDENTIFIER_VARIABLE, + false, + ) } } - else -> holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) - .range(element) - .textAttributes(MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME) - .create() + else -> highlightType(holder, element) } } } @@ -212,4 +214,100 @@ class MEExpressionAnnotator : Annotator { } } } + + private fun highlightDeclaration( + holder: AnnotationHolder, + declaration: MEDeclaration, + defaultColor: TextAttributesKey, + ) { + val isUnused = ReferencesSearch.search(declaration).findFirst() == null + + if (isUnused) { + val message = MCDevBundle("mixinextras.expression.lang.errors.unused_definition") + val annotation = holder.newAnnotation(HighlightSeverity.WARNING, message) + .range(declaration) + .highlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL) + + val containingAnnotation = declaration.findMultiInjectionHost()?.parentOfType()?.takeIf { + it.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) + } + if (containingAnnotation != null) { + val inspectionManager = InspectionManager.getInstance(containingAnnotation.project) + @Suppress("StatefulEp") // IntelliJ is wrong here + val fix = object : RemoveAnnotationQuickFix( + containingAnnotation, + containingAnnotation.parentOfType() + ) { + override fun getFamilyName() = MCDevBundle("mixinextras.expression.lang.errors.unused_symbol.fix") + } + val problemDescriptor = inspectionManager.createProblemDescriptor( + declaration, + message, + fix, + ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, + true + ) + annotation.newLocalQuickFix(fix, problemDescriptor).registerFix() + } + + annotation.create() + } else { + holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(declaration) + .textAttributes(defaultColor) + .create() + } + } + + private fun highlightType(holder: AnnotationHolder, type: MEName) { + val typeName = type.text + val isPrimitive = typeName != "void" && TypeConversionUtil.isPrimitive(typeName) + val isUnresolved = !isPrimitive && type.reference?.resolve() == null + + if (isUnresolved) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.unresolved_symbol") + ) + .range(type) + .highlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL) + .create() + } else { + holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(type) + .textAttributes( + if (isPrimitive) { + MEExpressionSyntaxHighlighter.IDENTIFIER_PRIMITIVE_TYPE + } else { + MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME + } + ) + .create() + } + } + + private fun highlightVariable( + holder: AnnotationHolder, + variable: MEName, + defaultColor: TextAttributesKey, + isMember: Boolean, + ) { + val variableName = variable.text + val isUnresolved = (variableName != "length" || !isMember) && variable.reference?.resolve() == null + + if (isUnresolved) { + holder.newAnnotation( + HighlightSeverity.ERROR, + MCDevBundle("mixinextras.expression.lang.errors.unresolved_symbol") + ) + .range(variable) + .highlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL) + .create() + } else { + holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) + .range(variable) + .textAttributes(defaultColor) + .create() + } + } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt index c1abe4a30..1144a1929 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt @@ -89,6 +89,10 @@ class MEExpressionColorSettingsPage : ColorSettingsPage { MCDevBundle.pointer("mixinextras.expression.lang.highlighting.class_name_identifier.display_name"), MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.primitive_type_identifier.display_name"), + MEExpressionSyntaxHighlighter.IDENTIFIER_PRIMITIVE_TYPE + ), AttributesDescriptor( MCDevBundle.pointer("mixinextras.expression.lang.highlighting.member_name_identifier.display_name"), MEExpressionSyntaxHighlighter.IDENTIFIER_MEMBER_NAME @@ -117,6 +121,7 @@ class MEExpressionColorSettingsPage : ColorSettingsPage { "call" to MEExpressionSyntaxHighlighter.IDENTIFIER_CALL, "class_name" to MEExpressionSyntaxHighlighter.IDENTIFIER_CLASS_NAME, "member_name" to MEExpressionSyntaxHighlighter.IDENTIFIER_MEMBER_NAME, + "primitive_type" to MEExpressionSyntaxHighlighter.IDENTIFIER_PRIMITIVE_TYPE, "variable" to MEExpressionSyntaxHighlighter.IDENTIFIER_VARIABLE, ) } @@ -131,7 +136,7 @@ class MEExpressionColorSettingsPage : ColorSettingsPage { ?, ClassName.class, foo.bar, - new int[] { 1, 2, 3 }, + new int[] { 1, 2, 3 }, 'a bad character: ' # other_identifier )[0] """.trimIndent() diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt index e1aee7ef6..d0463d1c2 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt @@ -95,6 +95,10 @@ class MEExpressionSyntaxHighlighter : SyntaxHighlighterBase() { "MEEXPRESSION_IDENTIFIER_CLASS_NAME", DefaultLanguageHighlighterColors.CLASS_REFERENCE ) + val IDENTIFIER_PRIMITIVE_TYPE = createTextAttributesKey( + "MEEXPRESSION_IDENTIFIER_PRIMITIVE_TYPE", + DefaultLanguageHighlighterColors.KEYWORD + ) val IDENTIFIER_MEMBER_NAME = createTextAttributesKey( "MEEXPRESSION_IDENTIFIER_MEMBER_NAME", DefaultLanguageHighlighterColors.INSTANCE_FIELD diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 007062d58..7e1c2a733 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -210,6 +210,9 @@ mixinextras.expression.lang.errors.invalid_number=Invalid number mixinextras.expression.lang.errors.missing_array_length=Array construction must contain a length mixinextras.expression.lang.errors.new_array_dim_expr_with_initializer=Cannot use initializer for array with specified length mixinextras.expression.lang.errors.new_no_constructor_args_or_array=Expected constructor arguments or array creation +mixinextras.expression.lang.errors.unresolved_symbol=Unresolved symbol +mixinextras.expression.lang.errors.unused_definition=Unused definition +mixinextras.expression.lang.errors.unused_symbol.fix=Remove definition mixinextras.expression.lang.display_name=MixinExtras Expressions mixinextras.expression.lang.highlighting.bad_char.display_name=Bad character @@ -227,6 +230,7 @@ mixinextras.expression.lang.highlighting.member_name_identifier.display_name=Ide mixinextras.expression.lang.highlighting.number.display_name=Number mixinextras.expression.lang.highlighting.operator.display_name=Operator mixinextras.expression.lang.highlighting.parens.display_name=Parentheses +mixinextras.expression.lang.highlighting.primitive_type_identifier.display_name=Identifier//Primitive type mixinextras.expression.lang.highlighting.string.display_name=String mixinextras.expression.lang.highlighting.string_escape.display_name=String escape mixinextras.expression.lang.highlighting.type_declaration_identifier.display_name=Identifier//Type declaration From 373a0cf4bd0b138875cafca0722a70b63b493cff Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 20 Mar 2024 10:52:22 +0000 Subject: [PATCH 047/100] Split MatchUtil into CompletionUtil --- .../MEExpressionCompletionContributor.kt | 115 +-- .../expression/MEExpressionCompletionUtil.kt | 830 ++++++++++++++++++ .../mixin/expression/MEExpressionMatchUtil.kt | 691 --------------- 3 files changed, 836 insertions(+), 800 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt index 8ce7a38f8..d3c7f4377 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt @@ -20,12 +20,6 @@ package com.demonwav.mcdev.platform.mixin.expression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MELitExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatementItem -import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil.notInTypePosition -import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil.validType import com.intellij.codeInsight.TailType import com.intellij.codeInsight.completion.BasicExpressionCompletionContributor import com.intellij.codeInsight.completion.CompletionContributor @@ -36,84 +30,13 @@ import com.intellij.codeInsight.completion.CompletionType import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.TailTypeDecorator -import com.intellij.openapi.editor.Editor -import com.intellij.patterns.PlatformPatterns.psiElement -import com.intellij.patterns.StandardPatterns.and -import com.intellij.patterns.StandardPatterns.not -import com.intellij.psi.tree.TokenSet import com.intellij.util.ProcessingContext -import com.intellij.util.text.CharArrayUtil class MEExpressionCompletionContributor : CompletionContributor() { - companion object { - private val NORMAL_ELEMENT = psiElement() - .inside(MEStatement::class.java) - .andNot(psiElement().inside(MELitExpression::class.java)) - .notInTypePosition() - private val TYPE_PATTERN = psiElement() - .inside(MEStatement::class.java) - .validType() - private val AFTER_END_EXPRESSION_PATTERN = psiElement().afterLeaf( - psiElement().withElementType( - TokenSet.create( - MEExpressionTypes.TOKEN_IDENTIFIER, - MEExpressionTypes.TOKEN_WILDCARD, - MEExpressionTypes.TOKEN_RIGHT_PAREN, - MEExpressionTypes.TOKEN_RIGHT_BRACKET, - MEExpressionTypes.TOKEN_RIGHT_BRACE, - MEExpressionTypes.TOKEN_BOOL_LIT, - MEExpressionTypes.TOKEN_CLASS, - MEExpressionTypes.TOKEN_INT_LIT, - MEExpressionTypes.TOKEN_DEC_LIT, - MEExpressionTypes.TOKEN_NULL_LIT, - MEExpressionTypes.TOKEN_STRING_TERMINATOR, - ) - ) - ) - - private val STATEMENT_KEYWORD_PLACE = psiElement().afterLeaf( - psiElement().withText("{").withParent(MEStatementItem::class.java) - ) - private val VALUE_KEYWORD_PLACE = and( - NORMAL_ELEMENT, - not(AFTER_END_EXPRESSION_PATTERN), - not(psiElement().afterLeaf(".")), - ) - private val CLASS_PLACE = and( - NORMAL_ELEMENT, - psiElement() - .afterLeaf(psiElement().withText(".").withParent(psiElement().withFirstChild(TYPE_PATTERN))), - ) - private val INSTANCEOF_PLACE = and( - NORMAL_ELEMENT, - AFTER_END_EXPRESSION_PATTERN, - ) - private val FROM_BYTECODE_PLACE = psiElement() - .inside(MEStatement::class.java) - .andNot(psiElement().inside(MELitExpression::class.java)) - - val DOT_CLASS_TAIL = object : TailType() { - override fun processTail(editor: Editor, tailOffset: Int): Int { - editor.document.insertString(tailOffset, ".class") - return moveCaret(editor, tailOffset, 6) - } - - override fun isApplicable(context: InsertionContext): Boolean { - val chars = context.document.charsSequence - val dotOffset = CharArrayUtil.shiftForward(chars, context.tailOffset, " \n\t") - if (!CharArrayUtil.regionMatches(chars, dotOffset, ".")) { - return true - } - val classOffset = CharArrayUtil.shiftForward(chars, dotOffset + 1, " \n\t") - return !CharArrayUtil.regionMatches(chars, classOffset, "class") - } - } - } - init { extend( CompletionType.BASIC, - STATEMENT_KEYWORD_PLACE, + MEExpressionCompletionUtil.STATEMENT_KEYWORD_PLACE, KeywordCompletionProvider( Keyword("return", TailType.INSERT_SPACE), Keyword("throw", TailType.INSERT_SPACE), @@ -121,7 +44,7 @@ class MEExpressionCompletionContributor : CompletionContributor() { ) extend( CompletionType.BASIC, - VALUE_KEYWORD_PLACE, + MEExpressionCompletionUtil.VALUE_KEYWORD_PLACE, KeywordCompletionProvider( Keyword("this"), Keyword("super"), @@ -133,21 +56,21 @@ class MEExpressionCompletionContributor : CompletionContributor() { ) extend( CompletionType.BASIC, - CLASS_PLACE, + MEExpressionCompletionUtil.CLASS_PLACE, KeywordCompletionProvider( Keyword("class") ) ) extend( CompletionType.BASIC, - INSTANCEOF_PLACE, + MEExpressionCompletionUtil.INSTANCEOF_PLACE, KeywordCompletionProvider( Keyword("instanceof", TailType.INSERT_SPACE) ) ) extend( CompletionType.BASIC, - FROM_BYTECODE_PLACE, + MEExpressionCompletionUtil.FROM_BYTECODE_PLACE, object : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, @@ -156,7 +79,7 @@ class MEExpressionCompletionContributor : CompletionContributor() { ) { val project = parameters.originalFile.project result.addAllElements( - MEExpressionMatchUtil.getCompletionVariantsFromBytecode(project, parameters.position) + MEExpressionCompletionUtil.getCompletionVariantsFromBytecode(project, parameters.position) ) } } @@ -187,30 +110,4 @@ class MEExpressionCompletionContributor : CompletionContributor() { } private class Keyword(val name: String, val tailType: TailType = TailType.NONE) - - class ParenthesesTailType(private val hasParameters: Boolean) : TailType() { - override fun processTail(editor: Editor, tailOffset: Int): Int { - editor.document.insertString(tailOffset, "()") - return moveCaret(editor, tailOffset, if (hasParameters) 1 else 2) - } - - override fun isApplicable(context: InsertionContext): Boolean { - val chars = context.document.charsSequence - val offset = CharArrayUtil.shiftForward(chars, context.tailOffset, " \n\t") - return !CharArrayUtil.regionMatches(chars, offset, "(") - } - } - - class BracketsTailType(private val dimensions: Int, private val hasInitializer: Boolean) : TailType() { - override fun processTail(editor: Editor, tailOffset: Int): Int { - editor.document.insertString(tailOffset, "[]".repeat(dimensions) + if (hasInitializer) "{}" else "") - return moveCaret(editor, tailOffset, if (hasInitializer) 2 * dimensions + 1 else 1) - } - - override fun isApplicable(context: InsertionContext): Boolean { - val chars = context.document.charsSequence - val offset = CharArrayUtil.shiftForward(chars, context.tailOffset, " \n\t") - return !CharArrayUtil.regionMatches(chars, offset, "[") - } - } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt new file mode 100644 index 000000000..728ba5afe --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -0,0 +1,830 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.insnOrNull +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEAssignStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECastExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MELitExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMemberAccessExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMethodCallExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENewExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEParenthesizedExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatementItem +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MESuperCallExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype +import com.demonwav.mcdev.platform.mixin.expression.psi.MEMatchableElement +import com.demonwav.mcdev.platform.mixin.expression.psi.MEPsiUtil +import com.demonwav.mcdev.platform.mixin.expression.psi.MERecursiveWalkingVisitor +import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil +import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil.notInTypePosition +import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil.validType +import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler +import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember +import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.platform.mixin.util.canonicalName +import com.demonwav.mcdev.platform.mixin.util.isPrimitive +import com.demonwav.mcdev.platform.mixin.util.mixinTargets +import com.demonwav.mcdev.platform.mixin.util.textify +import com.demonwav.mcdev.util.constantStringValue +import com.demonwav.mcdev.util.findContainingClass +import com.demonwav.mcdev.util.findContainingModifierList +import com.demonwav.mcdev.util.findModule +import com.demonwav.mcdev.util.findMultiInjectionHost +import com.demonwav.mcdev.util.mapFirstNotNull +import com.demonwav.mcdev.util.packageName +import com.intellij.codeInsight.TailType +import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.codeInsight.lookup.TailTypeDecorator +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.patterns.PlatformPatterns +import com.intellij.patterns.StandardPatterns +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiAnonymousClass +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.psi.tree.TokenSet +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.PsiUtil +import com.intellij.psi.util.parentOfType +import com.intellij.psi.util.parents +import com.intellij.util.PlatformIcons +import com.intellij.util.text.CharArrayUtil +import com.llamalad7.mixinextras.expression.impl.flow.ComplexFlowValue +import com.llamalad7.mixinextras.expression.impl.flow.DummyFlowValue +import com.llamalad7.mixinextras.expression.impl.flow.FlowValue +import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool +import com.llamalad7.mixinextras.utils.Decorations +import org.apache.commons.lang3.mutable.MutableInt +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.IincInsnNode +import org.objectweb.asm.tree.InsnNode +import org.objectweb.asm.tree.IntInsnNode +import org.objectweb.asm.tree.LdcInsnNode +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.tree.MultiANewArrayInsnNode +import org.objectweb.asm.tree.TypeInsnNode +import org.objectweb.asm.tree.VarInsnNode + +object MEExpressionCompletionUtil { + private const val DEBUG_COMPLETION = false + + private val NORMAL_ELEMENT = PlatformPatterns.psiElement() + .inside(MEStatement::class.java) + .andNot(PlatformPatterns.psiElement().inside(MELitExpression::class.java)) + .notInTypePosition() + private val TYPE_PATTERN = PlatformPatterns.psiElement() + .inside(MEStatement::class.java) + .validType() + private val AFTER_END_EXPRESSION_PATTERN = PlatformPatterns.psiElement().afterLeaf( + PlatformPatterns.psiElement().withElementType( + TokenSet.create( + MEExpressionTypes.TOKEN_IDENTIFIER, + MEExpressionTypes.TOKEN_WILDCARD, + MEExpressionTypes.TOKEN_RIGHT_PAREN, + MEExpressionTypes.TOKEN_RIGHT_BRACKET, + MEExpressionTypes.TOKEN_RIGHT_BRACE, + MEExpressionTypes.TOKEN_BOOL_LIT, + MEExpressionTypes.TOKEN_CLASS, + MEExpressionTypes.TOKEN_INT_LIT, + MEExpressionTypes.TOKEN_DEC_LIT, + MEExpressionTypes.TOKEN_NULL_LIT, + MEExpressionTypes.TOKEN_STRING_TERMINATOR, + ) + ) + ) + + val STATEMENT_KEYWORD_PLACE = PlatformPatterns.psiElement().afterLeaf( + PlatformPatterns.psiElement().withText("{").withParent(MEStatementItem::class.java) + ) + val VALUE_KEYWORD_PLACE = StandardPatterns.and( + NORMAL_ELEMENT, + StandardPatterns.not(AFTER_END_EXPRESSION_PATTERN), + StandardPatterns.not(PlatformPatterns.psiElement().afterLeaf(".")), + ) + val CLASS_PLACE = StandardPatterns.and( + NORMAL_ELEMENT, + PlatformPatterns.psiElement() + .afterLeaf( + PlatformPatterns.psiElement().withText(".") + .withParent(PlatformPatterns.psiElement().withFirstChild(TYPE_PATTERN)) + ), + ) + val INSTANCEOF_PLACE = StandardPatterns.and( + NORMAL_ELEMENT, + AFTER_END_EXPRESSION_PATTERN, + ) + val FROM_BYTECODE_PLACE = PlatformPatterns.psiElement() + .inside(MEStatement::class.java) + .andNot(PlatformPatterns.psiElement().inside(MELitExpression::class.java)) + + private val DOT_CLASS_TAIL = object : TailType() { + override fun processTail(editor: Editor, tailOffset: Int): Int { + editor.document.insertString(tailOffset, ".class") + return moveCaret(editor, tailOffset, 6) + } + + override fun isApplicable(context: InsertionContext): Boolean { + val chars = context.document.charsSequence + val dotOffset = CharArrayUtil.shiftForward(chars, context.tailOffset, " \n\t") + if (!CharArrayUtil.regionMatches(chars, dotOffset, ".")) { + return true + } + val classOffset = CharArrayUtil.shiftForward(chars, dotOffset + 1, " \n\t") + return !CharArrayUtil.regionMatches(chars, classOffset, "class") + } + } + + fun getCompletionVariantsFromBytecode(project: Project, contextElement: PsiElement): List { + val statement = contextElement.parentOfType() ?: return emptyList() + + val expressionAnnotation = contextElement.findMultiInjectionHost()?.parentOfType() + ?: return emptyList() + if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { + return emptyList() + } + + val modifierList = expressionAnnotation.findContainingModifierList() ?: return emptyList() + val module = modifierList.findModule() ?: return emptyList() + + val mixinClass = modifierList.findContainingClass() ?: return emptyList() + + val (handler, handlerAnnotation) = modifierList.annotations.mapFirstNotNull { annotation -> + val qName = annotation.qualifiedName ?: return@mapFirstNotNull null + val handler = MixinAnnotationHandler.forMixinAnnotation(qName, project) ?: return@mapFirstNotNull null + handler to annotation + } ?: return emptyList() + + val cursorOffset = contextElement.textRange.startOffset - statement.textRange.startOffset + + return mixinClass.mixinTargets.flatMap { targetClass -> + val poolFactory = MEExpressionMatchUtil.createIdentifierPoolFactory(module, targetClass, modifierList) + handler.resolveTarget(handlerAnnotation, targetClass) + .filterIsInstance() + .flatMap { methodTarget -> + + getCompletionVariantsFromBytecode( + project, + mixinClass, + cursorOffset, + statement.copy() as MEStatement, + targetClass, + methodTarget.classAndMethod.method, + poolFactory, + ) + } + } + } + + private fun getCompletionVariantsFromBytecode( + project: Project, + mixinClass: PsiClass, + cursorOffsetIn: Int, + statement: MEStatement, + targetClass: ClassNode, + targetMethod: MethodNode, + poolFactory: IdentifierPoolFactory, + ): List { + if (DEBUG_COMPLETION) { + println("======") + } + + if (targetMethod.instructions == null) { + return emptyList() + } + + val cursorOffset = MutableInt(cursorOffsetIn) + val pool = poolFactory(targetMethod) + val flows = MEExpressionMatchUtil.getFlowMap(project, targetClass, targetMethod) ?: return emptyList() + + removeExplicitCaptures(statement, cursorOffset) + replaceUnknownNamesWithWildcards(project, statement, cursorOffset, pool) + + val elementAtCursor = statement.findElementAt(cursorOffset.toInt()) ?: return emptyList() + + val wildcardReplacedStatement = statement.copy() as MEStatement + replaceCursorInputWithWildcard(project, wildcardReplacedStatement, cursorOffset.toInt()) + + var matchingFlows = mutableListOf() + for (statementToMatch in getStatementVariants(project.meExpressionElementFactory, wildcardReplacedStatement)) { + if (DEBUG_COMPLETION) { + println("Matching against statement ${statementToMatch.text}") + } + + val meStatement = MEExpressionMatchUtil.createExpression(statementToMatch.text) ?: continue + MEExpressionMatchUtil.findMatchingInstructions( + targetClass, + targetMethod, + pool, + flows, + meStatement, + targetMethod.instructions, + true + ) { match -> + matchingFlows += match.flow + if (DEBUG_COMPLETION) { + println("Matched ${match.flow.insnOrNull?.textify()}") + } + } + } + if (matchingFlows.isEmpty()) { + return emptyList() + } + + var subExpr: MEMatchableElement = statement + while (true) { + val inputExprOnCursor = subExpr.getInputExprs().firstOrNull { it.textRange.contains(cursorOffset.toInt()) } + ?: break + val wildcardReplacedExpr = inputExprOnCursor.copy() as MEExpression + cursorOffset.setValue(cursorOffset.toInt() - inputExprOnCursor.textRange.startOffset) + replaceCursorInputWithWildcard(project, wildcardReplacedExpr, cursorOffset.toInt()) + + val newMatchingFlows = mutableSetOf() + for (exprToMatch in getExpressionVariants(project.meExpressionElementFactory, wildcardReplacedExpr)) { + if (DEBUG_COMPLETION) { + println("Matching against expression ${exprToMatch.text}") + } + + val meExpression = MEExpressionMatchUtil.createExpression(exprToMatch.text) ?: continue + + val flattenedInstructions = mutableSetOf() + for (flow in matchingFlows) { + getInstructionsInFlowTree( + findFlowTreeRoot(flow), + flattenedInstructions, + subExpr !is MEExpressionStatement && subExpr !is MEParenthesizedExpression + ) + } + + MEExpressionMatchUtil.findMatchingInstructions( + targetClass, + targetMethod, + pool, + flows, + meExpression, + flattenedInstructions, + true + ) { match -> + newMatchingFlows += match.flow + if (DEBUG_COMPLETION) { + println("Matched ${match.flow.insnOrNull?.textify()}") + } + } + } + + if (newMatchingFlows.isEmpty()) { + return emptyList() + } + matchingFlows = newMatchingFlows.toMutableList() + + subExpr = inputExprOnCursor + } + + val cursorInstructions = mutableSetOf() + for (flow in matchingFlows) { + getInstructionsInFlowTree(findFlowTreeRoot(flow), cursorInstructions, false) + } + + if (DEBUG_COMPLETION) { + println("Found ${cursorInstructions.size} matching instructions:") + for (insn in cursorInstructions) { + println("- ${insn.textify()}") + } + } + + val isInsideMeType = PsiTreeUtil.getParentOfType( + elementAtCursor, + METype::class.java, + false, + MEExpression::class.java + ) != null + val isInsideNewExpr = PsiTreeUtil.getParentOfType( + elementAtCursor, + MENewExpression::class.java, + false, + MEExpression::class.java + ) != null + val cursorExprInTypePosition = !isInsideMeType && + elementAtCursor.parentOfType()?.let(METypeUtil::isExpressionInTypePosition) == true + val inTypePosition = isInsideMeType || isInsideNewExpr || cursorExprInTypePosition + val isPossiblyIncompleteCast = !inTypePosition && + elementAtCursor.parentOfType() + ?.parents(false) + ?.dropWhile { it is MEArrayAccessExpression && it.indexExpr == null } is MEParenthesizedExpression + val canCompleteExprs = !inTypePosition + val canCompleteTypes = inTypePosition || isPossiblyIncompleteCast + + return cursorInstructions.mapNotNull { insn -> + getCompletionForInstruction(insn, flows, mixinClass, canCompleteExprs, canCompleteTypes) + } + } + + private fun replaceUnknownNamesWithWildcards( + project: Project, + statement: MEStatement, + cursorOffset: MutableInt, + pool: IdentifierPool, + ) { + val unknownNames = mutableListOf() + statement.accept(object : MERecursiveWalkingVisitor() { + override fun visitType(o: METype) { + val name = o.meName + if (!name.isWildcard && !pool.typeExists(name.text)) { + unknownNames += name + } + } + + override fun visitNameExpression(o: MENameExpression) { + val name = o.meName + if (!name.isWildcard) { + if (METypeUtil.isExpressionDirectlyInTypePosition(o)) { + if (!pool.typeExists(name.text)) { + unknownNames += name + } + } else { + if (!pool.memberExists(name.text)) { + unknownNames += name + } + } + } + } + + override fun visitSuperCallExpression(o: MESuperCallExpression) { + val name = o.memberName + if (name != null && !name.isWildcard && !pool.memberExists(name.text)) { + unknownNames += name + } + super.visitSuperCallExpression(o) + } + + override fun visitMethodCallExpression(o: MEMethodCallExpression) { + val name = o.memberName + if (!name.isWildcard && !pool.memberExists(name.text)) { + unknownNames += name + } + super.visitMethodCallExpression(o) + } + + override fun visitStaticMethodCallExpression(o: MEStaticMethodCallExpression) { + val name = o.memberName + if (!name.isWildcard && !pool.memberExists(name.text)) { + unknownNames += name + } + super.visitStaticMethodCallExpression(o) + } + + override fun visitMemberAccessExpression(o: MEMemberAccessExpression) { + val name = o.memberName + if (!name.isWildcard && !pool.memberExists(name.text)) { + unknownNames += name + } + super.visitMemberAccessExpression(o) + } + + override fun visitNewExpression(o: MENewExpression) { + val name = o.type + if (name != null && !name.isWildcard && !pool.typeExists(name.text)) { + unknownNames += name + } + super.visitNewExpression(o) + } + }) + + for (unknownName in unknownNames) { + val startOffset = unknownName.textRange.startOffset + if (cursorOffset.toInt() > startOffset) { + cursorOffset.setValue(cursorOffset.toInt() - unknownName.textLength + 1) + } + + unknownName.replace(project.meExpressionElementFactory.createName("?")) + } + } + + private fun removeExplicitCaptures(statement: MEStatement, cursorOffset: MutableInt) { + val captures = mutableListOf() + + statement.accept(object : MERecursiveWalkingVisitor() { + override fun elementFinished(element: PsiElement) { + // do this on elementFinished to ensure that inner captures are replaced before outer captures + if (element is MECapturingExpression) { + captures += element + } + } + }) + + for (capture in captures) { + val innerExpr = capture.expression ?: continue + val textRange = capture.textRange + + if (cursorOffset.toInt() > textRange.startOffset) { + cursorOffset.setValue(cursorOffset.toInt() - if (cursorOffset.toInt() >= textRange.endOffset) 3 else 2) + } + + capture.replace(innerExpr) + } + } + + private fun replaceCursorInputWithWildcard(project: Project, element: MEMatchableElement, cursorOffset: Int) { + for (input in element.getInputExprs()) { + if (input.textRange.contains(cursorOffset)) { + input.replace(project.meExpressionElementFactory.createExpression("?")) + return + } + } + } + + private fun findFlowTreeRoot(flow: FlowValue): FlowValue { + val insn = flow.insnOrNull ?: return flow + return if (insn.opcode == Opcodes.NEW) { + flow.next.firstOrNull { + val nextInsn = it.left.insnOrNull ?: return@firstOrNull false + it.right == 0 && + nextInsn.opcode == Opcodes.INVOKESPECIAL && + (nextInsn as MethodInsnNode).name == "" + }?.left ?: flow + } else { + flow + } + } + + private fun getInstructionsInFlowTree( + flow: FlowValue, + outInstructions: MutableSet, + strict: Boolean + ) { + if (flow is DummyFlowValue || flow is ComplexFlowValue) { + return + } + + if (!strict) { + outInstructions += flow.insn + } + for (i in 0 until flow.inputCount()) { + getInstructionsInFlowTree(flow.getInput(i), outInstructions, false) + } + } + + private fun getCompletionForInstruction( + insn: AbstractInsnNode, + flows: FlowMap, + mixinClass: PsiClass, + canCompleteExprs: Boolean, + canCompleteTypes: Boolean + ): LookupElement? { + when (insn) { + is LdcInsnNode -> { + when (val cst = insn.cst) { + is Type -> { + if (canCompleteTypes && cst.isAccessibleFrom(mixinClass)) { + return object : TailTypeDecorator(createTypeLookup(cst)) { + override fun computeTailType(context: InsertionContext?) = DOT_CLASS_TAIL + } + } + } + // TODO: string literals? + } + } + is VarInsnNode -> { + // TODO: local variables + } + is IincInsnNode -> { + // TODO: local variables + } + is FieldInsnNode -> { + if (canCompleteExprs) { + val at = "at = @${MixinConstants.Annotations.AT}(value = \"FIELD\"," + + " target = \"L${insn.owner};${insn.name}:${insn.desc}\")" + var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) + .withIcon(PlatformIcons.FIELD_ICON) + .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) + .withDefinition(insn.name.toValidIdentifier(), at) + if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.PUTSTATIC) { + lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) + } + return lookup + } + } + is MethodInsnNode -> { + if (canCompleteExprs) { + val at = "at = @${MixinConstants.Annotations.AT}(value = \"INVOKE\"," + + " target = \"L${insn.owner};${insn.name}${insn.desc}\")" + var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) + .withIcon(PlatformIcons.METHOD_ICON) + .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) + .withDefinition(insn.name.toValidIdentifier(), at) + if (insn.opcode == Opcodes.INVOKESTATIC) { + lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) + } + return object : TailTypeDecorator(lookup) { + override fun computeTailType(context: InsertionContext?) = + ParenthesesTailType(!insn.desc.startsWith("()")) + } + } + } + is TypeInsnNode -> { + val type = Type.getObjectType(insn.desc) + if (canCompleteTypes && type.isAccessibleFrom(mixinClass)) { + val lookup = createTypeLookup(type) + when (insn.opcode) { + Opcodes.ANEWARRAY -> { + return object : TailTypeDecorator(lookup) { + override fun computeTailType(context: InsertionContext?) = + BracketsTailType( + 1, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + ) + } + } + Opcodes.NEW -> { + val initCall = flows[insn]?.next?.firstOrNull { + val nextInsn = it.left.insnOrNull ?: return@firstOrNull false + it.right == 0 && + nextInsn.opcode == Opcodes.INVOKESPECIAL && + (nextInsn as MethodInsnNode).name == "" + }?.left?.insn as MethodInsnNode? + return object : TailTypeDecorator(lookup) { + override fun computeTailType(context: InsertionContext?) = + ParenthesesTailType( + initCall?.desc?.startsWith("()") == false + ) + } + } + else -> return lookup + } + } + } + is IntInsnNode -> { + if (insn.opcode == Opcodes.NEWARRAY) { + if (canCompleteTypes) { + val type = when (insn.operand) { + Opcodes.T_BOOLEAN -> "boolean" + Opcodes.T_CHAR -> "char" + Opcodes.T_FLOAT -> "float" + Opcodes.T_DOUBLE -> "double" + Opcodes.T_BYTE -> "byte" + Opcodes.T_SHORT -> "short" + Opcodes.T_INT -> "int" + Opcodes.T_LONG -> "long" + else -> "unknown" // wtf? + } + return object : TailTypeDecorator( + LookupElementBuilder.create(type).withIcon(PlatformIcons.CLASS_ICON) + ) { + override fun computeTailType(context: InsertionContext?) = + BracketsTailType( + 1, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + ) + } + } + } + } + is MultiANewArrayInsnNode -> { + if (canCompleteTypes) { + val type = Type.getType(insn.desc) + return object : TailTypeDecorator( + createTypeLookup(type.elementType) + ) { + override fun computeTailType(context: InsertionContext?) = + BracketsTailType( + type.dimensions, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + ) + } + } + } + is InsnNode -> { + when (insn.opcode) { + Opcodes.ARRAYLENGTH -> { + if (canCompleteExprs) { + return LookupElementBuilder.create("length") + .withIcon(PlatformIcons.FIELD_ICON) + } + } + } + } + } + + return null + } + + private fun Type.typeNameToInsert(): String { + if (sort == Type.ARRAY) { + return elementType.typeNameToInsert() + "[]".repeat(dimensions) + } + if (sort != Type.OBJECT) { + return className + } + + val simpleName = internalName.substringAfterLast('/') + val lastValidCharIndex = (simpleName.length - 1 downTo 0).firstOrNull { + MEPsiUtil.isIdentifierStart(simpleName[it]) + } ?: return "_" + simpleName.filterInvalidIdentifierChars() + + return simpleName.substring(simpleName.lastIndexOf('$', lastValidCharIndex) + 1).toValidIdentifier() + } + + private fun String.toValidIdentifier(): String { + return when { + isEmpty() -> "_" + !MEPsiUtil.isIdentifierStart(this[0]) -> "_" + filterInvalidIdentifierChars() + else -> this[0] + substring(1).filterInvalidIdentifierChars() + } + } + + private fun String.filterInvalidIdentifierChars(): String { + return asSequence().joinToString("") { + if (MEPsiUtil.isIdentifierPart(it)) it.toString() else "_" + } + } + + private fun Type.presentableName(): String = when (sort) { + Type.ARRAY -> elementType.presentableName() + "[]".repeat(dimensions) + Type.OBJECT -> internalName.substringAfterLast('/') + else -> className + } + + private fun Type.isAccessibleFrom(fromClass: PsiClass): Boolean { + return when (sort) { + Type.ARRAY -> elementType.isAccessibleFrom(fromClass) + Type.OBJECT -> { + val facade = JavaPsiFacade.getInstance(fromClass.project) + val clazz = facade.findClass(canonicalName, fromClass.resolveScope) ?: return false + val pkg = fromClass.packageName?.let(facade::findPackage) ?: return false + clazz !is PsiAnonymousClass && PsiUtil.isAccessibleFromPackage(clazz, pkg) + } + else -> true + } + } + + private fun createTypeLookup(type: Type): LookupElement { + val definitionId = type.typeNameToInsert() + + val lookupElement = LookupElementBuilder.create(definitionId) + .withIcon(PlatformIcons.CLASS_ICON) + .withPresentableText(type.presentableName()) + + return if (type.isPrimitive) { + lookupElement + } else { + lookupElement.withDefinition(definitionId, "type = ${type.canonicalName}.class") + } + } + + private fun LookupElementBuilder.withDefinition(id: String, at: String) = withInsertHandler { context, _ -> + val contextElement = context.file.findElementAt(context.startOffset) ?: return@withInsertHandler + val injectionHost = contextElement.findMultiInjectionHost() ?: return@withInsertHandler + val expressionAnnotation = injectionHost.parentOfType() ?: return@withInsertHandler + if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { + return@withInsertHandler + } + val modifierList = expressionAnnotation.findContainingModifierList() ?: return@withInsertHandler + + // look for an existing definition with this id, skip if it exists + for (annotation in modifierList.annotations) { + if (annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) && + annotation.findDeclaredAttributeValue("id")?.constantStringValue == id + ) { + return@withInsertHandler + } + } + + // create and add the new @Definition annotation + val newAnnotation = JavaPsiFacade.getElementFactory(context.project).createAnnotationFromText( + "@${MixinConstants.MixinExtras.DEFINITION}(id = \"$id\", $at)", + modifierList, + ) + val addedAnnotation = modifierList.addAfter( + newAnnotation, + modifierList.annotations.lastOrNull { it.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) } + ) + + // add imports and reformat + JavaCodeStyleManager.getInstance(context.project).shortenClassReferences(addedAnnotation) + JavaCodeStyleManager.getInstance(context.project).optimizeImports(modifierList.containingFile) + CodeStyleManager.getInstance(context.project).reformat(modifierList) + } + + private fun getStatementVariants( + factory: MEExpressionElementFactory, + statement: MEStatement + ): List { + return if (statement is MEExpressionStatement) { + getExpressionVariants(factory, statement.expression) + } else { + listOf(statement) + } + } + + private fun getExpressionVariants( + factory: MEExpressionElementFactory, + expression: MEExpression + ): List { + val variants = mutableListOf(expression) + + val assignmentStatement = factory.createStatement("? = ?") as MEAssignStatement + assignmentStatement.targetExpr.replace(expression.copy()) + variants += assignmentStatement + + when (expression) { + is MEParenthesizedExpression -> { + val castExpr = factory.createExpression("(?) ?") as MECastExpression + castExpr.castTypeExpr!!.replace(expression.copy()) + variants += castExpr + } + is MENameExpression -> { + val callExpr = factory.createExpression("?()") as MEStaticMethodCallExpression + callExpr.memberName.replace(expression.meName) + variants += callExpr + + val classExpr = factory.createExpression("${expression.text}.class") as MEClassConstantExpression + variants += classExpr + } + is MEMemberAccessExpression -> { + val callExpr = factory.createExpression("?.?()") as MEMethodCallExpression + callExpr.receiverExpr.replace(expression.receiverExpr) + callExpr.memberName.replace(expression.memberName) + variants += callExpr + } + is MENewExpression -> { + val type = expression.type + if (type != null && !expression.hasConstructorArguments && !expression.isArrayCreation) { + val fixedNewExpr = factory.createExpression("new ?()") as MENewExpression + fixedNewExpr.type!!.replace(type) + variants += fixedNewExpr + + val fixedNewArrayExpr = factory.createExpression("new ?[?]") as MENewExpression + fixedNewArrayExpr.type!!.replace(type) + variants += fixedNewArrayExpr + } + } + } + + return variants + } + + private class ParenthesesTailType(private val hasParameters: Boolean) : TailType() { + override fun processTail(editor: Editor, tailOffset: Int): Int { + editor.document.insertString(tailOffset, "()") + return moveCaret(editor, tailOffset, if (hasParameters) 1 else 2) + } + + override fun isApplicable(context: InsertionContext): Boolean { + val chars = context.document.charsSequence + val offset = CharArrayUtil.shiftForward(chars, context.tailOffset, " \n\t") + return !CharArrayUtil.regionMatches(chars, offset, "(") + } + } + + private class BracketsTailType(private val dimensions: Int, private val hasInitializer: Boolean) : TailType() { + override fun processTail(editor: Editor, tailOffset: Int): Int { + editor.document.insertString(tailOffset, "[]".repeat(dimensions) + if (hasInitializer) "{}" else "") + return moveCaret(editor, tailOffset, if (hasInitializer) 2 * dimensions + 1 else 1) + } + + override fun isApplicable(context: InsertionContext): Boolean { + val chars = context.document.charsSequence + val offset = CharArrayUtil.shiftForward(chars, context.tailOffset, " \n\t") + return !CharArrayUtil.regionMatches(chars, offset, "[") + } + } +} diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 37d5331a9..977e3725a 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -20,98 +20,37 @@ package com.demonwav.mcdev.platform.mixin.expression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEAssignStatement -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECastExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMemberAccessExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMethodCallExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENameExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MENewExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEParenthesizedExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStaticMethodCallExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MESuperCallExpression -import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype -import com.demonwav.mcdev.platform.mixin.expression.psi.MEMatchableElement -import com.demonwav.mcdev.platform.mixin.expression.psi.MEPsiUtil -import com.demonwav.mcdev.platform.mixin.expression.psi.MERecursiveWalkingVisitor -import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil -import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.util.LocalInfo -import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.platform.mixin.util.cached -import com.demonwav.mcdev.platform.mixin.util.canonicalName -import com.demonwav.mcdev.platform.mixin.util.isPrimitive -import com.demonwav.mcdev.platform.mixin.util.mixinTargets -import com.demonwav.mcdev.platform.mixin.util.textify import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.findAnnotations -import com.demonwav.mcdev.util.findContainingClass -import com.demonwav.mcdev.util.findContainingModifierList -import com.demonwav.mcdev.util.findModule -import com.demonwav.mcdev.util.findMultiInjectionHost -import com.demonwav.mcdev.util.mapFirstNotNull -import com.demonwav.mcdev.util.packageName import com.demonwav.mcdev.util.resolveType import com.demonwav.mcdev.util.resolveTypeArray -import com.intellij.codeInsight.completion.InsertionContext -import com.intellij.codeInsight.lookup.LookupElement -import com.intellij.codeInsight.lookup.LookupElementBuilder -import com.intellij.codeInsight.lookup.TailTypeDecorator import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.module.Module import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.RecursionManager -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiAnonymousClass -import com.intellij.psi.PsiClass -import com.intellij.psi.PsiElement import com.intellij.psi.PsiModifierList -import com.intellij.psi.codeStyle.CodeStyleManager -import com.intellij.psi.codeStyle.JavaCodeStyleManager -import com.intellij.psi.util.PsiTreeUtil -import com.intellij.psi.util.PsiUtil -import com.intellij.psi.util.parentOfType -import com.intellij.psi.util.parents -import com.intellij.util.PlatformIcons import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression import com.llamalad7.mixinextras.expression.impl.flow.ComplexDataException -import com.llamalad7.mixinextras.expression.impl.flow.ComplexFlowValue -import com.llamalad7.mixinextras.expression.impl.flow.DummyFlowValue import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter import com.llamalad7.mixinextras.expression.impl.flow.FlowValue import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool -import com.llamalad7.mixinextras.utils.Decorations import java.util.Collections import java.util.IdentityHashMap -import org.apache.commons.lang3.mutable.MutableInt import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.FieldInsnNode -import org.objectweb.asm.tree.IincInsnNode -import org.objectweb.asm.tree.InsnNode -import org.objectweb.asm.tree.IntInsnNode -import org.objectweb.asm.tree.LdcInsnNode -import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode -import org.objectweb.asm.tree.MultiANewArrayInsnNode -import org.objectweb.asm.tree.TypeInsnNode import org.objectweb.asm.tree.VarInsnNode import org.objectweb.asm.tree.analysis.Analyzer @@ -120,7 +59,6 @@ typealias FlowMap = Map object MEExpressionMatchUtil { private val LOGGER = logger() - private const val DEBUG_COMPLETION = false fun getFlowMap(project: Project, classIn: ClassNode, methodIn: MethodNode): FlowMap? { if (methodIn.instructions == null) { @@ -309,635 +247,6 @@ object MEExpressionMatchUtil { } } - fun getCompletionVariantsFromBytecode(project: Project, contextElement: PsiElement): List { - val statement = contextElement.parentOfType() ?: return emptyList() - - val expressionAnnotation = contextElement.findMultiInjectionHost()?.parentOfType() - ?: return emptyList() - if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { - return emptyList() - } - - val modifierList = expressionAnnotation.findContainingModifierList() ?: return emptyList() - val module = modifierList.findModule() ?: return emptyList() - - val mixinClass = modifierList.findContainingClass() ?: return emptyList() - - val (handler, handlerAnnotation) = modifierList.annotations.mapFirstNotNull { annotation -> - val qName = annotation.qualifiedName ?: return@mapFirstNotNull null - val handler = MixinAnnotationHandler.forMixinAnnotation(qName, project) ?: return@mapFirstNotNull null - handler to annotation - } ?: return emptyList() - - val cursorOffset = contextElement.textRange.startOffset - statement.textRange.startOffset - - return mixinClass.mixinTargets.flatMap { targetClass -> - val poolFactory = createIdentifierPoolFactory(module, targetClass, modifierList) - handler.resolveTarget(handlerAnnotation, targetClass) - .filterIsInstance() - .flatMap { methodTarget -> - - getCompletionVariantsFromBytecode( - project, - mixinClass, - cursorOffset, - statement.copy() as MEStatement, - targetClass, - methodTarget.classAndMethod.method, - poolFactory, - ) - } - } - } - - private fun getCompletionVariantsFromBytecode( - project: Project, - mixinClass: PsiClass, - cursorOffsetIn: Int, - statement: MEStatement, - targetClass: ClassNode, - targetMethod: MethodNode, - poolFactory: IdentifierPoolFactory, - ): List { - if (DEBUG_COMPLETION) { - println("======") - } - - if (targetMethod.instructions == null) { - return emptyList() - } - - val cursorOffset = MutableInt(cursorOffsetIn) - val pool = poolFactory(targetMethod) - val flows = getFlowMap(project, targetClass, targetMethod) ?: return emptyList() - - removeExplicitCaptures(statement, cursorOffset) - replaceUnknownNamesWithWildcards(project, statement, cursorOffset, pool) - - val elementAtCursor = statement.findElementAt(cursorOffset.toInt()) ?: return emptyList() - - val wildcardReplacedStatement = statement.copy() as MEStatement - replaceCursorInputWithWildcard(project, wildcardReplacedStatement, cursorOffset.toInt()) - - var matchingFlows = mutableListOf() - for (statementToMatch in getStatementVariants(project.meExpressionElementFactory, wildcardReplacedStatement)) { - if (DEBUG_COMPLETION) { - println("Matching against statement ${statementToMatch.text}") - } - - val meStatement = createExpression(statementToMatch.text) ?: continue - findMatchingInstructions( - targetClass, - targetMethod, - pool, - flows, - meStatement, - targetMethod.instructions, - true - ) { match -> - matchingFlows += match.flow - if (DEBUG_COMPLETION) { - println("Matched ${match.flow.insnOrNull?.textify()}") - } - } - } - if (matchingFlows.isEmpty()) { - return emptyList() - } - - var subExpr: MEMatchableElement = statement - while (true) { - val inputExprOnCursor = subExpr.getInputExprs().firstOrNull { it.textRange.contains(cursorOffset.toInt()) } - ?: break - val wildcardReplacedExpr = inputExprOnCursor.copy() as MEExpression - cursorOffset.setValue(cursorOffset.toInt() - inputExprOnCursor.textRange.startOffset) - replaceCursorInputWithWildcard(project, wildcardReplacedExpr, cursorOffset.toInt()) - - val newMatchingFlows = mutableSetOf() - for (exprToMatch in getExpressionVariants(project.meExpressionElementFactory, wildcardReplacedExpr)) { - if (DEBUG_COMPLETION) { - println("Matching against expression ${exprToMatch.text}") - } - - val meExpression = createExpression(exprToMatch.text) ?: continue - - val flattenedInstructions = mutableSetOf() - for (flow in matchingFlows) { - getInstructionsInFlowTree( - findFlowTreeRoot(flow), - flattenedInstructions, - subExpr !is MEExpressionStatement && subExpr !is MEParenthesizedExpression - ) - } - - findMatchingInstructions( - targetClass, - targetMethod, - pool, - flows, - meExpression, - flattenedInstructions, - true - ) { match -> - newMatchingFlows += match.flow - if (DEBUG_COMPLETION) { - println("Matched ${match.flow.insnOrNull?.textify()}") - } - } - } - - if (newMatchingFlows.isEmpty()) { - return emptyList() - } - matchingFlows = newMatchingFlows.toMutableList() - - subExpr = inputExprOnCursor - } - - val cursorInstructions = mutableSetOf() - for (flow in matchingFlows) { - getInstructionsInFlowTree(findFlowTreeRoot(flow), cursorInstructions, false) - } - - if (DEBUG_COMPLETION) { - println("Found ${cursorInstructions.size} matching instructions:") - for (insn in cursorInstructions) { - println("- ${insn.textify()}") - } - } - - val isInsideMeType = PsiTreeUtil.getParentOfType( - elementAtCursor, - METype::class.java, - false, - MEExpression::class.java - ) != null - val isInsideNewExpr = PsiTreeUtil.getParentOfType( - elementAtCursor, - MENewExpression::class.java, - false, - MEExpression::class.java - ) != null - val cursorExprInTypePosition = !isInsideMeType && - elementAtCursor.parentOfType()?.let(METypeUtil::isExpressionInTypePosition) == true - val inTypePosition = isInsideMeType || isInsideNewExpr || cursorExprInTypePosition - val isPossiblyIncompleteCast = !inTypePosition && - elementAtCursor.parentOfType() - ?.parents(false) - ?.dropWhile { it is MEArrayAccessExpression && it.indexExpr == null } is MEParenthesizedExpression - val canCompleteExprs = !inTypePosition - val canCompleteTypes = inTypePosition || isPossiblyIncompleteCast - - return cursorInstructions.mapNotNull { insn -> - getCompletionForInstruction(insn, flows, mixinClass, canCompleteExprs, canCompleteTypes) - } - } - - private fun replaceUnknownNamesWithWildcards( - project: Project, - statement: MEStatement, - cursorOffset: MutableInt, - pool: IdentifierPool, - ) { - val unknownNames = mutableListOf() - statement.accept(object : MERecursiveWalkingVisitor() { - override fun visitType(o: METype) { - val name = o.meName - if (!name.isWildcard && !pool.typeExists(name.text)) { - unknownNames += name - } - } - - override fun visitNameExpression(o: MENameExpression) { - val name = o.meName - if (!name.isWildcard) { - if (METypeUtil.isExpressionDirectlyInTypePosition(o)) { - if (!pool.typeExists(name.text)) { - unknownNames += name - } - } else { - if (!pool.memberExists(name.text)) { - unknownNames += name - } - } - } - } - - override fun visitSuperCallExpression(o: MESuperCallExpression) { - val name = o.memberName - if (name != null && !name.isWildcard && !pool.memberExists(name.text)) { - unknownNames += name - } - super.visitSuperCallExpression(o) - } - - override fun visitMethodCallExpression(o: MEMethodCallExpression) { - val name = o.memberName - if (!name.isWildcard && !pool.memberExists(name.text)) { - unknownNames += name - } - super.visitMethodCallExpression(o) - } - - override fun visitStaticMethodCallExpression(o: MEStaticMethodCallExpression) { - val name = o.memberName - if (!name.isWildcard && !pool.memberExists(name.text)) { - unknownNames += name - } - super.visitStaticMethodCallExpression(o) - } - - override fun visitMemberAccessExpression(o: MEMemberAccessExpression) { - val name = o.memberName - if (!name.isWildcard && !pool.memberExists(name.text)) { - unknownNames += name - } - super.visitMemberAccessExpression(o) - } - - override fun visitNewExpression(o: MENewExpression) { - val name = o.type - if (name != null && !name.isWildcard && !pool.typeExists(name.text)) { - unknownNames += name - } - super.visitNewExpression(o) - } - }) - - for (unknownName in unknownNames) { - val startOffset = unknownName.textRange.startOffset - if (cursorOffset.toInt() > startOffset) { - cursorOffset.setValue(cursorOffset.toInt() - unknownName.textLength + 1) - } - - unknownName.replace(project.meExpressionElementFactory.createName("?")) - } - } - - private fun removeExplicitCaptures(statement: MEStatement, cursorOffset: MutableInt) { - val captures = mutableListOf() - - statement.accept(object : MERecursiveWalkingVisitor() { - override fun elementFinished(element: PsiElement) { - // do this on elementFinished to ensure that inner captures are replaced before outer captures - if (element is MECapturingExpression) { - captures += element - } - } - }) - - for (capture in captures) { - val innerExpr = capture.expression ?: continue - val textRange = capture.textRange - - if (cursorOffset.toInt() > textRange.startOffset) { - cursorOffset.setValue(cursorOffset.toInt() - if (cursorOffset.toInt() >= textRange.endOffset) 3 else 2) - } - - capture.replace(innerExpr) - } - } - - private fun replaceCursorInputWithWildcard(project: Project, element: MEMatchableElement, cursorOffset: Int) { - for (input in element.getInputExprs()) { - if (input.textRange.contains(cursorOffset)) { - input.replace(project.meExpressionElementFactory.createExpression("?")) - return - } - } - } - - private fun findFlowTreeRoot(flow: FlowValue): FlowValue { - val insn = flow.insnOrNull ?: return flow - return if (insn.opcode == Opcodes.NEW) { - flow.next.firstOrNull { - val nextInsn = it.left.insnOrNull ?: return@firstOrNull false - it.right == 0 && - nextInsn.opcode == Opcodes.INVOKESPECIAL && - (nextInsn as MethodInsnNode).name == "" - }?.left ?: flow - } else { - flow - } - } - - private fun getInstructionsInFlowTree( - flow: FlowValue, - outInstructions: MutableSet, - strict: Boolean - ) { - if (flow is DummyFlowValue || flow is ComplexFlowValue) { - return - } - - if (!strict) { - outInstructions += flow.insn - } - for (i in 0 until flow.inputCount()) { - getInstructionsInFlowTree(flow.getInput(i), outInstructions, false) - } - } - - private fun getCompletionForInstruction( - insn: AbstractInsnNode, - flows: FlowMap, - mixinClass: PsiClass, - canCompleteExprs: Boolean, - canCompleteTypes: Boolean - ): LookupElement? { - when (insn) { - is LdcInsnNode -> { - when (val cst = insn.cst) { - is Type -> { - if (canCompleteTypes && cst.isAccessibleFrom(mixinClass)) { - return object : TailTypeDecorator(createTypeLookup(cst)) { - override fun computeTailType(context: InsertionContext?) = - MEExpressionCompletionContributor.DOT_CLASS_TAIL - } - } - } - // TODO: string literals? - } - } - is VarInsnNode -> { - // TODO: local variables - } - is IincInsnNode -> { - // TODO: local variables - } - is FieldInsnNode -> { - if (canCompleteExprs) { - val at = "at = @${MixinConstants.Annotations.AT}(value = \"FIELD\"," + - " target = \"L${insn.owner};${insn.name}:${insn.desc}\")" - var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) - .withIcon(PlatformIcons.FIELD_ICON) - .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) - .withDefinition(insn.name.toValidIdentifier(), at) - if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.PUTSTATIC) { - lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) - } - return lookup - } - } - is MethodInsnNode -> { - if (canCompleteExprs) { - val at = "at = @${MixinConstants.Annotations.AT}(value = \"INVOKE\"," + - " target = \"L${insn.owner};${insn.name}${insn.desc}\")" - var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) - .withIcon(PlatformIcons.METHOD_ICON) - .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) - .withDefinition(insn.name.toValidIdentifier(), at) - if (insn.opcode == Opcodes.INVOKESTATIC) { - lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) - } - return object : TailTypeDecorator(lookup) { - override fun computeTailType(context: InsertionContext?) = - MEExpressionCompletionContributor.ParenthesesTailType(!insn.desc.startsWith("()")) - } - } - } - is TypeInsnNode -> { - val type = Type.getObjectType(insn.desc) - if (canCompleteTypes && type.isAccessibleFrom(mixinClass)) { - val lookup = createTypeLookup(type) - when (insn.opcode) { - Opcodes.ANEWARRAY -> { - return object : TailTypeDecorator(lookup) { - override fun computeTailType(context: InsertionContext?) = - MEExpressionCompletionContributor.BracketsTailType( - 1, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, - ) - } - } - Opcodes.NEW -> { - val initCall = flows[insn]?.next?.firstOrNull { - val nextInsn = it.left.insnOrNull ?: return@firstOrNull false - it.right == 0 && - nextInsn.opcode == Opcodes.INVOKESPECIAL && - (nextInsn as MethodInsnNode).name == "" - }?.left?.insn as MethodInsnNode? - return object : TailTypeDecorator(lookup) { - override fun computeTailType(context: InsertionContext?) = - MEExpressionCompletionContributor.ParenthesesTailType( - initCall?.desc?.startsWith("()") == false - ) - } - } - else -> return lookup - } - } - } - is IntInsnNode -> { - if (insn.opcode == Opcodes.NEWARRAY) { - if (canCompleteTypes) { - val type = when (insn.operand) { - Opcodes.T_BOOLEAN -> "boolean" - Opcodes.T_CHAR -> "char" - Opcodes.T_FLOAT -> "float" - Opcodes.T_DOUBLE -> "double" - Opcodes.T_BYTE -> "byte" - Opcodes.T_SHORT -> "short" - Opcodes.T_INT -> "int" - Opcodes.T_LONG -> "long" - else -> "unknown" // wtf? - } - return object : TailTypeDecorator( - LookupElementBuilder.create(type).withIcon(PlatformIcons.CLASS_ICON) - ) { - override fun computeTailType(context: InsertionContext?) = - MEExpressionCompletionContributor.BracketsTailType( - 1, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, - ) - } - } - } - } - is MultiANewArrayInsnNode -> { - if (canCompleteTypes) { - val type = Type.getType(insn.desc) - return object : TailTypeDecorator( - createTypeLookup(type.elementType) - ) { - override fun computeTailType(context: InsertionContext?) = - MEExpressionCompletionContributor.BracketsTailType( - type.dimensions, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, - ) - } - } - } - is InsnNode -> { - when (insn.opcode) { - Opcodes.ARRAYLENGTH -> { - if (canCompleteExprs) { - return LookupElementBuilder.create("length") - .withIcon(PlatformIcons.FIELD_ICON) - } - } - } - } - } - - return null - } - - private fun Type.typeNameToInsert(): String { - if (sort == Type.ARRAY) { - return elementType.typeNameToInsert() + "[]".repeat(dimensions) - } - if (sort != Type.OBJECT) { - return className - } - - val simpleName = internalName.substringAfterLast('/') - val lastValidCharIndex = (simpleName.length - 1 downTo 0).firstOrNull { - MEPsiUtil.isIdentifierStart(simpleName[it]) - } ?: return "_" + simpleName.filterInvalidIdentifierChars() - - return simpleName.substring(simpleName.lastIndexOf('$', lastValidCharIndex) + 1).toValidIdentifier() - } - - private fun String.toValidIdentifier(): String { - return when { - isEmpty() -> "_" - !MEPsiUtil.isIdentifierStart(this[0]) -> "_" + filterInvalidIdentifierChars() - else -> this[0] + substring(1).filterInvalidIdentifierChars() - } - } - - private fun String.filterInvalidIdentifierChars(): String { - return asSequence().joinToString("") { - if (MEPsiUtil.isIdentifierPart(it)) it.toString() else "_" - } - } - - private fun Type.presentableName(): String = when (sort) { - Type.ARRAY -> elementType.presentableName() + "[]".repeat(dimensions) - Type.OBJECT -> internalName.substringAfterLast('/') - else -> className - } - - private fun Type.isAccessibleFrom(fromClass: PsiClass): Boolean { - return when (sort) { - Type.ARRAY -> elementType.isAccessibleFrom(fromClass) - Type.OBJECT -> { - val facade = JavaPsiFacade.getInstance(fromClass.project) - val clazz = facade.findClass(canonicalName, fromClass.resolveScope) ?: return false - val pkg = fromClass.packageName?.let(facade::findPackage) ?: return false - clazz !is PsiAnonymousClass && PsiUtil.isAccessibleFromPackage(clazz, pkg) - } - else -> true - } - } - - private fun createTypeLookup(type: Type): LookupElement { - val definitionId = type.typeNameToInsert() - - val lookupElement = LookupElementBuilder.create(definitionId) - .withIcon(PlatformIcons.CLASS_ICON) - .withPresentableText(type.presentableName()) - - return if (type.isPrimitive) { - lookupElement - } else { - lookupElement.withDefinition(definitionId, "type = ${type.canonicalName}.class") - } - } - - private fun LookupElementBuilder.withDefinition(id: String, at: String) = withInsertHandler { context, _ -> - val contextElement = context.file.findElementAt(context.startOffset) ?: return@withInsertHandler - val injectionHost = contextElement.findMultiInjectionHost() ?: return@withInsertHandler - val expressionAnnotation = injectionHost.parentOfType() ?: return@withInsertHandler - if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { - return@withInsertHandler - } - val modifierList = expressionAnnotation.findContainingModifierList() ?: return@withInsertHandler - - // look for an existing definition with this id, skip if it exists - for (annotation in modifierList.annotations) { - if (annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) && - annotation.findDeclaredAttributeValue("id")?.constantStringValue == id - ) { - return@withInsertHandler - } - } - - // create and add the new @Definition annotation - val newAnnotation = JavaPsiFacade.getElementFactory(context.project).createAnnotationFromText( - "@${MixinConstants.MixinExtras.DEFINITION}(id = \"$id\", $at)", - modifierList, - ) - val addedAnnotation = modifierList.addAfter( - newAnnotation, - modifierList.annotations.lastOrNull { it.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) } - ) - - // add imports and reformat - JavaCodeStyleManager.getInstance(context.project).shortenClassReferences(addedAnnotation) - JavaCodeStyleManager.getInstance(context.project).optimizeImports(modifierList.containingFile) - CodeStyleManager.getInstance(context.project).reformat(modifierList) - } - - private fun getStatementVariants( - factory: MEExpressionElementFactory, - statement: MEStatement - ): List { - return if (statement is MEExpressionStatement) { - getExpressionVariants(factory, statement.expression) - } else { - listOf(statement) - } - } - - private fun getExpressionVariants( - factory: MEExpressionElementFactory, - expression: MEExpression - ): List { - val variants = mutableListOf(expression) - - val assignmentStatement = factory.createStatement("? = ?") as MEAssignStatement - assignmentStatement.targetExpr.replace(expression.copy()) - variants += assignmentStatement - - when (expression) { - is MEParenthesizedExpression -> { - val castExpr = factory.createExpression("(?) ?") as MECastExpression - castExpr.castTypeExpr!!.replace(expression.copy()) - variants += castExpr - } - is MENameExpression -> { - val callExpr = factory.createExpression("?()") as MEStaticMethodCallExpression - callExpr.memberName.replace(expression.meName) - variants += callExpr - - val classExpr = factory.createExpression("${expression.text}.class") as MEClassConstantExpression - variants += classExpr - } - is MEMemberAccessExpression -> { - val callExpr = factory.createExpression("?.?()") as MEMethodCallExpression - callExpr.receiverExpr.replace(expression.receiverExpr) - callExpr.memberName.replace(expression.memberName) - variants += callExpr - } - is MENewExpression -> { - val type = expression.type - if (type != null && !expression.hasConstructorArguments && !expression.isArrayCreation) { - val fixedNewExpr = factory.createExpression("new ?()") as MENewExpression - fixedNewExpr.type!!.replace(type) - variants += fixedNewExpr - - val fixedNewArrayExpr = factory.createExpression("new ?[?]") as MENewExpression - fixedNewArrayExpr.type!!.replace(type) - variants += fixedNewArrayExpr - } - } - } - - return variants - } - val FlowValue.insnOrNull: AbstractInsnNode? get() = try { insn } catch (e: ComplexDataException) { From 9d3ce10809fcc451576136e7759edaf2aa2a0c26 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 20 Mar 2024 11:52:51 +0000 Subject: [PATCH 048/100] Fold @At.target for definition completions --- .../expression/MEExpressionCompletionUtil.kt | 65 +++++++++++++++---- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 728ba5afe..125245631 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -57,6 +57,7 @@ import com.demonwav.mcdev.platform.mixin.util.textify import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.findContainingClass import com.demonwav.mcdev.util.findContainingModifierList +import com.demonwav.mcdev.util.findContainingNameValuePair import com.demonwav.mcdev.util.findModule import com.demonwav.mcdev.util.findMultiInjectionHost import com.demonwav.mcdev.util.mapFirstNotNull @@ -66,7 +67,10 @@ import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.lookup.TailTypeDecorator +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.FoldRegion import com.intellij.openapi.project.Project import com.intellij.patterns.PlatformPatterns import com.intellij.patterns.StandardPatterns @@ -75,6 +79,7 @@ import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiAnonymousClass import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement +import com.intellij.psi.PsiModifierList import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.tree.TokenSet @@ -711,37 +716,75 @@ object MEExpressionCompletionUtil { } private fun LookupElementBuilder.withDefinition(id: String, at: String) = withInsertHandler { context, _ -> - val contextElement = context.file.findElementAt(context.startOffset) ?: return@withInsertHandler - val injectionHost = contextElement.findMultiInjectionHost() ?: return@withInsertHandler - val expressionAnnotation = injectionHost.parentOfType() ?: return@withInsertHandler + context.laterRunnable = Runnable { + context.commitDocument() + CommandProcessor.getInstance().runUndoTransparentAction { + runWriteAction { + addDefinition(context, id, at) + } + } + } + } + + private fun addDefinition(context: InsertionContext, id: String, at: String) { + val contextElement = context.file.findElementAt(context.startOffset) ?: return + val injectionHost = contextElement.findMultiInjectionHost() ?: return + val expressionAnnotation = injectionHost.parentOfType() ?: return if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { - return@withInsertHandler + return } - val modifierList = expressionAnnotation.findContainingModifierList() ?: return@withInsertHandler + val modifierList = expressionAnnotation.findContainingModifierList() ?: return // look for an existing definition with this id, skip if it exists for (annotation in modifierList.annotations) { if (annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) && annotation.findDeclaredAttributeValue("id")?.constantStringValue == id ) { - return@withInsertHandler + return } } // create and add the new @Definition annotation - val newAnnotation = JavaPsiFacade.getElementFactory(context.project).createAnnotationFromText( + var newAnnotation = JavaPsiFacade.getElementFactory(context.project).createAnnotationFromText( "@${MixinConstants.MixinExtras.DEFINITION}(id = \"$id\", $at)", modifierList, ) - val addedAnnotation = modifierList.addAfter( + newAnnotation = modifierList.addAfter( newAnnotation, modifierList.annotations.lastOrNull { it.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) } - ) + ) as PsiAnnotation // add imports and reformat - JavaCodeStyleManager.getInstance(context.project).shortenClassReferences(addedAnnotation) + newAnnotation = + JavaCodeStyleManager.getInstance(context.project).shortenClassReferences(newAnnotation) as PsiAnnotation JavaCodeStyleManager.getInstance(context.project).optimizeImports(modifierList.containingFile) - CodeStyleManager.getInstance(context.project).reformat(modifierList) + val annotationIndex = modifierList.annotations.indexOf(newAnnotation) + val formattedModifierList = + CodeStyleManager.getInstance(context.project).reformat(modifierList) as PsiModifierList + newAnnotation = formattedModifierList.annotations.getOrNull(annotationIndex) ?: return + + // fold @At.target + val foldingModel = context.editor.foldingModel + val regionsToFold = mutableListOf() + val annotationRange = newAnnotation.textRange + for (foldRegion in foldingModel.allFoldRegions) { + if (!annotationRange.contains(foldRegion.textRange)) { + continue + } + val nameValuePair = newAnnotation.findElementAt(foldRegion.startOffset - annotationRange.startOffset) + ?.findContainingNameValuePair() ?: continue + if (nameValuePair.name == "target" && + nameValuePair.parentOfType()?.hasQualifiedName(MixinConstants.Annotations.AT) == true + ) { + regionsToFold += foldRegion + } + } + + foldingModel.runBatchFoldingOperation { + for (foldRegion in regionsToFold) { + foldRegion.isExpanded = false + } + } } private fun getStatementVariants( From a69e3ee48915a6cbb57e78e99ef7af2125046a29 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 20 Mar 2024 20:50:48 +0000 Subject: [PATCH 049/100] Local variable completions --- build.gradle.kts | 2 +- .../expression/MEExpressionCompletionUtil.kt | 384 ++++++++++++++---- 2 files changed, 297 insertions(+), 89 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 386e22e43..4a401e4c0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:1d1aefa") + implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:00d6f4e") implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 125245631..fbd08c3b5 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -48,18 +48,23 @@ import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil.notInTypePosition import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil.validType import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler +import com.demonwav.mcdev.platform.mixin.util.AsmDfaUtil import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.platform.mixin.util.SignatureToPsi import com.demonwav.mcdev.platform.mixin.util.canonicalName +import com.demonwav.mcdev.platform.mixin.util.hasAccess import com.demonwav.mcdev.platform.mixin.util.isPrimitive import com.demonwav.mcdev.platform.mixin.util.mixinTargets import com.demonwav.mcdev.platform.mixin.util.textify +import com.demonwav.mcdev.platform.mixin.util.toPsiType import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.findContainingClass import com.demonwav.mcdev.util.findContainingModifierList import com.demonwav.mcdev.util.findContainingNameValuePair import com.demonwav.mcdev.util.findModule import com.demonwav.mcdev.util.findMultiInjectionHost +import com.demonwav.mcdev.util.invokeLater import com.demonwav.mcdev.util.mapFirstNotNull import com.demonwav.mcdev.util.packageName import com.intellij.codeInsight.TailType @@ -67,8 +72,16 @@ import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.lookup.TailTypeDecorator +import com.intellij.codeInsight.template.Expression +import com.intellij.codeInsight.template.ExpressionContext +import com.intellij.codeInsight.template.Template +import com.intellij.codeInsight.template.TemplateBuilderImpl +import com.intellij.codeInsight.template.TemplateEditingAdapter +import com.intellij.codeInsight.template.TemplateManager +import com.intellij.codeInsight.template.TextResult import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.command.CommandProcessor +import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.FoldRegion import com.intellij.openapi.project.Project @@ -78,13 +91,17 @@ import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiAnonymousClass import com.intellij.psi.PsiClass +import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile import com.intellij.psi.PsiModifierList import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil import com.intellij.psi.tree.TokenSet import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiUtil +import com.intellij.psi.util.createSmartPointer import com.intellij.psi.util.parentOfType import com.intellij.psi.util.parents import com.intellij.util.PlatformIcons @@ -97,6 +114,7 @@ import com.llamalad7.mixinextras.utils.Decorations import org.apache.commons.lang3.mutable.MutableInt import org.objectweb.asm.Opcodes import org.objectweb.asm.Type +import org.objectweb.asm.signature.SignatureReader import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.FieldInsnNode @@ -231,6 +249,8 @@ object MEExpressionCompletionUtil { ): List { if (DEBUG_COMPLETION) { println("======") + println(targetMethod.textify()) + println("======") } if (targetMethod.instructions == null) { @@ -358,8 +378,17 @@ object MEExpressionCompletionUtil { val canCompleteExprs = !inTypePosition val canCompleteTypes = inTypePosition || isPossiblyIncompleteCast - return cursorInstructions.mapNotNull { insn -> - getCompletionForInstruction(insn, flows, mixinClass, canCompleteExprs, canCompleteTypes) + return cursorInstructions.flatMap { insn -> + getCompletionsForInstruction( + project, + targetClass, + targetMethod, + insn, + flows, + mixinClass, + canCompleteExprs, + canCompleteTypes + ) } } @@ -508,32 +537,47 @@ object MEExpressionCompletionUtil { } } - private fun getCompletionForInstruction( + private fun getCompletionsForInstruction( + project: Project, + targetClass: ClassNode, + targetMethod: MethodNode, insn: AbstractInsnNode, flows: FlowMap, mixinClass: PsiClass, canCompleteExprs: Boolean, canCompleteTypes: Boolean - ): LookupElement? { + ): List { when (insn) { is LdcInsnNode -> { when (val cst = insn.cst) { is Type -> { if (canCompleteTypes && cst.isAccessibleFrom(mixinClass)) { - return object : TailTypeDecorator(createTypeLookup(cst)) { - override fun computeTailType(context: InsertionContext?) = DOT_CLASS_TAIL - } + return listOf( + object : TailTypeDecorator(createTypeLookup(cst)) { + override fun computeTailType(context: InsertionContext?) = DOT_CLASS_TAIL + } + ) } } // TODO: string literals? } } - is VarInsnNode -> { - // TODO: local variables - } - is IincInsnNode -> { - // TODO: local variables - } + is VarInsnNode -> return createLocalVariableLookups( + project, + targetClass, + targetMethod, + insn, + insn.`var`, + mixinClass + ) + is IincInsnNode -> return createLocalVariableLookups( + project, + targetClass, + targetMethod, + insn, + insn.`var`, + mixinClass + ) is FieldInsnNode -> { if (canCompleteExprs) { val at = "at = @${MixinConstants.Annotations.AT}(value = \"FIELD\"," + @@ -541,11 +585,11 @@ object MEExpressionCompletionUtil { var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) .withIcon(PlatformIcons.FIELD_ICON) .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) - .withDefinition(insn.name.toValidIdentifier(), at) + .withDefinitionAndFoldTarget(insn.name.toValidIdentifier(), at) if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.PUTSTATIC) { lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) } - return lookup + return listOf(lookup) } } is MethodInsnNode -> { @@ -555,14 +599,16 @@ object MEExpressionCompletionUtil { var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) .withIcon(PlatformIcons.METHOD_ICON) .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) - .withDefinition(insn.name.toValidIdentifier(), at) + .withDefinitionAndFoldTarget(insn.name.toValidIdentifier(), at) if (insn.opcode == Opcodes.INVOKESTATIC) { lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) } - return object : TailTypeDecorator(lookup) { - override fun computeTailType(context: InsertionContext?) = - ParenthesesTailType(!insn.desc.startsWith("()")) - } + return listOf( + object : TailTypeDecorator(lookup) { + override fun computeTailType(context: InsertionContext?) = + ParenthesesTailType(!insn.desc.startsWith("()")) + } + ) } } is TypeInsnNode -> { @@ -571,13 +617,15 @@ object MEExpressionCompletionUtil { val lookup = createTypeLookup(type) when (insn.opcode) { Opcodes.ANEWARRAY -> { - return object : TailTypeDecorator(lookup) { - override fun computeTailType(context: InsertionContext?) = - BracketsTailType( - 1, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, - ) - } + return listOf( + object : TailTypeDecorator(lookup) { + override fun computeTailType(context: InsertionContext?) = + BracketsTailType( + 1, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + ) + } + ) } Opcodes.NEW -> { val initCall = flows[insn]?.next?.firstOrNull { @@ -586,14 +634,16 @@ object MEExpressionCompletionUtil { nextInsn.opcode == Opcodes.INVOKESPECIAL && (nextInsn as MethodInsnNode).name == "" }?.left?.insn as MethodInsnNode? - return object : TailTypeDecorator(lookup) { - override fun computeTailType(context: InsertionContext?) = - ParenthesesTailType( - initCall?.desc?.startsWith("()") == false - ) - } + return listOf( + object : TailTypeDecorator(lookup) { + override fun computeTailType(context: InsertionContext?) = + ParenthesesTailType( + initCall?.desc?.startsWith("()") == false + ) + } + ) } - else -> return lookup + else -> return listOf(lookup) } } } @@ -611,45 +661,48 @@ object MEExpressionCompletionUtil { Opcodes.T_LONG -> "long" else -> "unknown" // wtf? } - return object : TailTypeDecorator( - LookupElementBuilder.create(type).withIcon(PlatformIcons.CLASS_ICON) - ) { - override fun computeTailType(context: InsertionContext?) = - BracketsTailType( - 1, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, - ) - } + return listOf( + object : TailTypeDecorator( + LookupElementBuilder.create(type).withIcon(PlatformIcons.CLASS_ICON) + ) { + override fun computeTailType(context: InsertionContext?) = + BracketsTailType( + 1, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + ) + } + ) } } } is MultiANewArrayInsnNode -> { if (canCompleteTypes) { val type = Type.getType(insn.desc) - return object : TailTypeDecorator( - createTypeLookup(type.elementType) - ) { - override fun computeTailType(context: InsertionContext?) = - BracketsTailType( - type.dimensions, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, - ) - } + return listOf( + object : TailTypeDecorator( + createTypeLookup(type.elementType) + ) { + override fun computeTailType(context: InsertionContext?) = + BracketsTailType( + type.dimensions, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + ) + } + ) } } is InsnNode -> { when (insn.opcode) { Opcodes.ARRAYLENGTH -> { if (canCompleteExprs) { - return LookupElementBuilder.create("length") - .withIcon(PlatformIcons.FIELD_ICON) + return listOf(LookupElementBuilder.create("length").withIcon(PlatformIcons.FIELD_ICON)) } } } } } - return null + return emptyList() } private fun Type.typeNameToInsert(): String { @@ -715,32 +768,210 @@ object MEExpressionCompletionUtil { } } - private fun LookupElementBuilder.withDefinition(id: String, at: String) = withInsertHandler { context, _ -> + private fun createLocalVariableLookups( + project: Project, + targetClass: ClassNode, + targetMethod: MethodNode, + insn: AbstractInsnNode, + index: Int, + mixinClass: PsiClass, + ): List { + // ignore "this" + if (!targetMethod.hasAccess(Opcodes.ACC_STATIC) && index == 0) { + return emptyList() + } + + var argumentsSize = Type.getArgumentsAndReturnSizes(targetMethod.desc) shr 2 + if (!targetMethod.hasAccess(Opcodes.ACC_STATIC)) { + argumentsSize++ + } + val isArgsOnly = index < argumentsSize + + if (targetMethod.localVariables != null) { + val localsHere = targetMethod.localVariables.filter { localVariable -> + val validRange = targetMethod.instructions.indexOf(localVariable.start) + 1 until + targetMethod.instructions.indexOf(localVariable.end) + targetMethod.instructions.indexOf(insn) in validRange + } + val locals = localsHere.filter { it.index == index } + + val elementFactory = JavaPsiFacade.getElementFactory(project) + + return locals.map { localVariable -> + val localPsiType = if (localVariable.signature != null) { + val sigToPsi = SignatureToPsi(elementFactory, mixinClass) + SignatureReader(localVariable.signature).acceptType(sigToPsi) + sigToPsi.type + } else { + Type.getType(localVariable.desc).toPsiType(elementFactory, mixinClass) + } + val ordinal = localsHere.filter { it.desc == localVariable.desc }.indexOf(localVariable) + LookupElementBuilder.create(localVariable.name.toValidIdentifier()) + .withIcon(PlatformIcons.VARIABLE_ICON) + .withTailText(localPsiType.presentableText) + .withLocalDefinition( + localVariable.name.toValidIdentifier(), + Type.getType(localVariable.desc), + ordinal, + isArgsOnly, + mixinClass + ) + } + } + + // fallback to ASM dataflow + val localTypes = AsmDfaUtil.getLocalVariableTypes(project, targetClass, targetMethod, insn) + ?: return emptyList() + val localType = localTypes.getOrNull(index) ?: return emptyList() + val ordinal = localTypes.asSequence().take(index).filter { it == localType }.count() + val localName = localType.typeNameToInsert().replace("[]", "Array") + (ordinal + 1) + return listOf( + LookupElementBuilder.create(localName) + .withIcon(PlatformIcons.VARIABLE_ICON) + .withTailText(localType.presentableName()) + .withLocalDefinition(localName, localType, ordinal, isArgsOnly, mixinClass) + ) + } + + private fun LookupElementBuilder.withDefinition(id: String, at: String) = withDefinition(id, at) { _, _ -> } + + private fun LookupElementBuilder.withDefinitionAndFoldTarget(id: String, at: String) = + withDefinition(id, at) { context, annotation -> + val foldingModel = InjectedLanguageEditorUtil.getTopLevelEditor(context.editor).foldingModel + val regionsToFold = mutableListOf() + val annotationRange = annotation.textRange + for (foldRegion in foldingModel.allFoldRegions) { + if (!annotationRange.contains(foldRegion.textRange)) { + continue + } + val nameValuePair = annotation.findElementAt(foldRegion.startOffset - annotationRange.startOffset) + ?.findContainingNameValuePair() ?: continue + if (nameValuePair.name == "target" && + nameValuePair.parentOfType()?.hasQualifiedName(MixinConstants.Annotations.AT) == true + ) { + regionsToFold += foldRegion + } + } + + foldingModel.runBatchFoldingOperation { + for (foldRegion in regionsToFold) { + foldRegion.isExpanded = false + } + } + } + + private fun LookupElementBuilder.withLocalDefinition( + name: String, + type: Type, + ordinal: Int, + isArgsOnly: Boolean, + mixinClass: PsiClass, + ): LookupElementBuilder { + val definitionLocal = buildString { + append("local = @${MixinConstants.MixinExtras.LOCAL}(") + if (type.isAccessibleFrom(mixinClass)) { + append("type = ${type.className}.class, ") + } + append("ordinal = ") + append(ordinal) + if (isArgsOnly) { + append(", argsOnly = true") + } + append(")") + } + return withDefinition(name, definitionLocal) { context, annotation -> + invokeLater { + WriteCommandAction.runWriteCommandAction( + context.project, + "Choose How to Target Local Variable", + null, + { runLocalTemplate(context.project, context.editor, context.file, annotation, ordinal, name) }, + annotation.containingFile, + ) + } + } + } + + private fun runLocalTemplate( + project: Project, + editor: Editor, + file: PsiFile, + annotation: PsiAnnotation, + ordinal: Int, + name: String + ) { + val elementToReplace = + (annotation.findDeclaredAttributeValue("local") as? PsiAnnotation) + ?.findDeclaredAttributeValue("ordinal") + ?.findContainingNameValuePair() ?: return + + val hostEditor = InjectedLanguageEditorUtil.getTopLevelEditor(editor) + val hostElement = file.findElementAt(editor.caretModel.offset)?.findMultiInjectionHost() ?: return + + val template = TemplateBuilderImpl(annotation) + val lookupItems = arrayOf( + LookupElementBuilder.create("ordinal = $ordinal"), + LookupElementBuilder.create("name = \"$name\"") + ) + template.replaceElement( + elementToReplace, + object : Expression() { + override fun calculateLookupItems(context: ExpressionContext?) = lookupItems + override fun calculateQuickResult(context: ExpressionContext?) = calculateResult(context) + override fun calculateResult(context: ExpressionContext?) = TextResult("ordinal = $ordinal") + }, + true, + ) + + val prevCursorPosInLiteral = hostEditor.caretModel.offset - hostElement.textRange.startOffset + val hostElementPtr = hostElement.createSmartPointer(project) + hostEditor.caretModel.moveToOffset(annotation.textRange.startOffset) + TemplateManager.getInstance(project).startTemplate( + hostEditor, + template.buildInlineTemplate(), + object : TemplateEditingAdapter() { + override fun templateFinished(template: Template, brokenOff: Boolean) { + PsiDocumentManager.getInstance(project).commitDocument(hostEditor.document) + val newHostElement = hostElementPtr.element ?: return + hostEditor.caretModel.moveToOffset(newHostElement.textRange.startOffset + prevCursorPosInLiteral) + } + } + ) + } + + private inline fun LookupElementBuilder.withDefinition( + id: String, + at: String, + crossinline andThen: (InsertionContext, PsiAnnotation) -> Unit + ) = withInsertHandler { context, _ -> context.laterRunnable = Runnable { context.commitDocument() CommandProcessor.getInstance().runUndoTransparentAction { runWriteAction { - addDefinition(context, id, at) + val annotation = addDefinition(context, id, at) + if (annotation != null) { + andThen(context, annotation) + } } } } } - private fun addDefinition(context: InsertionContext, id: String, at: String) { - val contextElement = context.file.findElementAt(context.startOffset) ?: return - val injectionHost = contextElement.findMultiInjectionHost() ?: return - val expressionAnnotation = injectionHost.parentOfType() ?: return + private fun addDefinition(context: InsertionContext, id: String, at: String): PsiAnnotation? { + val contextElement = context.file.findElementAt(context.startOffset) ?: return null + val injectionHost = contextElement.findMultiInjectionHost() ?: return null + val expressionAnnotation = injectionHost.parentOfType() ?: return null if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { - return + return null } - val modifierList = expressionAnnotation.findContainingModifierList() ?: return + val modifierList = expressionAnnotation.findContainingModifierList() ?: return null // look for an existing definition with this id, skip if it exists for (annotation in modifierList.annotations) { if (annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) && annotation.findDeclaredAttributeValue("id")?.constantStringValue == id ) { - return + return null } } @@ -761,30 +992,7 @@ object MEExpressionCompletionUtil { val annotationIndex = modifierList.annotations.indexOf(newAnnotation) val formattedModifierList = CodeStyleManager.getInstance(context.project).reformat(modifierList) as PsiModifierList - newAnnotation = formattedModifierList.annotations.getOrNull(annotationIndex) ?: return - - // fold @At.target - val foldingModel = context.editor.foldingModel - val regionsToFold = mutableListOf() - val annotationRange = newAnnotation.textRange - for (foldRegion in foldingModel.allFoldRegions) { - if (!annotationRange.contains(foldRegion.textRange)) { - continue - } - val nameValuePair = newAnnotation.findElementAt(foldRegion.startOffset - annotationRange.startOffset) - ?.findContainingNameValuePair() ?: continue - if (nameValuePair.name == "target" && - nameValuePair.parentOfType()?.hasQualifiedName(MixinConstants.Annotations.AT) == true - ) { - regionsToFold += foldRegion - } - } - - foldingModel.runBatchFoldingOperation { - for (foldRegion in regionsToFold) { - foldRegion.isExpanded = false - } - } + return formattedModifierList.annotations.getOrNull(annotationIndex) } private fun getStatementVariants( From 24b559671cada9c371b12407e1a8acef092dfca7 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 20 Mar 2024 22:03:07 +0000 Subject: [PATCH 050/100] Some fixes to local variable completion --- .../expression/MEExpressionCompletionUtil.kt | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index fbd08c3b5..d08024abd 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -782,8 +782,8 @@ object MEExpressionCompletionUtil { } var argumentsSize = Type.getArgumentsAndReturnSizes(targetMethod.desc) shr 2 - if (!targetMethod.hasAccess(Opcodes.ACC_STATIC)) { - argumentsSize++ + if (targetMethod.hasAccess(Opcodes.ACC_STATIC)) { + argumentsSize-- } val isArgsOnly = index < argumentsSize @@ -805,16 +805,19 @@ object MEExpressionCompletionUtil { } else { Type.getType(localVariable.desc).toPsiType(elementFactory, mixinClass) } - val ordinal = localsHere.filter { it.desc == localVariable.desc }.indexOf(localVariable) + val localsOfMyType = localsHere.filter { it.desc == localVariable.desc } + val ordinal = localsOfMyType.indexOf(localVariable) + val isImplicit = localsOfMyType.size == 1 LookupElementBuilder.create(localVariable.name.toValidIdentifier()) .withIcon(PlatformIcons.VARIABLE_ICON) - .withTailText(localPsiType.presentableText) + .withTypeText(localPsiType.presentableText) .withLocalDefinition( localVariable.name.toValidIdentifier(), Type.getType(localVariable.desc), ordinal, isArgsOnly, - mixinClass + isImplicit, + mixinClass, ) } } @@ -825,11 +828,12 @@ object MEExpressionCompletionUtil { val localType = localTypes.getOrNull(index) ?: return emptyList() val ordinal = localTypes.asSequence().take(index).filter { it == localType }.count() val localName = localType.typeNameToInsert().replace("[]", "Array") + (ordinal + 1) + val isImplicit = localTypes.count { it == localType } == 1 return listOf( LookupElementBuilder.create(localName) .withIcon(PlatformIcons.VARIABLE_ICON) - .withTailText(localType.presentableName()) - .withLocalDefinition(localName, localType, ordinal, isArgsOnly, mixinClass) + .withTypeText(localType.presentableName()) + .withLocalDefinition(localName, localType, ordinal, isArgsOnly, isImplicit, mixinClass) ) } @@ -865,21 +869,35 @@ object MEExpressionCompletionUtil { type: Type, ordinal: Int, isArgsOnly: Boolean, + isImplicit: Boolean, mixinClass: PsiClass, ): LookupElementBuilder { val definitionLocal = buildString { append("local = @${MixinConstants.MixinExtras.LOCAL}(") - if (type.isAccessibleFrom(mixinClass)) { + val isTypeAccessible = type.isAccessibleFrom(mixinClass) + if (isTypeAccessible) { append("type = ${type.className}.class, ") } - append("ordinal = ") - append(ordinal) + if (!isTypeAccessible || !isImplicit) { + append("ordinal = ") + append(ordinal) + append(", ") + } if (isArgsOnly) { - append(", argsOnly = true") + append("argsOnly = true, ") + } + + if (endsWith(", ")) { + setLength(length - 2) } + append(")") } return withDefinition(name, definitionLocal) { context, annotation -> + if (isImplicit) { + return@withDefinition + } + invokeLater { WriteCommandAction.runWriteCommandAction( context.project, From 73f6b41a022faf35faa2a28ebcea5d80a761954f Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 20 Mar 2024 22:05:12 +0000 Subject: [PATCH 051/100] Fix can-be-implicit locals with inaccessible types not showing template --- .../mixin/expression/MEExpressionCompletionUtil.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index d08024abd..bfa060265 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -869,16 +869,18 @@ object MEExpressionCompletionUtil { type: Type, ordinal: Int, isArgsOnly: Boolean, - isImplicit: Boolean, + canBeImplicit: Boolean, mixinClass: PsiClass, ): LookupElementBuilder { + val isTypeAccessible = type.isAccessibleFrom(mixinClass) + val isImplicit = canBeImplicit && isTypeAccessible + val definitionLocal = buildString { append("local = @${MixinConstants.MixinExtras.LOCAL}(") - val isTypeAccessible = type.isAccessibleFrom(mixinClass) if (isTypeAccessible) { append("type = ${type.className}.class, ") } - if (!isTypeAccessible || !isImplicit) { + if (!isImplicit) { append("ordinal = ") append(ordinal) append(", ") From dbeb5ef387d1c783e1f43f8299b50749c7306552 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 20 Mar 2024 22:16:26 +0000 Subject: [PATCH 052/100] Show field and method types in completion list --- .../mixin/expression/MEExpressionCompletionUtil.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index bfa060265..de4010510 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -585,6 +585,7 @@ object MEExpressionCompletionUtil { var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) .withIcon(PlatformIcons.FIELD_ICON) .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) + .withTypeText(Type.getType(insn.desc).presentableName()) .withDefinitionAndFoldTarget(insn.name.toValidIdentifier(), at) if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.PUTSTATIC) { lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) @@ -599,6 +600,10 @@ object MEExpressionCompletionUtil { var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) .withIcon(PlatformIcons.METHOD_ICON) .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) + .withTailText( + "(" + Type.getArgumentTypes(insn.desc).joinToString { it.presentableName() } + ")" + ) + .withTypeText(Type.getReturnType(insn.desc).presentableName()) .withDefinitionAndFoldTarget(insn.name.toValidIdentifier(), at) if (insn.opcode == Opcodes.INVOKESTATIC) { lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) @@ -695,7 +700,11 @@ object MEExpressionCompletionUtil { when (insn.opcode) { Opcodes.ARRAYLENGTH -> { if (canCompleteExprs) { - return listOf(LookupElementBuilder.create("length").withIcon(PlatformIcons.FIELD_ICON)) + return listOf( + LookupElementBuilder.create("length") + .withIcon(PlatformIcons.FIELD_ICON) + .withTypeText("int") + ) } } } From 1b01e28ab2eda6ea4bd476d9e61f62ce99012772 Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 22 Mar 2024 13:58:25 +0000 Subject: [PATCH 053/100] Add completion tests, and make some fixes to completion --- build.gradle.kts | 2 +- .../meExpression/MEExpressionTestData.java | 62 +++ .../expression/MEExpressionCompletionUtil.kt | 83 ++- .../mixins/impl/MEAssignStatementImplMixin.kt | 2 +- .../kotlin/platform/mixin/BaseMixinTest.kt | 5 +- .../expression/MEExpressionCompletionTest.kt | 480 ++++++++++++++++++ 6 files changed, 615 insertions(+), 19 deletions(-) create mode 100644 mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java create mode 100644 src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 4a401e4c0..c361f1c1f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:00d6f4e") + testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:00d6f4e")!!) implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java new file mode 100644 index 000000000..9ed332da8 --- /dev/null +++ b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java @@ -0,0 +1,62 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.mixintestdata.meExpression; + +public class MEExpressionTestData { + private static final SynchedData STINGER_COUNT = null; + private SynchedDataManager synchedData; + + public void complexFunction() { + int one = 1; + String local1 = "Hello"; + String local2 = "World"; + + System.out.println(new StringBuilder(local1).append(", ").append(local2)); + System.out.println(one); + + InaccessibleType varOfInaccessibleType = new InaccessibleType(); + acceptInaccessibleType(varOfInaccessibleType); + noArgMethod(); + } + + private static void acceptInaccessibleType(InaccessibleType type) { + } + + private static void noArgMethod() { + } + + public int getStingerCount() { + return (Integer) this.synchedData.get(STINGER_COUNT); + } + + private static class InaccessibleType { + + } + + public static class SynchedDataManager { + public V get(SynchedData data) { + return null; + } + } + + public static class SynchedData { + } +} diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index de4010510..1d24dd13e 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -267,7 +267,9 @@ object MEExpressionCompletionUtil { val elementAtCursor = statement.findElementAt(cursorOffset.toInt()) ?: return emptyList() val wildcardReplacedStatement = statement.copy() as MEStatement - replaceCursorInputWithWildcard(project, wildcardReplacedStatement, cursorOffset.toInt()) + var cursorOffsetInCopyFile = + cursorOffset.toInt() - statement.textRange.startOffset + wildcardReplacedStatement.textRange.startOffset + replaceCursorInputWithWildcard(project, wildcardReplacedStatement, cursorOffsetInCopyFile) var matchingFlows = mutableListOf() for (statementToMatch in getStatementVariants(project.meExpressionElementFactory, wildcardReplacedStatement)) { @@ -295,13 +297,27 @@ object MEExpressionCompletionUtil { return emptyList() } + var roundNumber = 0 var subExpr: MEMatchableElement = statement while (true) { val inputExprOnCursor = subExpr.getInputExprs().firstOrNull { it.textRange.contains(cursorOffset.toInt()) } ?: break val wildcardReplacedExpr = inputExprOnCursor.copy() as MEExpression - cursorOffset.setValue(cursorOffset.toInt() - inputExprOnCursor.textRange.startOffset) - replaceCursorInputWithWildcard(project, wildcardReplacedExpr, cursorOffset.toInt()) + cursorOffsetInCopyFile = cursorOffset.toInt() - + inputExprOnCursor.textRange.startOffset + wildcardReplacedExpr.textRange.startOffset + + if (DEBUG_COMPLETION) { + val exprText = wildcardReplacedExpr.text + val cursorOffsetInExpr = cursorOffsetInCopyFile - wildcardReplacedExpr.textRange.startOffset + val exprWithCaretMarker = when { + cursorOffsetInExpr < 0 -> "$exprText" + cursorOffsetInExpr > exprText.length -> "$exprText" + else -> exprText.replaceRange(cursorOffsetInExpr, cursorOffsetInExpr, "") + } + println("=== Round ${++roundNumber}: handling $exprWithCaretMarker") + } + + replaceCursorInputWithWildcard(project, wildcardReplacedExpr, cursorOffsetInCopyFile) val newMatchingFlows = mutableSetOf() for (exprToMatch in getExpressionVariants(project.meExpressionElementFactory, wildcardReplacedExpr)) { @@ -374,11 +390,17 @@ object MEExpressionCompletionUtil { val isPossiblyIncompleteCast = !inTypePosition && elementAtCursor.parentOfType() ?.parents(false) - ?.dropWhile { it is MEArrayAccessExpression && it.indexExpr == null } is MEParenthesizedExpression + ?.dropWhile { it is MEArrayAccessExpression && it.indexExpr == null } + ?.firstOrNull() is MEParenthesizedExpression val canCompleteExprs = !inTypePosition val canCompleteTypes = inTypePosition || isPossiblyIncompleteCast - return cursorInstructions.flatMap { insn -> + if (DEBUG_COMPLETION) { + println("canCompleteExprs = $canCompleteExprs") + println("canCompleteTypes = $canCompleteTypes") + } + + val eliminableResults = cursorInstructions.flatMap { insn -> getCompletionsForInstruction( project, targetClass, @@ -390,6 +412,11 @@ object MEExpressionCompletionUtil { canCompleteTypes ) } + + // In the case of multiple instructions producing the same lookup, attempt to show only the "best" lookup. + // For example, if a local variable is only sometimes able to be targeted using implicit ordinals in this + // expression, prefer specifying the ordinal. + return eliminableResults.groupBy { it.lookupElement.lookupString }.values.map { it.max().lookupElement } } private fun replaceUnknownNamesWithWildcards( @@ -546,7 +573,7 @@ object MEExpressionCompletionUtil { mixinClass: PsiClass, canCompleteExprs: Boolean, canCompleteTypes: Boolean - ): List { + ): List { when (insn) { is LdcInsnNode -> { when (val cst = insn.cst) { @@ -555,7 +582,7 @@ object MEExpressionCompletionUtil { return listOf( object : TailTypeDecorator(createTypeLookup(cst)) { override fun computeTailType(context: InsertionContext?) = DOT_CLASS_TAIL - } + }.createEliminable() ) } } @@ -568,6 +595,7 @@ object MEExpressionCompletionUtil { targetMethod, insn, insn.`var`, + insn.opcode in Opcodes.ISTORE..Opcodes.ASTORE, mixinClass ) is IincInsnNode -> return createLocalVariableLookups( @@ -576,6 +604,7 @@ object MEExpressionCompletionUtil { targetMethod, insn, insn.`var`, + false, mixinClass ) is FieldInsnNode -> { @@ -590,7 +619,7 @@ object MEExpressionCompletionUtil { if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.PUTSTATIC) { lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) } - return listOf(lookup) + return listOf(lookup.createEliminable()) } } is MethodInsnNode -> { @@ -612,7 +641,7 @@ object MEExpressionCompletionUtil { object : TailTypeDecorator(lookup) { override fun computeTailType(context: InsertionContext?) = ParenthesesTailType(!insn.desc.startsWith("()")) - } + }.createEliminable() ) } } @@ -629,7 +658,7 @@ object MEExpressionCompletionUtil { 1, flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, ) - } + }.createEliminable() ) } Opcodes.NEW -> { @@ -645,10 +674,10 @@ object MEExpressionCompletionUtil { ParenthesesTailType( initCall?.desc?.startsWith("()") == false ) - } + }.createEliminable() ) } - else -> return listOf(lookup) + else -> return listOf(lookup.createEliminable()) } } } @@ -675,7 +704,7 @@ object MEExpressionCompletionUtil { 1, flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, ) - } + }.createEliminable() ) } } @@ -692,7 +721,7 @@ object MEExpressionCompletionUtil { type.dimensions, flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, ) - } + }.createEliminable() ) } } @@ -704,6 +733,7 @@ object MEExpressionCompletionUtil { LookupElementBuilder.create("length") .withIcon(PlatformIcons.FIELD_ICON) .withTypeText("int") + .createEliminable() ) } } @@ -783,8 +813,9 @@ object MEExpressionCompletionUtil { targetMethod: MethodNode, insn: AbstractInsnNode, index: Int, + isStore: Boolean, mixinClass: PsiClass, - ): List { + ): List { // ignore "this" if (!targetMethod.hasAccess(Opcodes.ACC_STATIC) && index == 0) { return emptyList() @@ -798,7 +829,16 @@ object MEExpressionCompletionUtil { if (targetMethod.localVariables != null) { val localsHere = targetMethod.localVariables.filter { localVariable -> - val validRange = targetMethod.instructions.indexOf(localVariable.start) + 1 until + val firstValidInstruction = if (isStore) { + generateSequence(localVariable.start) { it.previous } + .firstOrNull { it.opcode >= 0 } + } else { + localVariable.start.next + } + if (firstValidInstruction == null) { + return@filter false + } + val validRange = targetMethod.instructions.indexOf(firstValidInstruction) until targetMethod.instructions.indexOf(localVariable.end) targetMethod.instructions.indexOf(insn) in validRange } @@ -828,6 +868,7 @@ object MEExpressionCompletionUtil { isImplicit, mixinClass, ) + .createEliminable(if (isImplicit) -1 else 0) } } @@ -843,6 +884,7 @@ object MEExpressionCompletionUtil { .withIcon(PlatformIcons.VARIABLE_ICON) .withTypeText(localType.presentableName()) .withLocalDefinition(localName, localType, ordinal, isArgsOnly, isImplicit, mixinClass) + .createEliminable(if (isImplicit) -1 else 0) ) } @@ -1082,6 +1124,15 @@ object MEExpressionCompletionUtil { return variants } + private fun LookupElement.createEliminable(priority: Int = 0) = EliminableLookup(this, priority) + + private class EliminableLookup( + val lookupElement: LookupElement, + private val priority: Int + ) : Comparable { + override fun compareTo(other: EliminableLookup) = priority.compareTo(other.priority) + } + private class ParenthesesTailType(private val hasParameters: Boolean) : TailType() { override fun processTail(editor: Editor, tailOffset: Int): Int { editor.document.insertString(tailOffset, "()") diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt index fba4db160..f3fbfb396 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEAssignStatementImplMixin.kt @@ -50,7 +50,7 @@ abstract class MEAssignStatementImplMixin(node: ASTNode) : MEStatementImpl(node) } } - override fun getInputExprs() = listOfNotNull(targetExpr, rightExpr) + override fun getInputExprs() = targetExpr.getInputExprs() + listOfNotNull(rightExpr) protected abstract val targetExpr: MEExpression protected abstract val rightExpr: MEExpression? diff --git a/src/test/kotlin/platform/mixin/BaseMixinTest.kt b/src/test/kotlin/platform/mixin/BaseMixinTest.kt index 4b9dbd0ee..a2e406849 100644 --- a/src/test/kotlin/platform/mixin/BaseMixinTest.kt +++ b/src/test/kotlin/platform/mixin/BaseMixinTest.kt @@ -34,20 +34,23 @@ import org.junit.jupiter.api.BeforeEach abstract class BaseMixinTest : BaseMinecraftTest(PlatformType.MIXIN) { private var mixinLibrary: Library? = null + private var mixinExtrasLibrary: Library? = null private var testDataLibrary: Library? = null @BeforeEach fun initMixin() { runWriteTask { mixinLibrary = createLibrary(project, "mixin") + mixinExtrasLibrary = createLibrary(project, "mixinextras-common") // TODO: this will probably change testDataLibrary = createLibrary(project, "mixin-test-data") } ModuleRootModificationUtil.updateModel(module) { model -> model.addLibraryEntry(mixinLibrary ?: throw IllegalStateException("Mixin library not created")) + model.addLibraryEntry(mixinExtrasLibrary ?: throw IllegalStateException("MixinExtras library not created")) model.addLibraryEntry(testDataLibrary ?: throw IllegalStateException("Test data library not created")) val orderEntries = model.orderEntries - orderEntries.rotate(2) + orderEntries.rotate(3) model.rearrangeOrderEntries(orderEntries) } } diff --git a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt new file mode 100644 index 000000000..c763a72af --- /dev/null +++ b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt @@ -0,0 +1,480 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.framework.EdtInterceptor +import com.demonwav.mcdev.platform.mixin.BaseMixinTest +import com.intellij.codeInsight.lookup.impl.LookupImpl +import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assertions.fail +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(EdtInterceptor::class) +@DisplayName("MixinExtras expression completion test") +class MEExpressionCompletionTest : BaseMixinTest() { + private fun assertLookupAppears( + lookupString: String, + @Language("JAVA") code: String, + shouldAppear: Boolean = true + ) { + buildProject { + dir("test") { + java("MEExpressionCompletionTest.java", code) + } + } + + fixture.completeBasic() + + val lookups = fixture.lookupElementStrings + if (lookups != null) { + if (shouldAppear) { + assertTrue(lookupString in lookups) + } else { + assertFalse(lookupString in lookups) + } + } else { + if (shouldAppear) { + assertEquals(lookupString, fixture.elementAtCaret.text) + } else { + assertNotEquals(lookupString, fixture.elementAtCaret.text) + } + } + } + + private fun doBeforeAfterTest( + lookupString: String, + @Language("JAVA") code: String, + @Language("JAVA") expectedAfter: String? + ) { + buildProject { + dir("test") { + java("MEExpressionCompletionTest.java", code) + } + } + + val possibleItems = fixture.completeBasic() + if (possibleItems != null) { + val itemToComplete = possibleItems.firstOrNull { it.lookupString == lookupString } + if (expectedAfter != null) { + assertNotNull(itemToComplete, "Expected a completion matching \"$lookupString\"") + (fixture.lookup as LookupImpl).finishLookup('\n', itemToComplete) + } else { + assertNull(itemToComplete, "Expected no completions matching \"$lookupString\"") + return + } + } else if (expectedAfter == null) { + fail("Expected no completions matching \"$lookupString\"") + return + } + + fixture.checkResult(expectedAfter) + } + + @Test + @DisplayName("Local Variable Implicit Completion Test") + fun localVariableImplicitCompletionTest() { + doBeforeAfterTest( + "one", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import com.llamalad7.mixinextras.sugar.Local; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "one", local = @Local(type = int.class)) + @Expression("one") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + ) + } + + @Test + @DisplayName("Local Variable Ordinal Completion Test") + fun localVariableOrdinalCompletionTest() { + doBeforeAfterTest( + "local1", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import com.llamalad7.mixinextras.sugar.Local; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "local1", local = @Local(type = String.class, ordinal = 0)) + @Expression("local1") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + ) + } + + @Test + @DisplayName("Local Variable Inaccessible Type Completion Test") + fun localVariableInaccessibleTypeCompletionTest() { + doBeforeAfterTest( + "varOfInaccessibleType", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "acceptInaccessibleType", at = @At(value = "INVOKE", target = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;acceptInaccessibleType(Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}InaccessibleType;)V")) + @Expression("acceptInaccessibleType()") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import com.llamalad7.mixinextras.sugar.Local; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "acceptInaccessibleType", at = @At(value = "INVOKE", target = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;acceptInaccessibleType(Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}InaccessibleType;)V")) + @Definition(id = "varOfInaccessibleType", local = @Local(ordinal = 0)) + @Expression("acceptInaccessibleType(varOfInaccessibleType)") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + ) + } + + @Test + @DisplayName("Field Completion Test") + fun fieldCompletionTest() { + doBeforeAfterTest( + "out", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "out", at = @At(value = "FIELD", target = "Ljava/lang/System;out:Ljava/io/PrintStream;")) + @Expression("out") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + ) + } + + @Test + @DisplayName("Method Completion Test") + fun methodCompletionTest() { + doBeforeAfterTest( + "acceptInaccessibleType", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "acceptInaccessibleType", at = @At(value = "INVOKE", target = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;acceptInaccessibleType(Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}InaccessibleType;)V")) + @Expression("acceptInaccessibleType()") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + ) + } + + @Test + @DisplayName("Method No-Arg Completion Test") + fun methodNoArgCompletionTest() { + doBeforeAfterTest( + "noArgMethod", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "noArgMethod", at = @At(value = "INVOKE", target = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;noArgMethod()V")) + @Expression("noArgMethod()") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + ) + } + + @Test + @DisplayName("Type Completion Test") + fun typeCompletionTest() { + doBeforeAfterTest( + "StringBuilder", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("new ") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "StringBuilder", type = StringBuilder.class) + @Expression("new StringBuilder()") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + ) + } + + @Test + @DisplayName("Inaccessible Type Completion Test") + fun inaccessibleTypeCompletionTest() { + doBeforeAfterTest( + "InaccessibleType", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("new ") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + null, + ) + } + + @Test + @DisplayName("LHS Of Complete Assignment Test") + fun lhsOfCompleteAssignmentTest() { + assertLookupAppears( + "local1", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression(" = 'Hello'") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent() + ) + } + + @Test + @DisplayName("Cast Test") + fun castTest() { + assertLookupAppears( + "Integer", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("()") + @Inject(method = "getStingerCount", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent() + ) + } + + @Test + @DisplayName("Member Function Test") + fun memberFunctionTest() { + assertLookupAppears( + "get", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "Integer", type = Integer.class) + @Definition(id = "synchedData", at = @At(value = "FIELD", target = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;synchedData:Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}SynchedDataManager;")) + @Expression("(Integer) this.synchedData.") + @Inject(method = "getStingerCount", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent() + ) + } +} From d782821dac8d60131bd5271f0929ccaf09a24b9a Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 24 Mar 2024 20:20:35 +0000 Subject: [PATCH 054/100] Fix folding on completion --- .../platform/mixin/expression/MEExpressionCompletionUtil.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 1d24dd13e..ae69a85ca 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -69,6 +69,7 @@ import com.demonwav.mcdev.util.mapFirstNotNull import com.demonwav.mcdev.util.packageName import com.intellij.codeInsight.TailType import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.codeInsight.folding.CodeFoldingManager import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.lookup.TailTypeDecorator @@ -892,7 +893,9 @@ object MEExpressionCompletionUtil { private fun LookupElementBuilder.withDefinitionAndFoldTarget(id: String, at: String) = withDefinition(id, at) { context, annotation -> - val foldingModel = InjectedLanguageEditorUtil.getTopLevelEditor(context.editor).foldingModel + val hostEditor = InjectedLanguageEditorUtil.getTopLevelEditor(context.editor) + CodeFoldingManager.getInstance(context.project).updateFoldRegions(hostEditor) + val foldingModel = hostEditor.foldingModel val regionsToFold = mutableListOf() val annotationRange = annotation.textRange for (foldRegion in foldingModel.allFoldRegions) { From e43c74d4427e96bfa9e8faa1583386b01c911a1c Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 24 Mar 2024 23:28:12 +0000 Subject: [PATCH 055/100] Refactor mcdev settings, move shadow setting into a project setting --- src/main/kotlin/MinecraftConfigurable.kt | 7 -- .../kotlin/MinecraftProjectConfigurable.kt | 54 ++++++++++++++++ src/main/kotlin/MinecraftProjectSettings.kt | 44 +++++++++++++ src/main/kotlin/MinecraftSettings.kt | 64 +++---------------- .../mixin/action/GenerateShadowAction.kt | 4 +- src/main/resources/META-INF/plugin.xml | 5 ++ .../messages/MinecraftDevelopment.properties | 15 +++-- 7 files changed, 123 insertions(+), 70 deletions(-) create mode 100644 src/main/kotlin/MinecraftProjectConfigurable.kt create mode 100644 src/main/kotlin/MinecraftProjectSettings.kt diff --git a/src/main/kotlin/MinecraftConfigurable.kt b/src/main/kotlin/MinecraftConfigurable.kt index 815131f7f..6b0595105 100644 --- a/src/main/kotlin/MinecraftConfigurable.kt +++ b/src/main/kotlin/MinecraftConfigurable.kt @@ -84,13 +84,6 @@ class MinecraftConfigurable : Configurable { } } - group(MCDevBundle("minecraft.settings.mixin")) { - row { - checkBox(MCDevBundle("minecraft.settings.mixin.shadow_annotation_same_line")) - .bindSelected(settings::isShadowAnnotationsSameLine) - } - } - onApply { for (project in ProjectManager.getInstance().openProjects) { ProjectView.getInstance(project).refresh() diff --git a/src/main/kotlin/MinecraftProjectConfigurable.kt b/src/main/kotlin/MinecraftProjectConfigurable.kt new file mode 100644 index 000000000..2e683bb93 --- /dev/null +++ b/src/main/kotlin/MinecraftProjectConfigurable.kt @@ -0,0 +1,54 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev + +import com.demonwav.mcdev.asset.MCDevBundle +import com.intellij.openapi.options.Configurable +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogPanel +import com.intellij.ui.dsl.builder.bindSelected +import com.intellij.ui.dsl.builder.panel +import javax.swing.JComponent +import org.jetbrains.annotations.Nls + +class MinecraftProjectConfigurable(private val project: Project) : Configurable { + private lateinit var panel: DialogPanel + + @Nls + override fun getDisplayName() = MCDevBundle("minecraft.settings.project.display_name") + + override fun createComponent(): JComponent = panel { + val settings = MinecraftProjectSettings.getInstance(project) + + group(MCDevBundle("minecraft.settings.mixin")) { + row { + checkBox(MCDevBundle("minecraft.settings.mixin.shadow_annotation_same_line")) + .bindSelected(settings::isShadowAnnotationsSameLine) + } + } + }.also { panel = it } + + override fun isModified(): Boolean = panel.isModified() + + override fun apply() = panel.apply() + + override fun reset() = panel.reset() +} diff --git a/src/main/kotlin/MinecraftProjectSettings.kt b/src/main/kotlin/MinecraftProjectSettings.kt new file mode 100644 index 000000000..9c65fe30e --- /dev/null +++ b/src/main/kotlin/MinecraftProjectSettings.kt @@ -0,0 +1,44 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev + +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.util.xmlb.XmlSerializerUtil + +@Service(Service.Level.PROJECT) +@State(name = "MinecraftSettings", storages = [Storage("minecraft_dev.xml")]) +class MinecraftProjectSettings : PersistentStateComponent { + var isShadowAnnotationsSameLine = true + + override fun getState() = this + override fun loadState(state: MinecraftProjectSettings) { + XmlSerializerUtil.copyBean(state, this) + } + + companion object { + fun getInstance(project: Project) = project.service() + } +} diff --git a/src/main/kotlin/MinecraftSettings.kt b/src/main/kotlin/MinecraftSettings.kt index b4b596114..4a1335a09 100644 --- a/src/main/kotlin/MinecraftSettings.kt +++ b/src/main/kotlin/MinecraftSettings.kt @@ -25,66 +25,22 @@ import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.editor.markup.EffectType +import com.intellij.util.xmlb.XmlSerializerUtil @State(name = "MinecraftSettings", storages = [Storage("minecraft_dev.xml")]) -class MinecraftSettings : PersistentStateComponent { +class MinecraftSettings : PersistentStateComponent { + override fun getState() = this - data class State( - var isShowProjectPlatformIcons: Boolean = true, - var isShowEventListenerGutterIcons: Boolean = true, - var isShowChatColorGutterIcons: Boolean = true, - var isShowChatColorUnderlines: Boolean = false, - var underlineType: UnderlineType = UnderlineType.DOTTED, - - var isShadowAnnotationsSameLine: Boolean = true, - ) - - private var state = State() - - override fun getState(): State { - return state - } - - override fun loadState(state: State) { - this.state = state + override fun loadState(state: MinecraftSettings) { + XmlSerializerUtil.copyBean(state, this) } - // State mappings - var isShowProjectPlatformIcons: Boolean - get() = state.isShowProjectPlatformIcons - set(showProjectPlatformIcons) { - state.isShowProjectPlatformIcons = showProjectPlatformIcons - } - - var isShowEventListenerGutterIcons: Boolean - get() = state.isShowEventListenerGutterIcons - set(showEventListenerGutterIcons) { - state.isShowEventListenerGutterIcons = showEventListenerGutterIcons - } + var isShowProjectPlatformIcons = true + var isShowEventListenerGutterIcons = true + var isShowChatColorGutterIcons = true + var isShowChatColorUnderlines = false - var isShowChatColorGutterIcons: Boolean - get() = state.isShowChatColorGutterIcons - set(showChatColorGutterIcons) { - state.isShowChatColorGutterIcons = showChatColorGutterIcons - } - - var isShowChatColorUnderlines: Boolean - get() = state.isShowChatColorUnderlines - set(showChatColorUnderlines) { - state.isShowChatColorUnderlines = showChatColorUnderlines - } - - var underlineType: UnderlineType - get() = state.underlineType - set(underlineType) { - state.underlineType = underlineType - } - - var isShadowAnnotationsSameLine: Boolean - get() = state.isShadowAnnotationsSameLine - set(shadowAnnotationsSameLine) { - state.isShadowAnnotationsSameLine = shadowAnnotationsSameLine - } + var underlineType = UnderlineType.DOTTED enum class UnderlineType(private val regular: String, val effectType: EffectType) { diff --git a/src/main/kotlin/platform/mixin/action/GenerateShadowAction.kt b/src/main/kotlin/platform/mixin/action/GenerateShadowAction.kt index f53938550..293543c47 100644 --- a/src/main/kotlin/platform/mixin/action/GenerateShadowAction.kt +++ b/src/main/kotlin/platform/mixin/action/GenerateShadowAction.kt @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.action -import com.demonwav.mcdev.MinecraftSettings +import com.demonwav.mcdev.MinecraftProjectSettings import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.platform.mixin.util.findFields import com.demonwav.mcdev.platform.mixin.util.findMethods @@ -237,7 +237,7 @@ private fun copyAnnotation(modifiers: PsiModifierList, newModifiers: PsiModifier } inline fun disableAnnotationWrapping(project: Project, func: () -> Unit) { - if (!MinecraftSettings.instance.isShadowAnnotationsSameLine) { + if (!MinecraftProjectSettings.getInstance(project).isShadowAnnotationsSameLine) { func() return } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index b4963aa69..9204eb5f7 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -212,6 +212,10 @@ id="Settings.Minecraft" groupId="language" instance="com.demonwav.mcdev.MinecraftConfigurable"/> + + diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 7e1c2a733..4dc0febe6 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -190,16 +190,17 @@ nbt.file.save_notify.parse_exception.content=An unexpected exception happened, { intention.error.cannot.create.class.message=Cannot create class ''{0}''\n{1} intention.error.cannot.create.class.title=Failed to Create Class -minecraft.settings.display_name=Minecraft Development -minecraft.settings.title=Minecraft Development Settings minecraft.settings.change_update_channel=Change Plugin Update Channel -minecraft.settings.show_project_platform_icons=Show project platform icons -minecraft.settings.show_event_listener_gutter_icons=Show event listener gutter icons -minecraft.settings.show_chat_color_gutter_icons=Show chat color gutter icons -minecraft.settings.show_chat_color_underlines=Show chat color underlines minecraft.settings.chat_color_underline_style=Chat color underline style: -minecraft.settings.mixin=Mixin +minecraft.settings.display_name=Minecraft Development minecraft.settings.mixin.shadow_annotation_same_line=@Shadow annotations on same line +minecraft.settings.mixin=Mixin +minecraft.settings.project.display_name=Project-Specific Settings +minecraft.settings.show_chat_color_gutter_icons=Show chat color gutter icons +minecraft.settings.show_chat_color_underlines=Show chat color underlines +minecraft.settings.show_event_listener_gutter_icons=Show event listener gutter icons +minecraft.settings.show_project_platform_icons=Show project platform icons +minecraft.settings.title=Minecraft Development Settings mixinextras.expression.lang.errors.array_access_missing_index=Missing index mixinextras.expression.lang.errors.array_length_after_empty=Cannot specify array length after an unspecified array length From 166bfaf7b238a89f73f4ad1d468672c7167cbb6a Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 24 Mar 2024 23:49:11 +0000 Subject: [PATCH 056/100] Add setting for position of @Definition relative to @Expression --- .../kotlin/MinecraftProjectConfigurable.kt | 10 ++++++ src/main/kotlin/MinecraftProjectSettings.kt | 2 ++ .../expression/MEExpressionCompletionUtil.kt | 15 ++++++--- src/main/kotlin/util/BeforeOrAfter.kt | 32 +++++++++++++++++++ .../messages/MinecraftDevelopment.properties | 4 +++ .../expression/MEExpressionCompletionTest.kt | 4 +++ 6 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/util/BeforeOrAfter.kt diff --git a/src/main/kotlin/MinecraftProjectConfigurable.kt b/src/main/kotlin/MinecraftProjectConfigurable.kt index 2e683bb93..043e85d10 100644 --- a/src/main/kotlin/MinecraftProjectConfigurable.kt +++ b/src/main/kotlin/MinecraftProjectConfigurable.kt @@ -21,9 +21,12 @@ package com.demonwav.mcdev import com.demonwav.mcdev.asset.MCDevBundle +import com.demonwav.mcdev.util.BeforeOrAfter import com.intellij.openapi.options.Configurable import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogPanel +import com.intellij.ui.EnumComboBoxModel +import com.intellij.ui.dsl.builder.bindItem import com.intellij.ui.dsl.builder.bindSelected import com.intellij.ui.dsl.builder.panel import javax.swing.JComponent @@ -43,6 +46,13 @@ class MinecraftProjectConfigurable(private val project: Project) : Configurable checkBox(MCDevBundle("minecraft.settings.mixin.shadow_annotation_same_line")) .bindSelected(settings::isShadowAnnotationsSameLine) } + row { + label(MCDevBundle("minecraft.settings.mixin.definition_pos_relative_to_expression")) + comboBox(EnumComboBoxModel(BeforeOrAfter::class.java)) + .bindItem(settings::definitionPosRelativeToExpression) { + settings.definitionPosRelativeToExpression = it ?: BeforeOrAfter.AFTER + } + } } }.also { panel = it } diff --git a/src/main/kotlin/MinecraftProjectSettings.kt b/src/main/kotlin/MinecraftProjectSettings.kt index 9c65fe30e..3896d8ede 100644 --- a/src/main/kotlin/MinecraftProjectSettings.kt +++ b/src/main/kotlin/MinecraftProjectSettings.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev +import com.demonwav.mcdev.util.BeforeOrAfter import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.Service import com.intellij.openapi.components.State @@ -32,6 +33,7 @@ import com.intellij.util.xmlb.XmlSerializerUtil @State(name = "MinecraftSettings", storages = [Storage("minecraft_dev.xml")]) class MinecraftProjectSettings : PersistentStateComponent { var isShadowAnnotationsSameLine = true + var definitionPosRelativeToExpression = BeforeOrAfter.AFTER override fun getState() = this override fun loadState(state: MinecraftProjectSettings) { diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index ae69a85ca..3bbceece2 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression +import com.demonwav.mcdev.MinecraftProjectSettings import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.insnOrNull import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEAssignStatement @@ -58,6 +59,7 @@ import com.demonwav.mcdev.platform.mixin.util.isPrimitive import com.demonwav.mcdev.platform.mixin.util.mixinTargets import com.demonwav.mcdev.platform.mixin.util.textify import com.demonwav.mcdev.platform.mixin.util.toPsiType +import com.demonwav.mcdev.util.BeforeOrAfter import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.findContainingClass import com.demonwav.mcdev.util.findContainingModifierList @@ -1054,10 +1056,15 @@ object MEExpressionCompletionUtil { "@${MixinConstants.MixinExtras.DEFINITION}(id = \"$id\", $at)", modifierList, ) - newAnnotation = modifierList.addAfter( - newAnnotation, - modifierList.annotations.lastOrNull { it.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) } - ) as PsiAnnotation + var anchor = modifierList.annotations.lastOrNull { it.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) } + if (anchor == null) { + val definitionPosRelativeToExpression = + MinecraftProjectSettings.getInstance(context.project).definitionPosRelativeToExpression + if (definitionPosRelativeToExpression == BeforeOrAfter.AFTER) { + anchor = expressionAnnotation + } + } + newAnnotation = modifierList.addAfter(newAnnotation, anchor) as PsiAnnotation // add imports and reformat newAnnotation = diff --git a/src/main/kotlin/util/BeforeOrAfter.kt b/src/main/kotlin/util/BeforeOrAfter.kt new file mode 100644 index 000000000..448be3062 --- /dev/null +++ b/src/main/kotlin/util/BeforeOrAfter.kt @@ -0,0 +1,32 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.util + +import com.demonwav.mcdev.asset.MCDevBundle +import java.util.function.Supplier + +enum class BeforeOrAfter(private val myDisplayName: Supplier) { + BEFORE(MCDevBundle.pointer("minecraft.before")), + AFTER(MCDevBundle.pointer("minecraft.after")); + + val displayName get() = myDisplayName.get() + override fun toString() = displayName +} diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 4dc0febe6..f0974819e 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -193,6 +193,7 @@ intention.error.cannot.create.class.title=Failed to Create Class minecraft.settings.change_update_channel=Change Plugin Update Channel minecraft.settings.chat_color_underline_style=Chat color underline style: minecraft.settings.display_name=Minecraft Development +minecraft.settings.mixin.definition_pos_relative_to_expression=@Definition position relative to @Expression minecraft.settings.mixin.shadow_annotation_same_line=@Shadow annotations on same line minecraft.settings.mixin=Mixin minecraft.settings.project.display_name=Project-Specific Settings @@ -202,6 +203,9 @@ minecraft.settings.show_event_listener_gutter_icons=Show event listener gutter i minecraft.settings.show_project_platform_icons=Show project platform icons minecraft.settings.title=Minecraft Development Settings +minecraft.before=Before +minecraft.after=After + mixinextras.expression.lang.errors.array_access_missing_index=Missing index mixinextras.expression.lang.errors.array_length_after_empty=Cannot specify array length after an unspecified array length mixinextras.expression.lang.errors.empty_array_initializer=Array initializer cannot be empty diff --git a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt index c763a72af..d1d0a5d77 100644 --- a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt +++ b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt @@ -20,8 +20,10 @@ package com.demonwav.mcdev.platform.mixin.expression +import com.demonwav.mcdev.MinecraftProjectSettings import com.demonwav.mcdev.framework.EdtInterceptor import com.demonwav.mcdev.platform.mixin.BaseMixinTest +import com.demonwav.mcdev.util.BeforeOrAfter import com.intellij.codeInsight.lookup.impl.LookupImpl import org.intellij.lang.annotations.Language import org.junit.jupiter.api.Assertions.assertEquals @@ -78,6 +80,8 @@ class MEExpressionCompletionTest : BaseMixinTest() { } } + MinecraftProjectSettings.getInstance(fixture.project).definitionPosRelativeToExpression = BeforeOrAfter.BEFORE + val possibleItems = fixture.completeBasic() if (possibleItems != null) { val itemToComplete = possibleItems.firstOrNull { it.lookupString == lookupString } From d586b52a6ce738cb6de35011606d709d14549a62 Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 25 Mar 2024 00:28:44 +0000 Subject: [PATCH 057/100] Add folding for @Definitions --- .../kotlin/MinecraftProjectConfigurable.kt | 2 +- src/main/kotlin/MinecraftProjectSettings.kt | 2 +- .../expression/MEDefinitionFoldingBuilder.kt | 91 +++++++++++++++++++ .../folding/MixinFoldingOptionsProvider.kt | 5 + .../mixin/folding/MixinFoldingSettings.kt | 1 + src/main/resources/META-INF/plugin.xml | 1 + 6 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt diff --git a/src/main/kotlin/MinecraftProjectConfigurable.kt b/src/main/kotlin/MinecraftProjectConfigurable.kt index 043e85d10..0e676f0b7 100644 --- a/src/main/kotlin/MinecraftProjectConfigurable.kt +++ b/src/main/kotlin/MinecraftProjectConfigurable.kt @@ -50,7 +50,7 @@ class MinecraftProjectConfigurable(private val project: Project) : Configurable label(MCDevBundle("minecraft.settings.mixin.definition_pos_relative_to_expression")) comboBox(EnumComboBoxModel(BeforeOrAfter::class.java)) .bindItem(settings::definitionPosRelativeToExpression) { - settings.definitionPosRelativeToExpression = it ?: BeforeOrAfter.AFTER + settings.definitionPosRelativeToExpression = it ?: BeforeOrAfter.BEFORE } } } diff --git a/src/main/kotlin/MinecraftProjectSettings.kt b/src/main/kotlin/MinecraftProjectSettings.kt index 3896d8ede..f22bf178b 100644 --- a/src/main/kotlin/MinecraftProjectSettings.kt +++ b/src/main/kotlin/MinecraftProjectSettings.kt @@ -33,7 +33,7 @@ import com.intellij.util.xmlb.XmlSerializerUtil @State(name = "MinecraftSettings", storages = [Storage("minecraft_dev.xml")]) class MinecraftProjectSettings : PersistentStateComponent { var isShadowAnnotationsSameLine = true - var definitionPosRelativeToExpression = BeforeOrAfter.AFTER + var definitionPosRelativeToExpression = BeforeOrAfter.BEFORE override fun getState() = this override fun loadState(state: MinecraftProjectSettings) { diff --git a/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt b/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt new file mode 100644 index 000000000..dad25fc20 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt @@ -0,0 +1,91 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.MixinModuleType +import com.demonwav.mcdev.platform.mixin.folding.MixinFoldingSettings +import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.intellij.lang.ASTNode +import com.intellij.lang.folding.CustomFoldingBuilder +import com.intellij.lang.folding.FoldingDescriptor +import com.intellij.openapi.editor.Document +import com.intellij.openapi.util.TextRange +import com.intellij.psi.JavaRecursiveElementWalkingVisitor +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiJavaFile +import com.intellij.psi.PsiModifierList +import com.intellij.psi.util.PsiTreeUtil + +class MEDefinitionFoldingBuilder : CustomFoldingBuilder() { + override fun isDumbAware() = false + + override fun isRegionCollapsedByDefault(node: ASTNode): Boolean = + MixinFoldingSettings.instance.state.foldDefinitions + + override fun getLanguagePlaceholderText(node: ASTNode, range: TextRange) = "..." + + override fun buildLanguageFoldRegions( + descriptors: MutableList, + root: PsiElement, + document: Document, + quick: Boolean + ) { + if (root !is PsiJavaFile || !MixinModuleType.isInModule(root)) { + return + } + + root.accept(Visitor(descriptors)) + } + + private class Visitor(private val descriptors: MutableList) : + JavaRecursiveElementWalkingVisitor() { + override fun visitModifierList(list: PsiModifierList) { + val currentDefinitionList = mutableListOf() + val definitionLists = mutableListOf>() + + for (annotation in list.annotations) { + if (annotation.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION)) { + currentDefinitionList += annotation + } else if (currentDefinitionList.isNotEmpty()) { + definitionLists += currentDefinitionList.toList() + currentDefinitionList.clear() + } + } + + if (currentDefinitionList.isNotEmpty()) { + definitionLists += currentDefinitionList + } + + if (definitionLists.isEmpty()) { + return + } + + for (definitionList in definitionLists) { + val range = TextRange( + definitionList.first().parameterList.firstChild.nextSibling.textRange.startOffset, + PsiTreeUtil.getDeepestVisibleLast(definitionList.last())!!.textRange.startOffset, + ) + descriptors.add(FoldingDescriptor(list.node, range)) + } + } + } +} diff --git a/src/main/kotlin/platform/mixin/folding/MixinFoldingOptionsProvider.kt b/src/main/kotlin/platform/mixin/folding/MixinFoldingOptionsProvider.kt index 7f359fd58..ff2c3b769 100644 --- a/src/main/kotlin/platform/mixin/folding/MixinFoldingOptionsProvider.kt +++ b/src/main/kotlin/platform/mixin/folding/MixinFoldingOptionsProvider.kt @@ -56,5 +56,10 @@ class MixinFoldingOptionsProvider : { settings.state.foldAccessorMethodCalls }, { b -> settings.state.foldAccessorMethodCalls = b }, ) + checkBox( + "Fold MixinExtras expression definitions", + { settings.state.foldDefinitions }, + { b -> settings.state.foldDefinitions = b }, + ) } } diff --git a/src/main/kotlin/platform/mixin/folding/MixinFoldingSettings.kt b/src/main/kotlin/platform/mixin/folding/MixinFoldingSettings.kt index 081cc03aa..b264561f2 100644 --- a/src/main/kotlin/platform/mixin/folding/MixinFoldingSettings.kt +++ b/src/main/kotlin/platform/mixin/folding/MixinFoldingSettings.kt @@ -35,6 +35,7 @@ class MixinFoldingSettings : PersistentStateComponent + From efb945e3b2e02eb2609683245a830c370ea1f4a2 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 26 Mar 2024 12:02:59 +0000 Subject: [PATCH 058/100] Fix array literal input completion --- .../meExpression/MEExpressionTestData.java | 3 + .../expression/MEExpressionCompletionUtil.kt | 37 +++++--- .../expression/MEExpressionCompletionTest.kt | 90 +++++++++++++++++++ 3 files changed, 116 insertions(+), 14 deletions(-) diff --git a/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java index 9ed332da8..9d5dca03d 100644 --- a/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java +++ b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java @@ -35,6 +35,9 @@ public void complexFunction() { InaccessibleType varOfInaccessibleType = new InaccessibleType(); acceptInaccessibleType(varOfInaccessibleType); noArgMethod(); + + String[] strings1 = new String[] { local1, local2 }; + String[] strings2 = new String[one]; } private static void acceptInaccessibleType(InaccessibleType type) { diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 3bbceece2..a9795c7b1 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -109,6 +109,7 @@ import com.intellij.psi.util.parentOfType import com.intellij.psi.util.parents import com.intellij.util.PlatformIcons import com.intellij.util.text.CharArrayUtil +import com.llamalad7.mixinextras.expression.impl.flow.ArrayCreationInfo import com.llamalad7.mixinextras.expression.impl.flow.ComplexFlowValue import com.llamalad7.mixinextras.expression.impl.flow.DummyFlowValue import com.llamalad7.mixinextras.expression.impl.flow.FlowValue @@ -333,7 +334,7 @@ object MEExpressionCompletionUtil { val flattenedInstructions = mutableSetOf() for (flow in matchingFlows) { getInstructionsInFlowTree( - findFlowTreeRoot(flow), + flow, flattenedInstructions, subExpr !is MEExpressionStatement && subExpr !is MEParenthesizedExpression ) @@ -365,7 +366,7 @@ object MEExpressionCompletionUtil { val cursorInstructions = mutableSetOf() for (flow in matchingFlows) { - getInstructionsInFlowTree(findFlowTreeRoot(flow), cursorInstructions, false) + getInstructionsInFlowTree(flow, cursorInstructions, false) } if (DEBUG_COMPLETION) { @@ -536,34 +537,42 @@ object MEExpressionCompletionUtil { } } - private fun findFlowTreeRoot(flow: FlowValue): FlowValue { - val insn = flow.insnOrNull ?: return flow - return if (insn.opcode == Opcodes.NEW) { - flow.next.firstOrNull { + private fun getFlowInputs(flow: FlowValue): List { + val arrayCreationInfo = flow.getDecoration(Decorations.ARRAY_CREATION_INFO) + if (arrayCreationInfo != null) { + return arrayCreationInfo.values + } + + var rootFlow = flow + val insn = flow.insnOrNull ?: return emptyList() + if (insn.opcode == Opcodes.NEW) { + rootFlow = flow.next.firstOrNull { val nextInsn = it.left.insnOrNull ?: return@firstOrNull false it.right == 0 && nextInsn.opcode == Opcodes.INVOKESPECIAL && - (nextInsn as MethodInsnNode).name == "" - }?.left ?: flow - } else { - flow + (nextInsn as MethodInsnNode).name == "" + }?.left ?: rootFlow } + + return (0 until rootFlow.inputCount()).map(rootFlow::getInput) } private fun getInstructionsInFlowTree( flow: FlowValue, outInstructions: MutableSet, - strict: Boolean + strict: Boolean, ) { if (flow is DummyFlowValue || flow is ComplexFlowValue) { return } if (!strict) { - outInstructions += flow.insn + if (!outInstructions.add(flow.insn)) { + return + } } - for (i in 0 until flow.inputCount()) { - getInstructionsInFlowTree(flow.getInput(i), outInstructions, false) + for (input in getFlowInputs(flow)) { + getInstructionsInFlowTree(input, outInstructions, false) } } diff --git a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt index d1d0a5d77..413a56887 100644 --- a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt +++ b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt @@ -410,6 +410,46 @@ class MEExpressionCompletionTest : BaseMixinTest() { ) } + @Test + @DisplayName("Array Creation Completion Test") + fun arrayCreationCompletionTest() { + doBeforeAfterTest( + "String", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("new ") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent(), + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "String", type = String.class) + @Expression("new String[]") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent() + ) + } + @Test @DisplayName("LHS Of Complete Assignment Test") fun lhsOfCompleteAssignmentTest() { @@ -481,4 +521,54 @@ class MEExpressionCompletionTest : BaseMixinTest() { """.trimIndent() ) } + + @Test + @DisplayName("Array Length Test") + fun arrayLengthTest() { + assertLookupAppears( + "one", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "String", type = String.class) + @Expression("new String[]") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent() + ) + } + + @Test + @DisplayName("Array Element Test") + fun arrayElementTest() { + assertLookupAppears( + "local2", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Definition(id = "String", type = String.class) + @Expression("new String[]{?, }") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent() + ) + } } From 3380ca135a89a0df5c4f9fa5f79a3e82d657b321 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 26 Mar 2024 12:03:35 +0000 Subject: [PATCH 059/100] Fix ktlint --- .../platform/mixin/expression/MEExpressionCompletionUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index a9795c7b1..2819c5ad4 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -550,7 +550,7 @@ object MEExpressionCompletionUtil { val nextInsn = it.left.insnOrNull ?: return@firstOrNull false it.right == 0 && nextInsn.opcode == Opcodes.INVOKESPECIAL && - (nextInsn as MethodInsnNode).name == "" + (nextInsn as MethodInsnNode).name == "" }?.left ?: rootFlow } From c5d31c0b6fd30c5950653aa7e81e8f69a638bba3 Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 29 Mar 2024 10:48:02 +0000 Subject: [PATCH 060/100] Relax uniqueness in ME expression completions --- build.gradle.kts | 2 +- .../expression/MEDefinitionFoldingBuilder.kt | 4 +- .../expression/MEExpressionCompletionUtil.kt | 83 ++++++++++--------- 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c361f1c1f..ce62c869d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:00d6f4e")!!) + testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:d999081")!!) implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt b/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt index dad25fc20..816804cff 100644 --- a/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt +++ b/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt @@ -84,7 +84,9 @@ class MEDefinitionFoldingBuilder : CustomFoldingBuilder() { definitionList.first().parameterList.firstChild.nextSibling.textRange.startOffset, PsiTreeUtil.getDeepestVisibleLast(definitionList.last())!!.textRange.startOffset, ) - descriptors.add(FoldingDescriptor(list.node, range)) + if (!range.isEmpty) { + descriptors.add(FoldingDescriptor(list.node, range)) + } } } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 2819c5ad4..aaef444f5 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -420,7 +420,7 @@ object MEExpressionCompletionUtil { // In the case of multiple instructions producing the same lookup, attempt to show only the "best" lookup. // For example, if a local variable is only sometimes able to be targeted using implicit ordinals in this // expression, prefer specifying the ordinal. - return eliminableResults.groupBy { it.lookupElement.lookupString }.values.map { it.max().lookupElement } + return eliminableResults.groupBy { it.uniquenessKey }.values.map { it.max().lookupElement } } private fun replaceUnknownNamesWithWildcards( @@ -592,9 +592,10 @@ object MEExpressionCompletionUtil { is Type -> { if (canCompleteTypes && cst.isAccessibleFrom(mixinClass)) { return listOf( - object : TailTypeDecorator(createTypeLookup(cst)) { - override fun computeTailType(context: InsertionContext?) = DOT_CLASS_TAIL - }.createEliminable() + createTypeLookup(cst) + .withTailText(".class") + .withTail(DOT_CLASS_TAIL) + .createEliminable("class ${insn.cst}") ) } } @@ -631,7 +632,7 @@ object MEExpressionCompletionUtil { if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.PUTSTATIC) { lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) } - return listOf(lookup.createEliminable()) + return listOf(lookup.createEliminable("field ${insn.owner}.${insn.name}:${insn.desc}")) } } is MethodInsnNode -> { @@ -650,10 +651,8 @@ object MEExpressionCompletionUtil { lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) } return listOf( - object : TailTypeDecorator(lookup) { - override fun computeTailType(context: InsertionContext?) = - ParenthesesTailType(!insn.desc.startsWith("()")) - }.createEliminable() + lookup.withTail(ParenthesesTailType(!insn.desc.startsWith("()"))) + .createEliminable("invoke ${insn.owner}.${insn.name}${insn.desc}") ) } } @@ -664,13 +663,13 @@ object MEExpressionCompletionUtil { when (insn.opcode) { Opcodes.ANEWARRAY -> { return listOf( - object : TailTypeDecorator(lookup) { - override fun computeTailType(context: InsertionContext?) = - BracketsTailType( - 1, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, - ) - }.createEliminable() + lookup.withTail( + BracketsTailType( + 1, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + ) + ) + .createEliminable("new [${insn.desc}") ) } Opcodes.NEW -> { @@ -681,15 +680,11 @@ object MEExpressionCompletionUtil { (nextInsn as MethodInsnNode).name == "" }?.left?.insn as MethodInsnNode? return listOf( - object : TailTypeDecorator(lookup) { - override fun computeTailType(context: InsertionContext?) = - ParenthesesTailType( - initCall?.desc?.startsWith("()") == false - ) - }.createEliminable() + lookup.withTail(ParenthesesTailType(initCall?.desc?.startsWith("()") == false)) + .createEliminable("new ${insn.desc}${initCall?.desc}") ) } - else -> return listOf(lookup.createEliminable()) + else -> return listOf(lookup.createEliminable("type ${insn.desc}")) } } } @@ -708,15 +703,15 @@ object MEExpressionCompletionUtil { else -> "unknown" // wtf? } return listOf( - object : TailTypeDecorator( - LookupElementBuilder.create(type).withIcon(PlatformIcons.CLASS_ICON) - ) { - override fun computeTailType(context: InsertionContext?) = + LookupElementBuilder.create(type) + .withIcon(PlatformIcons.CLASS_ICON) + .withTail( BracketsTailType( 1, flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, ) - }.createEliminable() + ) + .createEliminable("new $type[]") ) } } @@ -725,15 +720,14 @@ object MEExpressionCompletionUtil { if (canCompleteTypes) { val type = Type.getType(insn.desc) return listOf( - object : TailTypeDecorator( - createTypeLookup(type.elementType) - ) { - override fun computeTailType(context: InsertionContext?) = + createTypeLookup(type.elementType) + .withTail( BracketsTailType( type.dimensions, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true ) - }.createEliminable() + ) + .createEliminable("new ${insn.desc}") ) } } @@ -745,7 +739,7 @@ object MEExpressionCompletionUtil { LookupElementBuilder.create("length") .withIcon(PlatformIcons.FIELD_ICON) .withTypeText("int") - .createEliminable() + .createEliminable("arraylength") ) } } @@ -805,7 +799,7 @@ object MEExpressionCompletionUtil { } } - private fun createTypeLookup(type: Type): LookupElement { + private fun createTypeLookup(type: Type): LookupElementBuilder { val definitionId = type.typeNameToInsert() val lookupElement = LookupElementBuilder.create(definitionId) @@ -869,18 +863,19 @@ object MEExpressionCompletionUtil { val localsOfMyType = localsHere.filter { it.desc == localVariable.desc } val ordinal = localsOfMyType.indexOf(localVariable) val isImplicit = localsOfMyType.size == 1 - LookupElementBuilder.create(localVariable.name.toValidIdentifier()) + val localName = localVariable.name.toValidIdentifier() + LookupElementBuilder.create(localName) .withIcon(PlatformIcons.VARIABLE_ICON) .withTypeText(localPsiType.presentableText) .withLocalDefinition( - localVariable.name.toValidIdentifier(), + localName, Type.getType(localVariable.desc), ordinal, isArgsOnly, isImplicit, mixinClass, ) - .createEliminable(if (isImplicit) -1 else 0) + .createEliminable("local $localName", if (isImplicit) -1 else 0) } } @@ -896,10 +891,14 @@ object MEExpressionCompletionUtil { .withIcon(PlatformIcons.VARIABLE_ICON) .withTypeText(localType.presentableName()) .withLocalDefinition(localName, localType, ordinal, isArgsOnly, isImplicit, mixinClass) - .createEliminable(if (isImplicit) -1 else 0) + .createEliminable("local $localName", if (isImplicit) -1 else 0) ) } + private fun LookupElement.withTail(tailType: TailType?) = object : TailTypeDecorator(this) { + override fun computeTailType(context: InsertionContext?) = tailType + } + private fun LookupElementBuilder.withDefinition(id: String, at: String) = withDefinition(id, at) { _, _ -> } private fun LookupElementBuilder.withDefinitionAndFoldTarget(id: String, at: String) = @@ -1143,9 +1142,11 @@ object MEExpressionCompletionUtil { return variants } - private fun LookupElement.createEliminable(priority: Int = 0) = EliminableLookup(this, priority) + private fun LookupElement.createEliminable(uniquenessKey: String, priority: Int = 0) = + EliminableLookup(uniquenessKey, this, priority) private class EliminableLookup( + val uniquenessKey: String, val lookupElement: LookupElement, private val priority: Int ) : Comparable { From 0517103b4d4f24be69ef093a109afee28e302521 Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 29 Mar 2024 14:32:43 +0000 Subject: [PATCH 061/100] Initial switch from @Definition.at to field and method --- .../expression/MEExpressionCompletionUtil.kt | 31 ++++---- .../mixin/expression/MEExpressionMatchUtil.kt | 29 ++++--- .../mixin/expression/MESourceMatchContext.kt | 20 +++-- .../expression/psi/mixins/MENameMixin.kt | 3 - .../impl/MEMemberAccessExpressionImplMixin.kt | 4 +- .../impl/MEMethodCallExpressionImplMixin.kt | 14 +++- .../mixins/impl/MENameExpressionImplMixin.kt | 41 +++++++++- .../psi/mixins/impl/MENameImplMixin.kt | 45 ----------- .../MEStaticMethodCallExpressionImplMixin.kt | 14 +++- .../impl/MESuperCallExpressionImplMixin.kt | 12 ++- .../mixinextras/ExpressionInjectionPoint.kt | 41 +++++----- .../mixin/reference/MixinSelectors.kt | 67 +--------------- src/main/kotlin/util/MemberReference.kt | 77 ++++++++++++++++--- 13 files changed, 205 insertions(+), 193 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index aaef444f5..20cbeedb5 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -622,13 +622,12 @@ object MEExpressionCompletionUtil { ) is FieldInsnNode -> { if (canCompleteExprs) { - val at = "at = @${MixinConstants.Annotations.AT}(value = \"FIELD\"," + - " target = \"L${insn.owner};${insn.name}:${insn.desc}\")" + val definitionValue = "field = \"L${insn.owner};${insn.name}:${insn.desc}\"" var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) .withIcon(PlatformIcons.FIELD_ICON) .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) .withTypeText(Type.getType(insn.desc).presentableName()) - .withDefinitionAndFoldTarget(insn.name.toValidIdentifier(), at) + .withDefinitionAndFold(insn.name.toValidIdentifier(), "field", definitionValue) if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.PUTSTATIC) { lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) } @@ -637,8 +636,7 @@ object MEExpressionCompletionUtil { } is MethodInsnNode -> { if (canCompleteExprs) { - val at = "at = @${MixinConstants.Annotations.AT}(value = \"INVOKE\"," + - " target = \"L${insn.owner};${insn.name}${insn.desc}\")" + val definitionValue = "method = \"L${insn.owner};${insn.name}${insn.desc}\"" var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) .withIcon(PlatformIcons.METHOD_ICON) .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) @@ -646,7 +644,7 @@ object MEExpressionCompletionUtil { "(" + Type.getArgumentTypes(insn.desc).joinToString { it.presentableName() } + ")" ) .withTypeText(Type.getReturnType(insn.desc).presentableName()) - .withDefinitionAndFoldTarget(insn.name.toValidIdentifier(), at) + .withDefinitionAndFold(insn.name.toValidIdentifier(), "method", definitionValue) if (insn.opcode == Opcodes.INVOKESTATIC) { lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) } @@ -899,10 +897,11 @@ object MEExpressionCompletionUtil { override fun computeTailType(context: InsertionContext?) = tailType } - private fun LookupElementBuilder.withDefinition(id: String, at: String) = withDefinition(id, at) { _, _ -> } + private fun LookupElementBuilder.withDefinition(id: String, definitionValue: String) = + withDefinition(id, definitionValue) { _, _ -> } - private fun LookupElementBuilder.withDefinitionAndFoldTarget(id: String, at: String) = - withDefinition(id, at) { context, annotation -> + private fun LookupElementBuilder.withDefinitionAndFold(id: String, foldAttribute: String, definitionValue: String) = + withDefinition(id, definitionValue) { context, annotation -> val hostEditor = InjectedLanguageEditorUtil.getTopLevelEditor(context.editor) CodeFoldingManager.getInstance(context.project).updateFoldRegions(hostEditor) val foldingModel = hostEditor.foldingModel @@ -914,7 +913,7 @@ object MEExpressionCompletionUtil { } val nameValuePair = annotation.findElementAt(foldRegion.startOffset - annotationRange.startOffset) ?.findContainingNameValuePair() ?: continue - if (nameValuePair.name == "target" && + if (nameValuePair.name == foldAttribute && nameValuePair.parentOfType()?.hasQualifiedName(MixinConstants.Annotations.AT) == true ) { regionsToFold += foldRegion @@ -939,7 +938,7 @@ object MEExpressionCompletionUtil { val isTypeAccessible = type.isAccessibleFrom(mixinClass) val isImplicit = canBeImplicit && isTypeAccessible - val definitionLocal = buildString { + val definitionValue = buildString { append("local = @${MixinConstants.MixinExtras.LOCAL}(") if (isTypeAccessible) { append("type = ${type.className}.class, ") @@ -959,7 +958,7 @@ object MEExpressionCompletionUtil { append(")") } - return withDefinition(name, definitionLocal) { context, annotation -> + return withDefinition(name, definitionValue) { context, annotation -> if (isImplicit) { return@withDefinition } @@ -1025,14 +1024,14 @@ object MEExpressionCompletionUtil { private inline fun LookupElementBuilder.withDefinition( id: String, - at: String, + definitionValue: String, crossinline andThen: (InsertionContext, PsiAnnotation) -> Unit ) = withInsertHandler { context, _ -> context.laterRunnable = Runnable { context.commitDocument() CommandProcessor.getInstance().runUndoTransparentAction { runWriteAction { - val annotation = addDefinition(context, id, at) + val annotation = addDefinition(context, id, definitionValue) if (annotation != null) { andThen(context, annotation) } @@ -1041,7 +1040,7 @@ object MEExpressionCompletionUtil { } } - private fun addDefinition(context: InsertionContext, id: String, at: String): PsiAnnotation? { + private fun addDefinition(context: InsertionContext, id: String, definitionValue: String): PsiAnnotation? { val contextElement = context.file.findElementAt(context.startOffset) ?: return null val injectionHost = contextElement.findMultiInjectionHost() ?: return null val expressionAnnotation = injectionHost.parentOfType() ?: return null @@ -1061,7 +1060,7 @@ object MEExpressionCompletionUtil { // create and add the new @Definition annotation var newAnnotation = JavaPsiFacade.getElementFactory(context.project).createAnnotationFromText( - "@${MixinConstants.MixinExtras.DEFINITION}(id = \"$id\", $at)", + "@${MixinConstants.MixinExtras.DEFINITION}(id = \"$id\", $definitionValue)", modifierList, ) var anchor = modifierList.annotations.lastOrNull { it.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 977e3725a..b1d240b8a 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -20,11 +20,12 @@ package com.demonwav.mcdev.platform.mixin.expression -import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.util.LocalInfo import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.platform.mixin.util.cached +import com.demonwav.mcdev.util.MemberReference +import com.demonwav.mcdev.util.computeStringArray import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.findAnnotations @@ -35,7 +36,6 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project -import com.intellij.openapi.util.RecursionManager import com.intellij.psi.PsiModifierList import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression @@ -44,12 +44,13 @@ import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter import com.llamalad7.mixinextras.expression.impl.flow.FlowValue import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool -import java.util.Collections import java.util.IdentityHashMap import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.VarInsnNode import org.objectweb.asm.tree.analysis.Analyzer @@ -150,14 +151,20 @@ object MEExpressionMatchUtil { val definitionId = annotation.findDeclaredAttributeValue("id")?.constantStringValue ?: "" - val ats = annotation.findDeclaredAttributeValue("at")?.findAnnotations() ?: emptyList() - for (at in ats) { - val matchingInsns = RecursionManager.doPreventingRecursion(at, true) { - AtResolver(at, targetClass, targetMethod) - .resolveInstructions() - .mapTo(Collections.newSetFromMap(IdentityHashMap())) { it.insn } - } ?: emptySet() - pool.addMember(definitionId) { it in matchingInsns } + val fields = annotation.findDeclaredAttributeValue("field")?.computeStringArray() ?: emptyList() + for (field in fields) { + val fieldRef = MemberReference.parse(field) ?: continue + pool.addMember(definitionId) { + it is FieldInsnNode && fieldRef.matchField(it.owner, it.name, it.desc) + } + } + + val methods = annotation.findDeclaredAttributeValue("method")?.computeStringArray() ?: emptyList() + for (method in methods) { + val methodRef = MemberReference.parse(method) ?: continue + pool.addMember(definitionId) { + it is MethodInsnNode && methodRef.matchMethod(it.owner, it.name, it.desc) + } } val types = annotation.findDeclaredAttributeValue("type")?.resolveTypeArray() ?: emptyList() diff --git a/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt b/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt index 857f87b50..8a6312d24 100644 --- a/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt +++ b/src/main/kotlin/platform/mixin/expression/MESourceMatchContext.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.platform.mixin.util.LocalInfo +import com.demonwav.mcdev.util.MemberReference import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement @@ -31,7 +32,8 @@ class MESourceMatchContext(val project: Project) { val captures: List get() = capturesInternal private val types = mutableMapOf>() - private val targetedElements = mutableMapOf>() + private val fields = mutableMapOf>() + private val methods = mutableMapOf>() private val localInfos = mutableMapOf>() init { @@ -52,19 +54,25 @@ class MESourceMatchContext(val project: Project) { fun getTypes(key: String): List = types[key] ?: emptyList() fun addType(key: String, desc: String) { - types.computeIfAbsent(key) { mutableListOf() } += desc + types.getOrPut(key, ::mutableListOf) += desc } - fun getTargetedElements(key: String): List = targetedElements[key] ?: emptyList() + fun getFields(key: String): List = fields[key] ?: emptyList() - fun addTargetedElement(key: String, element: PsiElement) { - targetedElements.computeIfAbsent(key) { mutableListOf() } += element + fun addField(key: String, field: MemberReference) { + fields.getOrPut(key, ::mutableListOf) += field + } + + fun getMethods(key: String): List = methods[key] ?: emptyList() + + fun addMethod(key: String, method: MemberReference) { + methods.getOrPut(key, ::mutableListOf) += method } fun getLocalInfos(key: String): List = localInfos[key] ?: emptyList() fun addLocalInfo(key: String, localInfo: LocalInfo) { - localInfos.computeIfAbsent(key) { mutableListOf() } += localInfo + localInfos.getOrPut(key, ::mutableListOf) += localInfo } fun reset() { diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt index d6e68c99f..5e344d9d2 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/MENameMixin.kt @@ -20,12 +20,9 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins -import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.intellij.psi.PsiElement interface MENameMixin : PsiElement { val isWildcard: Boolean val identifierElement: PsiElement? - - fun matchesJavaExpr(javaExpr: PsiElement, context: MESourceMatchContext): Boolean } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt index dd3300c11..4c4c4a11e 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMemberAccessExpressionImplMixin.kt @@ -24,6 +24,7 @@ import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.QualifiedMember import com.intellij.lang.ASTNode import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiElement @@ -59,7 +60,8 @@ abstract class MEMemberAccessExpressionImplMixin(node: ASTNode) : MEExpressionIm } } - return memberName.matchesJavaExpr(java, context) + val qualifier = QualifiedMember.resolveQualifier(java) ?: resolved.containingClass ?: return false + return context.getFields(memberName.text).any { it.matchField(resolved, qualifier) } } override fun getInputExprs() = listOf(receiverExpr) diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt index 0541cf8e3..2f397998e 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEMethodCallExpressionImplMixin.kt @@ -25,6 +25,7 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArguments import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.QualifiedMember import com.intellij.lang.ASTNode import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiElement @@ -43,15 +44,20 @@ abstract class MEMethodCallExpressionImplMixin(node: ASTNode) : MEExpressionImpl return false } - if (!memberName.matchesJavaExpr(java, context)) { - return false - } - val method = java.resolveMethod() ?: return false if (method.hasModifierProperty(PsiModifier.STATIC)) { return false } + if (!memberName.isWildcard) { + val methodId = memberName.text + val qualifier = + QualifiedMember.resolveQualifier(java.methodExpression) ?: method.containingClass ?: return false + if (context.getMethods(methodId).none { it.matchMethod(method, qualifier) }) { + return false + } + } + val javaReceiver = PsiUtil.skipParenthesizedExprDown(java.methodExpression.qualifierExpression) ?: JavaPsiFacade.getElementFactory(context.project).createExpressionFromText("this", null) context.fakeElementScope(java.methodExpression.qualifierExpression == null, java.methodExpression) { diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt index 674f511cb..decc485d4 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameExpressionImplMixin.kt @@ -24,12 +24,51 @@ import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.QualifiedMember +import com.demonwav.mcdev.platform.mixin.util.LocalVariables import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement +import com.intellij.psi.PsiField +import com.intellij.psi.PsiReferenceExpression +import com.intellij.psi.PsiVariable +import com.intellij.psi.util.PsiUtil abstract class MENameExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) { override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { - return MEName.matchesJavaExpr(java, context) + if (MEName.isWildcard) { + return true + } + + if (java !is PsiReferenceExpression) { + return false + } + val variable = java.resolve() as? PsiVariable ?: return false + + val name = MEName.text + + // match against fields + if (variable is PsiField) { + val qualifier = QualifiedMember.resolveQualifier(java) ?: variable.containingClass ?: return false + return context.getFields(name).any { it.matchField(variable, qualifier) } + } + + // match against local variables + val sourceArgs by lazy { + LocalVariables.guessLocalsAt(java, true, !PsiUtil.isAccessedForWriting(java)) + } + val sourceVariables by lazy { + LocalVariables.guessLocalsAt(java, false, !PsiUtil.isAccessedForWriting(java)) + } + for (localInfo in context.getLocalInfos(name)) { + val sourceLocals = if (localInfo.argsOnly) sourceArgs else sourceVariables + for (local in localInfo.matchSourceLocals(sourceLocals)) { + if (local.variable == variable) { + return true + } + } + } + + return false } override fun getInputExprs() = emptyList() diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt index 2dc41f9ab..50b02d3c7 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MENameImplMixin.kt @@ -20,63 +20,18 @@ package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl -import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MENameMixin import com.demonwav.mcdev.platform.mixin.expression.reference.MEDefinitionReference -import com.demonwav.mcdev.platform.mixin.util.LocalVariables import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiField import com.intellij.psi.PsiReference -import com.intellij.psi.PsiReferenceExpression -import com.intellij.psi.PsiVariable -import com.intellij.psi.util.PsiUtil abstract class MENameImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), MENameMixin { override val isWildcard get() = node.firstChildNode.elementType == MEExpressionTypes.TOKEN_WILDCARD override val identifierElement get() = if (isWildcard) null else firstChild - override fun matchesJavaExpr(javaExpr: PsiElement, context: MESourceMatchContext): Boolean { - if (isWildcard) { - return true - } - - // match against elements targeted with @At - val name = text - if (javaExpr in context.getTargetedElements(name)) { - return true - } - - // match against local variables - if (javaExpr !is PsiReferenceExpression) { - return false - } - val variable = javaExpr.resolve() as? PsiVariable ?: return false - if (variable is PsiField) { - return false - } - - val sourceArgs by lazy { - LocalVariables.guessLocalsAt(javaExpr, true, !PsiUtil.isAccessedForWriting(javaExpr)) - } - val sourceVariables by lazy { - LocalVariables.guessLocalsAt(javaExpr, false, !PsiUtil.isAccessedForWriting(javaExpr)) - } - for (localInfo in context.getLocalInfos(name)) { - val sourceLocals = if (localInfo.argsOnly) sourceArgs else sourceVariables - for (local in localInfo.matchSourceLocals(sourceLocals)) { - if (local.variable == variable) { - return true - } - } - } - - return false - } - override fun getReference(): PsiReference? { if (isWildcard) { return null diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt index ab068e3a9..7a578b242 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEStaticMethodCallExpressionImplMixin.kt @@ -24,6 +24,7 @@ import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArguments import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.QualifiedMember import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethodCallExpression @@ -35,15 +36,20 @@ abstract class MEStaticMethodCallExpressionImplMixin(node: ASTNode) : MEExpressi return false } - if (!memberName.matchesJavaExpr(java, context)) { - return false - } - val method = java.resolveMethod() ?: return false if (!method.hasModifierProperty(PsiModifier.STATIC)) { return false } + if (!memberName.isWildcard) { + val methodId = memberName.text + val qualifier = + QualifiedMember.resolveQualifier(java.methodExpression) ?: method.containingClass ?: return false + if (context.getMethods(methodId).none { it.matchMethod(method, qualifier) }) { + return false + } + } + return arguments?.matchesJava(java.argumentList, context) == true } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt index c389c8418..2dee0b9c7 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MESuperCallExpressionImplMixin.kt @@ -24,6 +24,7 @@ import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArguments import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEExpressionImpl +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.QualifiedMember import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethodCallExpression @@ -38,8 +39,15 @@ abstract class MESuperCallExpressionImplMixin(node: ASTNode) : MEExpressionImpl( return false } - if (memberName?.matchesJavaExpr(java, context) != true) { - return false + val memberName = this.memberName ?: return false + if (!memberName.isWildcard) { + val method = java.resolveMethod() ?: return false + val methodId = memberName.text + val qualifier = + QualifiedMember.resolveQualifier(java.methodExpression) ?: method.containingClass ?: return false + if (context.getMethods(methodId).none { it.matchMethod(method, qualifier) }) { + return false + } } return arguments?.matchesJava(java.argumentList, context) == true diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index 5abfd4d67..a7454e293 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -35,6 +35,8 @@ import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.NavigationVisit import com.demonwav.mcdev.platform.mixin.reference.MixinSelector import com.demonwav.mcdev.platform.mixin.util.LocalInfo import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.util.MemberReference +import com.demonwav.mcdev.util.computeStringArray import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.findAnnotations @@ -48,7 +50,6 @@ import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.lang.injection.InjectedLanguageManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project -import com.intellij.openapi.util.RecursionManager import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiClass @@ -64,8 +65,6 @@ import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode -private typealias SourceMatchContextFactory = (ClassNode, MethodNode) -> MESourceMatchContext - class ExpressionInjectionPoint : InjectionPoint() { override fun onCompleted(editor: Editor, reference: PsiLiteral) { val modifierList = reference.findContainingModifierList() ?: return @@ -107,15 +106,15 @@ class ExpressionInjectionPoint : InjectionPoint() { val parsedExprs = parseExpressions(project, modifierList, atId) parsedExprs.ifEmpty { return null } - val sourceMatchContextFactory = createSourceMatchContextFactory(project, modifierList) + val sourceMatchContext = createSourceMatchContext(project, modifierList) - return MyNavigationVisitor(parsedExprs.map { it.second }, sourceMatchContextFactory) + return MyNavigationVisitor(parsedExprs.map { it.second }, sourceMatchContext) } - private fun createSourceMatchContextFactory( + private fun createSourceMatchContext( project: Project, modifierList: PsiModifierList - ): SourceMatchContextFactory = { targetClass, targetMethod -> + ): MESourceMatchContext { val matchContext = MESourceMatchContext(project) for (annotation in modifierList.annotations) { @@ -125,14 +124,16 @@ class ExpressionInjectionPoint : InjectionPoint() { val definitionId = annotation.findDeclaredAttributeValue("id")?.constantStringValue ?: "" - val ats = annotation.findDeclaredAttributeValue("at")?.findAnnotations() ?: emptyList() - for (at in ats) { - val matches = RecursionManager.doPreventingRecursion(at, true) { - AtResolver(at, targetClass, targetMethod).resolveNavigationTargets() - } ?: continue - for (target in matches) { - matchContext.addTargetedElement(definitionId, target) - } + val fields = annotation.findDeclaredAttributeValue("field")?.computeStringArray() ?: emptyList() + for (field in fields) { + val fieldRef = MemberReference.parse(field) ?: continue + matchContext.addField(definitionId, fieldRef) + } + + val methods = annotation.findDeclaredAttributeValue("method")?.computeStringArray() ?: emptyList() + for (method in methods) { + val methodRef = MemberReference.parse(method) ?: continue + matchContext.addMethod(definitionId, methodRef) } val types = annotation.findDeclaredAttributeValue("type")?.resolveTypeArray() ?: emptyList() @@ -148,7 +149,7 @@ class ExpressionInjectionPoint : InjectionPoint() { } } - matchContext + return matchContext } override fun doCreateCollectVisitor( @@ -256,14 +257,8 @@ class ExpressionInjectionPoint : InjectionPoint() { private class MyNavigationVisitor( private val statements: List, - private val matchContextFactory: SourceMatchContextFactory + private val matchContext: MESourceMatchContext ) : NavigationVisitor() { - private lateinit var matchContext: MESourceMatchContext - - override fun configureBytecodeTarget(classNode: ClassNode, methodNode: MethodNode) { - matchContext = matchContextFactory(classNode, methodNode) - } - override fun visitElement(element: PsiElement) { for (statement in statements) { if (statement.matchesJava(element, matchContext)) { diff --git a/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt b/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt index 59ab0d8a0..18e7c4b1b 100644 --- a/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt +++ b/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt @@ -48,7 +48,6 @@ import com.demonwav.mcdev.util.resolveTypeArray import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.intellij.openapi.util.RecursionManager -import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.CommonClassNames import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation @@ -222,71 +221,7 @@ fun MemberReference.toMixinString(): String { } class MixinMemberParser : MixinSelectorParser { - override fun parse(value: String, context: PsiElement): MixinSelector? { - val reference = value.replace(" ", "") - val owner: String? - - var pos = reference.lastIndexOf('.') - if (pos != -1) { - // Everything before the dot is the qualifier/owner - owner = reference.substring(0, pos).replace('/', '.') - } else { - pos = reference.indexOf(';') - if (pos != -1 && reference.startsWith('L')) { - val internalOwner = reference.substring(1, pos) - if (!StringUtil.isJavaIdentifier(internalOwner.replace('/', '_'))) { - // Invalid: Qualifier should only contain slashes - return null - } - - owner = internalOwner.replace('/', '.') - - // if owner is all there is to the selector, match anything with the owner - if (pos == reference.length - 1) { - return MemberReference("", null, owner, matchAllNames = true, matchAllDescs = true) - } - } else { - // No owner/qualifier specified - pos = -1 - owner = null - } - } - - val descriptor: String? - val name: String - val matchAllNames = reference.getOrNull(pos + 1) == '*' - val matchAllDescs: Boolean - - // Find descriptor separator - val methodDescPos = reference.indexOf('(', pos + 1) - if (methodDescPos != -1) { - // Method descriptor - descriptor = reference.substring(methodDescPos) - name = reference.substring(pos + 1, methodDescPos) - matchAllDescs = false - } else { - val fieldDescPos = reference.indexOf(':', pos + 1) - if (fieldDescPos != -1) { - descriptor = reference.substring(fieldDescPos + 1) - name = reference.substring(pos + 1, fieldDescPos) - matchAllDescs = false - } else { - descriptor = null - matchAllDescs = reference.endsWith('*') - name = if (matchAllDescs) { - reference.substring(pos + 1, reference.lastIndex) - } else { - reference.substring(pos + 1) - } - } - } - - if (!matchAllNames && !StringUtil.isJavaIdentifier(name) && name != "" && name != "") { - return null - } - - return MemberReference(if (matchAllNames) "*" else name, descriptor, owner, matchAllNames, matchAllDescs) - } + override fun parse(value: String, context: PsiElement) = MemberReference.parse(value) } // Regex reference diff --git a/src/main/kotlin/util/MemberReference.kt b/src/main/kotlin/util/MemberReference.kt index 5b5921e73..1969957e7 100644 --- a/src/main/kotlin/util/MemberReference.kt +++ b/src/main/kotlin/util/MemberReference.kt @@ -21,14 +21,11 @@ package com.demonwav.mcdev.util import com.demonwav.mcdev.platform.mixin.reference.MixinSelector -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement +import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.PsiClass import com.intellij.psi.PsiField import com.intellij.psi.PsiMethod import java.io.Serializable -import java.lang.reflect.Type /** * Represents a reference to a class member (a method or a field). It may @@ -88,13 +85,71 @@ data class MemberReference( (this.descriptor == null || this.descriptor == desc) } - object Deserializer : JsonDeserializer { - override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): MemberReference { - val ref = json.asString - val className = ref.substringBefore('#') - val methodName = ref.substring(className.length + 1, ref.indexOf("(")) - val methodDesc = ref.substring(className.length + methodName.length + 1) - return MemberReference(methodName, methodDesc, className) + companion object { + fun parse(value: String): MemberReference? { + val reference = value.replace(" ", "") + val owner: String? + + var pos = reference.lastIndexOf('.') + if (pos != -1) { + // Everything before the dot is the qualifier/owner + owner = reference.substring(0, pos).replace('/', '.') + } else { + pos = reference.indexOf(';') + if (pos != -1 && reference.startsWith('L')) { + val internalOwner = reference.substring(1, pos) + if (!StringUtil.isJavaIdentifier(internalOwner.replace('/', '_'))) { + // Invalid: Qualifier should only contain slashes + return null + } + + owner = internalOwner.replace('/', '.') + + // if owner is all there is to the selector, match anything with the owner + if (pos == reference.length - 1) { + return MemberReference("", null, owner, matchAllNames = true, matchAllDescs = true) + } + } else { + // No owner/qualifier specified + pos = -1 + owner = null + } + } + + val descriptor: String? + val name: String + val matchAllNames = reference.getOrNull(pos + 1) == '*' + val matchAllDescs: Boolean + + // Find descriptor separator + val methodDescPos = reference.indexOf('(', pos + 1) + if (methodDescPos != -1) { + // Method descriptor + descriptor = reference.substring(methodDescPos) + name = reference.substring(pos + 1, methodDescPos) + matchAllDescs = false + } else { + val fieldDescPos = reference.indexOf(':', pos + 1) + if (fieldDescPos != -1) { + descriptor = reference.substring(fieldDescPos + 1) + name = reference.substring(pos + 1, fieldDescPos) + matchAllDescs = false + } else { + descriptor = null + matchAllDescs = reference.endsWith('*') + name = if (matchAllDescs) { + reference.substring(pos + 1, reference.lastIndex) + } else { + reference.substring(pos + 1) + } + } + } + + if (!matchAllNames && !StringUtil.isJavaIdentifier(name) && name != "" && name != "") { + return null + } + + return MemberReference(if (matchAllNames) "*" else name, descriptor, owner, matchAllNames, matchAllDescs) } } } From 56e0e6efd401a4f70f1590bef291262d986ef8fa Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 29 Mar 2024 14:47:23 +0000 Subject: [PATCH 062/100] Fix tests --- .../mixin/expression/MEExpressionCompletionTest.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt index 413a56887..6a6642809 100644 --- a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt +++ b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt @@ -199,7 +199,7 @@ class MEExpressionCompletionTest : BaseMixinTest() { @Mixin(MEExpressionTestData.class) class MEExpressionCompletionTest { - @Definition(id = "acceptInaccessibleType", at = @At(value = "INVOKE", target = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;acceptInaccessibleType(Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}InaccessibleType;)V")) + @Definition(id = "acceptInaccessibleType", method = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;acceptInaccessibleType(Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}InaccessibleType;)V") @Expression("acceptInaccessibleType()") @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) } @@ -217,7 +217,7 @@ class MEExpressionCompletionTest : BaseMixinTest() { @Mixin(MEExpressionTestData.class) class MEExpressionCompletionTest { - @Definition(id = "acceptInaccessibleType", at = @At(value = "INVOKE", target = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;acceptInaccessibleType(Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}InaccessibleType;)V")) + @Definition(id = "acceptInaccessibleType", method = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;acceptInaccessibleType(Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}InaccessibleType;)V") @Definition(id = "varOfInaccessibleType", local = @Local(ordinal = 0)) @Expression("acceptInaccessibleType(varOfInaccessibleType)") @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) @@ -258,7 +258,7 @@ class MEExpressionCompletionTest : BaseMixinTest() { @Mixin(MEExpressionTestData.class) class MEExpressionCompletionTest { - @Definition(id = "out", at = @At(value = "FIELD", target = "Ljava/lang/System;out:Ljava/io/PrintStream;")) + @Definition(id = "out", field = "Ljava/lang/System;out:Ljava/io/PrintStream;") @Expression("out") @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) } @@ -298,7 +298,7 @@ class MEExpressionCompletionTest : BaseMixinTest() { @Mixin(MEExpressionTestData.class) class MEExpressionCompletionTest { - @Definition(id = "acceptInaccessibleType", at = @At(value = "INVOKE", target = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;acceptInaccessibleType(Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}InaccessibleType;)V")) + @Definition(id = "acceptInaccessibleType", method = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;acceptInaccessibleType(Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}InaccessibleType;)V") @Expression("acceptInaccessibleType()") @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) } @@ -338,7 +338,7 @@ class MEExpressionCompletionTest : BaseMixinTest() { @Mixin(MEExpressionTestData.class) class MEExpressionCompletionTest { - @Definition(id = "noArgMethod", at = @At(value = "INVOKE", target = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;noArgMethod()V")) + @Definition(id = "noArgMethod", method = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;noArgMethod()V") @Expression("noArgMethod()") @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) } @@ -514,7 +514,7 @@ class MEExpressionCompletionTest : BaseMixinTest() { @Mixin(MEExpressionTestData.class) class MEExpressionCompletionTest { @Definition(id = "Integer", type = Integer.class) - @Definition(id = "synchedData", at = @At(value = "FIELD", target = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;synchedData:Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}SynchedDataManager;")) + @Definition(id = "synchedData", field = "Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData;synchedData:Lcom/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData${'$'}SynchedDataManager;") @Expression("(Integer) this.synchedData.") @Inject(method = "getStingerCount", at = @At("MIXINEXTRAS:EXPRESSION")) } From e81c885bf7103f417483722cfe0b05b106492b88 Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 29 Mar 2024 15:49:20 +0000 Subject: [PATCH 063/100] Add references to @Definition.field and method --- .../reference/MixinReferenceContributor.kt | 12 ++ .../target/DefinitionReferenceGTDHandler.kt | 45 +++++ .../reference/target/DefinitionReferences.kt | 163 ++++++++++++++++++ src/main/kotlin/util/MemberReference.kt | 14 ++ src/main/resources/META-INF/plugin.xml | 1 + 5 files changed, 235 insertions(+) create mode 100644 src/main/kotlin/platform/mixin/reference/target/DefinitionReferenceGTDHandler.kt create mode 100644 src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt diff --git a/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt b/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt index 416eacdf7..58d68e12e 100644 --- a/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt +++ b/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt @@ -20,6 +20,8 @@ package com.demonwav.mcdev.platform.mixin.reference +import com.demonwav.mcdev.platform.mixin.reference.target.FieldDefinitionReference +import com.demonwav.mcdev.platform.mixin.reference.target.MethodDefinitionReference import com.demonwav.mcdev.platform.mixin.reference.target.TargetReference import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.AT import com.demonwav.mcdev.util.insideAnnotationAttribute @@ -65,5 +67,15 @@ class MixinReferenceContributor : PsiReferenceContributor() { InvokerReference.ELEMENT_PATTERN, InvokerReference, ) + + // Definition references + registrar.registerReferenceProvider( + FieldDefinitionReference.ELEMENT_PATTERN, + FieldDefinitionReference, + ) + registrar.registerReferenceProvider( + MethodDefinitionReference.ELEMENT_PATTERN, + MethodDefinitionReference, + ) } } diff --git a/src/main/kotlin/platform/mixin/reference/target/DefinitionReferenceGTDHandler.kt b/src/main/kotlin/platform/mixin/reference/target/DefinitionReferenceGTDHandler.kt new file mode 100644 index 000000000..4489eff4e --- /dev/null +++ b/src/main/kotlin/platform/mixin/reference/target/DefinitionReferenceGTDHandler.kt @@ -0,0 +1,45 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.reference.target + +import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiLiteral +import com.intellij.psi.util.parentOfType + +class DefinitionReferenceGTDHandler : GotoDeclarationHandler { + override fun getGotoDeclarationTargets( + sourceElement: PsiElement?, + offset: Int, + editor: Editor? + ): Array? { + if (sourceElement == null) return null + val stringLiteral = sourceElement.parentOfType() ?: return null + if (FieldDefinitionReference.ELEMENT_PATTERN.accepts(stringLiteral)) { + return FieldDefinitionReference.resolveForNavigation(stringLiteral) + } + if (MethodDefinitionReference.ELEMENT_PATTERN.accepts(stringLiteral)) { + return MethodDefinitionReference.resolveForNavigation(stringLiteral) + } + return null + } +} diff --git a/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt b/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt new file mode 100644 index 000000000..3c61547b5 --- /dev/null +++ b/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt @@ -0,0 +1,163 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.reference.target + +import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler +import com.demonwav.mcdev.platform.mixin.reference.MixinReference +import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember +import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.util.MemberReference +import com.demonwav.mcdev.util.constantStringValue +import com.demonwav.mcdev.util.findContainingModifierList +import com.demonwav.mcdev.util.findField +import com.demonwav.mcdev.util.findMethods +import com.demonwav.mcdev.util.insideAnnotationAttribute +import com.demonwav.mcdev.util.mapFirstNotNull +import com.demonwav.mcdev.util.mapToArray +import com.demonwav.mcdev.util.reference.PolyReferenceResolver +import com.demonwav.mcdev.util.toTypedArray +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.openapi.project.Project +import com.intellij.patterns.PsiJavaPatterns +import com.intellij.patterns.StandardPatterns +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementResolveResult +import com.intellij.psi.PsiMember +import com.intellij.psi.ResolveResult +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.util.containers.sequenceOfNotNull +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.MethodInsnNode + +abstract class AbstractDefinitionReference : PolyReferenceResolver(), MixinReference { + abstract fun getFullReferenceIfMatches(memberReference: MemberReference, insn: AbstractInsnNode): MemberReference? + abstract fun getMatchesInClass(memberReference: MemberReference, clazz: PsiClass): Sequence + abstract fun referenceToString(memberReference: MemberReference): String + + override fun isUnresolved(context: PsiElement) = resolveInBytecode(context).isNotEmpty() + + override fun isValidAnnotation(name: String, project: Project) = name == MixinConstants.MixinExtras.DEFINITION + + override fun resolveReference(context: PsiElement): Array { + return resolveForNavigation(context).mapToArray(::PsiElementResolveResult) + } + + fun resolveForNavigation(context: PsiElement): Array { + val project = context.project + val facade = JavaPsiFacade.getInstance(project) + return resolveInBytecode(context).asSequence().flatMap { memberReference -> + val ownerClass = facade.findClass( + memberReference.owner!!.replace('$', '.'), + GlobalSearchScope.allScope(project) + ) ?: return@flatMap emptySequence() + getMatchesInClass(memberReference.withoutOwner, ownerClass) + }.toTypedArray() + } + + override fun collectVariants(context: PsiElement) = + resolveInBytecode( + context, + MemberReference("*", null, null, matchAllNames = true, matchAllDescs = true) + ).mapToArray { + LookupElementBuilder.create(referenceToString(it)) + .withPresentableText(it.presentableText) + .withLookupString(it.name) + } + + fun resolveInBytecode(context: PsiElement): List { + val memberReference = context.constantStringValue?.let(MemberReference::parse) ?: return emptyList() + return resolveInBytecode(context, memberReference) + } + + private fun resolveInBytecode(context: PsiElement, memberReference: MemberReference): List { + val project = context.project + val modifierList = context.findContainingModifierList() ?: return emptyList() + val (annotation, handler) = modifierList.annotations.mapFirstNotNull { annotation -> + val qName = annotation.qualifiedName ?: return@mapFirstNotNull null + val handler = MixinAnnotationHandler.forMixinAnnotation(qName, project) ?: return@mapFirstNotNull null + annotation to handler + } ?: return emptyList() + + val result = mutableListOf() + + for (target in handler.resolveTarget(annotation)) { + if (target !is MethodTargetMember) { + continue + } + + if (target.classAndMethod.method.instructions == null) { + continue + } + + for (insn in target.classAndMethod.method.instructions) { + val fullReference = getFullReferenceIfMatches(memberReference, insn) ?: continue + result += fullReference + } + } + + return result + } +} + +object FieldDefinitionReference : AbstractDefinitionReference() { + val ELEMENT_PATTERN = PsiJavaPatterns.psiLiteral(StandardPatterns.string()) + .insideAnnotationAttribute(MixinConstants.MixinExtras.DEFINITION, "field") + + override fun getFullReferenceIfMatches(memberReference: MemberReference, insn: AbstractInsnNode): MemberReference? { + if (insn !is FieldInsnNode || !memberReference.matchField(insn.owner, insn.name, insn.desc)) { + return null + } + + return MemberReference(insn.name, insn.desc, insn.owner.replace('/', '.')) + } + + override fun getMatchesInClass(memberReference: MemberReference, clazz: PsiClass) = + sequenceOfNotNull(clazz.findField(memberReference, checkBases = true)) + + override fun referenceToString(memberReference: MemberReference) = + "L${memberReference.owner?.replace('.', '/')};${memberReference.name}:${memberReference.descriptor}" + + override val description = "defined field '%s'" +} + +object MethodDefinitionReference : AbstractDefinitionReference() { + val ELEMENT_PATTERN = PsiJavaPatterns.psiLiteral(StandardPatterns.string()) + .insideAnnotationAttribute(MixinConstants.MixinExtras.DEFINITION, "method") + + override fun getFullReferenceIfMatches(memberReference: MemberReference, insn: AbstractInsnNode): MemberReference? { + if (insn !is MethodInsnNode || !memberReference.matchMethod(insn.owner, insn.name, insn.desc)) { + return null + } + + return MemberReference(insn.name, insn.desc, insn.owner.replace('/', '.')) + } + + override fun getMatchesInClass(memberReference: MemberReference, clazz: PsiClass) = + clazz.findMethods(memberReference, checkBases = true) + + override fun referenceToString(memberReference: MemberReference) = + "L${memberReference.owner?.replace('.', '/')};${memberReference.name}${memberReference.descriptor}" + + override val description = "defined method '%s'" +} diff --git a/src/main/kotlin/util/MemberReference.kt b/src/main/kotlin/util/MemberReference.kt index 1969957e7..945746fa5 100644 --- a/src/main/kotlin/util/MemberReference.kt +++ b/src/main/kotlin/util/MemberReference.kt @@ -26,6 +26,7 @@ import com.intellij.psi.PsiClass import com.intellij.psi.PsiField import com.intellij.psi.PsiMethod import java.io.Serializable +import org.objectweb.asm.Type /** * Represents a reference to a class member (a method or a field). It may @@ -62,6 +63,19 @@ data class MemberReference( override val fieldDescriptor = descriptor?.takeUnless { it.contains("(") } override val displayName = name + val presentableText: String get() = buildString { + if (owner != null) { + append(owner.substringAfterLast('.')) + append('.') + } + append(name) + if (descriptor != null && descriptor.startsWith("(")) { + append('(') + append(Type.getArgumentTypes(descriptor).joinToString { it.className.substringAfterLast('.') }) + append(')') + } + } + override fun canEverMatch(name: String): Boolean { return matchAllNames || this.name == name } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 17806dd7f..3f5cd58d7 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -460,6 +460,7 @@ + From c43ff4a0c08c22ad4e07b62041a64cd3aaf00b8d Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 29 Mar 2024 16:07:25 +0000 Subject: [PATCH 064/100] Add folding to @Definition.field and method --- .../expression/MEDefinitionFoldingBuilder.kt | 26 ++++++++++++++++++- .../expression/MEExpressionCompletionUtil.kt | 3 ++- .../folding/MixinFoldingOptionsProvider.kt | 5 ++++ .../mixin/folding/MixinFoldingSettings.kt | 1 + 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt b/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt index 816804cff..147b34023 100644 --- a/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt +++ b/src/main/kotlin/platform/mixin/expression/MEDefinitionFoldingBuilder.kt @@ -22,7 +22,10 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.platform.mixin.MixinModuleType import com.demonwav.mcdev.platform.mixin.folding.MixinFoldingSettings +import com.demonwav.mcdev.platform.mixin.reference.target.FieldDefinitionReference +import com.demonwav.mcdev.platform.mixin.reference.target.MethodDefinitionReference import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.util.MemberReference import com.intellij.lang.ASTNode import com.intellij.lang.folding.CustomFoldingBuilder import com.intellij.lang.folding.FoldingDescriptor @@ -32,6 +35,7 @@ import com.intellij.psi.JavaRecursiveElementWalkingVisitor import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiElement import com.intellij.psi.PsiJavaFile +import com.intellij.psi.PsiLiteralExpression import com.intellij.psi.PsiModifierList import com.intellij.psi.util.PsiTreeUtil @@ -41,7 +45,15 @@ class MEDefinitionFoldingBuilder : CustomFoldingBuilder() { override fun isRegionCollapsedByDefault(node: ASTNode): Boolean = MixinFoldingSettings.instance.state.foldDefinitions - override fun getLanguagePlaceholderText(node: ASTNode, range: TextRange) = "..." + override fun getLanguagePlaceholderText(node: ASTNode, range: TextRange): String { + val psi = node.psi + if (psi is PsiLiteralExpression) { + val value = psi.value as? String ?: return "..." + val memberReference = MemberReference.parse(value) ?: return "..." + return memberReference.presentableText + } + return "..." + } override fun buildLanguageFoldRegions( descriptors: MutableList, @@ -88,6 +100,18 @@ class MEDefinitionFoldingBuilder : CustomFoldingBuilder() { descriptors.add(FoldingDescriptor(list.node, range)) } } + + super.visitModifierList(list) + } + + override fun visitLiteralExpression(expression: PsiLiteralExpression) { + if (FieldDefinitionReference.ELEMENT_PATTERN.accepts(expression) || + MethodDefinitionReference.ELEMENT_PATTERN.accepts(expression) + ) { + if (MemberReference.parse(expression.value as String) != null) { + descriptors.add(FoldingDescriptor(expression.node, expression.textRange)) + } + } } } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 20cbeedb5..533d03667 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -914,7 +914,8 @@ object MEExpressionCompletionUtil { val nameValuePair = annotation.findElementAt(foldRegion.startOffset - annotationRange.startOffset) ?.findContainingNameValuePair() ?: continue if (nameValuePair.name == foldAttribute && - nameValuePair.parentOfType()?.hasQualifiedName(MixinConstants.Annotations.AT) == true + nameValuePair.parentOfType()?.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) + == true ) { regionsToFold += foldRegion } diff --git a/src/main/kotlin/platform/mixin/folding/MixinFoldingOptionsProvider.kt b/src/main/kotlin/platform/mixin/folding/MixinFoldingOptionsProvider.kt index ff2c3b769..44ccb2c9a 100644 --- a/src/main/kotlin/platform/mixin/folding/MixinFoldingOptionsProvider.kt +++ b/src/main/kotlin/platform/mixin/folding/MixinFoldingOptionsProvider.kt @@ -61,5 +61,10 @@ class MixinFoldingOptionsProvider : { settings.state.foldDefinitions }, { b -> settings.state.foldDefinitions = b }, ) + checkBox( + "Fold MixinExtras expression definition fields and methods", + { settings.state.foldDefinitionFieldsAndMethods }, + { b -> settings.state.foldDefinitionFieldsAndMethods = b }, + ) } } diff --git a/src/main/kotlin/platform/mixin/folding/MixinFoldingSettings.kt b/src/main/kotlin/platform/mixin/folding/MixinFoldingSettings.kt index b264561f2..61fee79af 100644 --- a/src/main/kotlin/platform/mixin/folding/MixinFoldingSettings.kt +++ b/src/main/kotlin/platform/mixin/folding/MixinFoldingSettings.kt @@ -36,6 +36,7 @@ class MixinFoldingSettings : PersistentStateComponent Date: Sat, 30 Mar 2024 14:59:53 +0000 Subject: [PATCH 065/100] Add comments to MEExpressionCompletionUtil to explain the completion process in more detail --- .../expression/MEExpressionCompletionUtil.kt | 74 +++++++++++++++---- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 533d03667..561fe5bde 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -228,7 +228,6 @@ object MEExpressionCompletionUtil { handler.resolveTarget(handlerAnnotation, targetClass) .filterIsInstance() .flatMap { methodTarget -> - getCompletionVariantsFromBytecode( project, mixinClass, @@ -251,6 +250,30 @@ object MEExpressionCompletionUtil { targetMethod: MethodNode, poolFactory: IdentifierPoolFactory, ): List { + /* + * MixinExtras isn't designed to match against incomplete expressions, which is what we need to do to produce + * completion options. The only support there is, is to match incomplete parameter lists and so on + * ("list inputs" to expressions). What follows is a kind of DIY match where we figure out different options + * for what the user might be trying to complete and hand it to MixinExtras to do the actual matching. Note that + * IntelliJ already inserts an identifier at the caret position to make auto-completion easier. + * + * We have four classes of problems to solve here: + * 1. There may already be a capture in the expression causing MixinExtras to return the wrong instructions. + * 2. There may be unresolved identifiers in the expression, causing MixinExtras to match nothing, which isn't + * ideal. + * 3. "this." expands to a field access, but the user may be trying to complete a method call (and other + * similar situations). + * 4. What the user is typing may form only a subexpression of a larger expression. For example, with + * "foo()", the user may actually be trying to type the expression "foo(x + y) + z". That is, "x", + * which is where the caret is, may not be a direct subexpression to the "foo" call expression, which itself + * may not be a direct subexpression of its parent. + * + * Throughout this process, we have to keep careful track of where the caret is, because: + * 1. As we make changes to the expression to the left of the caret, the caret may shift. + * 2. As we make copies of the element, or entirely new elements, that new element's textOffset may be different + * from the original one. + */ + if (DEBUG_COMPLETION) { println("======") println(targetMethod.textify()) @@ -265,16 +288,28 @@ object MEExpressionCompletionUtil { val pool = poolFactory(targetMethod) val flows = MEExpressionMatchUtil.getFlowMap(project, targetClass, targetMethod) ?: return emptyList() + // Removing all explicit captures from the expression solves problem 1 (see comment above). removeExplicitCaptures(statement, cursorOffset) - replaceUnknownNamesWithWildcards(project, statement, cursorOffset, pool) + // Replacing unresolved names with wildcards solves problem 2 (see comment above). + replaceUnresolvedNamesWithWildcards(project, statement, cursorOffset, pool) val elementAtCursor = statement.findElementAt(cursorOffset.toInt()) ?: return emptyList() + /* + * To solve problem 4 (see comment above), we first find matches for the top level statement, ignoring the + * subexpression that the caret is on. Then we iterate down into the subexpression that contains the caret and + * match that against all the statement's input flows in the same way as we matched the statement against all + * the instructions in the target method. Then we keep iterating until we reach the identifier the caret is on. + */ + + // Replace the subexpression the caret is on with a wildcard expression, so MixinExtras ignores it. val wildcardReplacedStatement = statement.copy() as MEStatement var cursorOffsetInCopyFile = cursorOffset.toInt() - statement.textRange.startOffset + wildcardReplacedStatement.textRange.startOffset replaceCursorInputWithWildcard(project, wildcardReplacedStatement, cursorOffsetInCopyFile) + // Iterate through possible "variants" of the statement that the user may be trying to complete; it doesn't + // matter if they don't parse, then we just skip them. This solves problem 3 (see comment above). var matchingFlows = mutableListOf() for (statementToMatch in getStatementVariants(project.meExpressionElementFactory, wildcardReplacedStatement)) { if (DEBUG_COMPLETION) { @@ -301,9 +336,11 @@ object MEExpressionCompletionUtil { return emptyList() } + // Iterate through subexpressions until we reach the identifier the caret is on var roundNumber = 0 var subExpr: MEMatchableElement = statement while (true) { + // Replace the subexpression the caret is on with a wildcard expression, so MixinExtras ignores it. val inputExprOnCursor = subExpr.getInputExprs().firstOrNull { it.textRange.contains(cursorOffset.toInt()) } ?: break val wildcardReplacedExpr = inputExprOnCursor.copy() as MEExpression @@ -323,6 +360,8 @@ object MEExpressionCompletionUtil { replaceCursorInputWithWildcard(project, wildcardReplacedExpr, cursorOffsetInCopyFile) + // Iterate through the possible "varaints" of the expression in the same way as we did for the statement + // above. This solves problem 3 (see comment above). val newMatchingFlows = mutableSetOf() for (exprToMatch in getExpressionVariants(project.meExpressionElementFactory, wildcardReplacedExpr)) { if (DEBUG_COMPLETION) { @@ -376,6 +415,9 @@ object MEExpressionCompletionUtil { } } + // Try to decide if we should be completing types or normal expressions. + // Not as easy as it sounds (think incomplete casts looking like parenthesized expressions). + // Note that it's possible to complete types and expressions at the same time. val isInsideMeType = PsiTreeUtil.getParentOfType( elementAtCursor, METype::class.java, @@ -423,18 +465,18 @@ object MEExpressionCompletionUtil { return eliminableResults.groupBy { it.uniquenessKey }.values.map { it.max().lookupElement } } - private fun replaceUnknownNamesWithWildcards( + private fun replaceUnresolvedNamesWithWildcards( project: Project, statement: MEStatement, cursorOffset: MutableInt, pool: IdentifierPool, ) { - val unknownNames = mutableListOf() + val unresolvedNames = mutableListOf() statement.accept(object : MERecursiveWalkingVisitor() { override fun visitType(o: METype) { val name = o.meName if (!name.isWildcard && !pool.typeExists(name.text)) { - unknownNames += name + unresolvedNames += name } } @@ -443,11 +485,11 @@ object MEExpressionCompletionUtil { if (!name.isWildcard) { if (METypeUtil.isExpressionDirectlyInTypePosition(o)) { if (!pool.typeExists(name.text)) { - unknownNames += name + unresolvedNames += name } } else { if (!pool.memberExists(name.text)) { - unknownNames += name + unresolvedNames += name } } } @@ -456,7 +498,7 @@ object MEExpressionCompletionUtil { override fun visitSuperCallExpression(o: MESuperCallExpression) { val name = o.memberName if (name != null && !name.isWildcard && !pool.memberExists(name.text)) { - unknownNames += name + unresolvedNames += name } super.visitSuperCallExpression(o) } @@ -464,7 +506,7 @@ object MEExpressionCompletionUtil { override fun visitMethodCallExpression(o: MEMethodCallExpression) { val name = o.memberName if (!name.isWildcard && !pool.memberExists(name.text)) { - unknownNames += name + unresolvedNames += name } super.visitMethodCallExpression(o) } @@ -472,7 +514,7 @@ object MEExpressionCompletionUtil { override fun visitStaticMethodCallExpression(o: MEStaticMethodCallExpression) { val name = o.memberName if (!name.isWildcard && !pool.memberExists(name.text)) { - unknownNames += name + unresolvedNames += name } super.visitStaticMethodCallExpression(o) } @@ -480,7 +522,7 @@ object MEExpressionCompletionUtil { override fun visitMemberAccessExpression(o: MEMemberAccessExpression) { val name = o.memberName if (!name.isWildcard && !pool.memberExists(name.text)) { - unknownNames += name + unresolvedNames += name } super.visitMemberAccessExpression(o) } @@ -488,19 +530,19 @@ object MEExpressionCompletionUtil { override fun visitNewExpression(o: MENewExpression) { val name = o.type if (name != null && !name.isWildcard && !pool.typeExists(name.text)) { - unknownNames += name + unresolvedNames += name } super.visitNewExpression(o) } }) - for (unknownName in unknownNames) { - val startOffset = unknownName.textRange.startOffset + for (unresolvedName in unresolvedNames) { + val startOffset = unresolvedName.textRange.startOffset if (cursorOffset.toInt() > startOffset) { - cursorOffset.setValue(cursorOffset.toInt() - unknownName.textLength + 1) + cursorOffset.setValue(cursorOffset.toInt() - unresolvedName.textLength + 1) } - unknownName.replace(project.meExpressionElementFactory.createName("?")) + unresolvedName.replace(project.meExpressionElementFactory.createName("?")) } } From 3fe87defc76cf0ace75cc5c43e0886af3cbeb563 Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 30 Mar 2024 18:20:45 +0000 Subject: [PATCH 066/100] Fix @Local.type extraction --- .../kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index b1d240b8a..83759f6a7 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -175,7 +175,7 @@ object MEExpressionMatchUtil { val locals = annotation.findDeclaredAttributeValue("local")?.findAnnotations() ?: emptyList() for (localAnnotation in locals) { - val localType = annotation.findDeclaredAttributeValue("type")?.resolveType() + val localType = localAnnotation.findDeclaredAttributeValue("type")?.resolveType() val localInfo = LocalInfo.fromAnnotation(localType, localAnnotation) pool.addMember(definitionId) { insn -> if (insn !is VarInsnNode) { From 77bcf22f88c5ed5a25ff11f2120baf4d29efb958 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 31 Mar 2024 09:35:27 +0100 Subject: [PATCH 067/100] Add string literal completion for ME expressions --- .../MEExpressionCompletionContributor.kt | 18 +++++++++++ .../expression/MEExpressionCompletionUtil.kt | 30 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt index d3c7f4377..0288b4792 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt @@ -68,6 +68,24 @@ class MEExpressionCompletionContributor : CompletionContributor() { Keyword("instanceof", TailType.INSERT_SPACE) ) ) + extend( + CompletionType.BASIC, + MEExpressionCompletionUtil.STRING_LITERAL_PLACE, + object : CompletionProvider() { + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + result.addAllElements( + MEExpressionCompletionUtil.getStringCompletions( + parameters.originalFile.project, + parameters.position + ) + ) + } + } + ) extend( CompletionType.BASIC, MEExpressionCompletionUtil.FROM_BYTECODE_PLACE, diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 561fe5bde..be604496d 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -180,6 +180,9 @@ object MEExpressionCompletionUtil { NORMAL_ELEMENT, AFTER_END_EXPRESSION_PATTERN, ) + val STRING_LITERAL_PLACE = PlatformPatterns.psiElement().withElementType( + TokenSet.create(MEExpressionTypes.TOKEN_STRING, MEExpressionTypes.TOKEN_STRING_TERMINATOR) + ) val FROM_BYTECODE_PLACE = PlatformPatterns.psiElement() .inside(MEStatement::class.java) .andNot(PlatformPatterns.psiElement().inside(MELitExpression::class.java)) @@ -201,6 +204,32 @@ object MEExpressionCompletionUtil { } } + fun getStringCompletions(project: Project, contextElement: PsiElement): List { + val expressionAnnotation = contextElement.findMultiInjectionHost()?.parentOfType() + ?: return emptyList() + if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { + return emptyList() + } + + val modifierList = expressionAnnotation.findContainingModifierList() ?: return emptyList() + + val (handler, handlerAnnotation) = modifierList.annotations.mapFirstNotNull { annotation -> + val qName = annotation.qualifiedName ?: return@mapFirstNotNull null + val handler = MixinAnnotationHandler.forMixinAnnotation(qName, project) ?: return@mapFirstNotNull null + handler to annotation + } ?: return emptyList() + + return handler.resolveTarget(handlerAnnotation).flatMap { + (it as? MethodTargetMember)?.classAndMethod?.method?.instructions?.mapNotNull { insn -> + if (insn is LdcInsnNode && insn.cst is String) { + LookupElementBuilder.create(insn.cst) + } else { + null + } + } ?: emptyList() + } + } + fun getCompletionVariantsFromBytecode(project: Project, contextElement: PsiElement): List { val statement = contextElement.parentOfType() ?: return emptyList() @@ -641,7 +670,6 @@ object MEExpressionCompletionUtil { ) } } - // TODO: string literals? } } is VarInsnNode -> return createLocalVariableLookups( From b4f511b878c584e90a7d2e9b776e796219451ab6 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 31 Mar 2024 09:42:00 +0100 Subject: [PATCH 068/100] Fix @Local.type for source matching too --- .../mixin/handlers/mixinextras/ExpressionInjectionPoint.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index a7454e293..e59582894 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -143,7 +143,7 @@ class ExpressionInjectionPoint : InjectionPoint() { val locals = annotation.findDeclaredAttributeValue("local")?.findAnnotations() ?: emptyList() for (localAnnotation in locals) { - val localType = annotation.findDeclaredAttributeValue("type")?.resolveType() + val localType = localAnnotation.findDeclaredAttributeValue("type")?.resolveType() val localInfo = LocalInfo.fromAnnotation(localType, localAnnotation) matchContext.addLocalInfo(definitionId, localInfo) } From 2f977ab40e8b94a18e061e7d8b4a60f0b92dda02 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 31 Mar 2024 09:45:33 +0100 Subject: [PATCH 069/100] Fix parsing of multiple @Expression annotations in a single modifier list --- .../handlers/mixinextras/ExpressionInjectionPoint.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index e59582894..750eb5bcd 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -42,6 +42,7 @@ import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.findAnnotations import com.demonwav.mcdev.util.findContainingModifierList import com.demonwav.mcdev.util.findModule +import com.demonwav.mcdev.util.findMultiInjectionHost import com.demonwav.mcdev.util.ifEmpty import com.demonwav.mcdev.util.parseArray import com.demonwav.mcdev.util.resolveType @@ -189,10 +190,13 @@ class ExpressionInjectionPoint : InjectionPoint() { ?: return@flatMap emptySequence>() expressionElements.asSequence().mapNotNull { expressionElement -> val text = expressionElement.constantStringValue ?: return@mapNotNull null - // TODO: get the right statement from the injected file val rootStatementPsi = InjectedLanguageManager.getInstance(project) .getInjectedPsiFiles(expressionElement)?.firstOrNull() - ?.let { (it.first as? MEExpressionFile)?.statements?.singleOrNull() } + ?.let { + (it.first as? MEExpressionFile)?.statements?.firstOrNull { stmt -> + stmt.findMultiInjectionHost()?.parentOfType() == exprAnnotation + } + } ?: project.meExpressionElementFactory.createFile("do {$text}").statements.singleOrNull() ?: project.meExpressionElementFactory.createStatement("empty") MEExpressionMatchUtil.createExpression(text)?.let { it to rootStatementPsi } From 4063f0924927cb861c0ccacc288f318c5c77739b Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 5 Apr 2024 10:54:25 +0100 Subject: [PATCH 070/100] Format MEExpressionLexer.flex --- src/main/grammars/MEExpressionLexer.flex | 194 ++++++++++++----------- 1 file changed, 99 insertions(+), 95 deletions(-) diff --git a/src/main/grammars/MEExpressionLexer.flex b/src/main/grammars/MEExpressionLexer.flex index 08f39f526..3badad0e2 100644 --- a/src/main/grammars/MEExpressionLexer.flex +++ b/src/main/grammars/MEExpressionLexer.flex @@ -33,109 +33,113 @@ import com.intellij.psi.TokenType; %unicode %function advance %type IElementType -%eof{ return; +%eof{ return; %eof} -WHITE_SPACE=[\ \n\t\r] -RESERVED=assert|break|case|catch|const|continue|default|else|finally|for|goto|if|switch|synchronized|try|while|yield|_ -WILDCARD="?" -NEW=new -INSTANCEOF=instanceof -BOOL_LIT=true|false -NULL_LIT=null -DO=do -RETURN=return -THROW=throw -THIS=this -SUPER=super -CLASS=class -IDENTIFIER=[A-Za-z_][A-Za-z0-9_]* -INT_LIT=([0-9]+|0x[0-9a-fA-F]+) -DEC_LIT=[0-9]*\.[0-9]+ -PLUS="+" -MINUS=- -MULT="*" -DIV="/" -MOD=% -BITWISE_NOT="~" -DOT="." -COMMA=, -LEFT_PAREN="(" -RIGHT_PAREN=")" -LEFT_BRACKET="[" -RIGHT_BRACKET="]" -LEFT_BRACE="{" -RIGHT_BRACE="}" -AT=@ -SHL=<< -SHR=>> -USHR=>>> -LT=< -LE=<= -GT=> -GE=>= -EQ=== -NE="!=" -BITWISE_AND=& -BITWISE_XOR="^" -BITWISE_OR="|" -ASSIGN== +WHITE_SPACE = [\ \n\t\r] +RESERVED = assert|break|case|catch|const|continue|default|else|finally|for|goto|if|switch|synchronized|try|while|yield|_ +WILDCARD = "?" +NEW = new +INSTANCEOF = instanceof +BOOL_LIT = true|false +NULL_LIT = null +DO = do +RETURN = return +THROW = throw +THIS = this +SUPER = super +CLASS = class +IDENTIFIER = [A-Za-z_][A-Za-z0-9_]* +INT_LIT = ( [0-9]+ | 0x[0-9a-fA-F]+ ) +DEC_LIT = [0-9]*\.[0-9]+ +PLUS = "+" +MINUS = - +MULT = "*" +DIV = "/" +MOD = % +BITWISE_NOT = "~" +DOT = "." +COMMA = , +LEFT_PAREN = "(" +RIGHT_PAREN = ")" +LEFT_BRACKET = "[" +RIGHT_BRACKET = "]" +LEFT_BRACE = "{" +RIGHT_BRACE = "}" +AT = @ +SHL = << +SHR = >> +USHR = >>> +LT = < +LE = <= +GT = > +GE = >= +EQ = == +NE = "!=" +BITWISE_AND = & +BITWISE_XOR = "^" +BITWISE_OR = "|" +ASSIGN = = -STRING_TERMINATOR=' -STRING_ESCAPE=\\'|\\\\ +STRING_TERMINATOR = ' +STRING_ESCAPE = \\'|\\\\ %state STRING %% - {WHITE_SPACE}+ { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; } - {RESERVED} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RESERVED; } - {WILDCARD} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_WILDCARD; } - {NEW} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NEW; } - {INSTANCEOF} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_INSTANCEOF; } - {BOOL_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BOOL_LIT; } - {NULL_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NULL_LIT; } - {DO} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DO; } - {RETURN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RETURN; } - {THROW} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_THROW; } - {THIS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_THIS; } - {SUPER} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SUPER; } - {CLASS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_CLASS; } - {IDENTIFIER} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_IDENTIFIER; } - {INT_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_INT_LIT; } - {DEC_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DEC_LIT; } - {PLUS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_PLUS; } - {MINUS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MINUS; } - {MULT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MULT; } - {DIV} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DIV; } - {MOD} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MOD; } - {BITWISE_NOT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_NOT; } - {DOT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DOT; } - {COMMA} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_COMMA; } - {LEFT_PAREN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_PAREN; } - {RIGHT_PAREN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_PAREN; } - {LEFT_BRACKET} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_BRACKET; } - {RIGHT_BRACKET} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_BRACKET; } - {LEFT_BRACE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_BRACE; } - {RIGHT_BRACE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_BRACE; } - {AT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_AT; } - {SHL} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SHL; } - {SHR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SHR; } - {USHR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_USHR; } - {LT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LT; } - {LE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LE; } - {GT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_GT; } - {GE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_GE; } - {EQ} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_EQ; } - {NE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NE; } - {BITWISE_AND} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_AND; } - {BITWISE_XOR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_XOR; } - {BITWISE_OR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_OR; } - {ASSIGN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_ASSIGN; } + { + {WHITE_SPACE}+ { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; } + {RESERVED} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RESERVED; } + {WILDCARD} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_WILDCARD; } + {NEW} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NEW; } + {INSTANCEOF} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_INSTANCEOF; } + {BOOL_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BOOL_LIT; } + {NULL_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NULL_LIT; } + {DO} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DO; } + {RETURN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RETURN; } + {THROW} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_THROW; } + {THIS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_THIS; } + {SUPER} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SUPER; } + {CLASS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_CLASS; } + {IDENTIFIER} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_IDENTIFIER; } + {INT_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_INT_LIT; } + {DEC_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DEC_LIT; } + {PLUS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_PLUS; } + {MINUS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MINUS; } + {MULT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MULT; } + {DIV} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DIV; } + {MOD} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MOD; } + {BITWISE_NOT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_NOT; } + {DOT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DOT; } + {COMMA} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_COMMA; } + {LEFT_PAREN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_PAREN; } + {RIGHT_PAREN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_PAREN; } + {LEFT_BRACKET} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_BRACKET; } + {RIGHT_BRACKET} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_BRACKET; } + {LEFT_BRACE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_BRACE; } + {RIGHT_BRACE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_BRACE; } + {AT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_AT; } + {SHL} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SHL; } + {SHR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SHR; } + {USHR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_USHR; } + {LT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LT; } + {LE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LE; } + {GT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_GT; } + {GE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_GE; } + {EQ} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_EQ; } + {NE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NE; } + {BITWISE_AND} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_AND; } + {BITWISE_XOR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_XOR; } + {BITWISE_OR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_OR; } + {ASSIGN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_ASSIGN; } + {STRING_TERMINATOR} { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING_TERMINATOR; } +} - {STRING_TERMINATOR} { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING_TERMINATOR; } - {STRING_ESCAPE} { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING_ESCAPE; } - {STRING_TERMINATOR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_STRING_TERMINATOR; } - [^'\\]+ { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING; } + { + {STRING_ESCAPE} { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING_ESCAPE; } + {STRING_TERMINATOR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_STRING_TERMINATOR; } + [^'\\]+ { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING; } +} [^] { return TokenType.BAD_CHARACTER; } From 8bb9eb1e329c7474f8e2ef9fdcb49328bc69592e Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 5 Apr 2024 11:29:28 +0100 Subject: [PATCH 071/100] Better handle parenthesized expressions in Java source matching --- .../psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt | 4 ++-- .../psi/mixins/impl/MEBinaryExpressionImplMixin.kt | 5 +++-- .../expression/psi/mixins/impl/MECastExpressionImplMixin.kt | 3 ++- .../expression/psi/mixins/impl/METhrowStatementImplMixin.kt | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt index 5bdeca1fb..dcc6f6f9f 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEArrayAccessExpressionImplMixin.kt @@ -46,8 +46,8 @@ abstract class MEArrayAccessExpressionImplMixin(node: ASTNode) : MEExpressionImp return false } - val javaArray = java.arrayExpression - val javaIndex = java.indexExpression ?: return false + val javaArray = PsiUtil.skipParenthesizedExprDown(java.arrayExpression) ?: return false + val javaIndex = PsiUtil.skipParenthesizedExprDown(java.indexExpression) ?: return false return arrayExpr.matchesJava(javaArray, context) && indexExpr?.matchesJava(javaIndex, context) == true } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt index 5b492107d..96c6ae245 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBinaryExpressionImplMixin.kt @@ -34,6 +34,7 @@ import com.intellij.psi.PsiInstanceOfExpression import com.intellij.psi.PsiTypeTestPattern import com.intellij.psi.tree.TokenSet import com.intellij.psi.util.JavaPsiPatternUtil +import com.intellij.psi.util.PsiUtil abstract class MEBinaryExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MEBinaryExpressionMixin { override val operator get() = node.findChildByType(operatorTokens)!!.elementType @@ -83,8 +84,8 @@ abstract class MEBinaryExpressionImplMixin(node: ASTNode) : MEExpressionImpl(nod return false } - val javaLeft = java.lOperand - val javaRight = java.rOperand ?: return false + val javaLeft = PsiUtil.skipParenthesizedExprDown(java.lOperand) ?: return false + val javaRight = PsiUtil.skipParenthesizedExprDown(java.rOperand) ?: return false return leftExpr.matchesJava(javaLeft, context) && rightExpr?.matchesJava(javaRight, context) == true } } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt index 119590f1f..866999357 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MECastExpressionImplMixin.kt @@ -33,6 +33,7 @@ import com.intellij.psi.PsiInstanceOfExpression import com.intellij.psi.PsiTypeCastExpression import com.intellij.psi.PsiTypeTestPattern import com.intellij.psi.util.JavaPsiPatternUtil +import com.intellij.psi.util.PsiUtil abstract class MECastExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node), MECastExpressionMixin { override val castType get() = castTypeExpr?.let(METypeUtil::convertExpressionToType) @@ -44,7 +45,7 @@ abstract class MECastExpressionImplMixin(node: ASTNode) : MEExpressionImpl(node) return when (java) { is PsiTypeCastExpression -> { val javaType = java.castType?.type ?: return false - val javaOperand = java.operand ?: return false + val javaOperand = PsiUtil.skipParenthesizedExprDown(java.operand) ?: return false castType?.matchesJava(javaType, context) == true && castedExpr?.matchesJava(javaOperand, context) == true } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt index e18fa1bed..4226d24d3 100644 --- a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/METhrowStatementImplMixin.kt @@ -26,6 +26,7 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.impl.MEStatementImpl import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement import com.intellij.psi.PsiThrowStatement +import com.intellij.psi.util.PsiUtil abstract class METhrowStatementImplMixin(node: ASTNode) : MEStatementImpl(node) { override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { @@ -33,7 +34,7 @@ abstract class METhrowStatementImplMixin(node: ASTNode) : MEStatementImpl(node) return false } - val javaException = java.exception ?: return false + val javaException = PsiUtil.skipParenthesizedExprDown(java.exception) ?: return false return valueExpr?.matchesJava(javaException, context) == true } From 8770366a48befdb67b85e8b8fb75669d32095c6a Mon Sep 17 00:00:00 2001 From: joe Date: Sat, 6 Apr 2024 11:31:17 +0100 Subject: [PATCH 072/100] Add method reference expressions --- build.gradle.kts | 2 +- .../meExpression/MEExpressionTestData.java | 16 +++ src/main/grammars/MEExpressionLexer.flex | 2 + src/main/grammars/MEExpressionParser.bnf | 28 +++++ .../mixin/expression/MEExpressionAnnotator.kt | 6 +- .../MEExpressionColorSettingsPage.kt | 5 + .../MEExpressionCompletionContributor.kt | 7 ++ .../expression/MEExpressionCompletionUtil.kt | 101 +++++++++++++++--- .../MEExpressionSyntaxHighlighter.kt | 8 ++ .../MEBoundReferenceExpressionImplMixin.kt | 64 +++++++++++ ...ConstructorReferenceExpressionImplMixin.kt | 47 ++++++++ ...EFreeMethodReferenceExpressionImplMixin.kt | 60 +++++++++++ .../messages/MinecraftDevelopment.properties | 1 + .../expression/MEExpressionCompletionTest.kt | 69 ++++++++++++ 14 files changed, 399 insertions(+), 17 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBoundReferenceExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEConstructorReferenceExpressionImplMixin.kt create mode 100644 src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEFreeMethodReferenceExpressionImplMixin.kt diff --git a/build.gradle.kts b/build.gradle.kts index d49536237..e42c073b5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:d999081")!!) + testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:e0f566c")!!) implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java index 9d5dca03d..e47074737 100644 --- a/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java +++ b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java @@ -20,6 +20,8 @@ package com.demonwav.mcdev.mixintestdata.meExpression; +import java.util.stream.Stream; + public class MEExpressionTestData { private static final SynchedData STINGER_COUNT = null; private SynchedDataManager synchedData; @@ -38,6 +40,8 @@ public void complexFunction() { String[] strings1 = new String[] { local1, local2 }; String[] strings2 = new String[one]; + + Stream.empty().map(this::nonStaticMapper).map(MEExpressionTestData::staticMapper).map(ConstructedByMethodReference::new); } private static void acceptInaccessibleType(InaccessibleType type) { @@ -50,6 +54,14 @@ public int getStingerCount() { return (Integer) this.synchedData.get(STINGER_COUNT); } + private Object nonStaticMapper(Object arg) { + return arg; + } + + private static Object staticMapper(Object arg) { + return arg; + } + private static class InaccessibleType { } @@ -62,4 +74,8 @@ public V get(SynchedData data) { public static class SynchedData { } + + public static class ConstructedByMethodReference { + public ConstructedByMethodReference(Object bar) {} + } } diff --git a/src/main/grammars/MEExpressionLexer.flex b/src/main/grammars/MEExpressionLexer.flex index 3badad0e2..24a23cdae 100644 --- a/src/main/grammars/MEExpressionLexer.flex +++ b/src/main/grammars/MEExpressionLexer.flex @@ -80,6 +80,7 @@ BITWISE_AND = & BITWISE_XOR = "^" BITWISE_OR = "|" ASSIGN = = +METHOD_REF = :: STRING_TERMINATOR = ' STRING_ESCAPE = \\'|\\\\ @@ -133,6 +134,7 @@ STRING_ESCAPE = \\'|\\\\ {BITWISE_XOR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_XOR; } {BITWISE_OR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_OR; } {ASSIGN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_ASSIGN; } + {METHOD_REF} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_METHOD_REF; } {STRING_TERMINATOR} { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING_TERMINATOR; } } diff --git a/src/main/grammars/MEExpressionParser.bnf b/src/main/grammars/MEExpressionParser.bnf index 75b2907c3..47d509e01 100644 --- a/src/main/grammars/MEExpressionParser.bnf +++ b/src/main/grammars/MEExpressionParser.bnf @@ -120,6 +120,9 @@ expression ::= capturingExpression | castExpression | parenthesizedExpression | methodCallExpression | + boundMethodReferenceExpression | + freeMethodReferenceExpression | + constructorReferenceExpression | arrayAccessExpression | memberAccessExpression | newExpression | @@ -170,6 +173,31 @@ staticMethodCallExpression ::= name TOKEN_LEFT_PAREN arguments rightParen { ] } +boundMethodReferenceExpression ::= expression !(TOKEN_METHOD_REF TOKEN_NEW) TOKEN_METHOD_REF name { + pin = 3 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEBoundReferenceExpressionImplMixin" + methods = [ + receiverExpr = "expression" + memberName = "name" + ] +} + +freeMethodReferenceExpression ::= TOKEN_METHOD_REF name { + pin = 1 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEFreeMethodReferenceExpressionImplMixin" + methods = [ + memberName = "name" + ] +} + +constructorReferenceExpression ::= type TOKEN_METHOD_REF TOKEN_NEW { + pin = 3 + mixin = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl.MEConstructorReferenceExpressionImplMixin" + methods = [ + className = "type" + ] +} + arrayAccessExpression ::= expression TOKEN_LEFT_BRACKET expression? rightBracket { pin = 2 implements = "com.demonwav.mcdev.platform.mixin.expression.psi.mixins.MEArrayAccessExpressionMixin" diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index 8d534e591..bcabbf930 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -23,9 +23,11 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEBinaryExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEBoundMethodReferenceExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclaration import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEDeclarationItem import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEFreeMethodReferenceExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MELitExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMemberAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMethodCallExpression @@ -76,7 +78,9 @@ class MEExpressionAnnotator : Annotator { ) is MESuperCallExpression, is MEMethodCallExpression, - is MEStaticMethodCallExpression -> highlightVariable( + is MEStaticMethodCallExpression, + is MEBoundMethodReferenceExpression, + is MEFreeMethodReferenceExpression -> highlightVariable( holder, element, MEExpressionSyntaxHighlighter.IDENTIFIER_CALL, diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt index 1144a1929..964bd4a67 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionColorSettingsPage.kt @@ -65,6 +65,10 @@ class MEExpressionColorSettingsPage : ColorSettingsPage { MCDevBundle.pointer("mixinextras.expression.lang.highlighting.dot.display_name"), MEExpressionSyntaxHighlighter.DOT ), + AttributesDescriptor( + MCDevBundle.pointer("mixinextras.expression.lang.highlighting.method_reference.display_name"), + MEExpressionSyntaxHighlighter.METHOD_REFERENCE + ), AttributesDescriptor( MCDevBundle.pointer("mixinextras.expression.lang.highlighting.comma.display_name"), MEExpressionSyntaxHighlighter.COMMA @@ -137,6 +141,7 @@ class MEExpressionColorSettingsPage : ColorSettingsPage { ClassName.class, foo.bar, new int[] { 1, 2, 3 }, + method::reference, 'a bad character: ' # other_identifier )[0] """.trimIndent() diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt index 0288b4792..339634a39 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionContributor.kt @@ -68,6 +68,13 @@ class MEExpressionCompletionContributor : CompletionContributor() { Keyword("instanceof", TailType.INSERT_SPACE) ) ) + extend( + CompletionType.BASIC, + MEExpressionCompletionUtil.METHOD_REFERENCE_PLACE, + KeywordCompletionProvider( + Keyword("new") + ) + ) extend( CompletionType.BASIC, MEExpressionCompletionUtil.STRING_LITERAL_PLACE, diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index be604496d..ebc8502d3 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -24,12 +24,14 @@ import com.demonwav.mcdev.MinecraftProjectSettings import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.insnOrNull import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEAssignStatement +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEBoundMethodReferenceExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECastExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEClassConstantExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEFreeMethodReferenceExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MELitExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMemberAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEMethodCallExpression @@ -116,6 +118,7 @@ import com.llamalad7.mixinextras.expression.impl.flow.FlowValue import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool import com.llamalad7.mixinextras.utils.Decorations import org.apache.commons.lang3.mutable.MutableInt +import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.signature.SignatureReader @@ -125,6 +128,7 @@ import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.IincInsnNode import org.objectweb.asm.tree.InsnNode import org.objectweb.asm.tree.IntInsnNode +import org.objectweb.asm.tree.InvokeDynamicInsnNode import org.objectweb.asm.tree.LdcInsnNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode @@ -142,22 +146,25 @@ object MEExpressionCompletionUtil { private val TYPE_PATTERN = PlatformPatterns.psiElement() .inside(MEStatement::class.java) .validType() - private val AFTER_END_EXPRESSION_PATTERN = PlatformPatterns.psiElement().afterLeaf( - PlatformPatterns.psiElement().withElementType( - TokenSet.create( - MEExpressionTypes.TOKEN_IDENTIFIER, - MEExpressionTypes.TOKEN_WILDCARD, - MEExpressionTypes.TOKEN_RIGHT_PAREN, - MEExpressionTypes.TOKEN_RIGHT_BRACKET, - MEExpressionTypes.TOKEN_RIGHT_BRACE, - MEExpressionTypes.TOKEN_BOOL_LIT, - MEExpressionTypes.TOKEN_CLASS, - MEExpressionTypes.TOKEN_INT_LIT, - MEExpressionTypes.TOKEN_DEC_LIT, - MEExpressionTypes.TOKEN_NULL_LIT, - MEExpressionTypes.TOKEN_STRING_TERMINATOR, + private val AFTER_END_EXPRESSION_PATTERN = StandardPatterns.or( + PlatformPatterns.psiElement().afterLeaf( + PlatformPatterns.psiElement().withElementType( + TokenSet.create( + MEExpressionTypes.TOKEN_IDENTIFIER, + MEExpressionTypes.TOKEN_WILDCARD, + MEExpressionTypes.TOKEN_RIGHT_PAREN, + MEExpressionTypes.TOKEN_RIGHT_BRACKET, + MEExpressionTypes.TOKEN_RIGHT_BRACE, + MEExpressionTypes.TOKEN_BOOL_LIT, + MEExpressionTypes.TOKEN_CLASS, + MEExpressionTypes.TOKEN_INT_LIT, + MEExpressionTypes.TOKEN_DEC_LIT, + MEExpressionTypes.TOKEN_NULL_LIT, + MEExpressionTypes.TOKEN_STRING_TERMINATOR, + ) ) - ) + ), + PlatformPatterns.psiElement().afterLeaf(PlatformPatterns.psiElement().withText("new").afterLeaf("::")), ) val STATEMENT_KEYWORD_PLACE = PlatformPatterns.psiElement().afterLeaf( @@ -167,6 +174,7 @@ object MEExpressionCompletionUtil { NORMAL_ELEMENT, StandardPatterns.not(AFTER_END_EXPRESSION_PATTERN), StandardPatterns.not(PlatformPatterns.psiElement().afterLeaf(".")), + StandardPatterns.not(PlatformPatterns.psiElement().afterLeaf("::")), ) val CLASS_PLACE = StandardPatterns.and( NORMAL_ELEMENT, @@ -180,6 +188,10 @@ object MEExpressionCompletionUtil { NORMAL_ELEMENT, AFTER_END_EXPRESSION_PATTERN, ) + val METHOD_REFERENCE_PLACE = StandardPatterns.and( + NORMAL_ELEMENT, + PlatformPatterns.psiElement().afterLeaf("::"), + ) val STRING_LITERAL_PLACE = PlatformPatterns.psiElement().withElementType( TokenSet.create(MEExpressionTypes.TOKEN_STRING, MEExpressionTypes.TOKEN_STRING_TERMINATOR) ) @@ -204,6 +216,23 @@ object MEExpressionCompletionUtil { } } + private val COLON_COLON_NEW_TAIL = object : TailType() { + override fun processTail(editor: Editor, tailOffset: Int): Int { + editor.document.insertString(tailOffset, "::new") + return moveCaret(editor, tailOffset, 5) + } + + override fun isApplicable(context: InsertionContext): Boolean { + val chars = context.document.charsSequence + val colonColonOffset = CharArrayUtil.shiftForward(chars, context.tailOffset, " \n\t") + if (!CharArrayUtil.regionMatches(chars, colonColonOffset, "::")) { + return true + } + val newOffset = CharArrayUtil.shiftForward(chars, colonColonOffset + 2, " \n\t") + return !CharArrayUtil.regionMatches(chars, newOffset, "new") + } + } + fun getStringCompletions(project: Project, contextElement: PsiElement): List { val expressionAnnotation = contextElement.findMultiInjectionHost()?.parentOfType() ?: return emptyList() @@ -548,6 +577,22 @@ object MEExpressionCompletionUtil { super.visitStaticMethodCallExpression(o) } + override fun visitBoundMethodReferenceExpression(o: MEBoundMethodReferenceExpression) { + val name = o.memberName + if (name != null && !name.isWildcard && !pool.memberExists(name.text)) { + unresolvedNames += name + } + super.visitBoundMethodReferenceExpression(o) + } + + override fun visitFreeMethodReferenceExpression(o: MEFreeMethodReferenceExpression) { + val name = o.memberName + if (name != null && !name.isWildcard && !pool.memberExists(name.text)) { + unresolvedNames += name + } + super.visitFreeMethodReferenceExpression(o) + } + override fun visitMemberAccessExpression(o: MEMemberAccessExpression) { val name = o.memberName if (!name.isWildcard && !pool.memberExists(name.text)) { @@ -813,6 +858,32 @@ object MEExpressionCompletionUtil { } } } + is InvokeDynamicInsnNode -> { + if (insn.bsm.owner == "java/lang/invoke/LambdaMetafactory") { + val handle = insn.bsmArgs.getOrNull(1) as? Handle ?: return emptyList() + val definitionValue = "method = \"L${handle.owner};${handle.name}${handle.desc}\"" + if (handle.tag !in Opcodes.H_INVOKEVIRTUAL..Opcodes.H_INVOKEINTERFACE) { + return emptyList() + } + if (handle.tag == Opcodes.H_NEWINVOKESPECIAL) { + return listOf( + createTypeLookup(Type.getObjectType(handle.owner)) + .withTailText("::new") + .withTail(COLON_COLON_NEW_TAIL) + .createEliminable("constructorRef ${handle.owner}") + ) + } else { + return listOf( + LookupElementBuilder.create(handle.name.toValidIdentifier()) + .withIcon(PlatformIcons.METHOD_ICON) + .withPresentableText(handle.owner.substringAfterLast('/') + "." + insn.name) + .withTypeText(Type.getReturnType(insn.desc).presentableName()) + .withDefinitionAndFold(insn.name.toValidIdentifier(), "method", definitionValue) + .createEliminable("methodRef ${handle.owner}.${handle.name}${handle.desc}") + ) + } + } + } } return emptyList() diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt index d0463d1c2..a44fdc28c 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionSyntaxHighlighter.kt @@ -71,6 +71,10 @@ class MEExpressionSyntaxHighlighter : SyntaxHighlighterBase() { "MEEXPRESSION_DOT", DefaultLanguageHighlighterColors.DOT ) + val METHOD_REFERENCE = createTextAttributesKey( + "MEEXPRESSION_METHOD_REFERENCE", + DefaultLanguageHighlighterColors.DOT + ) val COMMA = createTextAttributesKey( "MEEXPRESSION_COMMA", DefaultLanguageHighlighterColors.COMMA @@ -129,6 +133,7 @@ class MEExpressionSyntaxHighlighter : SyntaxHighlighterBase() { val BRACKETS_KEYS = arrayOf(BRACKETS) val BRACES_KEYS = arrayOf(BRACES) val DOT_KEYS = arrayOf(DOT) + val METHOD_REFERENCE_KEYS = arrayOf(METHOD_REFERENCE) val COMMA_KEYS = arrayOf(COMMA) val CAPTURE_KEYS = arrayOf(CAPTURE) val WILDCARD_KEYS = arrayOf(WILDCARD) @@ -168,6 +173,9 @@ class MEExpressionSyntaxHighlighter : SyntaxHighlighterBase() { if (tokenType == MEExpressionTypes.TOKEN_DOT) { return DOT_KEYS } + if (tokenType == MEExpressionTypes.TOKEN_METHOD_REF) { + return METHOD_REFERENCE_KEYS + } if (tokenType == MEExpressionTypes.TOKEN_COMMA) { return COMMA_KEYS } diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBoundReferenceExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBoundReferenceExpressionImplMixin.kt new file mode 100644 index 000000000..08e035b61 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEBoundReferenceExpressionImplMixin.kt @@ -0,0 +1,64 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.QualifiedMember +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiMethodReferenceExpression +import com.intellij.psi.util.PsiUtil + +abstract class MEBoundReferenceExpressionImplMixin(node: ASTNode) : MEExpressionImplMixin(node), MEExpression { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiMethodReferenceExpression) { + return false + } + + if (java.isConstructor) { + return false + } + + val qualifier = PsiUtil.skipParenthesizedExprDown(java.qualifierExpression) ?: return false + if (!receiverExpr.matchesJava(qualifier, context)) { + return false + } + + val memberName = this.memberName ?: return false + if (memberName.isWildcard) { + return true + } + + val method = java.resolve() as? PsiMethod ?: return false + val qualifierClass = QualifiedMember.resolveQualifier(java) ?: method.containingClass ?: return false + return context.getMethods(memberName.text).any { reference -> + reference.matchMethod(method, qualifierClass) + } + } + + override fun getInputExprs() = listOf(receiverExpr) + + abstract val receiverExpr: MEExpression + abstract val memberName: MEName? +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEConstructorReferenceExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEConstructorReferenceExpressionImplMixin.kt new file mode 100644 index 000000000..ecaecca53 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEConstructorReferenceExpressionImplMixin.kt @@ -0,0 +1,47 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethodReferenceExpression + +abstract class MEConstructorReferenceExpressionImplMixin(node: ASTNode) : MEExpressionImplMixin(node), MEExpression { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiMethodReferenceExpression) { + return false + } + + if (!java.isConstructor) { + return false + } + + val qualifierType = java.qualifierType?.type ?: return false + return className.matchesJava(qualifierType, context) + } + + override fun getInputExprs() = emptyList() + + abstract val className: METype +} diff --git a/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEFreeMethodReferenceExpressionImplMixin.kt b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEFreeMethodReferenceExpressionImplMixin.kt new file mode 100644 index 000000000..d264eb4ce --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/psi/mixins/impl/MEFreeMethodReferenceExpressionImplMixin.kt @@ -0,0 +1,60 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression.psi.mixins.impl + +import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpression +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEName +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiMethodReferenceExpression + +abstract class MEFreeMethodReferenceExpressionImplMixin(node: ASTNode) : MEExpressionImplMixin(node), MEExpression { + override fun matchesJava(java: PsiElement, context: MESourceMatchContext): Boolean { + if (java !is PsiMethodReferenceExpression) { + return false + } + + if (java.isConstructor) { + return false + } + + val qualifierClass = (java.qualifierType?.type as? PsiClassType)?.resolve() ?: return false + + // check wildcard after checking for the qualifier class, otherwise the reference could have been qualified by + // an expression. + val memberName = this.memberName ?: return false + if (memberName.isWildcard) { + return true + } + + val method = java.resolve() as? PsiMethod ?: return false + return context.getMethods(memberName.text).any { reference -> + reference.matchMethod(method, qualifierClass) + } + } + + override fun getInputExprs() = emptyList() + + abstract val memberName: MEName? +} diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index f0974819e..707f14ec5 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -232,6 +232,7 @@ mixinextras.expression.lang.highlighting.dot.display_name=Dot mixinextras.expression.lang.highlighting.identifier.display_name=Identifier mixinextras.expression.lang.highlighting.keyword.display_name=Keyword mixinextras.expression.lang.highlighting.member_name_identifier.display_name=Identifier//Member name +mixinextras.expression.lang.highlighting.method_reference.display_name=Method reference mixinextras.expression.lang.highlighting.number.display_name=Number mixinextras.expression.lang.highlighting.operator.display_name=Operator mixinextras.expression.lang.highlighting.parens.display_name=Parentheses diff --git a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt index 6a6642809..58b2f1090 100644 --- a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt +++ b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt @@ -571,4 +571,73 @@ class MEExpressionCompletionTest : BaseMixinTest() { """.trimIndent() ) } + + @Test + @DisplayName("Static Method Reference Test") + fun staticMethodReferenceTest() { + assertLookupAppears( + "staticMapper", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("::") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent() + ) + } + + @Test + @DisplayName("Non Static Method Reference Test") + fun nonStaticMethodReferenceTest() { + assertLookupAppears( + "nonStaticMapper", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("this::") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent() + ) + } + + @Test + @DisplayName("Constructor Method Reference Test") + fun constructorMethodReferenceTest() { + assertLookupAppears( + "ConstructedByMethodReference", + """ + package test; + + import com.demonwav.mcdev.mixintestdata.meExpression.MEExpressionTestData; + import com.llamalad7.mixinextras.expression.Expression; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Inject; + + @Mixin(MEExpressionTestData.class) + class MEExpressionCompletionTest { + @Expression("") + @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) + } + """.trimIndent() + ) + } } From 39e8062cefb16ba35d60aedf734e8c7162446d24 Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 7 Apr 2024 16:40:24 +0100 Subject: [PATCH 073/100] Fix presentable name of method reference completions --- .../mixin/expression/MEExpressionCompletionUtil.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index ebc8502d3..fa84a5522 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -860,6 +860,10 @@ object MEExpressionCompletionUtil { } is InvokeDynamicInsnNode -> { if (insn.bsm.owner == "java/lang/invoke/LambdaMetafactory") { + if (!canCompleteExprs) { + return emptyList() + } + val handle = insn.bsmArgs.getOrNull(1) as? Handle ?: return emptyList() val definitionValue = "method = \"L${handle.owner};${handle.name}${handle.desc}\"" if (handle.tag !in Opcodes.H_INVOKEVIRTUAL..Opcodes.H_INVOKEINTERFACE) { @@ -876,9 +880,9 @@ object MEExpressionCompletionUtil { return listOf( LookupElementBuilder.create(handle.name.toValidIdentifier()) .withIcon(PlatformIcons.METHOD_ICON) - .withPresentableText(handle.owner.substringAfterLast('/') + "." + insn.name) - .withTypeText(Type.getReturnType(insn.desc).presentableName()) - .withDefinitionAndFold(insn.name.toValidIdentifier(), "method", definitionValue) + .withPresentableText(handle.owner.substringAfterLast('/') + "." + handle.name) + .withTypeText(Type.getReturnType(handle.desc).presentableName()) + .withDefinitionAndFold(handle.name.toValidIdentifier(), "method", definitionValue) .createEliminable("methodRef ${handle.owner}.${handle.name}${handle.desc}") ) } From 45c192a48a21503f40029bf53a49cc5eef3fbd42 Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 7 Apr 2024 16:54:11 +0100 Subject: [PATCH 074/100] Override matches(Handle) for method MemberDefinition --- .../mixin/expression/MEExpressionMatchUtil.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 83759f6a7..8b193cc8c 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -44,7 +44,9 @@ import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter import com.llamalad7.mixinextras.expression.impl.flow.FlowValue import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool +import com.llamalad7.mixinextras.expression.impl.pool.MemberDefinition import java.util.IdentityHashMap +import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode @@ -162,9 +164,17 @@ object MEExpressionMatchUtil { val methods = annotation.findDeclaredAttributeValue("method")?.computeStringArray() ?: emptyList() for (method in methods) { val methodRef = MemberReference.parse(method) ?: continue - pool.addMember(definitionId) { - it is MethodInsnNode && methodRef.matchMethod(it.owner, it.name, it.desc) - } + pool.addMember( + definitionId, + object : MemberDefinition { + override fun matches(insn: AbstractInsnNode) = + insn is MethodInsnNode && methodRef.matchMethod(insn.owner, insn.name, insn.desc) + + override fun matches(handle: Handle) = + handle.tag in Opcodes.H_INVOKEVIRTUAL..Opcodes.H_INVOKEINTERFACE && + methodRef.matchMethod(handle.owner, handle.name, handle.desc) + } + ) } val types = annotation.findDeclaredAttributeValue("type")?.resolveTypeArray() ?: emptyList() From cec4f2995b3d2fb224179192a1e51e1e48fd78d7 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 10 Apr 2024 12:39:30 +0100 Subject: [PATCH 075/100] Update MixinExtras and don't assume that instructions are part of the method --- build.gradle.kts | 2 +- .../expression/MEExpressionCompletionUtil.kt | 50 ++++++++++++------- .../mixin/expression/MEExpressionMatchUtil.kt | 21 +++++++- .../mixin/handlers/InjectAnnotationHandler.kt | 3 ++ .../handlers/InjectorAnnotationHandler.kt | 5 ++ .../mixin/handlers/ModifyArgHandler.kt | 3 ++ .../mixin/handlers/ModifyArgsHandler.kt | 3 ++ .../mixin/handlers/ModifyConstantHandler.kt | 3 ++ .../mixin/handlers/ModifyVariableHandler.kt | 3 ++ .../mixin/handlers/RedirectInjectorHandler.kt | 3 ++ .../mixinextras/ExpressionInjectionPoint.kt | 7 ++- .../ModifyExpressionValueHandler.kt | 3 ++ .../mixinextras/ModifyReceiverHandler.kt | 3 ++ .../mixinextras/ModifyReturnValueHandler.kt | 3 ++ .../mixinextras/WrapOperationHandler.kt | 3 ++ .../mixinextras/WrapWithConditionHandler.kt | 3 ++ 16 files changed, 95 insertions(+), 23 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e42c073b5..19db1e9f0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:e0f566c")!!) + testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:9b5d2b2")!!) implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index fa84a5522..75e2213a9 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -78,7 +78,6 @@ import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.lookup.TailTypeDecorator import com.intellij.codeInsight.template.Expression -import com.intellij.codeInsight.template.ExpressionContext import com.intellij.codeInsight.template.Template import com.intellij.codeInsight.template.TemplateBuilderImpl import com.intellij.codeInsight.template.TemplateEditingAdapter @@ -111,10 +110,12 @@ import com.intellij.psi.util.parentOfType import com.intellij.psi.util.parents import com.intellij.util.PlatformIcons import com.intellij.util.text.CharArrayUtil -import com.llamalad7.mixinextras.expression.impl.flow.ArrayCreationInfo import com.llamalad7.mixinextras.expression.impl.flow.ComplexFlowValue import com.llamalad7.mixinextras.expression.impl.flow.DummyFlowValue import com.llamalad7.mixinextras.expression.impl.flow.FlowValue +import com.llamalad7.mixinextras.expression.impl.flow.expansion.InsnExpander +import com.llamalad7.mixinextras.expression.impl.flow.postprocessing.ArrayCreationInfo +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool import com.llamalad7.mixinextras.utils.Decorations import org.apache.commons.lang3.mutable.MutableInt @@ -136,6 +137,8 @@ import org.objectweb.asm.tree.MultiANewArrayInsnNode import org.objectweb.asm.tree.TypeInsnNode import org.objectweb.asm.tree.VarInsnNode +private typealias TemplateExpressionContext = com.intellij.codeInsight.template.ExpressionContext + object MEExpressionCompletionUtil { private const val DEBUG_COMPLETION = false @@ -382,7 +385,8 @@ object MEExpressionCompletionUtil { flows, meStatement, targetMethod.instructions, - true + ExpressionContext.Type.MODIFY_EXPRESSION_VALUE, // use most permissive type for completion + true, ) { match -> matchingFlows += match.flow if (DEBUG_COMPLETION) { @@ -428,7 +432,7 @@ object MEExpressionCompletionUtil { val meExpression = MEExpressionMatchUtil.createExpression(exprToMatch.text) ?: continue - val flattenedInstructions = mutableSetOf() + val flattenedInstructions = mutableSetOf() for (flow in matchingFlows) { getInstructionsInFlowTree( flow, @@ -443,8 +447,9 @@ object MEExpressionCompletionUtil { pool, flows, meExpression, - flattenedInstructions, - true + flattenedInstructions.map { it.insn }, + ExpressionContext.Type.MODIFY_EXPRESSION_VALUE, // use most permissive type for completion + true, ) { match -> newMatchingFlows += match.flow if (DEBUG_COMPLETION) { @@ -461,7 +466,7 @@ object MEExpressionCompletionUtil { subExpr = inputExprOnCursor } - val cursorInstructions = mutableSetOf() + val cursorInstructions = mutableSetOf() for (flow in matchingFlows) { getInstructionsInFlowTree(flow, cursorInstructions, false) } @@ -469,7 +474,7 @@ object MEExpressionCompletionUtil { if (DEBUG_COMPLETION) { println("Found ${cursorInstructions.size} matching instructions:") for (insn in cursorInstructions) { - println("- ${insn.textify()}") + println("- ${insn.insn.textify()}") } } @@ -509,7 +514,8 @@ object MEExpressionCompletionUtil { project, targetClass, targetMethod, - insn, + insn.insn, + insn.originalInsn, flows, mixinClass, canCompleteExprs, @@ -675,7 +681,7 @@ object MEExpressionCompletionUtil { private fun getInstructionsInFlowTree( flow: FlowValue, - outInstructions: MutableSet, + outInstructions: MutableSet, strict: Boolean, ) { if (flow is DummyFlowValue || flow is ComplexFlowValue) { @@ -683,7 +689,9 @@ object MEExpressionCompletionUtil { } if (!strict) { - if (!outInstructions.add(flow.insn)) { + val originalInsn = + flow.getDecoration(Decorations.EXPANSION_INFO)?.compound ?: flow.insn + if (!outInstructions.add(ExpandedInstruction(flow.insn, originalInsn))) { return } } @@ -697,6 +705,7 @@ object MEExpressionCompletionUtil { targetClass: ClassNode, targetMethod: MethodNode, insn: AbstractInsnNode, + originalInsn: AbstractInsnNode, flows: FlowMap, mixinClass: PsiClass, canCompleteExprs: Boolean, @@ -721,7 +730,7 @@ object MEExpressionCompletionUtil { project, targetClass, targetMethod, - insn, + originalInsn, insn.`var`, insn.opcode in Opcodes.ISTORE..Opcodes.ASTORE, mixinClass @@ -730,7 +739,7 @@ object MEExpressionCompletionUtil { project, targetClass, targetMethod, - insn, + originalInsn, insn.`var`, false, mixinClass @@ -960,7 +969,7 @@ object MEExpressionCompletionUtil { project: Project, targetClass: ClassNode, targetMethod: MethodNode, - insn: AbstractInsnNode, + originalInsn: AbstractInsnNode, index: Int, isStore: Boolean, mixinClass: PsiClass, @@ -989,7 +998,7 @@ object MEExpressionCompletionUtil { } val validRange = targetMethod.instructions.indexOf(firstValidInstruction) until targetMethod.instructions.indexOf(localVariable.end) - targetMethod.instructions.indexOf(insn) in validRange + targetMethod.instructions.indexOf(originalInsn) in validRange } val locals = localsHere.filter { it.index == index } @@ -1023,7 +1032,7 @@ object MEExpressionCompletionUtil { } // fallback to ASM dataflow - val localTypes = AsmDfaUtil.getLocalVariableTypes(project, targetClass, targetMethod, insn) + val localTypes = AsmDfaUtil.getLocalVariableTypes(project, targetClass, targetMethod, originalInsn) ?: return emptyList() val localType = localTypes.getOrNull(index) ?: return emptyList() val ordinal = localTypes.asSequence().take(index).filter { it == localType }.count() @@ -1145,9 +1154,10 @@ object MEExpressionCompletionUtil { template.replaceElement( elementToReplace, object : Expression() { - override fun calculateLookupItems(context: ExpressionContext?) = lookupItems - override fun calculateQuickResult(context: ExpressionContext?) = calculateResult(context) - override fun calculateResult(context: ExpressionContext?) = TextResult("ordinal = $ordinal") + override fun calculateLookupItems(context: TemplateExpressionContext?) = lookupItems + override fun calculateQuickResult(context: TemplateExpressionContext?) = calculateResult(context) + override fun calculateResult(context: TemplateExpressionContext?) = + TextResult("ordinal = $ordinal") }, true, ) @@ -1298,6 +1308,8 @@ object MEExpressionCompletionUtil { override fun compareTo(other: EliminableLookup) = priority.compareTo(other.priority) } + private data class ExpandedInstruction(val insn: AbstractInsnNode, val originalInsn: AbstractInsnNode) + private class ParenthesesTailType(private val hasParameters: Boolean) : TailType() { override fun processTail(editor: Editor, tailOffset: Int): Int { editor.document.insertString(tailOffset, "()") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 8b193cc8c..80169083c 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -20,6 +20,8 @@ package com.demonwav.mcdev.platform.mixin.expression +import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.util.LocalInfo import com.demonwav.mcdev.platform.mixin.util.MixinConstants @@ -218,6 +220,19 @@ object MEExpressionMatchUtil { } } + fun getContextType(project: Project, annotationName: String?): ExpressionContext.Type { + if (annotationName == null) { + return ExpressionContext.Type.CUSTOM + } + if (annotationName == MixinConstants.Annotations.SLICE) { + return ExpressionContext.Type.SLICE + } + + val handler = MixinAnnotationHandler.forMixinAnnotation(annotationName, project) as? InjectorAnnotationHandler + ?: return ExpressionContext.Type.CUSTOM + return handler.mixinExtrasExpressionContextType + } + inline fun findMatchingInstructions( targetClass: ClassNode, targetMethod: MethodNode, @@ -225,6 +240,7 @@ object MEExpressionMatchUtil { flows: FlowMap, expr: Expression, insns: Iterable, + contextType: ExpressionContext.Type, forCompletion: Boolean, callback: (ExpressionMatch) -> Unit ) { @@ -233,7 +249,7 @@ object MEExpressionMatchUtil { val captured = mutableListOf>() val sink = object : Expression.OutputSink { - override fun capture(node: FlowValue, expr: Expression?) { + override fun capture(node: FlowValue, expr: Expression?, ctx: ExpressionContext?) { captured += node to (expr?.src?.startIndex ?: 0) decorations.getOrPut(insn, ::mutableMapOf).putAll(node.decorations) } @@ -250,7 +266,8 @@ object MEExpressionMatchUtil { val flow = flows[insn] ?: continue try { - if (expr.matches(flow, ExpressionContext(pool, sink, targetClass, targetMethod, forCompletion))) { + val context = ExpressionContext(pool, sink, targetClass, targetMethod, contextType, forCompletion) + if (expr.matches(flow, context)) { for ((capturedFlow, startOffset) in captured) { val capturedInsn = capturedFlow.insnOrNull ?: continue callback(ExpressionMatch(flow, startOffset, decorations[capturedInsn].orEmpty())) diff --git a/src/main/kotlin/platform/mixin/handlers/InjectAnnotationHandler.kt b/src/main/kotlin/platform/mixin/handlers/InjectAnnotationHandler.kt index c9965ca10..0e73bb6b6 100644 --- a/src/main/kotlin/platform/mixin/handlers/InjectAnnotationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/InjectAnnotationHandler.kt @@ -37,6 +37,7 @@ import com.intellij.psi.PsiMethod import com.intellij.psi.PsiQualifiedReference import com.intellij.psi.PsiTypes import com.intellij.psi.util.parentOfType +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.ClassNode @@ -131,4 +132,6 @@ class InjectAnnotationHandler : InjectorAnnotationHandler() { } override val allowCoerce = true + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.INJECT } diff --git a/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt b/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt index 5c1079602..798eef15c 100644 --- a/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt @@ -46,6 +46,7 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiEllipsisType import com.intellij.psi.PsiType import com.intellij.psi.util.PsiModificationTracker +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import java.util.concurrent.ConcurrentHashMap import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.AbstractInsnNode @@ -167,6 +168,8 @@ abstract class InjectorAnnotationHandler : MixinAnnotationHandler { override val isEntryPoint = true + abstract val mixinExtrasExpressionContextType: ExpressionContext.Type + data class InsnResult(val method: ClassAndMethodNode, val result: CollectVisitor.Result<*>) companion object { @@ -211,4 +214,6 @@ object DefaultInjectorAnnotationHandler : InjectorAnnotationHandler() { ) = null override val isSoft = true + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.CUSTOM } diff --git a/src/main/kotlin/platform/mixin/handlers/ModifyArgHandler.kt b/src/main/kotlin/platform/mixin/handlers/ModifyArgHandler.kt index d34c4aad2..69a4197e6 100644 --- a/src/main/kotlin/platform/mixin/handlers/ModifyArgHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/ModifyArgHandler.kt @@ -30,6 +30,7 @@ import com.demonwav.mcdev.util.descriptor import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiMethod +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode @@ -135,4 +136,6 @@ class ModifyArgHandler : InjectorAnnotationHandler() { } } } + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_ARG } diff --git a/src/main/kotlin/platform/mixin/handlers/ModifyArgsHandler.kt b/src/main/kotlin/platform/mixin/handlers/ModifyArgsHandler.kt index 446e9a20f..0d3b5476e 100644 --- a/src/main/kotlin/platform/mixin/handlers/ModifyArgsHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/ModifyArgsHandler.kt @@ -27,6 +27,7 @@ import com.demonwav.mcdev.util.Parameter import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiTypes +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodInsnNode @@ -58,4 +59,6 @@ class ModifyArgsHandler : InjectorAnnotationHandler() { ), ) } + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_ARGS } diff --git a/src/main/kotlin/platform/mixin/handlers/ModifyConstantHandler.kt b/src/main/kotlin/platform/mixin/handlers/ModifyConstantHandler.kt index eebd528de..839d9f832 100644 --- a/src/main/kotlin/platform/mixin/handlers/ModifyConstantHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/ModifyConstantHandler.kt @@ -31,6 +31,7 @@ import com.intellij.psi.PsiMethod import com.intellij.psi.PsiType import com.intellij.psi.PsiTypes import com.intellij.psi.util.parentOfType +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode @@ -131,4 +132,6 @@ class ModifyConstantHandler : InjectorAnnotationHandler() { override fun isInsnAllowed(insn: AbstractInsnNode): Boolean { return insn.opcode in allowedOpcodes } + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_CONSTANT } diff --git a/src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt b/src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt index d9e4eeae9..42398c23d 100644 --- a/src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt @@ -32,6 +32,7 @@ import com.demonwav.mcdev.util.findContainingMethod import com.demonwav.mcdev.util.findModule import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.Type import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode @@ -85,4 +86,6 @@ class ModifyVariableHandler : InjectorAnnotationHandler() { return result } + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_VARIABLE } diff --git a/src/main/kotlin/platform/mixin/handlers/RedirectInjectorHandler.kt b/src/main/kotlin/platform/mixin/handlers/RedirectInjectorHandler.kt index 81e494291..70d2d7fd2 100644 --- a/src/main/kotlin/platform/mixin/handlers/RedirectInjectorHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/RedirectInjectorHandler.kt @@ -39,6 +39,7 @@ import com.intellij.psi.PsiElementFactory import com.intellij.psi.PsiManager import com.intellij.psi.PsiType import com.intellij.psi.PsiTypes +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode @@ -105,6 +106,8 @@ class RedirectInjectorHandler : InjectorAnnotationHandler() { override val allowCoerce = true + override val mixinExtrasExpressionContextType = ExpressionContext.Type.REDIRECT + private interface RedirectType { fun isInsnAllowed(node: AbstractInsnNode) = true diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index 750eb5bcd..b3b05d1ff 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -61,6 +61,7 @@ import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.util.parentOfType import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import java.util.IdentityHashMap import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode @@ -163,6 +164,8 @@ class ExpressionInjectionPoint : InjectionPoint() { val atId = at.findDeclaredAttributeValue("id")?.constantStringValue ?: "" + val contextType = MEExpressionMatchUtil.getContextType(project, at.parentOfType()?.qualifiedName) + val injectorAnnotation = AtResolver.findInjectorAnnotation(at) ?: return null val modifierList = injectorAnnotation.parent as? PsiModifierList ?: return null val parsedExprs = parseExpressions(project, modifierList, atId) @@ -172,7 +175,7 @@ class ExpressionInjectionPoint : InjectionPoint() { val poolFactory = MEExpressionMatchUtil.createIdentifierPoolFactory(module, targetClass, modifierList) - return MyCollectVisitor(mode, project, targetClass, parsedExprs, poolFactory) + return MyCollectVisitor(mode, project, targetClass, parsedExprs, poolFactory, contextType) } private fun parseExpressions( @@ -218,6 +221,7 @@ class ExpressionInjectionPoint : InjectionPoint() { private val targetClass: ClassNode, private val expressions: List>, private val poolFactory: IdentifierPoolFactory, + private val contextType: ExpressionContext.Type, ) : CollectVisitor(mode) { override fun accept(methodNode: MethodNode) { val insns = methodNode.instructions ?: return @@ -235,6 +239,7 @@ class ExpressionInjectionPoint : InjectionPoint() { flows, expr, insns, + contextType, false ) { match -> val capturedExpr = psiExpr.findElementAt(match.startOffset) diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt index ce56b3f4b..d39f196ad 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt @@ -27,6 +27,7 @@ import com.demonwav.mcdev.util.Parameter import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.utils.Decorations import com.llamalad7.mixinextras.utils.TypeUtils import org.objectweb.asm.Type @@ -76,4 +77,6 @@ class ModifyExpressionValueHandler : MixinExtrasInjectorAnnotationHandler() { } return psiReturnType } + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_EXPRESSION_VALUE } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt index 1680f4541..38ec7fc8a 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt @@ -23,6 +23,7 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode @@ -49,4 +50,6 @@ class ModifyReceiverHandler : MixinExtrasInjectorAnnotationHandler() { val params = getPsiParameters(target.insn, targetClass, annotation) ?: return null return ParameterGroup(params) to params[0].type } + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_RECEIVER } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt index 2537fb8eb..df9157186 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt @@ -25,6 +25,7 @@ import com.demonwav.mcdev.platform.mixin.util.getGenericReturnType import com.demonwav.mcdev.util.Parameter import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode @@ -40,4 +41,6 @@ class ModifyReturnValueHandler : MixinExtrasInjectorAnnotationHandler() { val returnType = targetMethod.getGenericReturnType(targetClass, annotation.project) return ParameterGroup(listOf(Parameter("original", returnType))) to returnType } + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_RETURN_VALUE } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt index 27f889991..bf1bcbb30 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt @@ -30,6 +30,7 @@ import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiElement import com.intellij.psi.PsiPrimitiveType import com.intellij.psi.PsiType +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.utils.Decorations import com.llamalad7.mixinextras.utils.TypeUtils import org.objectweb.asm.Type @@ -101,4 +102,6 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { return JavaPsiFacade.getElementFactory(project) .createTypeFromText("$OPERATION<${boxedType.canonicalText}>", context) } + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.WRAP_OPERATION } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt index 6edf8784d..df64324b7 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt @@ -24,6 +24,7 @@ import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType import com.intellij.psi.PsiTypes +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode @@ -47,4 +48,6 @@ class WrapWithConditionHandler : MixinExtrasInjectorAnnotationHandler() { val params = getPsiParameters(target.insn, targetClass, annotation) ?: return null return ParameterGroup(params) to PsiTypes.booleanType() } + + override val mixinExtrasExpressionContextType = ExpressionContext.Type.WRAP_WITH_CONDITION } From 175db4f7e936b22d596b52f9328cf486c6b1fd35 Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 11 Apr 2024 23:50:00 +0100 Subject: [PATCH 076/100] Wrap expanded instructions in a custom type to prevent them accidentally being used when an instruction in the original method is expected or vice versa. --- .../expression/MEExpressionCompletionUtil.kt | 109 +++++++++--------- .../mixin/expression/MEExpressionMatchUtil.kt | 36 ++++-- .../mixinextras/ExpressionInjectionPoint.kt | 8 +- 3 files changed, 84 insertions(+), 69 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 75e2213a9..c06fda783 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -21,7 +21,8 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.MinecraftProjectSettings -import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.insnOrNull +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.virtualInsn +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.virtualInsnOrNull import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEAssignStatement import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEBoundMethodReferenceExpression @@ -384,13 +385,13 @@ object MEExpressionCompletionUtil { pool, flows, meStatement, - targetMethod.instructions, + flows.keys, ExpressionContext.Type.MODIFY_EXPRESSION_VALUE, // use most permissive type for completion true, ) { match -> matchingFlows += match.flow if (DEBUG_COMPLETION) { - println("Matched ${match.flow.insnOrNull?.textify()}") + println("Matched ${match.flow.virtualInsnOrNull?.insn?.textify()}") } } } @@ -453,7 +454,7 @@ object MEExpressionCompletionUtil { ) { match -> newMatchingFlows += match.flow if (DEBUG_COMPLETION) { - println("Matched ${match.flow.insnOrNull?.textify()}") + println("Matched ${match.flow.virtualInsnOrNull?.insn?.textify()}") } } } @@ -474,7 +475,7 @@ object MEExpressionCompletionUtil { if (DEBUG_COMPLETION) { println("Found ${cursorInstructions.size} matching instructions:") for (insn in cursorInstructions) { - println("- ${insn.insn.textify()}") + println("- ${insn.insn.insn.textify()}") } } @@ -666,13 +667,13 @@ object MEExpressionCompletionUtil { } var rootFlow = flow - val insn = flow.insnOrNull ?: return emptyList() - if (insn.opcode == Opcodes.NEW) { + val insn = flow.virtualInsnOrNull ?: return emptyList() + if (insn.insn.opcode == Opcodes.NEW) { rootFlow = flow.next.firstOrNull { - val nextInsn = it.left.insnOrNull ?: return@firstOrNull false + val nextInsn = it.left.virtualInsnOrNull ?: return@firstOrNull false it.right == 0 && - nextInsn.opcode == Opcodes.INVOKESPECIAL && - (nextInsn as MethodInsnNode).name == "" + nextInsn.insn.opcode == Opcodes.INVOKESPECIAL && + (nextInsn.insn as MethodInsnNode).name == "" }?.left ?: rootFlow } @@ -691,7 +692,7 @@ object MEExpressionCompletionUtil { if (!strict) { val originalInsn = flow.getDecoration(Decorations.EXPANSION_INFO)?.compound ?: flow.insn - if (!outInstructions.add(ExpandedInstruction(flow.insn, originalInsn))) { + if (!outInstructions.add(ExpandedInstruction(flow.virtualInsn, originalInsn))) { return } } @@ -704,23 +705,23 @@ object MEExpressionCompletionUtil { project: Project, targetClass: ClassNode, targetMethod: MethodNode, - insn: AbstractInsnNode, + insn: VirtualInsn, originalInsn: AbstractInsnNode, flows: FlowMap, mixinClass: PsiClass, canCompleteExprs: Boolean, canCompleteTypes: Boolean ): List { - when (insn) { + when (insn.insn) { is LdcInsnNode -> { - when (val cst = insn.cst) { + when (val cst = insn.insn.cst) { is Type -> { if (canCompleteTypes && cst.isAccessibleFrom(mixinClass)) { return listOf( createTypeLookup(cst) .withTailText(".class") .withTail(DOT_CLASS_TAIL) - .createEliminable("class ${insn.cst}") + .createEliminable("class ${insn.insn.cst}") ) } } @@ -731,8 +732,8 @@ object MEExpressionCompletionUtil { targetClass, targetMethod, originalInsn, - insn.`var`, - insn.opcode in Opcodes.ISTORE..Opcodes.ASTORE, + insn.insn.`var`, + insn.insn.opcode in Opcodes.ISTORE..Opcodes.ASTORE, mixinClass ) is IincInsnNode -> return createLocalVariableLookups( @@ -740,49 +741,51 @@ object MEExpressionCompletionUtil { targetClass, targetMethod, originalInsn, - insn.`var`, + insn.insn.`var`, false, mixinClass ) is FieldInsnNode -> { if (canCompleteExprs) { - val definitionValue = "field = \"L${insn.owner};${insn.name}:${insn.desc}\"" - var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) + val definitionValue = "field = \"L${insn.insn.owner};${insn.insn.name}:${insn.insn.desc}\"" + var lookup = LookupElementBuilder.create(insn.insn.name.toValidIdentifier()) .withIcon(PlatformIcons.FIELD_ICON) - .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) - .withTypeText(Type.getType(insn.desc).presentableName()) - .withDefinitionAndFold(insn.name.toValidIdentifier(), "field", definitionValue) - if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.PUTSTATIC) { - lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) + .withPresentableText(insn.insn.owner.substringAfterLast('/') + "." + insn.insn.name) + .withTypeText(Type.getType(insn.insn.desc).presentableName()) + .withDefinitionAndFold(insn.insn.name.toValidIdentifier(), "field", definitionValue) + if (insn.insn.opcode == Opcodes.GETSTATIC || insn.insn.opcode == Opcodes.PUTSTATIC) { + lookup = lookup.withLookupString(insn.insn.owner.substringAfterLast('/') + "." + insn.insn.name) } - return listOf(lookup.createEliminable("field ${insn.owner}.${insn.name}:${insn.desc}")) + return listOf( + lookup.createEliminable("field ${insn.insn.owner}.${insn.insn.name}:${insn.insn.desc}") + ) } } is MethodInsnNode -> { if (canCompleteExprs) { - val definitionValue = "method = \"L${insn.owner};${insn.name}${insn.desc}\"" - var lookup = LookupElementBuilder.create(insn.name.toValidIdentifier()) + val definitionValue = "method = \"L${insn.insn.owner};${insn.insn.name}${insn.insn.desc}\"" + var lookup = LookupElementBuilder.create(insn.insn.name.toValidIdentifier()) .withIcon(PlatformIcons.METHOD_ICON) - .withPresentableText(insn.owner.substringAfterLast('/') + "." + insn.name) + .withPresentableText(insn.insn.owner.substringAfterLast('/') + "." + insn.insn.name) .withTailText( - "(" + Type.getArgumentTypes(insn.desc).joinToString { it.presentableName() } + ")" + "(" + Type.getArgumentTypes(insn.insn.desc).joinToString { it.presentableName() } + ")" ) - .withTypeText(Type.getReturnType(insn.desc).presentableName()) - .withDefinitionAndFold(insn.name.toValidIdentifier(), "method", definitionValue) - if (insn.opcode == Opcodes.INVOKESTATIC) { - lookup = lookup.withLookupString(insn.owner.substringAfterLast('/') + "." + insn.name) + .withTypeText(Type.getReturnType(insn.insn.desc).presentableName()) + .withDefinitionAndFold(insn.insn.name.toValidIdentifier(), "method", definitionValue) + if (insn.insn.opcode == Opcodes.INVOKESTATIC) { + lookup = lookup.withLookupString(insn.insn.owner.substringAfterLast('/') + "." + insn.insn.name) } return listOf( - lookup.withTail(ParenthesesTailType(!insn.desc.startsWith("()"))) - .createEliminable("invoke ${insn.owner}.${insn.name}${insn.desc}") + lookup.withTail(ParenthesesTailType(!insn.insn.desc.startsWith("()"))) + .createEliminable("invoke ${insn.insn.owner}.${insn.insn.name}${insn.insn.desc}") ) } } is TypeInsnNode -> { - val type = Type.getObjectType(insn.desc) + val type = Type.getObjectType(insn.insn.desc) if (canCompleteTypes && type.isAccessibleFrom(mixinClass)) { val lookup = createTypeLookup(type) - when (insn.opcode) { + when (insn.insn.opcode) { Opcodes.ANEWARRAY -> { return listOf( lookup.withTail( @@ -791,29 +794,29 @@ object MEExpressionCompletionUtil { flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, ) ) - .createEliminable("new [${insn.desc}") + .createEliminable("new [${insn.insn.desc}") ) } Opcodes.NEW -> { val initCall = flows[insn]?.next?.firstOrNull { - val nextInsn = it.left.insnOrNull ?: return@firstOrNull false + val nextInsn = it.left.virtualInsnOrNull ?: return@firstOrNull false it.right == 0 && - nextInsn.opcode == Opcodes.INVOKESPECIAL && - (nextInsn as MethodInsnNode).name == "" - }?.left?.insn as MethodInsnNode? + nextInsn.insn.opcode == Opcodes.INVOKESPECIAL && + (nextInsn.insn as MethodInsnNode).name == "" + }?.left?.virtualInsn?.insn as MethodInsnNode? return listOf( lookup.withTail(ParenthesesTailType(initCall?.desc?.startsWith("()") == false)) - .createEliminable("new ${insn.desc}${initCall?.desc}") + .createEliminable("new ${insn.insn.desc}${initCall?.desc}") ) } - else -> return listOf(lookup.createEliminable("type ${insn.desc}")) + else -> return listOf(lookup.createEliminable("type ${insn.insn.desc}")) } } } is IntInsnNode -> { - if (insn.opcode == Opcodes.NEWARRAY) { + if (insn.insn.opcode == Opcodes.NEWARRAY) { if (canCompleteTypes) { - val type = when (insn.operand) { + val type = when (insn.insn.operand) { Opcodes.T_BOOLEAN -> "boolean" Opcodes.T_CHAR -> "char" Opcodes.T_FLOAT -> "float" @@ -840,7 +843,7 @@ object MEExpressionCompletionUtil { } is MultiANewArrayInsnNode -> { if (canCompleteTypes) { - val type = Type.getType(insn.desc) + val type = Type.getType(insn.insn.desc) return listOf( createTypeLookup(type.elementType) .withTail( @@ -849,12 +852,12 @@ object MEExpressionCompletionUtil { flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true ) ) - .createEliminable("new ${insn.desc}") + .createEliminable("new ${insn.insn.desc}") ) } } is InsnNode -> { - when (insn.opcode) { + when (insn.insn.opcode) { Opcodes.ARRAYLENGTH -> { if (canCompleteExprs) { return listOf( @@ -868,12 +871,12 @@ object MEExpressionCompletionUtil { } } is InvokeDynamicInsnNode -> { - if (insn.bsm.owner == "java/lang/invoke/LambdaMetafactory") { + if (insn.insn.bsm.owner == "java/lang/invoke/LambdaMetafactory") { if (!canCompleteExprs) { return emptyList() } - val handle = insn.bsmArgs.getOrNull(1) as? Handle ?: return emptyList() + val handle = insn.insn.bsmArgs.getOrNull(1) as? Handle ?: return emptyList() val definitionValue = "method = \"L${handle.owner};${handle.name}${handle.desc}\"" if (handle.tag !in Opcodes.H_INVOKEVIRTUAL..Opcodes.H_INVOKEINTERFACE) { return emptyList() @@ -1308,7 +1311,7 @@ object MEExpressionCompletionUtil { override fun compareTo(other: EliminableLookup) = priority.compareTo(other.priority) } - private data class ExpandedInstruction(val insn: AbstractInsnNode, val originalInsn: AbstractInsnNode) + private data class ExpandedInstruction(val insn: VirtualInsn, val originalInsn: AbstractInsnNode) private class ParenthesesTailType(private val hasParameters: Boolean) : TailType() { override fun processTail(editor: Editor, tailOffset: Int): Int { diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 80169083c..88f71006a 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -44,9 +44,11 @@ import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression import com.llamalad7.mixinextras.expression.impl.flow.ComplexDataException import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter import com.llamalad7.mixinextras.expression.impl.flow.FlowValue +import com.llamalad7.mixinextras.expression.impl.flow.expansion.InsnExpander import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool import com.llamalad7.mixinextras.expression.impl.pool.MemberDefinition +import com.llamalad7.mixinextras.utils.Decorations import java.util.IdentityHashMap import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes @@ -60,7 +62,15 @@ import org.objectweb.asm.tree.VarInsnNode import org.objectweb.asm.tree.analysis.Analyzer typealias IdentifierPoolFactory = (MethodNode) -> IdentifierPool -typealias FlowMap = Map +typealias FlowMap = Map + +/** + * An instruction that MixinExtras generates (via instruction expansion), as opposed to an instruction in the original + * method. One type of instruction cannot be directly assigned to another, to avoid a method instruction being used when + * a virtual instruction is expected and vice versa. + */ +@JvmInline +value class VirtualInsn(val insn: AbstractInsnNode) object MEExpressionMatchUtil { private val LOGGER = logger() @@ -137,7 +147,7 @@ object MEExpressionMatchUtil { return@cached null } - interpreter.finish() + interpreter.finish().mapKeys { (insn, _) -> VirtualInsn(insn) } } } @@ -239,13 +249,13 @@ object MEExpressionMatchUtil { pool: IdentifierPool, flows: FlowMap, expr: Expression, - insns: Iterable, + insns: Iterable, contextType: ExpressionContext.Type, forCompletion: Boolean, callback: (ExpressionMatch) -> Unit ) { for (insn in insns) { - val decorations = IdentityHashMap>() + val decorations = IdentityHashMap>() val captured = mutableListOf>() val sink = object : Expression.OutputSink { @@ -255,12 +265,12 @@ object MEExpressionMatchUtil { } override fun decorate(insn: AbstractInsnNode, key: String, value: Any?) { - decorations.getOrPut(insn, ::mutableMapOf)[key] = value + decorations.getOrPut(VirtualInsn(insn), ::mutableMapOf)[key] = value } override fun decorateInjectorSpecific(insn: AbstractInsnNode, key: String, value: Any?) { // Our maps are per-injector anyway, so this is just a normal decoration. - decorations.getOrPut(insn, ::mutableMapOf)[key] = value + decorations.getOrPut(VirtualInsn(insn), ::mutableMapOf)[key] = value } } @@ -269,8 +279,11 @@ object MEExpressionMatchUtil { val context = ExpressionContext(pool, sink, targetClass, targetMethod, contextType, forCompletion) if (expr.matches(flow, context)) { for ((capturedFlow, startOffset) in captured) { - val capturedInsn = capturedFlow.insnOrNull ?: continue - callback(ExpressionMatch(flow, startOffset, decorations[capturedInsn].orEmpty())) + val capturedInsn = capturedFlow.virtualInsnOrNull ?: continue + val originalInsn = + capturedFlow.getDecoration(Decorations.EXPANSION_INFO)?.compound + ?: capturedInsn.insn + callback(ExpressionMatch(flow, originalInsn, startOffset, decorations[capturedInsn].orEmpty())) } } } catch (e: ProcessCanceledException) { @@ -281,14 +294,17 @@ object MEExpressionMatchUtil { } } - val FlowValue.insnOrNull: AbstractInsnNode? get() = try { - insn + val FlowValue.virtualInsn: VirtualInsn get() = VirtualInsn(insn) + + val FlowValue.virtualInsnOrNull: VirtualInsn? get() = try { + VirtualInsn(insn) } catch (e: ComplexDataException) { null } class ExpressionMatch @PublishedApi internal constructor( val flow: FlowValue, + val originalInsn: AbstractInsnNode, val startOffset: Int, val decorations: Map, ) diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index b3b05d1ff..680d8ea19 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -22,7 +22,6 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras import com.demonwav.mcdev.platform.mixin.expression.IdentifierPoolFactory import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil -import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.insnOrNull import com.demonwav.mcdev.platform.mixin.expression.MESourceMatchContext import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MECapturingExpression import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEStatement @@ -238,7 +237,7 @@ class ExpressionInjectionPoint : InjectionPoint() { pool, flows, expr, - insns, + flows.keys, contextType, false ) { match -> @@ -246,10 +245,7 @@ class ExpressionInjectionPoint : InjectionPoint() { ?.parentOfType(withSelf = true) ?.expression ?: psiExpr - val insn = match.flow.insnOrNull - if (insn != null) { - result.putIfAbsent(insn, capturedExpr to match.decorations) - } + result.putIfAbsent(match.originalInsn, capturedExpr to match.decorations) } } From b85cc23dfcd9e50f0b8fbb87e45711f64b74ba92 Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 12 Apr 2024 20:23:14 +0100 Subject: [PATCH 077/100] Fix IdentityHashMap with VirtualInsn keys --- .../kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 88f71006a..b4d298a0d 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -49,7 +49,6 @@ import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool import com.llamalad7.mixinextras.expression.impl.pool.MemberDefinition import com.llamalad7.mixinextras.utils.Decorations -import java.util.IdentityHashMap import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes import org.objectweb.asm.Type @@ -255,7 +254,7 @@ object MEExpressionMatchUtil { callback: (ExpressionMatch) -> Unit ) { for (insn in insns) { - val decorations = IdentityHashMap>() + val decorations = mutableMapOf>() val captured = mutableListOf>() val sink = object : Expression.OutputSink { From 7255353f2d6c8d5db2f42f5503f28360bbc7fd74 Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 13 Apr 2024 07:58:37 +0100 Subject: [PATCH 078/100] Update MixinExtras --- build.gradle.kts | 2 +- .../platform/mixin/expression/MEExpressionCompletionUtil.kt | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 19db1e9f0..ef5e31f96 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:9b5d2b2")!!) + testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:290f3d8")!!) implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index c06fda783..02ceeba0a 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -115,7 +115,6 @@ import com.llamalad7.mixinextras.expression.impl.flow.ComplexFlowValue import com.llamalad7.mixinextras.expression.impl.flow.DummyFlowValue import com.llamalad7.mixinextras.expression.impl.flow.FlowValue import com.llamalad7.mixinextras.expression.impl.flow.expansion.InsnExpander -import com.llamalad7.mixinextras.expression.impl.flow.postprocessing.ArrayCreationInfo import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool import com.llamalad7.mixinextras.utils.Decorations @@ -661,11 +660,6 @@ object MEExpressionCompletionUtil { } private fun getFlowInputs(flow: FlowValue): List { - val arrayCreationInfo = flow.getDecoration(Decorations.ARRAY_CREATION_INFO) - if (arrayCreationInfo != null) { - return arrayCreationInfo.values - } - var rootFlow = flow val insn = flow.virtualInsnOrNull ?: return emptyList() if (insn.insn.opcode == Opcodes.NEW) { From 87841dc07bb1b9bf4f012d26657a4a3b9fd94130 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 14 Apr 2024 12:45:19 +0100 Subject: [PATCH 079/100] Fix completion of new expressions with new MixinExtras update --- .../meExpression/MEExpressionTestData.java | 3 +++ .../expression/MEExpressionCompletionUtil.kt | 19 +++++++------------ .../expression/MEExpressionCompletionTest.kt | 8 +++++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java index e47074737..c5efbe9a6 100644 --- a/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java +++ b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/meExpression/MEExpressionTestData.java @@ -20,6 +20,7 @@ package com.demonwav.mcdev.mixintestdata.meExpression; +import java.util.ArrayList; import java.util.stream.Stream; public class MEExpressionTestData { @@ -34,6 +35,8 @@ public void complexFunction() { System.out.println(new StringBuilder(local1).append(", ").append(local2)); System.out.println(one); + new ArrayList<>(10); + InaccessibleType varOfInaccessibleType = new InaccessibleType(); acceptInaccessibleType(varOfInaccessibleType); noArgMethod(); diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 02ceeba0a..79bbc216b 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -115,6 +115,7 @@ import com.llamalad7.mixinextras.expression.impl.flow.ComplexFlowValue import com.llamalad7.mixinextras.expression.impl.flow.DummyFlowValue import com.llamalad7.mixinextras.expression.impl.flow.FlowValue import com.llamalad7.mixinextras.expression.impl.flow.expansion.InsnExpander +import com.llamalad7.mixinextras.expression.impl.flow.postprocessing.InstantiationInfo import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool import com.llamalad7.mixinextras.utils.Decorations @@ -663,12 +664,7 @@ object MEExpressionCompletionUtil { var rootFlow = flow val insn = flow.virtualInsnOrNull ?: return emptyList() if (insn.insn.opcode == Opcodes.NEW) { - rootFlow = flow.next.firstOrNull { - val nextInsn = it.left.virtualInsnOrNull ?: return@firstOrNull false - it.right == 0 && - nextInsn.insn.opcode == Opcodes.INVOKESPECIAL && - (nextInsn.insn as MethodInsnNode).name == "" - }?.left ?: rootFlow + rootFlow = flow.getDecoration(Decorations.INSTANTIATION_INFO)?.initCall ?: rootFlow } return (0 until rootFlow.inputCount()).map(rootFlow::getInput) @@ -792,12 +788,11 @@ object MEExpressionCompletionUtil { ) } Opcodes.NEW -> { - val initCall = flows[insn]?.next?.firstOrNull { - val nextInsn = it.left.virtualInsnOrNull ?: return@firstOrNull false - it.right == 0 && - nextInsn.insn.opcode == Opcodes.INVOKESPECIAL && - (nextInsn.insn as MethodInsnNode).name == "" - }?.left?.virtualInsn?.insn as MethodInsnNode? + val initCall = flows[insn] + ?.getDecoration(Decorations.INSTANTIATION_INFO) + ?.initCall + ?.virtualInsnOrNull + ?.insn as MethodInsnNode? return listOf( lookup.withTail(ParenthesesTailType(initCall?.desc?.startsWith("()") == false)) .createEliminable("new ${insn.insn.desc}${initCall?.desc}") diff --git a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt index 58b2f1090..5c12d882d 100644 --- a/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt +++ b/src/test/kotlin/platform/mixin/expression/MEExpressionCompletionTest.kt @@ -350,7 +350,7 @@ class MEExpressionCompletionTest : BaseMixinTest() { @DisplayName("Type Completion Test") fun typeCompletionTest() { doBeforeAfterTest( - "StringBuilder", + "ArrayList", """ package test; @@ -376,10 +376,12 @@ class MEExpressionCompletionTest : BaseMixinTest() { import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; + import java.util.ArrayList; + @Mixin(MEExpressionTestData.class) class MEExpressionCompletionTest { - @Definition(id = "StringBuilder", type = StringBuilder.class) - @Expression("new StringBuilder()") + @Definition(id = "ArrayList", type = ArrayList.class) + @Expression("new ArrayList()") @Inject(method = "complexFunction", at = @At("MIXINEXTRAS:EXPRESSION")) } """.trimIndent(), From d93dd138e2f148180d28e90091e4909324070c63 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 14 Apr 2024 13:16:46 +0100 Subject: [PATCH 080/100] Update MixinExtras, fixes compound instructions --- build.gradle.kts | 2 +- .../expression/MEExpressionCompletionUtil.kt | 17 +++-------------- .../mixin/expression/MEExpressionMatchUtil.kt | 7 ++----- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ef5e31f96..c4572f82b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for MixinExtras expression library - testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:290f3d8")!!) + testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:86c9835")!!) implementation("org.spongepowered:mixin:0.8.4") implementation("org.ow2.asm:asm-util:9.3") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 79bbc216b..115f24b70 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -660,16 +660,6 @@ object MEExpressionCompletionUtil { } } - private fun getFlowInputs(flow: FlowValue): List { - var rootFlow = flow - val insn = flow.virtualInsnOrNull ?: return emptyList() - if (insn.insn.opcode == Opcodes.NEW) { - rootFlow = flow.getDecoration(Decorations.INSTANTIATION_INFO)?.initCall ?: rootFlow - } - - return (0 until rootFlow.inputCount()).map(rootFlow::getInput) - } - private fun getInstructionsInFlowTree( flow: FlowValue, outInstructions: MutableSet, @@ -680,14 +670,13 @@ object MEExpressionCompletionUtil { } if (!strict) { - val originalInsn = - flow.getDecoration(Decorations.EXPANSION_INFO)?.compound ?: flow.insn + val originalInsn = InsnExpander.getRepresentative(flow) ?: flow.insn if (!outInstructions.add(ExpandedInstruction(flow.virtualInsn, originalInsn))) { return } } - for (input in getFlowInputs(flow)) { - getInstructionsInFlowTree(input, outInstructions, false) + for (i in 0 until flow.inputCount()) { + getInstructionsInFlowTree(flow.getInput(i), outInstructions, false) } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index b4d298a0d..f1663f4ea 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -48,7 +48,6 @@ import com.llamalad7.mixinextras.expression.impl.flow.expansion.InsnExpander import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool import com.llamalad7.mixinextras.expression.impl.pool.MemberDefinition -import com.llamalad7.mixinextras.utils.Decorations import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes import org.objectweb.asm.Type @@ -146,7 +145,7 @@ object MEExpressionMatchUtil { return@cached null } - interpreter.finish().mapKeys { (insn, _) -> VirtualInsn(insn) } + interpreter.finish().asSequence().mapNotNull { flow -> flow.virtualInsnOrNull?.let { it to flow } }.toMap() } } @@ -279,9 +278,7 @@ object MEExpressionMatchUtil { if (expr.matches(flow, context)) { for ((capturedFlow, startOffset) in captured) { val capturedInsn = capturedFlow.virtualInsnOrNull ?: continue - val originalInsn = - capturedFlow.getDecoration(Decorations.EXPANSION_INFO)?.compound - ?: capturedInsn.insn + val originalInsn = InsnExpander.getRepresentative(capturedFlow) ?: capturedInsn.insn callback(ExpressionMatch(flow, originalInsn, startOffset, decorations[capturedInsn].orEmpty())) } } From ab50622ea269abeb26452318c606bb1aaa372f1a Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 16 Apr 2024 20:45:59 +0100 Subject: [PATCH 081/100] New: Add support for string concat expressions in MixinExtras. (#2281) --- .../mixinextras/MixinExtrasInjectorAnnotationHandler.kt | 3 +++ .../handlers/mixinextras/ModifyExpressionValueHandler.kt | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt index dfaf12b53..5604134f6 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt @@ -83,6 +83,9 @@ abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler( }, SIMPLE_EXPRESSION { override fun matches(target: TargetInsn) = target.hasDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) + }, + STRING_CONCAT_EXPRESSION { + override fun matches(target: TargetInsn) = target.hasDecoration(Decorations.IS_STRING_CONCAT_EXPRESSION) }; abstract fun matches(target: TargetInsn): Boolean diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt index d39f196ad..4b1bbc983 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt @@ -38,7 +38,7 @@ import org.objectweb.asm.tree.MethodNode class ModifyExpressionValueHandler : MixinExtrasInjectorAnnotationHandler() { override val supportedInstructionTypes = listOf( InstructionType.METHOD_CALL, InstructionType.FIELD_GET, InstructionType.INSTANTIATION, InstructionType.CONSTANT, - InstructionType.SIMPLE_EXPRESSION + InstructionType.SIMPLE_EXPRESSION, InstructionType.STRING_CONCAT_EXPRESSION ) override fun extraTargetRestrictions(insn: AbstractInsnNode): Boolean { @@ -68,6 +68,9 @@ class ModifyExpressionValueHandler : MixinExtrasInjectorAnnotationHandler() { target: TargetInsn, annotation: PsiAnnotation ): PsiType? { + if (target.hasDecoration(Decorations.IS_STRING_CONCAT_EXPRESSION)) { + return PsiType.getJavaLangString(annotation.manager, annotation.resolveScope) + } val psiReturnType = getPsiReturnType(target.insn, annotation) val rawReturnType = getInsnReturnType(target.insn) val exprType = target.getDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) From 5c1cd92f87195eb578cc1173513232a9c7fb93ec Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 19 Apr 2024 19:39:55 +0100 Subject: [PATCH 082/100] Address PR comments --- src/main/grammars/MEExpressionLexer.flex | 103 +++++++++--------- .../mixin/expression/MEExpressionInjector.kt | 2 + .../platform/mixin/util/LocalVariables.kt | 4 + 3 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/main/grammars/MEExpressionLexer.flex b/src/main/grammars/MEExpressionLexer.flex index 24a23cdae..c7d9fad8a 100644 --- a/src/main/grammars/MEExpressionLexer.flex +++ b/src/main/grammars/MEExpressionLexer.flex @@ -30,11 +30,12 @@ import com.intellij.psi.TokenType; %public %class MEExpressionLexer %implements FlexLexer -%unicode %function advance %type IElementType -%eof{ return; -%eof} + +%state STRING + +%unicode WHITE_SPACE = [\ \n\t\r] RESERVED = assert|break|case|catch|const|continue|default|else|finally|for|goto|if|switch|synchronized|try|while|yield|_ @@ -85,63 +86,61 @@ METHOD_REF = :: STRING_TERMINATOR = ' STRING_ESCAPE = \\'|\\\\ -%state STRING - %% { - {WHITE_SPACE}+ { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; } - {RESERVED} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RESERVED; } - {WILDCARD} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_WILDCARD; } - {NEW} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NEW; } - {INSTANCEOF} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_INSTANCEOF; } - {BOOL_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BOOL_LIT; } - {NULL_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NULL_LIT; } - {DO} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DO; } - {RETURN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RETURN; } - {THROW} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_THROW; } - {THIS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_THIS; } - {SUPER} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SUPER; } - {CLASS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_CLASS; } - {IDENTIFIER} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_IDENTIFIER; } - {INT_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_INT_LIT; } - {DEC_LIT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DEC_LIT; } - {PLUS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_PLUS; } - {MINUS} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MINUS; } - {MULT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MULT; } - {DIV} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DIV; } - {MOD} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_MOD; } - {BITWISE_NOT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_NOT; } - {DOT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_DOT; } - {COMMA} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_COMMA; } - {LEFT_PAREN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_PAREN; } - {RIGHT_PAREN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_PAREN; } - {LEFT_BRACKET} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_BRACKET; } - {RIGHT_BRACKET} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_BRACKET; } - {LEFT_BRACE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LEFT_BRACE; } - {RIGHT_BRACE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_RIGHT_BRACE; } - {AT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_AT; } - {SHL} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SHL; } - {SHR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_SHR; } - {USHR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_USHR; } - {LT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LT; } - {LE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_LE; } - {GT} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_GT; } - {GE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_GE; } - {EQ} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_EQ; } - {NE} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_NE; } - {BITWISE_AND} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_AND; } - {BITWISE_XOR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_XOR; } - {BITWISE_OR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_BITWISE_OR; } - {ASSIGN} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_ASSIGN; } - {METHOD_REF} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_METHOD_REF; } + {WHITE_SPACE}+ { return TokenType.WHITE_SPACE; } + {RESERVED} { return MEExpressionTypes.TOKEN_RESERVED; } + {WILDCARD} { return MEExpressionTypes.TOKEN_WILDCARD; } + {NEW} { return MEExpressionTypes.TOKEN_NEW; } + {INSTANCEOF} { return MEExpressionTypes.TOKEN_INSTANCEOF; } + {BOOL_LIT} { return MEExpressionTypes.TOKEN_BOOL_LIT; } + {NULL_LIT} { return MEExpressionTypes.TOKEN_NULL_LIT; } + {DO} { return MEExpressionTypes.TOKEN_DO; } + {RETURN} { return MEExpressionTypes.TOKEN_RETURN; } + {THROW} { return MEExpressionTypes.TOKEN_THROW; } + {THIS} { return MEExpressionTypes.TOKEN_THIS; } + {SUPER} { return MEExpressionTypes.TOKEN_SUPER; } + {CLASS} { return MEExpressionTypes.TOKEN_CLASS; } + {IDENTIFIER} { return MEExpressionTypes.TOKEN_IDENTIFIER; } + {INT_LIT} { return MEExpressionTypes.TOKEN_INT_LIT; } + {DEC_LIT} { return MEExpressionTypes.TOKEN_DEC_LIT; } + {PLUS} { return MEExpressionTypes.TOKEN_PLUS; } + {MINUS} { return MEExpressionTypes.TOKEN_MINUS; } + {MULT} { return MEExpressionTypes.TOKEN_MULT; } + {DIV} { return MEExpressionTypes.TOKEN_DIV; } + {MOD} { return MEExpressionTypes.TOKEN_MOD; } + {BITWISE_NOT} { return MEExpressionTypes.TOKEN_BITWISE_NOT; } + {DOT} { return MEExpressionTypes.TOKEN_DOT; } + {COMMA} { return MEExpressionTypes.TOKEN_COMMA; } + {LEFT_PAREN} { return MEExpressionTypes.TOKEN_LEFT_PAREN; } + {RIGHT_PAREN} { return MEExpressionTypes.TOKEN_RIGHT_PAREN; } + {LEFT_BRACKET} { return MEExpressionTypes.TOKEN_LEFT_BRACKET; } + {RIGHT_BRACKET} { return MEExpressionTypes.TOKEN_RIGHT_BRACKET; } + {LEFT_BRACE} { return MEExpressionTypes.TOKEN_LEFT_BRACE; } + {RIGHT_BRACE} { return MEExpressionTypes.TOKEN_RIGHT_BRACE; } + {AT} { return MEExpressionTypes.TOKEN_AT; } + {SHL} { return MEExpressionTypes.TOKEN_SHL; } + {SHR} { return MEExpressionTypes.TOKEN_SHR; } + {USHR} { return MEExpressionTypes.TOKEN_USHR; } + {LT} { return MEExpressionTypes.TOKEN_LT; } + {LE} { return MEExpressionTypes.TOKEN_LE; } + {GT} { return MEExpressionTypes.TOKEN_GT; } + {GE} { return MEExpressionTypes.TOKEN_GE; } + {EQ} { return MEExpressionTypes.TOKEN_EQ; } + {NE} { return MEExpressionTypes.TOKEN_NE; } + {BITWISE_AND} { return MEExpressionTypes.TOKEN_BITWISE_AND; } + {BITWISE_XOR} { return MEExpressionTypes.TOKEN_BITWISE_XOR; } + {BITWISE_OR} { return MEExpressionTypes.TOKEN_BITWISE_OR; } + {ASSIGN} { return MEExpressionTypes.TOKEN_ASSIGN; } + {METHOD_REF} { return MEExpressionTypes.TOKEN_METHOD_REF; } {STRING_TERMINATOR} { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING_TERMINATOR; } } { - {STRING_ESCAPE} { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING_ESCAPE; } + {STRING_ESCAPE} { return MEExpressionTypes.TOKEN_STRING_ESCAPE; } {STRING_TERMINATOR} { yybegin(YYINITIAL); return MEExpressionTypes.TOKEN_STRING_TERMINATOR; } - [^'\\]+ { yybegin(STRING); return MEExpressionTypes.TOKEN_STRING; } + [^'\\]+ { return MEExpressionTypes.TOKEN_STRING; } } [^] { return TokenType.BAD_CHARACTER; } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt index b832e932e..22de47c71 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionInjector.kt @@ -94,6 +94,8 @@ class MEExpressionInjector : MultiHostInjector { return } + // A Frankenstein injection is an injection where we don't know the entire contents, and therefore errors should + // not be reported. var isFrankenstein = false registrar.startInjecting(MEExpressionLanguage) diff --git a/src/main/kotlin/platform/mixin/util/LocalVariables.kt b/src/main/kotlin/platform/mixin/util/LocalVariables.kt index d153ebd5c..6c5b15441 100644 --- a/src/main/kotlin/platform/mixin/util/LocalVariables.kt +++ b/src/main/kotlin/platform/mixin/util/LocalVariables.kt @@ -861,6 +861,10 @@ object LocalVariables { } } + /** + * Represents a local variable in source code and its probable relationship to the bytecode. Don't store instances + * of this class. + */ data class SourceLocalVariable( val name: String, val type: PsiType, From 3ecde2dd5961afd995ca0c2f2dec3339037d5e22 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 2 Jul 2024 16:34:19 +0100 Subject: [PATCH 083/100] MixinExtras Expressions: Migrate to Expressions library. --- build.gradle.kts | 10 +++-- .../expression/MEExpressionCompletionUtil.kt | 10 ++--- .../mixin/expression/MEExpressionMatchUtil.kt | 7 ++- .../mixin/expression/MEExpressionService.kt | 44 +++++++++++++++++++ .../mixin/expression/MEFlowContext.kt | 26 +++++++++++ .../MixinExtrasInjectorAnnotationHandler.kt | 12 ++--- .../ModifyExpressionValueHandler.kt | 12 ++--- .../mixinextras/WrapOperationHandler.kt | 22 ++++++---- .../kotlin/platform/mixin/util/AsmUtil.kt | 4 +- .../kotlin/platform/mixin/BaseMixinTest.kt | 2 +- 10 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionService.kt create mode 100644 src/main/kotlin/platform/mixin/expression/MEFlowContext.kt diff --git a/build.gradle.kts b/build.gradle.kts index d4ac4965b..99e7431fb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,7 +89,7 @@ repositories { mavenCentral() // TODO: temporary waiting for MixinExtras expression library - maven("https://repo.spongepowered.org/") + maven("https://repo.spongepowered.org/maven/") maven("https://jitpack.io/") { content { includeGroupByRegex("com\\.github\\..+") @@ -101,9 +101,11 @@ dependencies { // Add tools.jar for the JDI API implementation(files(Jvm.current().toolsJar)) - // TODO: temporary waiting for MixinExtras expression library - testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:86c9835")!!) - implementation("org.spongepowered:mixin:0.8.4") + // TODO: temporary waiting for a release + fun mixinExtras(variant: String) = "com.github.LlamaLad7.MixinExtras:mixinextras-$variant:4d2e01e" + + implementation(mixinExtras("expressions")) + testLibs(mixinExtras("common")) implementation("org.ow2.asm:asm-util:9.3") // Kotlin diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 115f24b70..dcda90ba2 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -118,7 +118,7 @@ import com.llamalad7.mixinextras.expression.impl.flow.expansion.InsnExpander import com.llamalad7.mixinextras.expression.impl.flow.postprocessing.InstantiationInfo import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool -import com.llamalad7.mixinextras.utils.Decorations +import com.llamalad7.mixinextras.expression.impl.utils.FlowDecorations import org.apache.commons.lang3.mutable.MutableInt import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes @@ -770,7 +770,7 @@ object MEExpressionCompletionUtil { lookup.withTail( BracketsTailType( 1, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + flows[insn]?.hasDecoration(FlowDecorations.ARRAY_CREATION_INFO) == true, ) ) .createEliminable("new [${insn.insn.desc}") @@ -778,7 +778,7 @@ object MEExpressionCompletionUtil { } Opcodes.NEW -> { val initCall = flows[insn] - ?.getDecoration(Decorations.INSTANTIATION_INFO) + ?.getDecoration(FlowDecorations.INSTANTIATION_INFO) ?.initCall ?.virtualInsnOrNull ?.insn as MethodInsnNode? @@ -811,7 +811,7 @@ object MEExpressionCompletionUtil { .withTail( BracketsTailType( 1, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + flows[insn]?.hasDecoration(FlowDecorations.ARRAY_CREATION_INFO) == true, ) ) .createEliminable("new $type[]") @@ -827,7 +827,7 @@ object MEExpressionCompletionUtil { .withTail( BracketsTailType( type.dimensions, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true + flows[insn]?.hasDecoration(FlowDecorations.ARRAY_CREATION_INFO) == true ) ) .createEliminable("new ${insn.insn.desc}") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index f1663f4ea..1461facc4 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -40,6 +40,7 @@ import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.psi.PsiModifierList import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade +import com.llamalad7.mixinextras.expression.impl.ExpressionService import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression import com.llamalad7.mixinextras.expression.impl.flow.ComplexDataException import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter @@ -73,13 +74,17 @@ value class VirtualInsn(val insn: AbstractInsnNode) object MEExpressionMatchUtil { private val LOGGER = logger() + init { + ExpressionService.offerInstance(MEExpressionService) + } + fun getFlowMap(project: Project, classIn: ClassNode, methodIn: MethodNode): FlowMap? { if (methodIn.instructions == null) { return null } return methodIn.cached(classIn, project) { classNode, methodNode -> - val interpreter = object : FlowInterpreter(classNode, methodNode) { + val interpreter = object : FlowInterpreter(classNode, methodNode, MEFlowContext(project)) { override fun newValue(type: Type?): FlowValue? { ProgressManager.checkCanceled() return super.newValue(type) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt new file mode 100644 index 000000000..5a3408652 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt @@ -0,0 +1,44 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.util.toPsiType +import com.demonwav.mcdev.util.descriptor +import com.intellij.psi.GenericsUtil +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiManager +import com.llamalad7.mixinextras.expression.impl.ExpressionService +import com.llamalad7.mixinextras.expression.impl.flow.FlowContext +import org.objectweb.asm.Type + +object MEExpressionService : ExpressionService() { + override fun getCommonSuperClass(ctx: FlowContext, type1: Type, type2: Type): Type { + ctx as MEFlowContext + val elementFactory = JavaPsiFacade.getElementFactory(ctx.project) + return Type.getType( + GenericsUtil.getLeastUpperBound( + type1.toPsiType(elementFactory), + type2.toPsiType(elementFactory), + PsiManager.getInstance(ctx.project) + )?.descriptor ?: error("Failed to merge types $type1 and $type2!") + ) + } +} diff --git a/src/main/kotlin/platform/mixin/expression/MEFlowContext.kt b/src/main/kotlin/platform/mixin/expression/MEFlowContext.kt new file mode 100644 index 000000000..e7d22f578 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEFlowContext.kt @@ -0,0 +1,26 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.intellij.openapi.project.Project +import com.llamalad7.mixinextras.expression.impl.flow.FlowContext + +class MEFlowContext(val project: Project) : FlowContext diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt index 5604134f6..2e76bcb9b 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt @@ -38,7 +38,7 @@ import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiElement import com.intellij.psi.PsiType import com.intellij.psi.PsiTypes -import com.llamalad7.mixinextras.utils.Decorations +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionDecorations import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode @@ -78,14 +78,16 @@ abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler( }, SIMPLE_OPERATION { override fun matches(target: TargetInsn) = - target.hasDecoration(Decorations.SIMPLE_OPERATION_ARGS) && - target.hasDecoration(Decorations.SIMPLE_OPERATION_RETURN_TYPE) + target.hasDecoration(ExpressionDecorations.SIMPLE_OPERATION_ARGS) && + target.hasDecoration(ExpressionDecorations.SIMPLE_OPERATION_RETURN_TYPE) }, SIMPLE_EXPRESSION { - override fun matches(target: TargetInsn) = target.hasDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) + override fun matches(target: TargetInsn) = + target.hasDecoration(ExpressionDecorations.SIMPLE_EXPRESSION_TYPE) }, STRING_CONCAT_EXPRESSION { - override fun matches(target: TargetInsn) = target.hasDecoration(Decorations.IS_STRING_CONCAT_EXPRESSION) + override fun matches(target: TargetInsn) = + target.hasDecoration(ExpressionDecorations.IS_STRING_CONCAT_EXPRESSION) }; abstract fun matches(target: TargetInsn): Boolean diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt index 4b1bbc983..f16bf4924 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt @@ -28,8 +28,8 @@ import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext -import com.llamalad7.mixinextras.utils.Decorations -import com.llamalad7.mixinextras.utils.TypeUtils +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionASMUtils +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionDecorations import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode @@ -57,8 +57,8 @@ class ModifyExpressionValueHandler : MixinExtrasInjectorAnnotationHandler() { } override fun intLikeTypePositions(target: TargetInsn): List { - val expressionType = target.getDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) - if (expressionType == TypeUtils.INTLIKE_TYPE) { + val expressionType = target.getDecoration(ExpressionDecorations.SIMPLE_EXPRESSION_TYPE) + if (expressionType == ExpressionASMUtils.INTLIKE_TYPE) { return listOf(MethodSignature.TypePosition.Return, MethodSignature.TypePosition.Param(0)) } return emptyList() @@ -68,12 +68,12 @@ class ModifyExpressionValueHandler : MixinExtrasInjectorAnnotationHandler() { target: TargetInsn, annotation: PsiAnnotation ): PsiType? { - if (target.hasDecoration(Decorations.IS_STRING_CONCAT_EXPRESSION)) { + if (target.hasDecoration(ExpressionDecorations.IS_STRING_CONCAT_EXPRESSION)) { return PsiType.getJavaLangString(annotation.manager, annotation.resolveScope) } val psiReturnType = getPsiReturnType(target.insn, annotation) val rawReturnType = getInsnReturnType(target.insn) - val exprType = target.getDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) + val exprType = target.getDecoration(ExpressionDecorations.SIMPLE_EXPRESSION_TYPE) if (exprType != null && rawReturnType != exprType) { // The expression knows more than the standard logic does. return exprType.toPsiType(JavaPsiFacade.getElementFactory(annotation.project)) diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt index 2acb6be78..ef1726cc8 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt @@ -29,8 +29,8 @@ import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext -import com.llamalad7.mixinextras.utils.Decorations -import com.llamalad7.mixinextras.utils.TypeUtils +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionASMUtils +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionDecorations import org.objectweb.asm.Type import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode @@ -60,11 +60,14 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { } override fun intLikeTypePositions(target: TargetInsn) = buildList { - if (target.getDecoration(Decorations.SIMPLE_OPERATION_RETURN_TYPE) == TypeUtils.INTLIKE_TYPE) { + if ( + target.getDecoration(ExpressionDecorations.SIMPLE_OPERATION_RETURN_TYPE) + == ExpressionASMUtils.INTLIKE_TYPE + ) { add(MethodSignature.TypePosition.Return) } - target.getDecoration>(Decorations.SIMPLE_OPERATION_ARGS)?.forEachIndexed { i, it -> - if (it == TypeUtils.INTLIKE_TYPE) { + target.getDecoration>(ExpressionDecorations.SIMPLE_OPERATION_ARGS)?.forEachIndexed { i, it -> + if (it == ExpressionASMUtils.INTLIKE_TYPE) { add(MethodSignature.TypePosition.Param(i)) } } @@ -76,8 +79,11 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { annotation: PsiAnnotation ): List? { getPsiParameters(target.insn, targetClass, annotation)?.let { return it } - val args = target.getDecoration>(Decorations.SIMPLE_OPERATION_ARGS) ?: return null - return args.toList().toParameters(annotation, target.getDecoration(Decorations.SIMPLE_OPERATION_PARAM_NAMES)) + val args = target.getDecoration>(ExpressionDecorations.SIMPLE_OPERATION_ARGS) ?: return null + return args.toList().toParameters( + annotation, + target.getDecoration(ExpressionDecorations.SIMPLE_OPERATION_PARAM_NAMES) + ) } private fun getReturnType( @@ -85,7 +91,7 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { annotation: PsiAnnotation ): PsiType? { getPsiReturnType(target.insn, annotation)?.let { return it } - val type = target.getDecoration(Decorations.SIMPLE_OPERATION_RETURN_TYPE) ?: return null + val type = target.getDecoration(ExpressionDecorations.SIMPLE_OPERATION_RETURN_TYPE) ?: return null return type.toPsiType(JavaPsiFacade.getElementFactory(annotation.project)) } diff --git a/src/main/kotlin/platform/mixin/util/AsmUtil.kt b/src/main/kotlin/platform/mixin/util/AsmUtil.kt index 161ad6a56..981be7320 100644 --- a/src/main/kotlin/platform/mixin/util/AsmUtil.kt +++ b/src/main/kotlin/platform/mixin/util/AsmUtil.kt @@ -76,7 +76,7 @@ import com.intellij.psi.util.CachedValue import com.intellij.psi.util.PsiUtil import com.intellij.refactoring.util.LambdaRefactoringUtil import com.intellij.util.CommonJavaRefactoringUtil -import com.llamalad7.mixinextras.utils.TypeUtils +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionASMUtils import java.io.PrintWriter import java.io.StringWriter import java.lang.reflect.InvocationTargetException @@ -143,7 +143,7 @@ private fun hasModifier(access: Int, @PsiModifier.ModifierConstant modifier: Str } fun Type.toPsiType(elementFactory: PsiElementFactory, context: PsiElement? = null): PsiType { - if (this == TypeUtils.INTLIKE_TYPE) { + if (this == ExpressionASMUtils.INTLIKE_TYPE) { return PsiTypes.intType() } val javaClassName = className.replace("(\\$)(\\D)".toRegex()) { "." + it.groupValues[2] } diff --git a/src/test/kotlin/platform/mixin/BaseMixinTest.kt b/src/test/kotlin/platform/mixin/BaseMixinTest.kt index a2e406849..caac9bc5d 100644 --- a/src/test/kotlin/platform/mixin/BaseMixinTest.kt +++ b/src/test/kotlin/platform/mixin/BaseMixinTest.kt @@ -41,7 +41,7 @@ abstract class BaseMixinTest : BaseMinecraftTest(PlatformType.MIXIN) { fun initMixin() { runWriteTask { mixinLibrary = createLibrary(project, "mixin") - mixinExtrasLibrary = createLibrary(project, "mixinextras-common") // TODO: this will probably change + mixinExtrasLibrary = createLibrary(project, "mixinextras-common") testDataLibrary = createLibrary(project, "mixin-test-data") } From d8a9f837ed1e4f8ee74fad3fec0eccd9ba49b1df Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 2 Jul 2024 19:11:42 +0100 Subject: [PATCH 084/100] Fix: Resolve being unable to get the descriptor for some complex types. --- src/main/kotlin/util/bytecode-utils.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/util/bytecode-utils.kt b/src/main/kotlin/util/bytecode-utils.kt index 5eab8bbcf..777210c54 100644 --- a/src/main/kotlin/util/bytecode-utils.kt +++ b/src/main/kotlin/util/bytecode-utils.kt @@ -69,7 +69,9 @@ fun getPrimitiveType(internalName: Char): PsiPrimitiveType? { } val PsiType.descriptor - get() = appendDescriptor(StringBuilder()).toString() + get() = erasure().appendDescriptor(StringBuilder()).toString() + +private fun PsiType.erasure() = TypeConversionUtil.erasure(this)!! fun getPrimitiveWrapperClass(internalName: Char, project: Project): PsiClass? { val type = getPrimitiveType(internalName) ?: return null From c88fd70b656a73cfd11459579370cb0107de9d35 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 2 Jul 2024 19:46:04 +0100 Subject: [PATCH 085/100] MixinExtras Expressions: Recreate `ClassInfo#getCommonSuperClassOrInterface`. It's quite scuffed and often returns `Object` even when there is a suitable common interface, but what's important is that this matches the runtime logic. --- .../mixin/expression/MEExpressionService.kt | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt index 5a3408652..473717217 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt @@ -22,9 +22,13 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.platform.mixin.util.toPsiType import com.demonwav.mcdev.util.descriptor -import com.intellij.psi.GenericsUtil +import com.intellij.openapi.project.Project import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiElementFactory import com.intellij.psi.PsiManager +import com.intellij.psi.PsiType import com.llamalad7.mixinextras.expression.impl.ExpressionService import com.llamalad7.mixinextras.expression.impl.flow.FlowContext import org.objectweb.asm.Type @@ -34,11 +38,39 @@ object MEExpressionService : ExpressionService() { ctx as MEFlowContext val elementFactory = JavaPsiFacade.getElementFactory(ctx.project) return Type.getType( - GenericsUtil.getLeastUpperBound( - type1.toPsiType(elementFactory), - type2.toPsiType(elementFactory), - PsiManager.getInstance(ctx.project) - )?.descriptor ?: error("Failed to merge types $type1 and $type2!") + getCommonSuperClass( + ctx.project, + type1.toPsiType(elementFactory) as PsiClassType, + type2.toPsiType(elementFactory) as PsiClassType + )?.descriptor ?: error("Could not intersect types $type1 and $type2!") ) } + + // Copied from ClassInfo + private fun getCommonSuperClass( + project: Project, + type1: PsiType, + type2: PsiType + ): PsiClassType? { + val left = (type1 as? PsiClassType)?.resolve() ?: return null + val right = (type2 as? PsiClassType)?.resolve() ?: return null + + fun objectType() = PsiType.getJavaLangObject(PsiManager.getInstance(project), left.resolveScope) + fun PsiClass.type() = PsiElementFactory.getInstance(project).createType(this) + + if (left.isInheritor(right, true)) { + return right.type() + } + if (right.isInheritor(left, true)) { + return left.type() + } + if (left.isInterface || right.isInterface) { + return objectType() + } + + return generateSequence(left) { it.superClass } + .firstOrNull { right.isInheritor(it, true) } + ?.type() + ?: objectType() + } } From a316086a50583a9b78711ea04051222a4f169550 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Wed, 3 Jul 2024 14:46:48 +0100 Subject: [PATCH 086/100] MixinExtras: Fix completion confidence. --- .../platform/mixin/completion/MixinCompletionConfidence.kt | 1 + src/main/kotlin/platform/mixin/util/MixinConstants.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/kotlin/platform/mixin/completion/MixinCompletionConfidence.kt b/src/main/kotlin/platform/mixin/completion/MixinCompletionConfidence.kt index d8d5535a8..d3a5c35f0 100644 --- a/src/main/kotlin/platform/mixin/completion/MixinCompletionConfidence.kt +++ b/src/main/kotlin/platform/mixin/completion/MixinCompletionConfidence.kt @@ -39,6 +39,7 @@ class MixinCompletionConfidence : CompletionConfidence() { PsiJavaPatterns.psiAnnotation().qName( StandardPatterns.or( StandardPatterns.string().startsWith(MixinConstants.PACKAGE), + StandardPatterns.string().startsWith(MixinConstants.MixinExtras.PACKAGE), StandardPatterns.string() .oneOf(MixinAnnotationHandler.getBuiltinHandlers().map { it.first }.toList()), ) diff --git a/src/main/kotlin/platform/mixin/util/MixinConstants.kt b/src/main/kotlin/platform/mixin/util/MixinConstants.kt index 67f145214..173fc2050 100644 --- a/src/main/kotlin/platform/mixin/util/MixinConstants.kt +++ b/src/main/kotlin/platform/mixin/util/MixinConstants.kt @@ -84,6 +84,7 @@ object MixinConstants { } object MixinExtras { + const val PACKAGE = "com.llamalad7.mixinextras." const val OPERATION = "com.llamalad7.mixinextras.injector.wrapoperation.Operation" const val WRAP_OPERATION = "com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation" const val WRAP_METHOD = "com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod" From b30178169fc76b96f702b43d031754a0a8d78be6 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Wed, 3 Jul 2024 18:07:01 +0100 Subject: [PATCH 087/100] Expressions: Autocomplete `method`s and `field`s using flows not instructions. --- .../reference/target/DefinitionReferences.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt b/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt index 3c61547b5..f9b6b69c8 100644 --- a/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt +++ b/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.platform.mixin.reference.target +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.reference.MixinReference import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember @@ -46,12 +47,12 @@ import com.intellij.psi.PsiMember import com.intellij.psi.ResolveResult import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.containers.sequenceOfNotNull -import org.objectweb.asm.tree.AbstractInsnNode +import com.llamalad7.mixinextras.expression.impl.flow.FlowValue import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.MethodInsnNode abstract class AbstractDefinitionReference : PolyReferenceResolver(), MixinReference { - abstract fun getFullReferenceIfMatches(memberReference: MemberReference, insn: AbstractInsnNode): MemberReference? + abstract fun getFullReferenceIfMatches(memberReference: MemberReference, node: FlowValue): MemberReference? abstract fun getMatchesInClass(memberReference: MemberReference, clazz: PsiClass): Sequence abstract fun referenceToString(memberReference: MemberReference): String @@ -110,8 +111,14 @@ abstract class AbstractDefinitionReference : PolyReferenceResolver(), MixinRefer continue } - for (insn in target.classAndMethod.method.instructions) { - val fullReference = getFullReferenceIfMatches(memberReference, insn) ?: continue + val flow = MEExpressionMatchUtil.getFlowMap( + project, + target.classAndMethod.clazz, + target.classAndMethod.method + ) ?: continue + + for (node in flow.values) { + val fullReference = getFullReferenceIfMatches(memberReference, node) ?: continue result += fullReference } } @@ -124,7 +131,8 @@ object FieldDefinitionReference : AbstractDefinitionReference() { val ELEMENT_PATTERN = PsiJavaPatterns.psiLiteral(StandardPatterns.string()) .insideAnnotationAttribute(MixinConstants.MixinExtras.DEFINITION, "field") - override fun getFullReferenceIfMatches(memberReference: MemberReference, insn: AbstractInsnNode): MemberReference? { + override fun getFullReferenceIfMatches(memberReference: MemberReference, node: FlowValue): MemberReference? { + val insn = node.insn if (insn !is FieldInsnNode || !memberReference.matchField(insn.owner, insn.name, insn.desc)) { return null } @@ -145,7 +153,8 @@ object MethodDefinitionReference : AbstractDefinitionReference() { val ELEMENT_PATTERN = PsiJavaPatterns.psiLiteral(StandardPatterns.string()) .insideAnnotationAttribute(MixinConstants.MixinExtras.DEFINITION, "method") - override fun getFullReferenceIfMatches(memberReference: MemberReference, insn: AbstractInsnNode): MemberReference? { + override fun getFullReferenceIfMatches(memberReference: MemberReference, node: FlowValue): MemberReference? { + val insn = node.insn if (insn !is MethodInsnNode || !memberReference.matchMethod(insn.owner, insn.name, insn.desc)) { return null } From a9d6263e4e5ee9e7acc82615206012b3768445f7 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Wed, 3 Jul 2024 18:50:47 +0100 Subject: [PATCH 088/100] Expressions: Autocomplete `method`s for method references. --- build.gradle.kts | 2 +- .../mixin/reference/target/DefinitionReferences.kt | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 99e7431fb..88631624a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -102,7 +102,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for a release - fun mixinExtras(variant: String) = "com.github.LlamaLad7.MixinExtras:mixinextras-$variant:4d2e01e" + fun mixinExtras(variant: String) = "com.github.LlamaLad7.MixinExtras:mixinextras-$variant:371c39e" implementation(mixinExtras("expressions")) testLibs(mixinExtras("common")) diff --git a/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt b/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt index f9b6b69c8..a6927b8a8 100644 --- a/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt +++ b/src/main/kotlin/platform/mixin/reference/target/DefinitionReferences.kt @@ -48,6 +48,8 @@ import com.intellij.psi.ResolveResult import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.containers.sequenceOfNotNull import com.llamalad7.mixinextras.expression.impl.flow.FlowValue +import com.llamalad7.mixinextras.expression.impl.flow.postprocessing.LMFInfo +import com.llamalad7.mixinextras.expression.impl.utils.FlowDecorations import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.MethodInsnNode @@ -154,12 +156,20 @@ object MethodDefinitionReference : AbstractDefinitionReference() { .insideAnnotationAttribute(MixinConstants.MixinExtras.DEFINITION, "method") override fun getFullReferenceIfMatches(memberReference: MemberReference, node: FlowValue): MemberReference? { + val info = node.getDecoration(FlowDecorations.LMF_INFO) val insn = node.insn - if (insn !is MethodInsnNode || !memberReference.matchMethod(insn.owner, insn.name, insn.desc)) { + val (owner, name, desc) = when { + info != null && (info.type == LMFInfo.Type.FREE_METHOD || info.type == LMFInfo.Type.BOUND_METHOD) -> + Triple(info.impl.owner, info.impl.name, info.impl.desc) + + insn is MethodInsnNode -> Triple(insn.owner, insn.name, insn.desc) + else -> return null + } + if (!memberReference.matchMethod(owner, name, desc)) { return null } - return MemberReference(insn.name, insn.desc, insn.owner.replace('/', '.')) + return MemberReference(name, desc, owner.replace('/', '.')) } override fun getMatchesInClass(memberReference: MemberReference, clazz: PsiClass) = From 91c75c7b36627eee7b7a2a2a62f1ef9c306034d4 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 4 Jul 2024 12:30:44 +0100 Subject: [PATCH 089/100] Expressions: A class constant is an expression. --- .../platform/mixin/expression/MEExpressionCompletionUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index dcda90ba2..b3e5d705a 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -695,7 +695,7 @@ object MEExpressionCompletionUtil { is LdcInsnNode -> { when (val cst = insn.insn.cst) { is Type -> { - if (canCompleteTypes && cst.isAccessibleFrom(mixinClass)) { + if (canCompleteExprs && cst.isAccessibleFrom(mixinClass)) { return listOf( createTypeLookup(cst) .withTailText(".class") From 2d797a997bc146447ac0f0b04832c0a74dd066a8 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 4 Jul 2024 13:10:40 +0100 Subject: [PATCH 090/100] Expressions: Show instantiation desc in autocomplete. --- .../expression/MEExpressionCompletionUtil.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index b3e5d705a..4e8bd7fe9 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.MinecraftProjectSettings +import com.demonwav.mcdev.platform.mixin.expression.MEExpressionCompletionUtil.presentableName import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.virtualInsn import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.virtualInsnOrNull import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression @@ -746,9 +747,7 @@ object MEExpressionCompletionUtil { var lookup = LookupElementBuilder.create(insn.insn.name.toValidIdentifier()) .withIcon(PlatformIcons.METHOD_ICON) .withPresentableText(insn.insn.owner.substringAfterLast('/') + "." + insn.insn.name) - .withTailText( - "(" + Type.getArgumentTypes(insn.insn.desc).joinToString { it.presentableName() } + ")" - ) + .withDescTailText(insn.insn.desc) .withTypeText(Type.getReturnType(insn.insn.desc).presentableName()) .withDefinitionAndFold(insn.insn.name.toValidIdentifier(), "method", definitionValue) if (insn.insn.opcode == Opcodes.INVOKESTATIC) { @@ -781,10 +780,13 @@ object MEExpressionCompletionUtil { ?.getDecoration(FlowDecorations.INSTANTIATION_INFO) ?.initCall ?.virtualInsnOrNull - ?.insn as MethodInsnNode? + ?.insn as? MethodInsnNode + ?: return emptyList() return listOf( - lookup.withTail(ParenthesesTailType(initCall?.desc?.startsWith("()") == false)) - .createEliminable("new ${insn.insn.desc}${initCall?.desc}") + lookup + .withDescTailText(initCall.desc) + .withTail(ParenthesesTailType(!initCall.desc.startsWith("()"))) + .createEliminable("new ${insn.insn.desc}${initCall.desc}") ) } else -> return listOf(lookup.createEliminable("type ${insn.insn.desc}")) @@ -1028,6 +1030,11 @@ object MEExpressionCompletionUtil { ) } + private fun LookupElementBuilder.withDescTailText(desc: String) = + withTailText( + Type.getArgumentTypes(desc).joinToString(prefix = "(", postfix = ")") { it.presentableName() } + ) + private fun LookupElement.withTail(tailType: TailType?) = object : TailTypeDecorator(this) { override fun computeTailType(context: InsertionContext?) = tailType } From 72f389d75af396db4701e4ee4b4a566346f8fff6 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 4 Jul 2024 13:26:23 +0100 Subject: [PATCH 091/100] Expressions: Make completions always unique to stop them being filtered. We filter duplicates ourselves. --- .../expression/MEExpressionCompletionUtil.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 4e8bd7fe9..e58d93b9f 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -728,7 +728,7 @@ object MEExpressionCompletionUtil { is FieldInsnNode -> { if (canCompleteExprs) { val definitionValue = "field = \"L${insn.insn.owner};${insn.insn.name}:${insn.insn.desc}\"" - var lookup = LookupElementBuilder.create(insn.insn.name.toValidIdentifier()) + var lookup = createUniqueLookup(insn.insn.name.toValidIdentifier()) .withIcon(PlatformIcons.FIELD_ICON) .withPresentableText(insn.insn.owner.substringAfterLast('/') + "." + insn.insn.name) .withTypeText(Type.getType(insn.insn.desc).presentableName()) @@ -744,7 +744,7 @@ object MEExpressionCompletionUtil { is MethodInsnNode -> { if (canCompleteExprs) { val definitionValue = "method = \"L${insn.insn.owner};${insn.insn.name}${insn.insn.desc}\"" - var lookup = LookupElementBuilder.create(insn.insn.name.toValidIdentifier()) + var lookup = createUniqueLookup(insn.insn.name.toValidIdentifier()) .withIcon(PlatformIcons.METHOD_ICON) .withPresentableText(insn.insn.owner.substringAfterLast('/') + "." + insn.insn.name) .withDescTailText(insn.insn.desc) @@ -808,7 +808,7 @@ object MEExpressionCompletionUtil { else -> "unknown" // wtf? } return listOf( - LookupElementBuilder.create(type) + createUniqueLookup(type) .withIcon(PlatformIcons.CLASS_ICON) .withTail( BracketsTailType( @@ -841,7 +841,7 @@ object MEExpressionCompletionUtil { Opcodes.ARRAYLENGTH -> { if (canCompleteExprs) { return listOf( - LookupElementBuilder.create("length") + createUniqueLookup("length") .withIcon(PlatformIcons.FIELD_ICON) .withTypeText("int") .createEliminable("arraylength") @@ -870,7 +870,7 @@ object MEExpressionCompletionUtil { ) } else { return listOf( - LookupElementBuilder.create(handle.name.toValidIdentifier()) + createUniqueLookup(handle.name.toValidIdentifier()) .withIcon(PlatformIcons.METHOD_ICON) .withPresentableText(handle.owner.substringAfterLast('/') + "." + handle.name) .withTypeText(Type.getReturnType(handle.desc).presentableName()) @@ -937,7 +937,7 @@ object MEExpressionCompletionUtil { private fun createTypeLookup(type: Type): LookupElementBuilder { val definitionId = type.typeNameToInsert() - val lookupElement = LookupElementBuilder.create(definitionId) + val lookupElement = createUniqueLookup(definitionId) .withIcon(PlatformIcons.CLASS_ICON) .withPresentableText(type.presentableName()) @@ -999,7 +999,7 @@ object MEExpressionCompletionUtil { val ordinal = localsOfMyType.indexOf(localVariable) val isImplicit = localsOfMyType.size == 1 val localName = localVariable.name.toValidIdentifier() - LookupElementBuilder.create(localName) + createUniqueLookup(localName) .withIcon(PlatformIcons.VARIABLE_ICON) .withTypeText(localPsiType.presentableText) .withLocalDefinition( @@ -1022,7 +1022,7 @@ object MEExpressionCompletionUtil { val localName = localType.typeNameToInsert().replace("[]", "Array") + (ordinal + 1) val isImplicit = localTypes.count { it == localType } == 1 return listOf( - LookupElementBuilder.create(localName) + createUniqueLookup(localName) .withIcon(PlatformIcons.VARIABLE_ICON) .withTypeText(localType.presentableName()) .withLocalDefinition(localName, localType, ordinal, isArgsOnly, isImplicit, mixinClass) @@ -1285,6 +1285,8 @@ object MEExpressionCompletionUtil { return variants } + private fun createUniqueLookup(text: String) = LookupElementBuilder.create(Any(), text) + private fun LookupElement.createEliminable(uniquenessKey: String, priority: Int = 0) = EliminableLookup(uniquenessKey, this, priority) From 5c32ef94beaa3fcf78d664104c04caf120846377 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 4 Jul 2024 14:27:17 +0100 Subject: [PATCH 092/100] Expressions: Overhaul array completions. Make the preview text more representative of the actual completion and support array literals which were previously missing. --- .../expression/MEExpressionCompletionUtil.kt | 82 +++++++++---------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index e58d93b9f..05f9f62e3 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -21,7 +21,6 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.MinecraftProjectSettings -import com.demonwav.mcdev.platform.mixin.expression.MEExpressionCompletionUtil.presentableName import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.virtualInsn import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil.virtualInsnOrNull import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEArrayAccessExpression @@ -692,6 +691,7 @@ object MEExpressionCompletionUtil { canCompleteExprs: Boolean, canCompleteTypes: Boolean ): List { + val flow = flows[insn] when (insn.insn) { is LdcInsnNode -> { when (val cst = insn.insn.cst) { @@ -765,18 +765,11 @@ object MEExpressionCompletionUtil { val lookup = createTypeLookup(type) when (insn.insn.opcode) { Opcodes.ANEWARRAY -> { - return listOf( - lookup.withTail( - BracketsTailType( - 1, - flows[insn]?.hasDecoration(FlowDecorations.ARRAY_CREATION_INFO) == true, - ) - ) - .createEliminable("new [${insn.insn.desc}") - ) + val arrayType = Type.getType('[' + Type.getObjectType(insn.insn.desc).descriptor) + return createNewArrayCompletion(flow, arrayType) } Opcodes.NEW -> { - val initCall = flows[insn] + val initCall = flow ?.getDecoration(FlowDecorations.INSTANTIATION_INFO) ?.initCall ?.virtualInsnOrNull @@ -796,44 +789,27 @@ object MEExpressionCompletionUtil { is IntInsnNode -> { if (insn.insn.opcode == Opcodes.NEWARRAY) { if (canCompleteTypes) { - val type = when (insn.insn.operand) { - Opcodes.T_BOOLEAN -> "boolean" - Opcodes.T_CHAR -> "char" - Opcodes.T_FLOAT -> "float" - Opcodes.T_DOUBLE -> "double" - Opcodes.T_BYTE -> "byte" - Opcodes.T_SHORT -> "short" - Opcodes.T_INT -> "int" - Opcodes.T_LONG -> "long" - else -> "unknown" // wtf? - } - return listOf( - createUniqueLookup(type) - .withIcon(PlatformIcons.CLASS_ICON) - .withTail( - BracketsTailType( - 1, - flows[insn]?.hasDecoration(FlowDecorations.ARRAY_CREATION_INFO) == true, - ) - ) - .createEliminable("new $type[]") + val arrayType = Type.getType( + when (insn.insn.operand) { + Opcodes.T_BOOLEAN -> "[B" + Opcodes.T_CHAR -> "[C" + Opcodes.T_FLOAT -> "[F" + Opcodes.T_DOUBLE -> "[D" + Opcodes.T_BYTE -> "[B" + Opcodes.T_SHORT -> "[S" + Opcodes.T_INT -> "[I" + Opcodes.T_LONG -> "[J" + else -> "[Lnull;" // wtf? + } ) + return createNewArrayCompletion(flow, arrayType) } } } is MultiANewArrayInsnNode -> { if (canCompleteTypes) { - val type = Type.getType(insn.insn.desc) - return listOf( - createTypeLookup(type.elementType) - .withTail( - BracketsTailType( - type.dimensions, - flows[insn]?.hasDecoration(FlowDecorations.ARRAY_CREATION_INFO) == true - ) - ) - .createEliminable("new ${insn.insn.desc}") - ) + val arrayType = Type.getType(insn.insn.desc) + return createNewArrayCompletion(flow, arrayType) } } is InsnNode -> { @@ -948,6 +924,22 @@ object MEExpressionCompletionUtil { } } + private fun createNewArrayCompletion(flow: FlowValue?, arrayType: Type): List { + val hasInitializer = flow?.hasDecoration(FlowDecorations.ARRAY_CREATION_INFO) == true + val initializerText = if (hasInitializer) "{}" else "" + return listOf( + createTypeLookup(arrayType.elementType) + .withTailText("[]".repeat(arrayType.dimensions) + initializerText) + .withTail( + BracketsTailType( + arrayType.dimensions, + hasInitializer, + ) + ) + .createEliminable("new ${arrayType.descriptor}$initializerText") + ) + } + private fun createLocalVariableLookups( project: Project, targetClass: ClassNode, @@ -1278,6 +1270,10 @@ object MEExpressionCompletionUtil { val fixedNewArrayExpr = factory.createExpression("new ?[?]") as MENewExpression fixedNewArrayExpr.type!!.replace(type) variants += fixedNewArrayExpr + + val arrayLitExpr = factory.createExpression("new ?[]{?}") as MENewExpression + arrayLitExpr.type!!.replace(type) + variants += arrayLitExpr } } } From d139d74a6ad5bfdf1c0638e6746a0eb7bfb7024d Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 4 Jul 2024 14:52:30 +0100 Subject: [PATCH 093/100] Expressions: Fix super call completions. --- .../platform/mixin/expression/MEExpressionCompletionUtil.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 05f9f62e3..1c2617d10 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -1276,6 +1276,12 @@ object MEExpressionCompletionUtil { variants += arrayLitExpr } } + is MESuperCallExpression -> { + // Might be missing its parentheses + val callExpr = factory.createExpression("super.?()") as MESuperCallExpression + expression.memberName?.let { callExpr.memberName!!.replace(it) } + variants += callExpr + } } return variants From a4769e3979585143337378187547c668c14ec42f Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 4 Jul 2024 15:57:49 +0100 Subject: [PATCH 094/100] Expressions: Confidently suggest completions after `::`. --- .../MEExpressionTypedHandlerDelegate.kt | 42 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 1 + 2 files changed, 43 insertions(+) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionTypedHandlerDelegate.kt diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionTypedHandlerDelegate.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionTypedHandlerDelegate.kt new file mode 100644 index 000000000..6d714b43b --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionTypedHandlerDelegate.kt @@ -0,0 +1,42 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.expression.gen.psi.MEExpressionTypes +import com.intellij.codeInsight.AutoPopupController +import com.intellij.codeInsight.editorActions.TypedHandlerDelegate +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.intellij.psi.util.elementType + +class MEExpressionTypedHandlerDelegate : TypedHandlerDelegate() { + override fun checkAutoPopup(charTyped: Char, project: Project, editor: Editor, file: PsiFile): Result { + if (charTyped == ':' && file.language == MEExpressionLanguage) { + AutoPopupController.getInstance(project).autoPopupMemberLookup(editor) { + val offset = editor.caretModel.offset + it.findElementAt(offset - 1).elementType == MEExpressionTypes.TOKEN_METHOD_REF + } + return Result.STOP + } + return Result.CONTINUE + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 307909a6e..273df3975 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -514,6 +514,7 @@ + From 95fab6f518371d924fc54cf7dbfb45b70afca4af Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 4 Jul 2024 17:01:18 +0100 Subject: [PATCH 095/100] Expressions: Add intention action to define unresolved identifiers. --- .../mixin/expression/MEExpressionAnnotator.kt | 49 +++++++++++++++++++ .../expression/MEExpressionCompletionUtil.kt | 15 ++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index bcabbf930..1c70d2326 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -40,16 +40,24 @@ import com.demonwav.mcdev.platform.mixin.expression.gen.psi.METype import com.demonwav.mcdev.platform.mixin.expression.psi.METypeUtil import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.util.findMultiInjectionHost +import com.intellij.codeInsight.AutoPopupController import com.intellij.codeInspection.InspectionManager +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.RemoveAnnotationQuickFix +import com.intellij.lang.annotation.AnnotationBuilder import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.Annotator import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.openapi.project.Project import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile import com.intellij.psi.PsiModifierListOwner +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.util.TypeConversionUtil import com.intellij.psi.util.parentOfType @@ -275,6 +283,7 @@ class MEExpressionAnnotator : Annotator { ) .range(type) .highlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL) + .withDefinitionFix(type) .create() } else { holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) @@ -306,6 +315,7 @@ class MEExpressionAnnotator : Annotator { ) .range(variable) .highlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL) + .withDefinitionFix(variable) .create() } else { holder.newSilentAnnotation(HighlightSeverity.TEXT_ATTRIBUTES) @@ -314,4 +324,43 @@ class MEExpressionAnnotator : Annotator { .create() } } + + private fun AnnotationBuilder.withDefinitionFix(name: MEName) = + withFix(AddDefinitionInspection(name)) + + private class AddDefinitionInspection(name: MEName) : LocalQuickFixAndIntentionActionOnPsiElement(name) { + private val id = name.text + + override fun getFamilyName(): String = "Add @Definition" + + override fun getText(): String = "$familyName(id = \"$id\")" + + override fun invoke( + project: Project, + file: PsiFile, + editor: Editor?, + startElement: PsiElement, + endElement: PsiElement + ) { + if (editor == null) { + MEExpressionCompletionUtil.addDefinition( + startElement, + id, + "" + ) + return + } + val annotation = MEExpressionCompletionUtil.addDefinition( + startElement, + id, + "dummy" + ) ?: return + val dummy = annotation.findAttribute("dummy") as? PsiElement ?: return + val hostEditor = InjectedLanguageEditorUtil.getTopLevelEditor(editor) + hostEditor.caretModel.moveToOffset(dummy.textOffset) + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(hostEditor.document) + hostEditor.document.replaceString(dummy.textRange.startOffset, dummy.textRange.endOffset, "") + AutoPopupController.getInstance(project).autoPopupMemberLookup(hostEditor, null) + } + } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 1c2617d10..3801ad83f 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -1178,6 +1178,11 @@ object MEExpressionCompletionUtil { private fun addDefinition(context: InsertionContext, id: String, definitionValue: String): PsiAnnotation? { val contextElement = context.file.findElementAt(context.startOffset) ?: return null + return addDefinition(contextElement, id, definitionValue) + } + + fun addDefinition(contextElement: PsiElement, id: String, definitionValue: String): PsiAnnotation? { + val project = contextElement.project val injectionHost = contextElement.findMultiInjectionHost() ?: return null val expressionAnnotation = injectionHost.parentOfType() ?: return null if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { @@ -1195,14 +1200,14 @@ object MEExpressionCompletionUtil { } // create and add the new @Definition annotation - var newAnnotation = JavaPsiFacade.getElementFactory(context.project).createAnnotationFromText( + var newAnnotation = JavaPsiFacade.getElementFactory(project).createAnnotationFromText( "@${MixinConstants.MixinExtras.DEFINITION}(id = \"$id\", $definitionValue)", modifierList, ) var anchor = modifierList.annotations.lastOrNull { it.hasQualifiedName(MixinConstants.MixinExtras.DEFINITION) } if (anchor == null) { val definitionPosRelativeToExpression = - MinecraftProjectSettings.getInstance(context.project).definitionPosRelativeToExpression + MinecraftProjectSettings.getInstance(project).definitionPosRelativeToExpression if (definitionPosRelativeToExpression == BeforeOrAfter.AFTER) { anchor = expressionAnnotation } @@ -1211,11 +1216,11 @@ object MEExpressionCompletionUtil { // add imports and reformat newAnnotation = - JavaCodeStyleManager.getInstance(context.project).shortenClassReferences(newAnnotation) as PsiAnnotation - JavaCodeStyleManager.getInstance(context.project).optimizeImports(modifierList.containingFile) + JavaCodeStyleManager.getInstance(project).shortenClassReferences(newAnnotation) as PsiAnnotation + JavaCodeStyleManager.getInstance(project).optimizeImports(modifierList.containingFile) val annotationIndex = modifierList.annotations.indexOf(newAnnotation) val formattedModifierList = - CodeStyleManager.getInstance(context.project).reformat(modifierList) as PsiModifierList + CodeStyleManager.getInstance(project).reformat(modifierList) as PsiModifierList return formattedModifierList.annotations.getOrNull(annotationIndex) } From e654d593a5b7e853aedd5bb5009fd4faa939afa3 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 4 Jul 2024 18:49:31 +0100 Subject: [PATCH 096/100] Expressions: Fix `@Local`s not properly handling compound insns. Also adapt to related MixinExtras changes. --- build.gradle.kts | 2 +- .../mixin/expression/MEExpressionMatchUtil.kt | 27 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 88631624a..0e8cf8ae6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -102,7 +102,7 @@ dependencies { implementation(files(Jvm.current().toolsJar)) // TODO: temporary waiting for a release - fun mixinExtras(variant: String) = "com.github.LlamaLad7.MixinExtras:mixinextras-$variant:371c39e" + fun mixinExtras(variant: String) = "com.github.LlamaLad7.MixinExtras:mixinextras-$variant:2ad48e8" implementation(mixinExtras("expressions")) testLibs(mixinExtras("common")) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index 1461facc4..541cdd455 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -48,7 +48,7 @@ import com.llamalad7.mixinextras.expression.impl.flow.FlowValue import com.llamalad7.mixinextras.expression.impl.flow.expansion.InsnExpander import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool -import com.llamalad7.mixinextras.expression.impl.pool.MemberDefinition +import com.llamalad7.mixinextras.expression.impl.pool.SimpleMemberDefinition import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes import org.objectweb.asm.Type @@ -171,9 +171,12 @@ object MEExpressionMatchUtil { val fields = annotation.findDeclaredAttributeValue("field")?.computeStringArray() ?: emptyList() for (field in fields) { val fieldRef = MemberReference.parse(field) ?: continue - pool.addMember(definitionId) { - it is FieldInsnNode && fieldRef.matchField(it.owner, it.name, it.desc) - } + pool.addMember( + definitionId, + SimpleMemberDefinition { + it is FieldInsnNode && fieldRef.matchField(it.owner, it.name, it.desc) + } + ) } val methods = annotation.findDeclaredAttributeValue("method")?.computeStringArray() ?: emptyList() @@ -181,7 +184,7 @@ object MEExpressionMatchUtil { val methodRef = MemberReference.parse(method) ?: continue pool.addMember( definitionId, - object : MemberDefinition { + object : SimpleMemberDefinition { override fun matches(insn: AbstractInsnNode) = insn is MethodInsnNode && methodRef.matchMethod(insn.owner, insn.name, insn.desc) @@ -202,20 +205,22 @@ object MEExpressionMatchUtil { for (localAnnotation in locals) { val localType = localAnnotation.findDeclaredAttributeValue("type")?.resolveType() val localInfo = LocalInfo.fromAnnotation(localType, localAnnotation) - pool.addMember(definitionId) { insn -> - if (insn !is VarInsnNode) { + pool.addMember(definitionId) { node -> + val virtualInsn = node.insn + if (virtualInsn !is VarInsnNode) { return@addMember false } - val actualInsn = if (insn.opcode >= Opcodes.ISTORE && insn.opcode <= Opcodes.ASTORE) { - insn.next ?: return@addMember false + val physicalInsn = InsnExpander.getRepresentative(node) + val actualInsn = if (virtualInsn.opcode >= Opcodes.ISTORE && virtualInsn.opcode <= Opcodes.ASTORE) { + physicalInsn.next ?: return@addMember false } else { - insn + physicalInsn } val unfilteredLocals = localInfo.getLocals(module, targetClass, targetMethod, actualInsn) ?: return@addMember false val filteredLocals = localInfo.matchLocals(unfilteredLocals, CollectVisitor.Mode.MATCH_ALL) - filteredLocals.any { it.index == insn.`var` } + filteredLocals.any { it.index == virtualInsn.`var` } } } } From a46940cb4cc75e6076653a044fe66833a11c1383 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 9 Jul 2024 20:07:59 +0100 Subject: [PATCH 097/100] Refactor: Add `project` as parameter to `MEExpressionCompletionUtil.addDefinition` --- .../platform/mixin/expression/MEExpressionAnnotator.kt | 2 ++ .../mixin/expression/MEExpressionCompletionUtil.kt | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt index 1c70d2326..638bf6f13 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionAnnotator.kt @@ -344,6 +344,7 @@ class MEExpressionAnnotator : Annotator { ) { if (editor == null) { MEExpressionCompletionUtil.addDefinition( + project, startElement, id, "" @@ -351,6 +352,7 @@ class MEExpressionAnnotator : Annotator { return } val annotation = MEExpressionCompletionUtil.addDefinition( + project, startElement, id, "dummy" diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 3801ad83f..c172b57d6 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -1178,11 +1178,15 @@ object MEExpressionCompletionUtil { private fun addDefinition(context: InsertionContext, id: String, definitionValue: String): PsiAnnotation? { val contextElement = context.file.findElementAt(context.startOffset) ?: return null - return addDefinition(contextElement, id, definitionValue) + return addDefinition(context.project, contextElement, id, definitionValue) } - fun addDefinition(contextElement: PsiElement, id: String, definitionValue: String): PsiAnnotation? { - val project = contextElement.project + fun addDefinition( + project: Project, + contextElement: PsiElement, + id: String, + definitionValue: String + ): PsiAnnotation? { val injectionHost = contextElement.findMultiInjectionHost() ?: return null val expressionAnnotation = injectionHost.parentOfType() ?: return null if (!expressionAnnotation.hasQualifiedName(MixinConstants.MixinExtras.EXPRESSION)) { From a7298d37732f2b80f608c0aa0773353d0371662d Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 12 Jul 2024 19:29:42 +0100 Subject: [PATCH 098/100] Use maven central MixinExtras library --- build.gradle.kts | 14 ++------------ gradle/libs.versions.toml | 3 +++ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 8005fa282..422de09d4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -87,25 +87,15 @@ repositories { } } mavenCentral() - - // TODO: temporary waiting for MixinExtras expression library maven("https://repo.spongepowered.org/maven/") - maven("https://jitpack.io/") { - content { - includeGroupByRegex("com\\.github\\..+") - } - } } dependencies { // Add tools.jar for the JDI API implementation(files(Jvm.current().toolsJar)) - // TODO: temporary waiting for a release - fun mixinExtras(variant: String) = "com.github.LlamaLad7.MixinExtras:mixinextras-$variant:2ad48e8" - - implementation(mixinExtras("expressions")) - testLibs(mixinExtras("common")) + implementation(libs.mixinExtras.expressions) + testLibs(libs.mixinExtras.common) implementation("org.ow2.asm:asm-util:9.3") // Kotlin diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f8dabc6d7..941db8f7c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,7 @@ coroutines-jdk8 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", ve coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" } mappingIo = "net.fabricmc:mapping-io:0.2.1" +mixinExtras-expressions = "io.github.llamalad7:mixinextras-expressions:0.0.1" # GrammarKit jflex-lib = "org.jetbrains.idea:jflex:1.7.0-b7f882a" @@ -40,6 +41,8 @@ junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "jun junit-entine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit-platform" } +mixinExtras-common = "io.github.llamalad7:mixinextras-common:0.5.0-beta.1" + [bundles] coroutines = ["coroutines-core", "coroutines-jdk8", "coroutines-swing"] asm = ["asm", "asm-tree", "asm-analysis"] From 9d1ec2fa39b568769b01e5dd3037d416fa02f402 Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 12 Jul 2024 20:33:41 +0100 Subject: [PATCH 099/100] Remove reference to light service in plugin.xml --- src/main/resources/META-INF/plugin.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 87902834d..b15efd6b2 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -327,7 +327,6 @@ - From ce61bcf3a00dcfea22cba107c6bd6189696b2b82 Mon Sep 17 00:00:00 2001 From: joe Date: Fri, 12 Jul 2024 20:55:40 +0100 Subject: [PATCH 100/100] Use ReentrantReadWriteLock.read and write extension functions --- src/main/kotlin/util/psi-utils.kt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/util/psi-utils.kt b/src/main/kotlin/util/psi-utils.kt index be6bd764d..548ea40ed 100644 --- a/src/main/kotlin/util/psi-utils.kt +++ b/src/main/kotlin/util/psi-utils.kt @@ -75,8 +75,9 @@ import com.intellij.util.IncorrectOperationException import com.siyeh.ig.psiutils.ImportUtils import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap -import java.util.concurrent.locks.ReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write // Parent fun PsiElement.findModule(): Module? = ModuleUtilCore.findModuleForPsiElement(this) @@ -254,7 +255,7 @@ inline fun PsiElement.cached(vararg dependencies: Any, crossinline compute: } @PublishedApi -internal val CACHE_LOCKS_KEY = Key.create, ReadWriteLock>>("mcdev.cacheLock") +internal val CACHE_LOCKS_KEY = Key.create, ReentrantReadWriteLock>>("mcdev.cacheLock") inline fun PsiElement.lockedCached( key: Key>, @@ -264,18 +265,14 @@ inline fun PsiElement.lockedCached( val cacheLocks = (this as UserDataHolderEx).putUserDataIfAbsent(CACHE_LOCKS_KEY, ConcurrentHashMap()) val cacheLock = cacheLocks.computeIfAbsent(key) { ReentrantReadWriteLock() } - cacheLock.readLock().lock() - try { + cacheLock.read { val value = getUserData(key)?.upToDateOrNull if (value != null) { return value.get() } - } finally { - cacheLock.readLock().unlock() } - cacheLock.writeLock().lock() - try { + cacheLock.write { val value = getUserData(key)?.upToDateOrNull if (value != null) { return value.get() @@ -284,8 +281,6 @@ inline fun PsiElement.lockedCached( return CachedValuesManager.getCachedValue(this, key) { CachedValueProvider.Result.create(compute(), *(dependencies.toList() + this).toTypedArray()) } - } finally { - cacheLock.writeLock().unlock() } }