From 386b61b8bea26405080581e9e0daa6fa50c84b96 Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Wed, 15 Jan 2025 16:54:39 +0100 Subject: [PATCH 1/6] fix unresolved paths in struct field / schema field completion --- .../providers/StructFieldsCompletionProvider.kt | 12 ++++++++++-- .../org/move/lang/core/psi/ext/MvSchemaLitField.kt | 4 +++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt b/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt index f4d4e1568..584132dde 100644 --- a/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt +++ b/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt @@ -9,6 +9,7 @@ import com.intellij.psi.PsiElement import com.intellij.util.ProcessingContext import org.move.lang.core.completion.MvCompletionContext import org.move.lang.core.completion.createLookupElement +import org.move.lang.core.completion.safeGetOriginalOrSelf import org.move.lang.core.psi.* import org.move.lang.core.psi.ext.* import org.move.lang.core.resolve2.ref.FieldResolveVariant @@ -41,8 +42,13 @@ object StructFieldsCompletionProvider: MvCompletionProvider() { when (element) { is MvPatField -> { val patStruct = element.patStruct + // Path resolution is cached, but sometimes path changes so much that it can't be retrieved + // from cache anymore. In this case we need to get the old path. + // "safe" here means that if tree changes too much (=any of the ancestors of path are changed), + // then it's a no-op and we continue working with current path. + val struct = patStruct.path.safeGetOriginalOrSelf().maybeStruct ?: return addFieldsToCompletion( - patStruct.path.maybeStruct ?: return, + struct, patStruct.fieldNames, result, completionCtx @@ -50,8 +56,10 @@ object StructFieldsCompletionProvider: MvCompletionProvider() { } is MvStructLitField -> { val structLit = element.parentStructLitExpr + // see MvPatField's comment above + val struct = structLit.path.safeGetOriginalOrSelf().maybeStruct ?: return addFieldsToCompletion( - structLit.path.maybeStruct ?: return, + struct, structLit.providedFieldNames, result, completionCtx diff --git a/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt b/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt index 3dffba8c6..f3f07b963 100644 --- a/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt +++ b/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt @@ -2,6 +2,7 @@ package org.move.lang.core.psi.ext import com.intellij.lang.ASTNode import org.move.lang.MvElementTypes +import org.move.lang.core.completion.safeGetOriginalOrSelf import org.move.lang.core.psi.* import org.move.lang.core.resolve.RsResolveProcessor import org.move.lang.core.resolve.SimpleScopeEntry @@ -65,7 +66,8 @@ fun processSchemaLitFieldResolveVariants( processor: RsResolveProcessor ): Boolean { val schemaLit = literalField.schemaLit ?: return false - val schema = schemaLit.path.maybeSchema ?: return false + // safeGetOriginalOrSelf() to prevent cache misses for the path cache in completion + val schema = schemaLit.path.safeGetOriginalOrSelf().maybeSchema ?: return false return schema.fieldsAsBindings .any { field -> processor.process(SimpleScopeEntry(field.name, field, setOf(Namespace.NAME))) From 988c76f417abdcb4f961b78f01670f1014c8fb06 Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Wed, 15 Jan 2025 18:09:08 +0100 Subject: [PATCH 2/6] fix autopopup for struct pattern fields --- .../core/completion/MvCompletionConfidence.kt | 10 ++- .../completion/CompletionAutoPopupTest.kt | 70 +++++++++++++++++++ .../completion/names/StructsCompletionTest.kt | 16 +++++ .../tests/completion/CompletionTestCase.kt | 3 +- 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/test/kotlin/org/move/lang/completion/CompletionAutoPopupTest.kt diff --git a/src/main/kotlin/org/move/lang/core/completion/MvCompletionConfidence.kt b/src/main/kotlin/org/move/lang/core/completion/MvCompletionConfidence.kt index e422c5e59..f68e04de8 100644 --- a/src/main/kotlin/org/move/lang/core/completion/MvCompletionConfidence.kt +++ b/src/main/kotlin/org/move/lang/core/completion/MvCompletionConfidence.kt @@ -1,13 +1,13 @@ package org.move.lang.core.completion import com.intellij.codeInsight.completion.CompletionConfidence -import com.intellij.openapi.editor.Editor import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.util.ThreeState import org.move.lang.MvElementTypes.IDENTIFIER import org.move.lang.core.psi.MvPatBinding import org.move.lang.core.psi.MvLetStmt +import org.move.lang.core.psi.MvPatField import org.move.lang.core.psi.ext.elementType import org.move.lang.core.psi.ext.bindingOwner @@ -17,8 +17,12 @@ class MvCompletionConfidence : CompletionConfidence() { // If the identifier is uppercase, the user probably wants to type a destructuring pattern // (`let Foo { ... }`), so we show the completion popup in this case if (contextElement.elementType == IDENTIFIER) { - val parent = contextElement.parent - if (parent is MvPatBinding && parent.bindingOwner is MvLetStmt) { + val binding = contextElement.parent + if (binding is MvPatBinding && binding.bindingOwner is MvLetStmt) { + // let S { va/*caret*/ } + if (binding.parent is MvPatField) { + return ThreeState.UNSURE + } val identText = contextElement.node.chars if (identText.firstOrNull()?.isLowerCase() == true) { return ThreeState.YES diff --git a/src/test/kotlin/org/move/lang/completion/CompletionAutoPopupTest.kt b/src/test/kotlin/org/move/lang/completion/CompletionAutoPopupTest.kt new file mode 100644 index 000000000..292f6b101 --- /dev/null +++ b/src/test/kotlin/org/move/lang/completion/CompletionAutoPopupTest.kt @@ -0,0 +1,70 @@ +package org.move.lang.completion + +import com.intellij.testFramework.fixtures.CompletionAutoPopupTester +import com.intellij.util.ThrowableRunnable +import org.intellij.lang.annotations.Language +import org.move.utils.tests.NamedAddress +import org.move.utils.tests.completion.CompletionTestCase + +class CompletionAutoPopupTest: CompletionTestCase() { + private lateinit var tester: CompletionAutoPopupTester + + fun `test popup is not shown when typing variable name`() = checkPopupIsNotShownAfterTyping( + """ + module 0x1::m { + struct MyStruct { val: u8 } + fun main() { + let tr/*caret*/ + } + } + """, "u" + ) + + fun `test popup is shown when typing name starting with upper case`() = checkPopupIsShownAfterTyping( + """ + module 0x1::m { + struct MyStruct { val: u8 } + fun main() { + let Str/*caret*/ + } + } + """, "u" + ) + + fun `test popup is shown for struct pat fields`() = checkPopupIsShownAfterTyping( + """ + module 0x1::m { + struct MyStruct { val: u8 } + fun main() { + let MyStruct { v/*caret*/ }; + } + } + """, "a" + ) + + override fun setUp() { + super.setUp() + tester = CompletionAutoPopupTester(myFixture) + } + + override fun runTestRunnable(testRunnable: ThrowableRunnable) { + tester.runWithAutoPopupEnabled(testRunnable) + } + + override fun runInDispatchThread(): Boolean = false + + private fun checkPopupIsShownAfterTyping(@Language("Move") code: String, toType: String) { + configureAndType(code, toType) + assertNotNull(tester.lookup) + } + + private fun checkPopupIsNotShownAfterTyping(@Language("Move") code: String, toType: String) { + configureAndType(code, toType) + assertNull(tester.lookup) + } + + private fun configureAndType(code: String, toType: String) { + InlineFile(code).withCaret() + tester.typeWithPauses(toType) + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt b/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt index 24a138741..f16e63010 100644 --- a/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt +++ b/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt @@ -170,6 +170,22 @@ class StructsCompletionTest: CompletionTestCase() { """) fun `test struct fields completion in struct pattern`() = doSingleCompletion(""" + module 0x1::M { + struct T { my_field: u8 } + fun main() { + let T { my_/*caret*/: field } = call(); + } + } + """, """ + module 0x1::M { + struct T { my_field: u8 } + fun main() { + let T { my_field/*caret*/: field } = call(); + } + } + """) + + fun `test struct fields completion in struct pattern shorthand`() = doSingleCompletion(""" module 0x1::M { struct T { my_field: u8 } fun main() { diff --git a/src/test/kotlin/org/move/utils/tests/completion/CompletionTestCase.kt b/src/test/kotlin/org/move/utils/tests/completion/CompletionTestCase.kt index c2c58f2fd..f39e15ef3 100644 --- a/src/test/kotlin/org/move/utils/tests/completion/CompletionTestCase.kt +++ b/src/test/kotlin/org/move/utils/tests/completion/CompletionTestCase.kt @@ -2,8 +2,9 @@ package org.move.utils.tests.completion import org.intellij.lang.annotations.Language import org.move.utils.tests.MvLightTestBase +import org.move.utils.tests.MvTestBase -abstract class CompletionTestCase: MvLightTestBase() { +abstract class CompletionTestCase: MvTestBase() { lateinit var completionFixture: MvCompletionTestFixture override fun setUp() { From f3c28177c814753c077a6246d774e0d5686f95a5 Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Wed, 15 Jan 2025 18:15:44 +0100 Subject: [PATCH 3/6] fix completion for full struct pat fields --- .../completion/providers/CommonCompletionProvider.kt | 6 ++++++ .../move/lang/completion/names/StructsCompletionTest.kt | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/main/kotlin/org/move/lang/core/completion/providers/CommonCompletionProvider.kt b/src/main/kotlin/org/move/lang/core/completion/providers/CommonCompletionProvider.kt index 74fe25e66..71f2fe45f 100644 --- a/src/main/kotlin/org/move/lang/core/completion/providers/CommonCompletionProvider.kt +++ b/src/main/kotlin/org/move/lang/core/completion/providers/CommonCompletionProvider.kt @@ -22,6 +22,7 @@ import org.move.lang.core.resolve2.processFieldLookupResolveVariants import org.move.lang.core.resolve2.processLabelResolveVariants import org.move.lang.core.resolve2.processMethodResolveVariants import org.move.lang.core.resolve2.processPatBindingResolveVariants +import org.move.lang.core.resolve2.processStructPatFieldResolveVariants import org.move.lang.core.types.infer.InferenceContext import org.move.lang.core.types.infer.substitute import org.move.lang.core.types.ty.* @@ -69,6 +70,11 @@ object CommonCompletionProvider: MvCompletionProvider() { val processor = skipAlreadyProvidedFields(element, processor0) processPatBindingResolveVariants(element, true, processor) } + // `let Res { my_f/*caret*/: field }` + is MvPatFieldFull -> { + val processor = skipAlreadyProvidedFields(element, processor0) + processStructPatFieldResolveVariants(element, processor) + } // loop labels is MvLabel -> processLabelResolveVariants(element, it) // `spec ITEM {}` module items, where ITEM is a reference to the function/struct/enum diff --git a/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt b/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt index f16e63010..e14777b0a 100644 --- a/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt +++ b/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt @@ -185,6 +185,15 @@ class StructsCompletionTest: CompletionTestCase() { } """) + fun `test struct fields completion in struct pattern with existing fields`() = checkNoCompletion(""" + module 0x1::M { + struct T { my_field: u8 } + fun main() { + let T { my_/*caret*/: field, my_field: field2 } = call(); + } + } + """) + fun `test struct fields completion in struct pattern shorthand`() = doSingleCompletion(""" module 0x1::M { struct T { my_field: u8 } From 42187a0d8ef21fa909cba9793571f41a1437d21a Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Wed, 15 Jan 2025 18:29:15 +0100 Subject: [PATCH 4/6] more fixes for stale path cache --- .../StructFieldsCompletionProvider.kt | 12 +++++------ .../org/move/lang/core/psi/ext/MvPath.kt | 2 ++ .../lang/core/psi/ext/MvSchemaLitField.kt | 5 +++-- .../lang/core/resolve2/NameResolution2.kt | 21 ++++++++++--------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt b/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt index 584132dde..6ebaa8982 100644 --- a/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt +++ b/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt @@ -9,7 +9,7 @@ import com.intellij.psi.PsiElement import com.intellij.util.ProcessingContext import org.move.lang.core.completion.MvCompletionContext import org.move.lang.core.completion.createLookupElement -import org.move.lang.core.completion.safeGetOriginalOrSelf +import org.move.lang.core.completion.getOriginalOrSelf import org.move.lang.core.psi.* import org.move.lang.core.psi.ext.* import org.move.lang.core.resolve2.ref.FieldResolveVariant @@ -44,9 +44,9 @@ object StructFieldsCompletionProvider: MvCompletionProvider() { val patStruct = element.patStruct // Path resolution is cached, but sometimes path changes so much that it can't be retrieved // from cache anymore. In this case we need to get the old path. - // "safe" here means that if tree changes too much (=any of the ancestors of path are changed), + // OLD: "safe" here means that if tree changes too much (=any of the ancestors of path are changed), // then it's a no-op and we continue working with current path. - val struct = patStruct.path.safeGetOriginalOrSelf().maybeStruct ?: return + val struct = patStruct.path.getOriginalOrSelf().maybeFieldsOwner ?: return addFieldsToCompletion( struct, patStruct.fieldNames, @@ -57,7 +57,7 @@ object StructFieldsCompletionProvider: MvCompletionProvider() { is MvStructLitField -> { val structLit = element.parentStructLitExpr // see MvPatField's comment above - val struct = structLit.path.safeGetOriginalOrSelf().maybeStruct ?: return + val struct = structLit.path.getOriginalOrSelf().maybeFieldsOwner ?: return addFieldsToCompletion( struct, structLit.providedFieldNames, @@ -70,12 +70,12 @@ object StructFieldsCompletionProvider: MvCompletionProvider() { private fun addFieldsToCompletion( - referredStruct: MvStruct, + fieldsOwner: MvFieldsOwner, providedFieldNames: Set, result: CompletionResultSet, completionContext: MvCompletionContext, ) { - for (field in referredStruct.namedFields.filter { it.name !in providedFieldNames }) { + for (field in fieldsOwner.namedFields.filter { it.name !in providedFieldNames }) { val scopeEntry = FieldResolveVariant(field.name, field) createLookupElement(scopeEntry, completionContext) result.addElement( diff --git a/src/main/kotlin/org/move/lang/core/psi/ext/MvPath.kt b/src/main/kotlin/org/move/lang/core/psi/ext/MvPath.kt index e47e59cdd..4de0e7435 100644 --- a/src/main/kotlin/org/move/lang/core/psi/ext/MvPath.kt +++ b/src/main/kotlin/org/move/lang/core/psi/ext/MvPath.kt @@ -63,6 +63,8 @@ val MvPath.identifierName: String? get() = identifier?.text val MvPath.maybeStruct get() = reference?.resolveFollowingAliases() as? MvStruct +val MvPath.maybeFieldsOwner get() = reference?.resolveFollowingAliases() as? MvFieldsOwner + val MvPath.maybeSchema get() = reference?.resolveFollowingAliases() as? MvSchema //fun MvPath.allowedNamespaces(isCompletion: Boolean = false): Set { diff --git a/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt b/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt index f3f07b963..4f6f90e69 100644 --- a/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt +++ b/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt @@ -2,6 +2,7 @@ package org.move.lang.core.psi.ext import com.intellij.lang.ASTNode import org.move.lang.MvElementTypes +import org.move.lang.core.completion.getOriginalOrSelf import org.move.lang.core.completion.safeGetOriginalOrSelf import org.move.lang.core.psi.* import org.move.lang.core.resolve.RsResolveProcessor @@ -66,8 +67,8 @@ fun processSchemaLitFieldResolveVariants( processor: RsResolveProcessor ): Boolean { val schemaLit = literalField.schemaLit ?: return false - // safeGetOriginalOrSelf() to prevent cache misses for the path cache in completion - val schema = schemaLit.path.safeGetOriginalOrSelf().maybeSchema ?: return false + // getOriginalOrSelf() to prevent cache misses for the path cache in completion + val schema = schemaLit.path.getOriginalOrSelf().maybeSchema ?: return false return schema.fieldsAsBindings .any { field -> processor.process(SimpleScopeEntry(field.name, field, setOf(Namespace.NAME))) diff --git a/src/main/kotlin/org/move/lang/core/resolve2/NameResolution2.kt b/src/main/kotlin/org/move/lang/core/resolve2/NameResolution2.kt index f40726ada..87052591d 100644 --- a/src/main/kotlin/org/move/lang/core/resolve2/NameResolution2.kt +++ b/src/main/kotlin/org/move/lang/core/resolve2/NameResolution2.kt @@ -1,5 +1,6 @@ package org.move.lang.core.resolve2 +import org.move.lang.core.completion.getOriginalOrSelf import org.move.lang.core.psi.* import org.move.lang.core.psi.ext.* import org.move.lang.core.resolve.* @@ -66,9 +67,9 @@ fun processStructPatFieldResolveVariants( patFieldFull: MvPatFieldFull, processor: RsResolveProcessor ): Boolean { - val resolved = patFieldFull.patStruct.path.reference?.resolveFollowingAliases() - val resolvedStruct = resolved as? MvFieldsOwner ?: return false - return processNamedFieldDeclarations(resolvedStruct, processor) + // used in completion + val fieldsOwner = patFieldFull.patStruct.path.getOriginalOrSelf().maybeFieldsOwner ?: return false + return processNamedFieldDeclarations(fieldsOwner, processor) } fun processPatBindingResolveVariants( @@ -79,10 +80,10 @@ fun processPatBindingResolveVariants( // field pattern shorthand if (binding.parent is MvPatField) { val parentPat = binding.parent.parent as MvPatStruct - val structItem = parentPat.path.reference?.resolveFollowingAliases() + val fieldsOwner = parentPat.path.getOriginalOrSelf().maybeFieldsOwner // can be null if unresolved - if (structItem is MvFieldsOwner) { - if (processNamedFieldDeclarations(structItem, originalProcessor)) return true + if (fieldsOwner != null) { + if (processNamedFieldDeclarations(fieldsOwner, originalProcessor)) return true if (isCompletion) return false } } @@ -254,14 +255,14 @@ fun walkUpThroughScopes( return false } -private fun processFieldDeclarations(item: MvFieldsOwner, processor: RsResolveProcessor): Boolean = - item.fields.any { field -> +private fun processFieldDeclarations(fieldsOwner: MvFieldsOwner, processor: RsResolveProcessor): Boolean = + fieldsOwner.fields.any { field -> val name = field.name ?: return@any false processor.process(name, NAMES, field) } -private fun processNamedFieldDeclarations(struct: MvFieldsOwner, processor: RsResolveProcessor): Boolean = - struct.namedFields.any { field -> +private fun processNamedFieldDeclarations(fieldsOwner: MvFieldsOwner, processor: RsResolveProcessor): Boolean = + fieldsOwner.namedFields.any { field -> val name = field.name processor.process(name, NAMES, field) } From 2e1b1fbbe4d28d520a88f8232b87af159dbbaa66 Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Wed, 15 Jan 2025 20:06:19 +0100 Subject: [PATCH 5/6] allow inferring expected type for binary expressions --- .../providers/CommonCompletionProvider.kt | 16 ++++++---------- .../providers/MvPathCompletionProvider2.kt | 2 +- .../completion/sort/MvCompletionWeighers.kt | 10 ++++++---- .../core/types/infer/TypeInferenceWalker.kt | 2 +- .../lang/completion/CompletionPrioritiesTest.kt | 13 ++++++++++++- .../completion/lookups/LookupElementTest.kt | 5 ++++- .../org/move/lang/types/ExpectedTypeTest.kt | 17 ++++++++++++++--- 7 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/org/move/lang/core/completion/providers/CommonCompletionProvider.kt b/src/main/kotlin/org/move/lang/core/completion/providers/CommonCompletionProvider.kt index 71f2fe45f..6dbb4bf42 100644 --- a/src/main/kotlin/org/move/lang/core/completion/providers/CommonCompletionProvider.kt +++ b/src/main/kotlin/org/move/lang/core/completion/providers/CommonCompletionProvider.kt @@ -48,7 +48,7 @@ object CommonCompletionProvider: MvCompletionProvider() { // handles dot expr if (element is MvMethodOrField) { - addMethodOrFieldVariants(element, result) + addMethodOrFieldVariants(element, result, completionCtx) } addCompletionVariants(element, result, completionCtx) @@ -84,30 +84,26 @@ object CommonCompletionProvider: MvCompletionProvider() { } @VisibleForTesting - fun addMethodOrFieldVariants(element: MvMethodOrField, result: CompletionResultSet) { - val msl = element.isMsl() - val receiverTy = element.inferReceiverTy(msl).knownOrNull() ?: return - val expectedTy = getExpectedTypeForEnclosingPathOrDotExpr(element, msl) - - val ctx = MvCompletionContext(element, msl, expectedTy) + fun addMethodOrFieldVariants(element: MvMethodOrField, result: CompletionResultSet, ctx: MvCompletionContext) { + val receiverTy = element.inferReceiverTy(ctx.msl).knownOrNull() ?: return val tyAdt = receiverTy.derefIfNeeded() as? TyAdt if (tyAdt != null) { collectCompletionVariants(result, ctx, subst = tyAdt.substitution) { - processFieldLookupResolveVariants(element, tyAdt, msl, it) + processFieldLookupResolveVariants(element, tyAdt, ctx.msl, it) } } processMethodResolveVariants(element, receiverTy, ctx.msl, createProcessor { e -> val function = e.element as? MvFunction ?: return@createProcessor val subst = function.tyVarsSubst - val declaredFuncTy = function.functionTy(msl).substitute(subst) as TyFunction + val declaredFuncTy = function.functionTy(ctx.msl).substitute(subst) as TyFunction val declaredSelfTy = declaredFuncTy.paramTypes.first() val autoborrowedReceiverTy = TyReference.autoborrow(receiverTy, declaredSelfTy) ?: error("unreachable, references always compatible") - val inferenceCtx = InferenceContext(msl) + val inferenceCtx = InferenceContext(ctx.msl) inferenceCtx.combineTypes(declaredSelfTy, autoborrowedReceiverTy) result.addElement( diff --git a/src/main/kotlin/org/move/lang/core/completion/providers/MvPathCompletionProvider2.kt b/src/main/kotlin/org/move/lang/core/completion/providers/MvPathCompletionProvider2.kt index 0d6f1bf03..3611c7268 100644 --- a/src/main/kotlin/org/move/lang/core/completion/providers/MvPathCompletionProvider2.kt +++ b/src/main/kotlin/org/move/lang/core/completion/providers/MvPathCompletionProvider2.kt @@ -226,7 +226,7 @@ fun getExpectedTypeForEnclosingPathOrDotExpr(element: MvReferenceElement, msl: B is MvPathType, is MvPathExpr, is MvDotExpr -> { - val inference = (ancestor as MvElement).inference(msl) ?: return TyUnknown + val inference = ancestor.inference(msl) ?: return TyUnknown return inferExpectedTy(ancestor, inference) } } diff --git a/src/main/kotlin/org/move/lang/core/completion/sort/MvCompletionWeighers.kt b/src/main/kotlin/org/move/lang/core/completion/sort/MvCompletionWeighers.kt index f87e1b598..b08ca8c94 100644 --- a/src/main/kotlin/org/move/lang/core/completion/sort/MvCompletionWeighers.kt +++ b/src/main/kotlin/org/move/lang/core/completion/sort/MvCompletionWeighers.kt @@ -110,8 +110,9 @@ private fun preferTrue( property: (P) -> Boolean, id: String ): MvCompletionWeigher = object : MvCompletionWeigher { - override fun weigh(element: LookupElement): Boolean = - if (element is MvLookupElement) !property(element.props) else true + override fun weigh(element: LookupElement): Boolean { + return if (element is MvLookupElement) !property(element.props) else true + } override val id: String get() = id } @@ -173,8 +174,9 @@ private fun splitIntoGroups(weighersWithAnchors: List): List { - val rsElement = element.`as`(LookupElement::class.java) - return weigher.weigh(rsElement ?: element) + val mvLookupElement = element.`as`(MvLookupElement::class.java) + return weigher.weigh(mvLookupElement ?: element) } } diff --git a/src/main/kotlin/org/move/lang/core/types/infer/TypeInferenceWalker.kt b/src/main/kotlin/org/move/lang/core/types/infer/TypeInferenceWalker.kt index 433ac13dd..5714d3051 100644 --- a/src/main/kotlin/org/move/lang/core/types/infer/TypeInferenceWalker.kt +++ b/src/main/kotlin/org/move/lang/core/types/infer/TypeInferenceWalker.kt @@ -898,7 +898,7 @@ class TypeInferenceWalker( typeErrorEncountered = true } if (rightExpr != null) { - val rightTy = rightExpr.inferType() + val rightTy = rightExpr.inferType(leftTy) if (!rightTy.supportsArithmeticOp()) { ctx.reportTypeError(TypeError.UnsupportedBinaryOp(rightExpr, rightTy, op)) typeErrorEncountered = true diff --git a/src/test/kotlin/org/move/lang/completion/CompletionPrioritiesTest.kt b/src/test/kotlin/org/move/lang/completion/CompletionPrioritiesTest.kt index b896446bc..0356bf51d 100644 --- a/src/test/kotlin/org/move/lang/completion/CompletionPrioritiesTest.kt +++ b/src/test/kotlin/org/move/lang/completion/CompletionPrioritiesTest.kt @@ -5,7 +5,7 @@ import org.move.lang.core.psi.MvQualNamedElement import org.move.utils.tests.MoveV2 import org.move.utils.tests.completion.CompletionTestCase -class CompletionPrioritiesTest : CompletionTestCase() { +class CompletionPrioritiesTest: CompletionTestCase() { fun `test local before builtin before unimported`() = checkCompletionsOrder( listOf("borrow_local", "borrow_global", "borrow_global_mut", "borrow"), """ @@ -199,6 +199,17 @@ module 0x1::Main { """ ) + fun `test use binary op types for completion sorting`() = checkCompletionsOrder( + listOf("def_val_2", "def_val"), """ + module std::modules { + struct Ss { def_val: u8, def_val_2: u16 } + fun main(s: Ss) { + 1u16 + s.de/*caret*/; + } + } + """.trimIndent() + ) + private fun checkCompletionsOrder(listStart: List, @Language("Move") code: String) { val variants = completionFixture.invokeCompletion(code) val lookupStrings = variants.map { it.lookupString } diff --git a/src/test/kotlin/org/move/lang/completion/lookups/LookupElementTest.kt b/src/test/kotlin/org/move/lang/completion/lookups/LookupElementTest.kt index dbad12a65..10b61b0b8 100644 --- a/src/test/kotlin/org/move/lang/completion/lookups/LookupElementTest.kt +++ b/src/test/kotlin/org/move/lang/completion/lookups/LookupElementTest.kt @@ -1,5 +1,6 @@ package org.move.lang.completion.lookups +import com.intellij.codeInsight.completion.CompletionContext import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.CompletionSorter import com.intellij.codeInsight.completion.PrefixMatcher @@ -14,6 +15,7 @@ import org.move.lang.core.completion.providers.CommonCompletionProvider import org.move.lang.core.psi.MvElement import org.move.lang.core.psi.MvNamedElement import org.move.lang.core.psi.ext.MvMethodOrField +import org.move.lang.core.psi.ext.isMsl import org.move.lang.core.resolve.SimpleScopeEntry import org.move.lang.core.resolve.ref.MvReferenceElement import org.move.lang.core.resolve.ref.NAMES @@ -236,7 +238,8 @@ class LookupElementTest: MvTestBase() { } if (element is MvMethodOrField) { - CommonCompletionProvider.addMethodOrFieldVariants(element, result) + val ctx = MvCompletionContext(element, element.isMsl()) + CommonCompletionProvider.addMethodOrFieldVariants(element, result, ctx) } val lookup = lookups.single { diff --git a/src/test/kotlin/org/move/lang/types/ExpectedTypeTest.kt b/src/test/kotlin/org/move/lang/types/ExpectedTypeTest.kt index b49e1319f..68a2c2d36 100644 --- a/src/test/kotlin/org/move/lang/types/ExpectedTypeTest.kt +++ b/src/test/kotlin/org/move/lang/types/ExpectedTypeTest.kt @@ -199,12 +199,23 @@ class ExpectedTypeTest : TypificationTestCase() { """ ) - fun `test unknown if inside other expr`() = testExpectedTyExpr( + fun `test use binary operation lhs integer`() = testExpectedTyExpr( """ module 0x1::Main { fun call() { - let a: u8 = 1 + my_ref; - //^ + 1 + my_ref; + //^ integer + } + } + """ + ) + + fun `test use binary operation lhs u8`() = testExpectedTyExpr( + """ + module 0x1::Main { + fun call() { + 1u8 + my_ref; + //^ u8 } } """ From c5b71960bbb9de574fbd547f796e35c407b797b4 Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Wed, 15 Jan 2025 20:26:39 +0100 Subject: [PATCH 6/6] proper call for getOriginalOrSelf --- .../StructFieldsCompletionProvider.kt | 4 ++-- .../move/lang/core/psi/ext/MvSchemaLitField.kt | 3 +-- .../move/lang/core/resolve2/NameResolution2.kt | 4 ++-- .../core/resolve2/ref/MvPath2ReferenceImpl.kt | 6 +++++- .../move/lang/core/types/infer/ExpectedType.kt | 1 + .../lang/core/types/infer/InferenceContext.kt | 2 ++ .../completion/names/StructsCompletionTest.kt | 18 +++++++++++++++++- 7 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt b/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt index 6ebaa8982..11d9682db 100644 --- a/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt +++ b/src/main/kotlin/org/move/lang/core/completion/providers/StructFieldsCompletionProvider.kt @@ -46,7 +46,7 @@ object StructFieldsCompletionProvider: MvCompletionProvider() { // from cache anymore. In this case we need to get the old path. // OLD: "safe" here means that if tree changes too much (=any of the ancestors of path are changed), // then it's a no-op and we continue working with current path. - val struct = patStruct.path.getOriginalOrSelf().maybeFieldsOwner ?: return + val struct = patStruct.path.maybeFieldsOwner ?: return addFieldsToCompletion( struct, patStruct.fieldNames, @@ -57,7 +57,7 @@ object StructFieldsCompletionProvider: MvCompletionProvider() { is MvStructLitField -> { val structLit = element.parentStructLitExpr // see MvPatField's comment above - val struct = structLit.path.getOriginalOrSelf().maybeFieldsOwner ?: return + val struct = structLit.path.maybeFieldsOwner ?: return addFieldsToCompletion( struct, structLit.providedFieldNames, diff --git a/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt b/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt index 4f6f90e69..2af85efa7 100644 --- a/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt +++ b/src/main/kotlin/org/move/lang/core/psi/ext/MvSchemaLitField.kt @@ -67,8 +67,7 @@ fun processSchemaLitFieldResolveVariants( processor: RsResolveProcessor ): Boolean { val schemaLit = literalField.schemaLit ?: return false - // getOriginalOrSelf() to prevent cache misses for the path cache in completion - val schema = schemaLit.path.getOriginalOrSelf().maybeSchema ?: return false + val schema = schemaLit.path.maybeSchema ?: return false return schema.fieldsAsBindings .any { field -> processor.process(SimpleScopeEntry(field.name, field, setOf(Namespace.NAME))) diff --git a/src/main/kotlin/org/move/lang/core/resolve2/NameResolution2.kt b/src/main/kotlin/org/move/lang/core/resolve2/NameResolution2.kt index 87052591d..791d482fd 100644 --- a/src/main/kotlin/org/move/lang/core/resolve2/NameResolution2.kt +++ b/src/main/kotlin/org/move/lang/core/resolve2/NameResolution2.kt @@ -68,7 +68,7 @@ fun processStructPatFieldResolveVariants( processor: RsResolveProcessor ): Boolean { // used in completion - val fieldsOwner = patFieldFull.patStruct.path.getOriginalOrSelf().maybeFieldsOwner ?: return false + val fieldsOwner = patFieldFull.patStruct.path.maybeFieldsOwner ?: return false return processNamedFieldDeclarations(fieldsOwner, processor) } @@ -80,7 +80,7 @@ fun processPatBindingResolveVariants( // field pattern shorthand if (binding.parent is MvPatField) { val parentPat = binding.parent.parent as MvPatStruct - val fieldsOwner = parentPat.path.getOriginalOrSelf().maybeFieldsOwner + val fieldsOwner = parentPat.path.maybeFieldsOwner // can be null if unresolved if (fieldsOwner != null) { if (processNamedFieldDeclarations(fieldsOwner, originalProcessor)) return true diff --git a/src/main/kotlin/org/move/lang/core/resolve2/ref/MvPath2ReferenceImpl.kt b/src/main/kotlin/org/move/lang/core/resolve2/ref/MvPath2ReferenceImpl.kt index 3bcd9d93e..e857f5fa1 100644 --- a/src/main/kotlin/org/move/lang/core/resolve2/ref/MvPath2ReferenceImpl.kt +++ b/src/main/kotlin/org/move/lang/core/resolve2/ref/MvPath2ReferenceImpl.kt @@ -2,6 +2,7 @@ package org.move.lang.core.resolve2.ref import com.intellij.psi.ResolveResult import org.move.cli.MoveProject +import org.move.lang.core.completion.getOriginalOrSelf import org.move.lang.core.psi.* import org.move.lang.core.psi.ext.* import org.move.lang.core.resolve.* @@ -64,7 +65,10 @@ class MvPath2ReferenceImpl(element: MvPath): MvPolyVariantReferenceBase( } private fun getResolvedPathFromInference(path: MvPath, msl: Boolean): List>? { - return path.inference(msl)?.getResolvedPath(path) + // Path resolution is cached, but sometimes path changes so much that it can't be retrieved + // from cache anymore. In this case we need to get the old path. + val originalPath = path.getOriginalOrSelf() + return originalPath.inference(msl)?.getResolvedPath(originalPath) ?.map { RsPathResolveResult(it.element, it.isVisible) } diff --git a/src/main/kotlin/org/move/lang/core/types/infer/ExpectedType.kt b/src/main/kotlin/org/move/lang/core/types/infer/ExpectedType.kt index 7fac89726..f99707b4e 100644 --- a/src/main/kotlin/org/move/lang/core/types/infer/ExpectedType.kt +++ b/src/main/kotlin/org/move/lang/core/types/infer/ExpectedType.kt @@ -1,6 +1,7 @@ package org.move.lang.core.types.infer import com.intellij.psi.PsiElement +import org.move.lang.core.completion.getOriginalOrSelf import org.move.lang.core.psi.* import org.move.lang.core.psi.ext.elementType import org.move.lang.core.types.ty.Ty diff --git a/src/main/kotlin/org/move/lang/core/types/infer/InferenceContext.kt b/src/main/kotlin/org/move/lang/core/types/infer/InferenceContext.kt index 21f2307d1..ce92fd5d5 100644 --- a/src/main/kotlin/org/move/lang/core/types/infer/InferenceContext.kt +++ b/src/main/kotlin/org/move/lang/core/types/infer/InferenceContext.kt @@ -7,6 +7,8 @@ import com.intellij.psi.util.CachedValue import org.jetbrains.annotations.TestOnly import org.move.cli.settings.isDebugModeEnabled import org.move.ide.formatter.impl.location +import org.move.lang.core.completion.getOriginalOrSelf +import org.move.lang.core.completion.safeGetOriginalOrSelf import org.move.lang.core.psi.* import org.move.lang.core.psi.ext.* import org.move.lang.core.resolve.ScopeEntry diff --git a/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt b/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt index e14777b0a..b557d149d 100644 --- a/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt +++ b/src/test/kotlin/org/move/lang/completion/names/StructsCompletionTest.kt @@ -194,7 +194,7 @@ class StructsCompletionTest: CompletionTestCase() { } """) - fun `test struct fields completion in struct pattern shorthand`() = doSingleCompletion(""" + fun `test struct fields completion in struct pattern shorthand 1`() = doSingleCompletion(""" module 0x1::M { struct T { my_field: u8 } fun main() { @@ -210,6 +210,22 @@ class StructsCompletionTest: CompletionTestCase() { } """) + fun `test struct fields completion in struct pattern shorthand 2`() = doSingleCompletion(""" + module 0x1::modules { + struct Ss has key { def_val: u8, rut_def_val: u16 } + fun main(s: Ss) { + let Ss { def/*caret*/, rut_def_val } = s; + } + } + """, """ + module 0x1::modules { + struct Ss has key { def_val: u8, rut_def_val: u16 } + fun main(s: Ss) { + let Ss { def_val/*caret*/, rut_def_val } = s; + } + } + """) + fun `test no completion in struct pattern if field already specified`() = checkNoCompletion(""" module 0x1::M { struct T { offered: u8, collateral: u8 }