diff --git a/src/main/kotlin/org/move/ide/annotator/MvErrorAnnotator.kt b/src/main/kotlin/org/move/ide/annotator/MvErrorAnnotator.kt index 44439718..25c2dd58 100644 --- a/src/main/kotlin/org/move/ide/annotator/MvErrorAnnotator.kt +++ b/src/main/kotlin/org/move/ide/annotator/MvErrorAnnotator.kt @@ -160,6 +160,8 @@ class MvErrorAnnotator: MvAnnotatorBase() { } } + override fun visitPatTupleStruct(o: MvPatTupleStruct) = checkPatTupleStruct(moveHolder, o) + override fun visitStructLitExpr(o: MvStructLitExpr) { val nameElement = o.path.referenceNameElement ?: return val struct = o.path.maybeStruct ?: return @@ -356,6 +358,23 @@ class MvErrorAnnotator: MvAnnotatorBase() { } } } + + private fun checkPatTupleStruct(holder: MvAnnotationHolder, patTupleStruct: MvPatTupleStruct) { + val declaration = patTupleStruct.path.reference?.resolveFollowingAliases() as? MvFieldsOwner ?: return + + val declarationFieldsAmount = declaration.fields.size + // Rest is non-binding, meaning it is accepted even if all fields are already bound + val bodyFieldsAmount = patTupleStruct.patList.filterNot { it is MvPatRest }.size + + if (bodyFieldsAmount < declarationFieldsAmount && patTupleStruct.patRest == null) { + Diagnostic.MissingFieldsInTuplePattern( + patTupleStruct, + declaration, + declarationFieldsAmount, + bodyFieldsAmount + ).addToHolder(holder) + } + } } private fun checkMissingFields( diff --git a/src/main/kotlin/org/move/lang/core/psi/ext/MvPatTupleStruct.kt b/src/main/kotlin/org/move/lang/core/psi/ext/MvPatTupleStruct.kt new file mode 100644 index 00000000..50b5eff5 --- /dev/null +++ b/src/main/kotlin/org/move/lang/core/psi/ext/MvPatTupleStruct.kt @@ -0,0 +1,6 @@ +package org.move.lang.core.psi.ext + +import org.move.lang.core.psi.MvPatRest +import org.move.lang.core.psi.MvPatTupleStruct + +val MvPatTupleStruct.patRest: MvPatRest? get() = patList.firstOrNull { it is MvPatRest } as? MvPatRest \ No newline at end of file diff --git a/src/main/kotlin/org/move/lang/utils/Diagnostic.kt b/src/main/kotlin/org/move/lang/utils/Diagnostic.kt index 417cf019..e9305f38 100644 --- a/src/main/kotlin/org/move/lang/utils/Diagnostic.kt +++ b/src/main/kotlin/org/move/lang/utils/Diagnostic.kt @@ -230,6 +230,23 @@ sealed class Diagnostic( } } + class MissingFieldsInTuplePattern( + pat: MvPat, + private val declaration: MvFieldsOwner, + private val expectedAmount: Int, + private val actualAmount: Int + ): Diagnostic(pat) { + + override fun prepare(): PreparedAnnotation { + val itemType = if (declaration is MvEnumVariant) "Enum variant" else "Tuple struct" + return PreparedAnnotation( + ERROR, + "$itemType pattern does not correspond to its declaration: " + + "expected $expectedAmount ${pluralize("field", expectedAmount)}, found $actualAmount" + ) + } + } + class MissingFieldsInStructPattern( patStruct: MvPatStruct, private val declaration: MvFieldsOwner, diff --git a/src/test/kotlin/org/move/ide/annotator/errors/StructFieldsNumberErrorTest.kt b/src/test/kotlin/org/move/ide/annotator/errors/StructFieldsNumberErrorTest.kt index 0331ccd5..a37ac4ca 100644 --- a/src/test/kotlin/org/move/ide/annotator/errors/StructFieldsNumberErrorTest.kt +++ b/src/test/kotlin/org/move/ide/annotator/errors/StructFieldsNumberErrorTest.kt @@ -53,4 +53,30 @@ class StructFieldsNumberErrorTest: AnnotatorTestCase(MvErrorAnnotator::class) { } """) + fun `test missing positional fields for struct`() = checkErrors(""" + module 0x1::m { + struct S(u8, u8); + fun main(s: S) { + let S (val) = s; + } + } + """) + + fun `test missing positional fields with rest`() = checkErrors(""" + module 0x1::m { + struct S(u8, u8); + fun main(s: S) { + let S(val, ..) = s; + } + } + """) + + fun `test missing positional fields for enum variant`() = checkErrors(""" + module 0x1::m { + enum S { Inner(u8, u8) } + fun main(s: S) { + let S::Inner(val) = s; + } + } + """) }