diff --git a/src/main/kotlin/org/move/ide/annotator/MvSyntaxErrorAnnotator.kt b/src/main/kotlin/org/move/ide/annotator/MvSyntaxErrorAnnotator.kt index 4c60f5dc5..4db8a9ca3 100644 --- a/src/main/kotlin/org/move/ide/annotator/MvSyntaxErrorAnnotator.kt +++ b/src/main/kotlin/org/move/ide/annotator/MvSyntaxErrorAnnotator.kt @@ -64,6 +64,8 @@ class MvSyntaxErrorAnnotator: MvAnnotatorBase() { } private fun checkIndexExpr(holder: MvAnnotationHolder, indexExpr: MvIndexExpr) { + // always supported in specs + if (indexExpr.isMsl()) return if (!indexExpr.project.moveSettings.enableIndexExpr) { Diagnostic .IndexExprIsNotSupportedInCompilerV1(indexExpr) diff --git a/src/main/kotlin/org/move/ide/inspections/MvUnusedImportInspection.kt b/src/main/kotlin/org/move/ide/inspections/MvUnusedImportInspection.kt index fa8d1da7f..114a56e09 100644 --- a/src/main/kotlin/org/move/ide/inspections/MvUnusedImportInspection.kt +++ b/src/main/kotlin/org/move/ide/inspections/MvUnusedImportInspection.kt @@ -9,7 +9,6 @@ import org.move.ide.inspections.imports.ImportAnalyzer2 class MvUnusedImportInspection: MvLocalInspectionTool() { override fun buildMvVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = ImportAnalyzer2(holder) -// override fun buildMvVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = ImportAnalyzer(holder) @Suppress("CompanionObjectInExtension") companion object { diff --git a/src/main/kotlin/org/move/ide/inspections/imports/BasePathType.kt b/src/main/kotlin/org/move/ide/inspections/imports/BasePathType.kt index 2f2d2df16..0218a7ede 100644 --- a/src/main/kotlin/org/move/ide/inspections/imports/BasePathType.kt +++ b/src/main/kotlin/org/move/ide/inspections/imports/BasePathType.kt @@ -1,7 +1,16 @@ package org.move.ide.inspections.imports +import com.intellij.openapi.util.Key +import com.intellij.psi.util.CachedValue +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValueProvider.Result +import com.intellij.psi.util.PsiModificationTracker import org.move.lang.core.psi.* +import org.move.lang.core.psi.NamedItemScope.* import org.move.lang.core.psi.ext.* +import org.move.utils.cache +import org.move.utils.cacheManager +import org.move.utils.cacheResult // classifies foo of `foo::bar::baz` sealed class BasePathType { @@ -34,42 +43,30 @@ fun MvPath.basePathType(): BasePathType? { return qualifier.referenceName?.let { BasePathType.Module(it) } } -// only Main/Test for now -val MvElement.usageScope: NamedItemScope - get() { - var parentElement = this.parent - while (parentElement != null) { -// if (parentElement is MslOnlyElement) return ItemScope.MAIN - if (parentElement is MvDocAndAttributeOwner && parentElement.hasTestOnlyAttr) { - return NamedItemScope.TEST - } - if (parentElement is MvDocAndAttributeOwner && parentElement.hasVerifyOnlyAttr) { - return NamedItemScope.VERIFY +private val USAGE_SCOPE_KEY: Key> = Key.create("USAGE_SCOPE_KEY") + +class UsageScopeProvider(val scopeElement: MvElement): CachedValueProvider { + override fun compute(): Result { + var scope = MAIN + for (ancestor in scopeElement.ancestorsOfType()) { + // msl items + if (scopeElement is MvSpecCodeBlock || scopeElement is MvItemSpecRef) { + scope = VERIFY + break } - if (parentElement is MvFunction && parentElement.hasTestAttr) { - return NamedItemScope.TEST + // explicit attrs, #[test], #[test_only], #[verify_only] + val attributeScope = ancestor.itemScopeFromAttributes() + if (attributeScope != null) { + scope = attributeScope + break } - parentElement = parentElement.parent } - return NamedItemScope.MAIN + return scopeElement.cacheResult(scope, listOf(PsiModificationTracker.MODIFICATION_COUNT)) } +} -// only Main/Test for now -val MvUseStmt.declaredItemScope: NamedItemScope +val MvElement.usageScope: NamedItemScope get() { - if (this.hasTestOnlyAttr) { - return NamedItemScope.TEST - } - var parentElement = this.parent - while (parentElement != null) { -// if (parentElement is MslOnlyElement) return ItemScope.MAIN - if (parentElement is MvDocAndAttributeOwner && parentElement.hasTestOnlyAttr) { - return NamedItemScope.TEST - } - if (parentElement is MvFunction && parentElement.hasTestAttr) { - return NamedItemScope.TEST - } - parentElement = parentElement.parent - } - return NamedItemScope.MAIN + return project.cacheManager + .cache(this, USAGE_SCOPE_KEY, UsageScopeProvider(this)) } diff --git a/src/main/kotlin/org/move/ide/inspections/imports/ImportAnalyzer.kt b/src/main/kotlin/org/move/ide/inspections/imports/ImportAnalyzer.kt index 7754a22c4..ea4a0af08 100644 --- a/src/main/kotlin/org/move/ide/inspections/imports/ImportAnalyzer.kt +++ b/src/main/kotlin/org/move/ide/inspections/imports/ImportAnalyzer.kt @@ -10,9 +10,7 @@ import org.move.stdext.chain class ImportAnalyzer2(val holder: ProblemsHolder): MvVisitor() { override fun visitModule(o: MvModule) = analyzeImportsOwner(o) -// override fun visitModuleBlock(o: MvModuleBlock) = analyzeImportsOwner(o) override fun visitScript(o: MvScript) = analyzeImportsOwner(o) -// override fun visitScriptBlock(o: MvScriptBlock) = analyzeImportsOwner(o) override fun visitModuleSpecBlock(o: MvModuleSpecBlock) = analyzeImportsOwner(o) fun analyzeImportsOwner(importsOwner: MvItemsOwner) { @@ -24,28 +22,36 @@ class ImportAnalyzer2(val holder: ProblemsHolder): MvVisitor() { val allUseItemsHit = mutableSetOf() val rootItemOwnerWithSiblings = rootItemsOwner.itemsOwnerWithSiblings - val paths = rootItemOwnerWithSiblings - .flatMap { it.descendantsOfType() } - .filter { it.basePath() == it } - .filter { it.usageScope == itemScope } - .filter { !it.hasAncestor() } + val allFiles = rootItemOwnerWithSiblings.mapNotNull { it.containingMoveFile }.distinct() + val fileItemOwners = allFiles + // collect every possible MvItemOwner + .flatMap { it.descendantsOfType().flatMap { i -> i.itemsOwnerWithSiblings } } + .distinct() + .associateWith { itemOwner -> + itemOwner.useItems.filter { it.scope == itemScope } + } + + val reachablePaths = + rootItemOwnerWithSiblings + .flatMap { it.descendantsOfType() } + .filter { it.basePath() == it } + .filter { it.usageScope == itemScope } + .filter { !it.hasAncestor() } - for (path in paths) { + for (path in reachablePaths) { val basePathType = path.basePathType() for (itemsOwner in path.ancestorsOfType()) { - val useItems = - itemsOwner.itemsOwnerWithSiblings - .flatMap { it.useItems }.filter { it.scope == itemScope } - + val reachableUseItems = + itemsOwner.itemsOwnerWithSiblings.flatMap { fileItemOwners[it]!! } val useItemHit = when (basePathType) { is BasePathType.Item -> { - useItems.filter { it.type == ITEM } + reachableUseItems.filter { it.type == ITEM } // only hit first encountered to remove duplicates .firstOrNull { it.nameOrAlias == basePathType.itemName } } is BasePathType.Module -> { - useItems.filter { it.type == MODULE || it.type == SELF_MODULE } + reachableUseItems.filter { it.type == MODULE || it.type == SELF_MODULE } // only hit first encountered to remove duplicates .firstOrNull { it.nameOrAlias == basePathType.moduleName } } @@ -63,7 +69,7 @@ class ImportAnalyzer2(val holder: ProblemsHolder): MvVisitor() { // includes self val reachableItemsOwners = rootItemsOwner.descendantsOfTypeOrSelf() for (itemsOwner in reachableItemsOwners) { - val scopeUseStmts = itemsOwner.useStmtList.filter { it.declaredItemScope == itemScope } + val scopeUseStmts = itemsOwner.useStmtList.filter { it.usageScope == itemScope } for (useStmt in scopeUseStmts) { val unusedUseItems = useStmt.useItems.toSet() - allUseItemsHit holder.registerStmtSpeckError2(useStmt, unusedUseItems) diff --git a/src/main/kotlin/org/move/ide/inspections/imports/UseItem.kt b/src/main/kotlin/org/move/ide/inspections/imports/UseItem.kt index 4305a93af..7d3f91aa9 100644 --- a/src/main/kotlin/org/move/ide/inspections/imports/UseItem.kt +++ b/src/main/kotlin/org/move/ide/inspections/imports/UseItem.kt @@ -21,17 +21,16 @@ data class UseItem( val scope: NamedItemScope ) -val MvItemsOwner.useItems: List - get() = this.useStmtList.flatMap { it.useItems } +val MvItemsOwner.useItems: List get() = this.useStmtList.flatMap { it.useItems } val MvUseStmt.useItems: List get() { val items = mutableListOf() - val stmtItemScope = this.declaredItemScope - this.forEachLeafSpeck { path, useAlias -> - val useSpeck = path.parent as MvUseSpeck - val nameOrAlias = useAlias?.name ?: path.referenceName ?: return@forEachLeafSpeck false - val pathKind = path.pathKind() + val stmtItemScope = this.usageScope + this.forEachLeafSpeck { speckPath, useAlias -> + val useSpeck = speckPath.parent as MvUseSpeck + val nameOrAlias = useAlias?.name ?: speckPath.referenceName ?: return@forEachLeafSpeck false + val pathKind = speckPath.pathKind() when (pathKind) { is PathKind.QualifiedPath.Module -> items.add(UseItem(useSpeck, nameOrAlias, MODULE, stmtItemScope)) diff --git a/src/main/kotlin/org/move/ide/utils/imports/ImportUtils.kt b/src/main/kotlin/org/move/ide/utils/imports/ImportUtils.kt index 9f63e6b98..70f86c011 100644 --- a/src/main/kotlin/org/move/ide/utils/imports/ImportUtils.kt +++ b/src/main/kotlin/org/move/ide/utils/imports/ImportUtils.kt @@ -1,5 +1,6 @@ package org.move.ide.utils.imports +import org.move.ide.inspections.imports.usageScope import org.move.lang.core.psi.* import org.move.lang.core.psi.ext.* import org.move.lang.core.types.ItemQualName @@ -13,8 +14,8 @@ fun ImportCandidate.import(context: MvElement) { checkWriteAccessAllowed() val insertionScope = context.containingModule ?: context.containingScript ?: return val insertTestOnly = - insertionScope.itemScope == NamedItemScope.MAIN - && context.itemScope == NamedItemScope.TEST + insertionScope.usageScope == NamedItemScope.MAIN + && context.usageScope == NamedItemScope.TEST insertionScope.insertUseItem(qualName, insertTestOnly) } diff --git a/src/main/kotlin/org/move/lang/core/psi/NamedItemScope.kt b/src/main/kotlin/org/move/lang/core/psi/NamedItemScope.kt index 60b8bef2f..8d7f04f67 100644 --- a/src/main/kotlin/org/move/lang/core/psi/NamedItemScope.kt +++ b/src/main/kotlin/org/move/lang/core/psi/NamedItemScope.kt @@ -1,10 +1,6 @@ package org.move.lang.core.psi -import com.intellij.openapi.util.Key -import com.intellij.psi.util.CachedValue -import com.intellij.psi.util.CachedValuesManager import org.move.lang.core.psi.ext.* -import org.move.lang.core.resolve.ref.MvReferenceElement enum class NamedItemScope { MAIN, @@ -19,129 +15,22 @@ enum class NamedItemScope { } return this } - - companion object { - fun all(): Set = setOf(MAIN, TEST, VERIFY) - } -} - -//fun MvElement.isVisibleInContext(contextScope: NamedItemScope): Boolean { -// val itemScope = this.itemScope -// // MAIN scope items are visible from any context -// return itemScope == NamedItemScope.MAIN || itemScope == contextScope -//} - -fun MvNamedElement.isVisibleInContext(refItemScopes: Set): Boolean { -// for (requiredScope in this.itemScopes) { -// if (!contextScopes.contains(requiredScope)) return false -// } - val requiredScopes = this.namedItemScopes - return (requiredScopes - refItemScopes).isEmpty() - // MAIN scope items are visible from any context -// return itemScope == ItemScope.MAIN || itemScope == contextScope -} - - -private val ITEM_SCOPES_KEY = - Key.create>>("org.move.ITEM_SCOPES_KEY") - -val MvReferenceElement.refItemScopes: Set - get() { - return (this as MvElement).itemScopes - } - -val MvNamedElement.namedItemScopes: Set - get() { - return (this as MvElement).itemScopes - } - -val MvElement.itemScopes: Set - get() { - // TODO: special case module items to use stub-only in some cases -// project.cacheManager.cache(this, ITEM_SCOPES_KEY) { - val scopes = mutableSetOf(NamedItemScope.MAIN) - var element: MvElement? = this - if (element is MvStruct || element is MvFunction) { - val attributeScopes = (element as MvDocAndAttributeOwner).explicitItemScopes() - return attributeScopes - } - while (element != null) { - when (element) { - is MvDocAndAttributeOwner -> { - val attributeScopes = element.explicitItemScopes() - scopes.addAll(attributeScopes) -// val ownerItemScope = element.explicitAttributeItemScope() -// if (ownerItemScope != null) { -// scopes.add(ownerItemScope) -// } - } - is MvSpecCodeBlock, is MvItemSpecRef -> scopes.add(NamedItemScope.VERIFY) - } - element = element.parent as? MvElement - } - return scopes -// cacheResult(scopes, listOf(PsiModificationTracker.MODIFICATION_COUNT)) - } - -val MvElement.itemScope: NamedItemScope - get() { - return CachedValuesManager.getProjectPsiDependentCache(this) { - var element = it - while (element != null) { - when (element) { - is MvDocAndAttributeOwner -> { - val ownerItemScope = element.explicitAttributeItemScope() - if (ownerItemScope != null) { - return@getProjectPsiDependentCache ownerItemScope - } - } - is MvSpecCodeBlock, is MvItemSpecRef -> return@getProjectPsiDependentCache NamedItemScope.VERIFY - } - element = element.parent as? MvElement - } - NamedItemScope.MAIN - } - } - -private fun MvDocAndAttributeOwner.explicitItemScopes(): Set { - val scopes = mutableSetOf() - when { - this.hasTestOnlyAttr -> scopes.add(NamedItemScope.TEST) - this.hasVerifyOnlyAttr -> scopes.add(NamedItemScope.VERIFY) - this is MvStruct -> { - scopes.addAll(this.module.explicitItemScopes()) - } - this is MvFunction -> { - if (this.hasTestAttr) { - scopes.add(NamedItemScope.TEST) - } - scopes.addAll(this.module?.explicitItemScopes().orEmpty()) - } - } -// if (this.hasTestOnlyAttr || (this is MvFunction && this.hasTestAttr)) { -// scopes.add(NamedItemScope.TEST) -// } -// if (this.hasVerifyOnlyAttr) { -// scopes.add(NamedItemScope.VERIFY) -// } - return scopes } -private fun MvDocAndAttributeOwner.explicitAttributeItemScope(): NamedItemScope? = +fun MvDocAndAttributeOwner.itemScopeFromAttributes(): NamedItemScope? = when (this) { -// is MvSpecInlineFunction -> ItemScope.MAIN is MvFunction -> when { this.hasTestOnlyAttr || this.hasTestAttr -> NamedItemScope.TEST this.hasVerifyOnlyAttr -> NamedItemScope.VERIFY else -> - this.module?.explicitAttributeItemScope() ?: NamedItemScope.MAIN + this.module?.itemScopeFromAttributes() ?: NamedItemScope.MAIN } is MvStruct -> when { this.hasTestOnlyAttr -> NamedItemScope.TEST this.hasVerifyOnlyAttr -> NamedItemScope.VERIFY - else -> this.module.explicitAttributeItemScope() ?: NamedItemScope.MAIN + else -> this.module.itemScopeFromAttributes() ?: NamedItemScope.MAIN } else -> when { diff --git a/src/main/kotlin/org/move/lang/core/psi/ext/MvVisibilityOwner.kt b/src/main/kotlin/org/move/lang/core/psi/ext/MvVisibilityOwner.kt index f8b9e426f..96e839347 100644 --- a/src/main/kotlin/org/move/lang/core/psi/ext/MvVisibilityOwner.kt +++ b/src/main/kotlin/org/move/lang/core/psi/ext/MvVisibilityOwner.kt @@ -42,7 +42,6 @@ val MvVisibilityOwner.visibility2: Visibility2 PACKAGE -> containingMovePackage?.let { Visibility2.Restricted.Package(it) } ?: Visibility2.Public FRIEND -> { val module = this.containingModule ?: return Visibility2.Private - // todo: make lazy Visibility2.Restricted.Friend(lazy { module.friendModules }) } SCRIPT -> Visibility2.Restricted.Script diff --git a/src/main/kotlin/org/move/lang/core/psi/ext/PsiElement.kt b/src/main/kotlin/org/move/lang/core/psi/ext/PsiElement.kt index 6b07bc495..7ac2f3ff6 100644 --- a/src/main/kotlin/org/move/lang/core/psi/ext/PsiElement.kt +++ b/src/main/kotlin/org/move/lang/core/psi/ext/PsiElement.kt @@ -142,6 +142,11 @@ inline fun PsiElement.ancestorsOfType(): Sequence { return this.ancestors.filterIsInstance() } +inline fun PsiElement.ancestorsOfTypeWithSelf(): Sequence { + return (sequenceOf(this) + this.ancestors).filterIsInstance() +// return this.ancestors.filterIsInstance() +} + fun PsiElement.findFirstParent(strict: Boolean = true, cond: Condition) = PsiTreeUtil.findFirstParent(this, strict, cond) diff --git a/src/main/kotlin/org/move/lang/core/resolve/Processors.kt b/src/main/kotlin/org/move/lang/core/resolve/Processors.kt index 2b76c71a1..3e42bec52 100644 --- a/src/main/kotlin/org/move/lang/core/resolve/Processors.kt +++ b/src/main/kotlin/org/move/lang/core/resolve/Processors.kt @@ -558,10 +558,10 @@ fun RsResolveProcessor.processAllItems( namespaces: Set, vararg collections: Iterable, ): Boolean { - return sequenceOf(*collections).flatten().any { e -> - val name = e.name ?: return false - val visibilityFilter = e.visInfo().createFilter() - process(ScopeEntryWithVisibility(name, e, namespaces, visibilityFilter)) + return sequenceOf(*collections).flatten().any { itemElement -> + val name = itemElement.name ?: return false + val visibilityFilter = itemElement.visInfo().createFilter() + process(ScopeEntryWithVisibility(name, itemElement, namespaces, visibilityFilter)) } } diff --git a/src/main/kotlin/org/move/lang/core/resolve2/LexicalDeclarations2.kt b/src/main/kotlin/org/move/lang/core/resolve2/LexicalDeclarations2.kt index ef5d6b8cc..4a63c071e 100644 --- a/src/main/kotlin/org/move/lang/core/resolve2/LexicalDeclarations2.kt +++ b/src/main/kotlin/org/move/lang/core/resolve2/LexicalDeclarations2.kt @@ -227,18 +227,19 @@ fun processItemsInScope( private fun MvItemsOwner.processUseSpeckElements(ns: Set, processor: RsResolveProcessor): Boolean { var stop = false for (useStmt in this.useStmtList) { - useStmt.forEachLeafSpeck { path, alias -> + val stmtUsageScope = useStmt.usageScope + useStmt.forEachLeafSpeck { speckPath, alias -> val name = if (alias != null) { alias.name ?: return@forEachLeafSpeck false } else { - var n = path.referenceName ?: return@forEachLeafSpeck false + var n = speckPath.referenceName ?: return@forEachLeafSpeck false // 0x1::m::Self -> 0x1::m if (n == "Self") { - n = path.qualifier?.referenceName ?: return@forEachLeafSpeck false + n = speckPath.qualifier?.referenceName ?: return@forEachLeafSpeck false } n } - val resolvedItem = path.reference?.resolve() + val resolvedItem = speckPath.reference?.resolve() if (resolvedItem == null) { if (alias != null) { // aliased element cannot be resolved, but alias itself is valid, resolve to it @@ -250,9 +251,8 @@ private fun MvItemsOwner.processUseSpeckElements(ns: Set, processor: val element = alias ?: resolvedItem val namespace = resolvedItem.namespace - val useSpeckUsageScope = path.usageScope val visibilityFilter = - resolvedItem.visInfo(adjustScope = useSpeckUsageScope).createFilter() + resolvedItem.visInfo(adjustScope = stmtUsageScope).createFilter() if (namespace in ns && processor.process(name, element, ns, visibilityFilter)) { stop = true diff --git a/src/main/kotlin/org/move/lang/core/resolve2/Visibility2.kt b/src/main/kotlin/org/move/lang/core/resolve2/Visibility2.kt index aee6ab9d5..6a0341bee 100644 --- a/src/main/kotlin/org/move/lang/core/resolve2/Visibility2.kt +++ b/src/main/kotlin/org/move/lang/core/resolve2/Visibility2.kt @@ -17,32 +17,30 @@ import org.move.lang.core.resolve.ref.Visibility2.* data class ItemVisibilityInfo( val item: MvNamedElement, - val usageScope: NamedItemScope, + val itemScopeAdjustment: NamedItemScope, val vis: Visibility2, ) fun MvNamedElement.visInfo(adjustScope: NamedItemScope = MAIN): ItemVisibilityInfo { - // todo: can be lazy - val itemUsageScope = this.itemScope.shrinkScope(adjustScope) val visibility = (this as? MvVisibilityOwner)?.visibility2 ?: Public - return ItemVisibilityInfo(this, usageScope = itemUsageScope, vis = visibility) + return ItemVisibilityInfo(this, itemScopeAdjustment = adjustScope, vis = visibility) } /** Creates filter which determines whether item with [this] visibility is visible from specific [ModInfo] */ fun ItemVisibilityInfo.createFilter(): VisibilityFilter { - val (item, itemUsageScope, visibility) = this - return VisibilityFilter { element, namespaces -> + val (item, itemScopeAdjustment, visibility) = this + return VisibilityFilter { methodOrPath, namespaces -> // inside msl everything is visible - if (element.isMsl()) return@VisibilityFilter Visible + if (methodOrPath.isMsl()) return@VisibilityFilter Visible // if inside MvAttrItem like abort_code= - val attrItem = element.ancestorStrict() + val attrItem = methodOrPath.ancestorStrict() if (attrItem != null) return@VisibilityFilter Visible - val pathUsageScope = element.usageScope + val pathUsageScope = methodOrPath.usageScope - val path = element as? MvPath + val path = methodOrPath as? MvPath if (path != null) { val useSpeck = path.useSpeck if (useSpeck != null) { @@ -64,6 +62,7 @@ fun ItemVisibilityInfo.createFilter(): VisibilityFilter { // 0x0::builtins module items are always visible if (itemModule != null && itemModule.isBuiltins) return@VisibilityFilter Visible + val itemUsageScope = item.usageScope.shrinkScope(itemScopeAdjustment) // #[test_only] items in non-test-only scope if (itemUsageScope != MAIN) { // cannot be used everywhere, need to check for scope compatibility @@ -73,7 +72,7 @@ fun ItemVisibilityInfo.createFilter(): VisibilityFilter { // we're in non-msl scope at this point, msl only items aren't available if (item is MslOnlyElement) return@VisibilityFilter Invisible - val pathModule = element.containingModule + val pathModule = methodOrPath.containingModule // local methods, Self::method - everything is visible if (itemModule == pathModule) return@VisibilityFilter Visible @@ -91,19 +90,19 @@ fun ItemVisibilityInfo.createFilter(): VisibilityFilter { Invisible } is Restricted.Script -> { - val containingFunction = element.containingFunction + val containingFunction = methodOrPath.containingFunction if (containingFunction != null) { if (containingFunction.isEntry || containingFunction.isPublicScript ) return@VisibilityFilter Visible } - if (element.containingScript != null) return@VisibilityFilter Visible + if (methodOrPath.containingScript != null) return@VisibilityFilter Visible Invisible } is Restricted.Package -> { if (!item.project.moveSettings.enablePublicPackage) { return@VisibilityFilter Invisible } - val pathPackage = element.containingMovePackage ?: return@VisibilityFilter Invisible + val pathPackage = methodOrPath.containingMovePackage ?: return@VisibilityFilter Invisible if (visibility.originPackage == pathPackage) Visible else Invisible } } diff --git a/src/main/kotlin/org/move/lang/core/resolve2/ref/Path2ReferenceImpl.kt b/src/main/kotlin/org/move/lang/core/resolve2/ref/Path2ReferenceImpl.kt index 22a4a16f8..cb0b9ca97 100644 --- a/src/main/kotlin/org/move/lang/core/resolve2/ref/Path2ReferenceImpl.kt +++ b/src/main/kotlin/org/move/lang/core/resolve2/ref/Path2ReferenceImpl.kt @@ -40,11 +40,14 @@ class Path2ReferenceImpl(element: MvPath): fun rawMultiResolve(): List> = rawCachedMultiResolve() private fun rawCachedMultiResolve(): List> { - val rawResult = MvResolveCache.getInstance(element.project) - .resolveWithCaching(element, ResolveCacheDependency.LOCAL_AND_RUST_STRUCTURE, Resolver) - return rawResult.orEmpty() + return MvResolveCache + .getInstance(element.project) + .resolveWithCaching(element, cacheDependency, Resolver) + .orEmpty() } + private val cacheDependency: ResolveCacheDependency get() = ResolveCacheDependency.LOCAL_AND_RUST_STRUCTURE + private object Resolver: (MvElement) -> List> { override fun invoke(path: MvElement): List> { // should not really happen 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 b54d8f6a6..9b1f17448 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 @@ -167,7 +167,7 @@ class InferenceContext( // private val pathTypes = mutableMapOf() private val methodOrPathTypes = mutableMapOf() -// val resolvedPaths = mutableMapOf>() + // val resolvedPaths = mutableMapOf>() val resolvedFields = mutableMapOf() val resolvedMethodCalls = mutableMapOf() @@ -210,17 +210,17 @@ class InferenceContext( fallbackUnresolvedTypeVarsIfPossible() - exprTypes.replaceAll { _, ty -> fullyResolve(ty) } - patTypes.replaceAll { _, ty -> fullyResolve(ty) } + exprTypes.replaceAll { _, ty -> fullyResolveTypeVars(ty) } + patTypes.replaceAll { _, ty -> fullyResolveTypeVars(ty) } // for call expressions, we need to leave unresolved ty vars intact // to determine whether an explicit type annotation required callableTypes.replaceAll { _, ty -> resolveTypeVarsIfPossible(ty) } - exprExpectedTypes.replaceAll { _, ty -> fullyResolveWithOrigins(ty) } - typeErrors.replaceAll { err -> fullyResolveWithOrigins(err) } + exprExpectedTypes.replaceAll { _, ty -> fullyResolveTypeVarsWithOrigins(ty) } + typeErrors.replaceAll { err -> fullyResolveTypeVarsWithOrigins(err) } // pathTypes.replaceAll { _, ty -> fullyResolveWithOrigins(ty) } - methodOrPathTypes.replaceAll { _, ty -> fullyResolveWithOrigins(ty) } + methodOrPathTypes.replaceAll { _, ty -> fullyResolveTypeVarsWithOrigins(ty) } return InferenceResult( patTypes, @@ -239,7 +239,8 @@ class InferenceContext( val allTypes = exprTypes.values.asSequence() + patTypes.values.asSequence() for (ty in allTypes) { ty.visitInferTys { tyInfer -> - val rty = shallowResolve(tyInfer) + val rty = resolveTyInfer(tyInfer) +// val rty = resolveIfTyInfer(tyInfer) if (rty is TyInfer) { fallbackIfPossible(rty) } @@ -308,17 +309,13 @@ class InferenceContext( } } +// fun compareTypes(ty1: Ty, ty2: Ty): RelateResult = +// this.freezeUnification { this.combineTypes(ty1, ty2) } + fun combineTypes(ty1: Ty, ty2: Ty): RelateResult { -// try { - val resolvedTy1 = shallowResolve(ty1) - val resolvedTy2 = shallowResolve(ty2) + val resolvedTy1 = resolveIfTyInfer(ty1) + val resolvedTy2 = resolveIfTyInfer(ty2) return combineTypesResolved(resolvedTy1, resolvedTy2) -// } catch (e: UnificationError) { -// if (e.combine == null) { -// e.combine = CombiningContext(ty1, ty2) -// } -// throw e -// } } @Suppress("NAME_SHADOWING") @@ -369,16 +366,16 @@ class InferenceContext( return Ok(Unit) } - private fun combineIntVar(ty1: TyInfer, ty2: Ty): RelateResult { + private fun combineIntVar(ty1: TyInfer.IntVar, ty2: Ty): RelateResult { // skip unification for isCompatible check to prevent bugs if (skipUnification) return Ok(Unit) - when (ty1) { - is TyInfer.IntVar -> when (ty2) { - is TyInfer.IntVar -> intUnificationTable.unifyVarVar(ty1, ty2) - is TyInteger, is TyUnknown -> intUnificationTable.unifyVarValue(ty1, ty2) - else -> return Err(CombineTypeError.TypeMismatch(ty1, ty2)) + when (ty2) { + is TyInfer.IntVar -> intUnificationTable.unifyVarVar(ty1, ty2) + is TyInteger -> intUnificationTable.unifyVarValue(ty1, ty2) + is TyUnknown -> { + // do nothing, unknown should no influence IntVar } - is TyInfer.TyVar -> error("unreachable") + else -> return Err(CombineTypeError.TypeMismatch(ty1, ty2)) } return Ok(Unit) } @@ -431,53 +428,63 @@ class InferenceContext( return combineTypes(inferred, expected).into() } - fun shallowResolve(ty: Ty): Ty { - if (ty !is TyInfer) return ty + fun resolveIfTyInfer(ty: Ty) = if (ty is TyInfer) resolveTyInfer(ty) else ty - return when (ty) { - is TyInfer.IntVar -> intUnificationTable.findValue(ty) ?: ty - is TyInfer.TyVar -> varUnificationTable.findValue(ty)?.let(this::shallowResolve) ?: ty + fun resolveTyInfer(tyInfer: TyInfer): Ty { + return when (tyInfer) { + is TyInfer.IntVar -> intUnificationTable.findValue(tyInfer) ?: tyInfer + is TyInfer.TyVar -> varUnificationTable.findValue(tyInfer)?.let(this::resolveIfTyInfer) ?: tyInfer } } fun > resolveTypeVarsIfPossible(ty: T): T { - return ty.foldTyInferWith(this::shallowResolve) + return if (ty.hasTyInfer) ty.foldTyInferWith(this::resolveTyInfer) else ty } - fun > fullyResolve(value: T): T = value.foldWith(fullTypeResolver) + /// every TyVar unresolved at the end of this function converted into TyUnknown + fun > fullyResolveTypeVars(value: T): T = value.foldWith(fullTypeResolver) private inner class FullTypeResolver: TypeFolder() { override fun fold(ty: Ty): Ty { - if (!ty.needsInfer) return ty - val res = shallowResolve(ty) - return if (res is TyInfer) TyUnknown else res.innerFoldWith(this) + if (!ty.hasTyInfer) return ty + // try to resolve TyInfer shallow + val resTy = if (ty is TyInfer) resolveTyInfer(ty) else ty + + // if still unresolved, return unknown type + if (resTy is TyInfer) return TyUnknown + + return resTy.innerFoldWith(this) } } private val fullTypeResolver: FullTypeResolver = FullTypeResolver() /** - * Similar to [fullyResolve], but replaces unresolved [TyInfer.TyVar] to its [TyInfer.TyVar.origin] + * Similar to [fullyResolveTypeVars], but replaces unresolved [TyInfer.TyVar] to its [TyInfer.TyVar.origin] * instead of [TyUnknown] */ - fun > fullyResolveWithOrigins(value: T): T { + fun > fullyResolveTypeVarsWithOrigins(value: T): T { return value.foldWith(fullTypeWithOriginsResolver) } + private val fullTypeWithOriginsResolver: FullTypeWithOriginsResolver = FullTypeWithOriginsResolver() + private inner class FullTypeWithOriginsResolver: TypeFolder() { override fun fold(ty: Ty): Ty { if (!ty.hasTyInfer) return ty - return when (val res = shallowResolve(ty)) { + val resTy = if (ty is TyInfer) resolveTyInfer(ty) else ty + return when (resTy) { + // if it's TyUnknown, check whether the original ty has an origin, use that is TyUnknown -> (ty as? TyInfer.TyVar)?.origin ?: TyUnknown - is TyInfer.TyVar -> res.origin ?: TyUnknown - is TyInfer -> TyUnknown - else -> res.innerFoldWith(this) + // replace TyVar with the origin TyTypeParameter + is TyInfer.TyVar -> resTy.origin ?: TyUnknown + // replace integer with TyUnknown, todo: why? + is TyInfer.IntVar -> TyUnknown + else -> resTy.innerFoldWith(this) } } } - private val fullTypeWithOriginsResolver: FullTypeWithOriginsResolver = FullTypeWithOriginsResolver() - // Awful hack: check that inner expressions did not annotated as an error // to disallow annotation intersections. This should be done in a different way fun reportTypeError(typeError: TypeError) { 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 f2340e397..4c39ce2a5 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 @@ -770,6 +770,7 @@ class TypeInferenceWalker( var typeErrorEncountered = false val leftTy = leftExpr.inferType() +// val leftTy = leftExpr.inferType() if (!leftTy.supportsArithmeticOp()) { ctx.reportTypeError(TypeError.UnsupportedBinaryOp(leftExpr, leftTy, op)) typeErrorEncountered = true @@ -831,14 +832,12 @@ class TypeInferenceWalker( var typeErrorEncountered = false val leftTy = leftExpr.inferType() -// val leftTy = inferExprTy(leftExpr) if (!leftTy.supportsOrdering()) { ctx.reportTypeError(TypeError.UnsupportedBinaryOp(leftExpr, leftTy, op)) typeErrorEncountered = true } if (rightExpr != null) { val rightTy = rightExpr.inferType() -// val rightTy = inferExprTy(rightExpr) if (!rightTy.supportsOrdering()) { ctx.reportTypeError(TypeError.UnsupportedBinaryOp(rightExpr, rightTy, op)) typeErrorEncountered = true @@ -884,7 +883,8 @@ class TypeInferenceWalker( } private fun Ty.supportsArithmeticOp(): Boolean { - val ty = resolveTypeVarsWithObligations(this) + val ty = this +// val ty = resolveTypeVarsWithObligations(this) return ty is TyInteger || ty is TyNum || ty is TyInfer.TyVar diff --git a/src/test/kotlin/org/move/ide/annotator/syntaxErrors/compilerV2/IndexExprTest.kt b/src/test/kotlin/org/move/ide/annotator/syntaxErrors/compilerV2/IndexExprTest.kt index acfcb260c..68d27b7ca 100644 --- a/src/test/kotlin/org/move/ide/annotator/syntaxErrors/compilerV2/IndexExprTest.kt +++ b/src/test/kotlin/org/move/ide/annotator/syntaxErrors/compilerV2/IndexExprTest.kt @@ -37,4 +37,12 @@ class IndexExprTest: AnnotatorTestCase(MvSyntaxErrorAnnotator::class) { } } """) + + fun `test no error for specs in compiler v1`() = checkWarnings(""" + module 0x1::m { + spec module { + invariant forall ind in 0..10: vec[ind] < 10; + } + } + """) } \ No newline at end of file diff --git a/src/test/kotlin/org/move/ide/inspections/MvUnresolvedReferenceInspectionTest.kt b/src/test/kotlin/org/move/ide/inspections/MvUnresolvedReferenceInspectionTest.kt index 5dfabd524..cba7d8981 100644 --- a/src/test/kotlin/org/move/ide/inspections/MvUnresolvedReferenceInspectionTest.kt +++ b/src/test/kotlin/org/move/ide/inspections/MvUnresolvedReferenceInspectionTest.kt @@ -1,5 +1,7 @@ package org.move.ide.inspections +import org.move.ide.inspections.fixes.CompilerV2Feat.INDEXING +import org.move.utils.tests.CompilerV2Features import org.move.utils.tests.DebugMode import org.move.utils.tests.NamedAddress import org.move.utils.tests.annotation.InspectionTestBase @@ -461,4 +463,15 @@ module 0x1::m { use std::m; } """) + + fun `test no error for invariant index variable`() = checkByText( + """ + module 0x1::m { + spec module { + let vec = vector[1, 2, 3]; + invariant forall ind in 0..10: vec[ind] < 10; + } + } + """ + ) } diff --git a/src/test/kotlin/org/move/lang/resolve/ResolveSpecsTest.kt b/src/test/kotlin/org/move/lang/resolve/ResolveSpecsTest.kt index df7d9365b..7a3b3b971 100644 --- a/src/test/kotlin/org/move/lang/resolve/ResolveSpecsTest.kt +++ b/src/test/kotlin/org/move/lang/resolve/ResolveSpecsTest.kt @@ -1,5 +1,7 @@ package org.move.lang.resolve +import org.move.ide.inspections.fixes.CompilerV2Feat.INDEXING +import org.move.utils.tests.CompilerV2Features import org.move.utils.tests.resolve.ResolveTestCase class ResolveSpecsTest: ResolveTestCase() { @@ -955,4 +957,24 @@ module 0x1::main { } """) + + fun `test resolve invariant index variable in loop condition spec`() = checkByCode( + """ + module 0x1::m { + fun main() { + while ({ + spec { + invariant forall ind in 0..10: + //X + ind < 10; + //^ + }; + true + }) { + + } + } + } + """ + ) } diff --git a/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt b/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt index 9357a5c0c..6839aa894 100644 --- a/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt +++ b/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt @@ -1822,38 +1822,46 @@ module 0x1::main { } """) - fun `test no unification error for vector literal in specs with unknown type`() = testExpr(""" + fun `test unknown does not influence integer type for function for lhs`() = testExpr( + """ module 0x1::option { - struct Option has copy, drop, store { - vec: vector - } - spec fun spec_some(e: Element): Option { - Option { vec: vector[] } + fun some(e: Element): Element { e } + fun main() { + let unknown/*: unknown*/ = unknown_variable; + let a2 = 1; + some(a2) == unknown; + //^ integer } - spec module { - let addr1 = unknown_variable; // has unknown type - let addr2 = @0x1; - addr1 == spec_some(addr2); - addr2; - //^ address + } + """) + + fun `test unknown does not influence integer type for function for rhs`() = testExpr( + """ + module 0x1::option { + fun some(e: Element): Element { e } + fun main() { + let unknown/*: unknown*/ = unknown_variable; + let a2 = 1; + unknown == some(a2); + //^ integer } } """) - fun `test no unification error for vec(e) in specs with unknown type`() = testExpr(""" + fun `test no error for eq exprs when combining unknown type items`() = testExpr( + allowErrors = false, + code = """ module 0x1::option { struct Option has copy, drop, store { vec: vector } - spec fun spec_some(e: Element): Option { - Option { vec: vec(e) } - } - spec module { - let addr1 = unknown_variable; // has unknown type - let addr2 = @0x1; - addr1 == spec_some(addr2); - addr2; - //^ address + fun some(e: Element): Option { Option { vec: vector[e] } } + fun main() { + let unknown/*: unknown*/ = unknown_variable; + let a2 = @0x1; + unknown != some(a2); + unknown == some(a2); + //^ 0x1::option::Option
} } """) diff --git a/src/test/kotlin/org/move/utils/tests/types/TypificationTestCase.kt b/src/test/kotlin/org/move/utils/tests/types/TypificationTestCase.kt index 7b23f676a..eeeb5942f 100644 --- a/src/test/kotlin/org/move/utils/tests/types/TypificationTestCase.kt +++ b/src/test/kotlin/org/move/utils/tests/types/TypificationTestCase.kt @@ -42,10 +42,10 @@ abstract class TypificationTestCase : MvTestBase() { } } - protected fun testExpr(@Language("Move") code: String) { + protected fun testExpr(@Language("Move") code: String, allowErrors: Boolean = true) { InlineFile(myFixture, code, "main.move") check() -// if (!allowErrors) checkNoInferenceErrors() + if (!allowErrors) checkNoInferenceErrors() checkAllExpressionsTypified() } @@ -110,7 +110,8 @@ abstract class TypificationTestCase : MvTestBase() { } private fun checkNoInferenceErrors() { - val errors = myFixture.file.descendantsOfType().asSequence() + val errors = myFixture.file + .descendantsOfType() .flatMap { it.inference(false).typeErrors.asSequence() } .map { it.element to it.message() } .toList() @@ -143,6 +144,29 @@ abstract class TypificationTestCase : MvTestBase() { } } +// private fun checkNoTypeErrors() { +// val inferenceOwners = myFixture.file.descendantsOfType() +// val fileTypeErrors = inferenceOwners +// .flatMap { it.inference(false).typeErrors }.toList() +// if (fileTypeErrors.isNotEmpty()) { +// +// } +// val notTypifiedExprs = myFixture.file +// .descendantsOfType().toList() +// .filter { expr -> +// expr.inference(false)?.hasExprType(expr) == false +// } +// if (notTypifiedExprs.isNotEmpty()) { +// error( +// notTypifiedExprs.joinToString( +// "\n", +// "Some expressions are not typified during type inference: \n", +// "\nNote: All `MvExpr`s must be typified during type inference" +// ) { "\tAt `${it.text}` (line ${it.lineNumber})" } +// ) +// } +// } + private val PsiElement.lineNumber: Int get() = myFixture.getDocument(myFixture.file).getLineNumber(textOffset) }