Skip to content

Commit

Permalink
fix parser and highlighting for qualified path in attributes (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkurnikov authored Nov 26, 2024
1 parent e76003b commit bda3084
Show file tree
Hide file tree
Showing 18 changed files with 235 additions and 84 deletions.
15 changes: 7 additions & 8 deletions src/main/grammars/MoveParser.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,13 @@ QualPathCodeFragmentElement ::= PathImpl
Attr ::= '#' '[' <<comma_separated_list AttrItem>> ']' { pin = 1 }
private Attr_first ::= '#'

AttrItem ::= IDENTIFIER (AttrItemList | AttrItemInitializer)?
{
implements = [
"org.move.lang.core.resolve.ref.MvReferenceElement"
// "org.move.lang.core.types.infer.MvInferenceContextOwner"
]
mixin = "org.move.lang.core.psi.ext.MvAttrItemMixin"
}
AttrItem ::= PathImpl (AttrItemList | AttrItemInitializer)?
//{
// implements = [
// "org.move.lang.core.resolve.ref.MvReferenceElement"
// ]
// mixin = "org.move.lang.core.psi.ext.MvAttrItemMixin"
//}
AttrItemList ::= '(' <<comma_separated_list AttrItem>>? ')' { pin = 1 }
//AttrItemArgument ::= IDENTIFIER ('=' Expr)?
//{
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/org/move/ide/MvHighlighter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class MvHighlighter : SyntaxHighlighterBase() {
BYTE_STRING_LITERAL, HEX_STRING_LITERAL -> MvColor.STRING
INTEGER_LITERAL -> MvColor.NUMBER

QUOTE_IDENTIFIER -> MvColor.LABEL

in MOVE_KEYWORDS, BOOL_LITERAL -> MvColor.KEYWORD
else -> null
}
Expand Down
44 changes: 39 additions & 5 deletions src/main/kotlin/org/move/ide/annotator/HighlightingAnnotator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package org.move.ide.annotator

import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.psi.PsiElement
import com.intellij.psi.TokenType.WHITE_SPACE
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.tree.TokenSet
import org.move.cli.settings.moveSettings
import org.move.ide.colors.MvColor
import org.move.lang.MvElementTypes.*
import org.move.lang.core.CONTEXTUAL_KEYWORDS
import org.move.lang.core.psi.*
import org.move.lang.core.psi.ext.*
import org.move.lang.core.tokenSetOf
import org.move.lang.core.types.infer.inference
import org.move.lang.core.types.ty.Ty
import org.move.lang.core.types.ty.TyAdt
Expand All @@ -31,6 +35,7 @@ val SPEC_BUILTIN_FUNCTIONS = setOf(

class HighlightingAnnotator: MvAnnotatorBase() {
override fun annotateInternal(element: PsiElement, holder: AnnotationHolder) {
if (element.elementType == WHITE_SPACE) return
val color = when {
element is LeafPsiElement -> highlightLeaf(element)
element is MvLitExpr && element.text.startsWith("@") -> MvColor.ADDRESS
Expand All @@ -51,9 +56,26 @@ class HighlightingAnnotator: MvAnnotatorBase() {
leafType == QUOTE_IDENTIFIER -> MvColor.LABEL
leafType == HEX_INTEGER_LITERAL -> MvColor.NUMBER
parent is MvAssertMacroExpr -> MvColor.MACRO
parent is MvCopyExpr && element.text == "copy" -> MvColor.KEYWORD

// covers `#, [, ]`, in #[test(my_signer = @0x1)]
parent is MvAttr -> MvColor.ATTRIBUTE
parent is MvCopyExpr
&& element.text == "copy" -> MvColor.KEYWORD
// covers `(, )`, in #[test(my_signer = @0x1)]
parent is MvAttrItemList -> MvColor.ATTRIBUTE
// covers `=` in #[test(my_signer = @0x1)]
parent is MvAttrItemInitializer -> MvColor.ATTRIBUTE

leafType == COLON_COLON -> {
val firstParent =
element.findFirstParent(true) { it is MvAttrItemInitializer || it is MvAttrItem }
if (firstParent == null || firstParent is MvAttrItemInitializer) {
// belongs to the expression path, not highlighted
return null
}
// belongs to the attr item identifier
return MvColor.ATTRIBUTE
}

else -> null
}
}
Expand Down Expand Up @@ -94,7 +116,7 @@ class HighlightingAnnotator: MvAnnotatorBase() {

private fun highlightBindingPat(bindingPat: MvPatBinding): MvColor {
val bindingOwner = bindingPat.parent
if (bindingPat.isReceiverStyleFunctionsEnabled &&
if (bindingPat.isMethodsEnabled &&
bindingOwner is MvFunctionParameter && bindingOwner.isSelfParam
) {
return MvColor.SELF_PARAMETER
Expand All @@ -112,6 +134,9 @@ class HighlightingAnnotator: MvAnnotatorBase() {
val identifierName = path.identifierName
if (identifierName == "Self") return MvColor.KEYWORD

val rootPath = path.rootPath()
if (rootPath.parent is MvAttrItem) return MvColor.ATTRIBUTE

// any qual :: access is not highlighted
if (path.qualifier != null) return null

Expand Down Expand Up @@ -156,7 +181,7 @@ class HighlightingAnnotator: MvAnnotatorBase() {
item is MvConst -> MvColor.CONSTANT
else -> {
val itemParent = item.parent
if (itemParent.isReceiverStyleFunctionsEnabled
if (itemParent.isMethodsEnabled
&& itemParent is MvFunctionParameter && itemParent.isSelfParam
) {
MvColor.SELF_PARAMETER
Expand Down Expand Up @@ -197,7 +222,16 @@ class HighlightingAnnotator: MvAnnotatorBase() {
else -> MvColor.VARIABLE
}

private val PsiElement.isReceiverStyleFunctionsEnabled
private val PsiElement.isMethodsEnabled
get() =
project.moveSettings.enableReceiverStyleFunctions

companion object {
private val HIGHLIGHTED_ELEMENTS = TokenSet.orSet(
tokenSetOf(
IDENTIFIER, QUOTE_IDENTIFIER
),
CONTEXTUAL_KEYWORDS
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class MvUnusedTestSignerInspection: MvLocalInspectionTool() {
val innerAttrItems = attrItem.attrItemList?.attrItemList.orEmpty()
for (innerAttrItem in innerAttrItems) {
val refName = innerAttrItem.unqualifiedName ?: continue
if (innerAttrItem.unresolved) {
if (innerAttrItem.path.unresolved) {
holder.registerProblem(
innerAttrItem,
"Unused test signer",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class MvUnusedVariableInspection: MvLocalInspectionTool() {
if (bindingName.startsWith("_")) return

val references = binding.searchReferences()
// filter out #[test] attributes
.filter { it.element !is MvAttrItem }
// filter out `#[test(signer1, signer2)]` declarations
.filter { it.element.parent !is MvAttrItem }
if (references.none()) {
val fixes = when (binding.bindingTypeOwner) {
is MvFunctionParameter -> arrayOf(
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/org/move/lang/MoveParserDefinition.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ class MoveParserDefinition : ParserDefinition {
/**
* Should be increased after any change of parser rules
*/
const val PARSER_VERSION: Int = LEXER_VERSION + 53
const val PARSER_VERSION: Int = LEXER_VERSION + 54
}
}
21 changes: 12 additions & 9 deletions src/main/kotlin/org/move/lang/core/MvTokenType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ class MvTokenType(debugName: String): IElementType(debugName, MoveLanguage)

fun tokenSetOf(vararg tokens: IElementType) = TokenSet.create(*tokens)

val MOVE_KEYWORDS = tokenSetOf(
LET, MUT, ABORT, BREAK, CONTINUE, IF, ELSE, LOOP, RETURN, AS, WHILE, FOR,
SCRIPT_KW, ADDRESS, MODULE_KW, PUBLIC, FUN, STRUCT_KW, ACQUIRES, USE, HAS, PHANTOM,
MOVE, CONST_KW, NATIVE, FRIEND, ENTRY, INLINE,
ASSUME, ASSERT, REQUIRES, ENSURES, INVARIANT, MODIFIES, PRAGMA, INCLUDE, ABORTS_IF, WITH, UPDATE, DECREASES,
SPEC, SCHEMA_KW, GLOBAL, LOCAL,
EMITS, APPLY, TO, EXCEPT, INTERNAL,
FORALL, EXISTS, IN, WHERE, MATCH_KW,
val CONTEXTUAL_KEYWORDS = tokenSetOf(ENUM_KW, MATCH_KW)
val MOVE_KEYWORDS = TokenSet.orSet(
tokenSetOf(
LET, MUT, ABORT, BREAK, CONTINUE, IF, ELSE, LOOP, RETURN, AS, WHILE, FOR,
SCRIPT_KW, ADDRESS, MODULE_KW, PUBLIC, FUN, STRUCT_KW, ACQUIRES, USE, HAS, PHANTOM,
MOVE, CONST_KW, NATIVE, FRIEND, ENTRY, INLINE,
ASSUME, ASSERT, REQUIRES, ENSURES, INVARIANT, MODIFIES, PRAGMA, INCLUDE, ABORTS_IF, WITH, UPDATE, DECREASES,
SPEC, SCHEMA_KW, GLOBAL, LOCAL,
EMITS, APPLY, TO, EXCEPT, INTERNAL,
FORALL, EXISTS, IN, WHERE, ENUM_KW, MATCH_KW,
),
// CONTEXTUAL_KEYWORDS
)
val CONTEXTUAL_KEYWORDS = tokenSetOf(MATCH_KW)

val FUNCTION_MODIFIERS = tokenSetOf(VISIBILITY_MODIFIER, NATIVE, ENTRY, INLINE)
val TYPES = tokenSetOf(PATH_TYPE, REF_TYPE, TUPLE_TYPE)
Expand Down
44 changes: 29 additions & 15 deletions src/main/kotlin/org/move/lang/core/psi/ext/MvAttrItem.kt
Original file line number Diff line number Diff line change
@@ -1,41 +1,55 @@
package org.move.lang.core.psi.ext

import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import org.move.lang.core.psi.*
import org.move.lang.core.psi.impl.MvNamedElementImpl
import org.move.lang.core.resolve.ref.MvPath2Reference
import org.move.lang.core.resolve.ref.MvPolyVariantReference
import org.move.lang.core.resolve.ref.MvPolyVariantReferenceBase
import org.move.lang.core.resolve.ref.MvPolyVariantReferenceCached

val MvAttrItem.unqualifiedName: String get() = this.identifier.text
import org.move.lang.core.resolve2.PathKind
import org.move.lang.core.resolve2.pathKind

val MvAttrItem.unqualifiedIdent: PsiElement?
get() {
val path = this.path
if (path.pathKind(false) !is PathKind.UnqualifiedPath) return null
return path.identifier
}
val MvAttrItem.unqualifiedName: String? get() = this.unqualifiedIdent?.text

val MvAttrItem.attr: MvAttr? get() = this.parent as? MvAttr
val MvAttrItem.innerAttrItems: List<MvAttrItem> get() = this.attrItemList?.attrItemList.orEmpty()

val MvAttrItem.isAbortCode: Boolean get() = this.identifier.textMatches("abort_code")
val MvAttrItem.isTest: Boolean get() = this.identifier.textMatches("test")
val MvAttrItem.isAbortCode: Boolean get() = this.unqualifiedIdent?.textMatches("abort_code") == true
val MvAttrItem.isTest: Boolean get() = this.unqualifiedIdent?.textMatches("test") == true

class AttrItemReferenceImpl(
element: MvAttrItem,
element: MvPath,
val ownerFunction: MvFunction
) : MvPolyVariantReferenceCached<MvAttrItem>(element) {
) : MvPolyVariantReferenceBase<MvPath>(element), MvPath2Reference {

override fun multiResolveInner(): List<MvNamedElement> {
override fun multiResolve(): List<MvNamedElement> {
return ownerFunction.parameters
.map { it.patBinding }
.filter { it.name == element.referenceName }
}

// override fun multiResolveInner(): List<MvNamedElement> {
// }
}

abstract class MvAttrItemMixin(node: ASTNode): MvNamedElementImpl(node),
MvAttrItem {

override fun getReference(): MvPolyVariantReference? {
val attr = this.ancestorStrict<MvAttr>() ?: return null
attr.attrItemList
.singleOrNull()
?.takeIf { it.isTest } ?: return null
val ownerFunction = attr.attributeOwner as? MvFunction ?: return null
return AttrItemReferenceImpl(this, ownerFunction)
}
// override fun getReference(): MvPolyVariantReference? {
// val attr = this.ancestorStrict<MvAttr>() ?: return null
// attr.attrItemList
// .singleOrNull()
// ?.takeIf { it.isTest } ?: return null
// val ownerFunction = attr.attributeOwner as? MvFunction ?: return null
// return AttrItemReferenceImpl(this, ownerFunction)
// }

}
19 changes: 17 additions & 2 deletions src/main/kotlin/org/move/lang/core/psi/ext/MvPath.kt
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ fun MvPath.allowedNamespaces(isCompletion: Boolean = false): Set<Namespace> {

// can be anything in completion
parent is MvPathExpr -> if (isCompletion) ALL_NAMESPACES else NAMES
// }

parent is MvSchemaLit
|| parent is MvSchemaRef -> SCHEMAS
parent is MvStructLitExpr
Expand All @@ -137,6 +137,9 @@ fun MvPath.allowedNamespaces(isCompletion: Boolean = false): Set<Namespace> {
parent is MvFriendDecl -> MODULES
parent is MvModuleSpec -> MODULES

// should not be used for attr items
parent is MvAttrItem -> NONE

else -> debugErrorOrFallback(
"Cannot build path namespaces: unhandled parent type ${parent?.elementType}",
NAMES
Expand All @@ -157,7 +160,19 @@ val MvPath.qualifier: MvPath?

abstract class MvPathMixin(node: ASTNode): MvElementImpl(node), MvPath {

override fun getReference(): MvPath2Reference? = MvPath2ReferenceImpl(this)
override fun getReference(): MvPath2Reference? {
if (referenceName == null) return null
return when (val parent = parent) {
is MvAttrItem -> {
val attrItemList = (parent.parent as? MvAttrItemList) ?: return null
val parentAttrItem = (attrItemList.parent as? MvAttrItem)?.takeIf { it.isTest } ?: return null
val attr = parentAttrItem.attr ?: return null
val ownerFunction = attr.attributeOwner as? MvFunction ?: return null
AttrItemReferenceImpl(this, ownerFunction)
}
else -> MvPath2ReferenceImpl(this)
}
}
}

val MvPath.hasColonColon: Boolean get() = colonColon != null
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/move/lang/core/stubs/Stubs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ interface MvAttributeOwnerStub {
var verifyOnly = false
for (attrItem in query.attrItems) {
hasAttrs = true
if (attrItem.referenceName == "test_only") {
if (attrItem.unqualifiedName == "test_only") {
testOnly = true
}
if (attrItem.referenceName == "verify_only") {
if (attrItem.unqualifiedName == "verify_only") {
verifyOnly = true
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.move.ide.annotator

import org.move.ide.colors.MvColor
import org.move.utils.tests.annotation.AnnotatorTestCase

class AttrHighlightingAnnotatorTest: AnnotatorTestCase(HighlightingAnnotator::class) {
override fun setUp() {
super.setUp()
annotationFixture.registerSeverities(MvColor.entries.map(MvColor::testSeverity))
}

fun `test highlight annotator identifier`() = checkHighlighting("""
module 0x1::m {
<ATTRIBUTE>#</ATTRIBUTE><ATTRIBUTE>[</ATTRIBUTE><ATTRIBUTE>view</ATTRIBUTE><ATTRIBUTE>]</ATTRIBUTE>
fun foo() {}
}
""")

fun `test highlight annotator fq name`() = checkHighlighting("""
module 0x1::m {
<ATTRIBUTE>#</ATTRIBUTE><ATTRIBUTE>[</ATTRIBUTE><ATTRIBUTE>lint</ATTRIBUTE><ATTRIBUTE>::</ATTRIBUTE><ATTRIBUTE>view</ATTRIBUTE><ATTRIBUTE>]</ATTRIBUTE>
fun foo() {}
}
""")

fun `test highlight annotator initializer`() = checkHighlighting("""
module 0x1::m {
<ATTRIBUTE>#</ATTRIBUTE><ATTRIBUTE>[</ATTRIBUTE><ATTRIBUTE>test</ATTRIBUTE><ATTRIBUTE>(</ATTRIBUTE><ATTRIBUTE>signer</ATTRIBUTE> <ATTRIBUTE>=</ATTRIBUTE> <ADDRESS>@0x1</ADDRESS>)<ATTRIBUTE>]</ATTRIBUTE>
fun foo() {}
}
""")
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.move.utils.tests.annotation.AnnotatorTestCase
class HighlightingAnnotatorTest: AnnotatorTestCase(HighlightingAnnotator::class) {
override fun setUp() {
super.setUp()
annotationFixture.registerSeverities(MvColor.values().map(MvColor::testSeverity))
annotationFixture.registerSeverities(MvColor.entries.map(MvColor::testSeverity))
}

fun `test block comment do not break the highlighting`() = checkHighlighting(
Expand Down Expand Up @@ -355,13 +355,6 @@ class HighlightingAnnotatorTest: AnnotatorTestCase(HighlightingAnnotator::class)
}
""")

fun `test attribute highlighting`() = checkHighlighting("""
module 0x1::m {
<ATTRIBUTE>#</ATTRIBUTE><ATTRIBUTE>[</ATTRIBUTE><ATTRIBUTE>view</ATTRIBUTE><ATTRIBUTE>]</ATTRIBUTE>
fun foo() {}
}
""")

@MoveV2
fun `test enums and variants are highlighted`() = checkHighlighting("""
module 0x1::m {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,4 +562,11 @@ module 0x1::m {
}
}
""")

fun `test no error for path in attr`() = checkByText("""
module 0x1::m {
#[lint::my_lint]
fun main() {}
}
""")
}
9 changes: 9 additions & 0 deletions src/test/kotlin/org/move/lang/resolve/ResolveVariablesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -753,4 +753,13 @@ module 0x1::string_tests {
}
}
""")

fun `test no attr item signer reference for not direct children of test`() = checkByCode("""
module 0x1::m {
#[test(unknown_attr(my_signer = @0x1))]
//^ unresolved
fun test_main(my_signer: signer) {
}
}
""")
}
Loading

0 comments on commit bda3084

Please sign in to comment.