diff --git a/build.gradle.kts b/build.gradle.kts index 714f42c55..9443053d5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,30 +12,26 @@ fun prop(name: String): String = ?: error("Property `$name` is not defined in gradle.properties for environment `$platformVersion`") val pluginJarName = "intellij-move-$platformVersion" -val pluginVersion = "1.29.0" +val pluginVersion = "1.30.0" val pluginGroup = "org.move" val javaVersion = JavaVersion.VERSION_17 -val kotlinJvmTarget = "17" -val kotlinStdlibVersion = "1.8.21" +val kotlinStdlibVersion = "1.9.0" group = pluginGroup version = pluginVersion plugins { id("java") - kotlin("jvm") version "1.8.21" - id("org.jetbrains.intellij") version "1.13.3" + kotlin("jvm") version "1.9.0" + id("org.jetbrains.intellij") version "1.15.0" id("org.jetbrains.grammarkit") version "2022.3.1" id("net.saliman.properties") version "1.5.2" } dependencies { - // kotlin stdlib source code - implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinStdlibVersion") - implementation("org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinStdlibVersion") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinStdlibVersion") - implementation("io.sentry:sentry:5.5.2") { + implementation("io.sentry:sentry:6.25.0") { exclude("org.slf4j") } implementation("com.github.ajalt.clikt:clikt:3.5.2") @@ -80,9 +76,6 @@ allprojects { main { kotlin.srcDirs("src/$platformVersion/main/kotlin") } -// test { -// kotlin.srcDirs("src/$platformVersion/test/kotlin") -// } } } @@ -103,11 +96,11 @@ allprojects { tasks { // workaround for gradle not seeing tests in 2021.3+ - val test by getting(Test::class) { - setScanForTestClasses(false) - // Only run tests from classes that end with "Test" - include("**/*Test.class") - } +// val test by getting(Test::class) { +// isScanForTestClasses = false +// // Only run tests from classes that end with "Test" +// include("**/*Test.class") +// } patchPluginXml { version.set("$pluginVersion.$platformVersion") @@ -148,7 +141,7 @@ allprojects { generateMoveLexer, generateMoveParser ) kotlinOptions { - jvmTarget = kotlinJvmTarget + jvmTarget = "17" languageVersion = "1.8" apiVersion = "1.6" freeCompilerArgs = listOf("-Xjvm-default=all") diff --git a/changelog/1.30.0.md b/changelog/1.30.0.md new file mode 100644 index 000000000..cfeaab299 --- /dev/null +++ b/changelog/1.30.0.md @@ -0,0 +1,23 @@ +# INTELLIJ MOVE CHANGELOG: 1.30.0 + +17 Jul 2023 + +## Features + +* Add "Download Aptos" button to the Move settings. + +* Address validation in the `Move.toml` file. + +* Function naming inspection. + +* Highlight resources and mutable references in the code. + +* Support underscores in integer and hex literals ([#107](https://github.com/pontem-network/intellij-move/issues/107)). + +## Fixes + +* More fixes for dot expr type inference. + +## Internal + +* Add `[dependencies]` and library path to the Sentry diagnostics. It will hopefully allow to fix issues with the dependency paths. \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb702f..15de90249 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/grammars/MoveLexer.flex b/src/main/grammars/MoveLexer.flex index 9cdff1bb0..ca14ba1d0 100644 --- a/src/main/grammars/MoveLexer.flex +++ b/src/main/grammars/MoveLexer.flex @@ -72,8 +72,8 @@ BECH32_ADDRESS=wallet1[A-Z0-9a-z&&[^boi1]]{6,83} POLKADOT_ADDRESS=[1-9A-HJ-NP-Za-km-z]{40}[1-9A-HJ-NP-Za-km-z]* BOOL_LITERAL=(true)|(false) -INTEGER_LITERAL=[0-9]+[a-zA-Z0-9]* -HEX_INTEGER_LITERAL=0x[0-9a-zA-Z]* +INTEGER_LITERAL=[0-9]+[a-zA-Z0-9_]* +HEX_INTEGER_LITERAL=0x[0-9a-zA-Z_]* HEX_STRING_LITERAL=x\" ( [0-9a-zA-Z]* ) (\")? BYTE_STRING_LITERAL=b\" ( [^\\\"\n] | \\[^] )* (\")? //BYTE_STRING_LITERAL=b\" ( [^\"\n] | (\\\")] )* (\")? diff --git a/src/main/grammars/MoveParser.bnf b/src/main/grammars/MoveParser.bnf index cb575a04a..a80cc3ae6 100644 --- a/src/main/grammars/MoveParser.bnf +++ b/src/main/grammars/MoveParser.bnf @@ -752,13 +752,14 @@ private ExprStmt_expr ::= ItemSpecBlockExpr private ExprStmt_recover ::= !(';' | '}' | <> | Expr_first) private Expr_first ::= let | if | while | loop | continue | break | return | spec | copy | move | abort +// | (<> ('<' | '[')) | IDENTIFIER | '*' | '&' | '!' | '|' | '{' | '(' | AnyLitToken_first | (AddressRef '::') private SpecExpr_first ::= include | apply | pragma | emits | assume | assert | ensures | axiom | modifies | aborts_if | aborts_with | requires - | invariant | choose + | invariant | choose | decreases | update /////////////////////////////////////////////////////////////////////////////////////////////////// // Blocks @@ -1050,7 +1051,7 @@ private DotExpr_field ::= '.' (!'.') StructDotField { pin = 2 consumeTokenMethod = "consumeTokenFast" } -StructDotField ::= IDENTIFIER +StructDotField ::= !(<> ('[' | '<')) IDENTIFIER !('(' | '::' | '!' | '{') { implements = ["org.move.lang.core.resolve.ref.MvStructFieldReferenceElement"] mixin = "org.move.lang.core.psi.ext.MvStructDotMixin" diff --git a/src/main/kotlin/org/move/cli/MoveProjectsService.kt b/src/main/kotlin/org/move/cli/MoveProjectsService.kt index 15febefcc..fcb296a31 100644 --- a/src/main/kotlin/org/move/cli/MoveProjectsService.kt +++ b/src/main/kotlin/org/move/cli/MoveProjectsService.kt @@ -161,40 +161,29 @@ class MoveProjectsService(val project: Project) : Disposable { private fun modifyProjects( updater: (List) -> CompletableFuture> ): CompletableFuture> { - val refreshStatusPublisher = - project.messageBus.syncPublisher(MOVE_PROJECTS_REFRESH_TOPIC) - val wrappedUpdater = { projects: List -> - refreshStatusPublisher.onRefreshStarted() updater(projects) } return projects.updateAsync(wrappedUpdater) .thenApply { projects -> buildWatcher.updateProjects(projects) - resetIDEState(projects) - projects - } - } - - private fun resetIDEState(projects: Collection) { - invokeAndWaitIfNeeded { - runWriteAction { - projectsIndex.resetIndex() - - PsiManager.getInstance(project).dropPsiCaches() - project.movePsiManager.incStructureModificationCount() - - // In unit tests roots change is done by the test framework in most cases - runOnlyInNonLightProject(project) { - ProjectRootManagerEx.getInstanceEx(project) - .makeRootsChange(EmptyRunnable.getInstance(), false, true) + invokeAndWaitIfNeeded { + runWriteAction { + projectsIndex.resetIndex() + + // In unit tests roots change is done by the test framework in most cases + runOnlyInNonLightProject(project) { + ProjectRootManagerEx.getInstanceEx(project) + .makeRootsChange(EmptyRunnable.getInstance(), false, true) + } + // increments structure modification counter in the subscriber + project.messageBus + .syncPublisher(MOVE_PROJECTS_TOPIC).moveProjectsUpdated(this, projects) + } } - project.messageBus - .syncPublisher(MOVE_PROJECTS_TOPIC) - .moveProjectsUpdated(this, projects) + projects } - } } override fun dispose() {} @@ -202,15 +191,10 @@ class MoveProjectsService(val project: Project) : Disposable { companion object { private val LOG = logger() - val MOVE_PROJECTS_TOPIC: Topic = Topic( + val MOVE_PROJECTS_TOPIC: Topic = Topic.create( "move projects changes", MoveProjectsListener::class.java ) - - val MOVE_PROJECTS_REFRESH_TOPIC: Topic = Topic( - "Move refresh", - MoveProjectsRefreshListener::class.java - ) } fun interface MoveProjectsListener { diff --git a/src/main/kotlin/org/move/cli/manifest/MoveToml.kt b/src/main/kotlin/org/move/cli/manifest/MoveToml.kt index dd2e598ce..2542ea557 100644 --- a/src/main/kotlin/org/move/cli/manifest/MoveToml.kt +++ b/src/main/kotlin/org/move/cli/manifest/MoveToml.kt @@ -115,10 +115,10 @@ class MoveToml( projectRoot: Path ): Pair? { val localPathValue = depTable["local"]?.stringValue() ?: return null - val localPath = + val normalizedLocalPath = projectRoot.resolve(localPathValue).toAbsolutePath().normalize() val subst = parseAddrSubst(depTable) - return Pair(TomlDependency.Local(depName, localPath), subst) + return Pair(TomlDependency.Local(depName, normalizedLocalPath), subst) } private fun parseGitDependency( diff --git a/src/main/kotlin/org/move/cli/module/BuildLibraryRootsProvider.kt b/src/main/kotlin/org/move/cli/module/BuildLibraryRootsProvider.kt index fd8fd9340..666efa0c3 100644 --- a/src/main/kotlin/org/move/cli/module/BuildLibraryRootsProvider.kt +++ b/src/main/kotlin/org/move/cli/module/BuildLibraryRootsProvider.kt @@ -32,7 +32,27 @@ class MoveLibrary( override fun getPresentableText(): String = if (version != null) "$name $version" else name } -private val MoveProject.ideaLibraries: Collection +class BuildLibraryRootsProvider : AdditionalLibraryRootsProvider() { + override fun getAdditionalProjectLibraries(project: Project): Collection { + return project.moveProjects + .allProjects + .smartFlatMap { it.ideaLibraries } + .toMutableSet() + } + + override fun getRootsToWatch(project: Project): List { + return getAdditionalProjectLibraries(project).flatMap { it.sourceRoots } + } +} + +private fun Collection.smartFlatMap(transform: (U) -> Collection): Collection = + when (size) { + 0 -> emptyList() + 1 -> transform(first()) + else -> this.flatMap(transform) + } + +private val MoveProject.ideaLibraries: Collection get() { return this.dependencies .map { it.first } @@ -50,16 +70,3 @@ private val MoveProject.ideaLibraries: Collection } } - -class BuildLibraryRootsProvider : AdditionalLibraryRootsProvider() { - override fun getAdditionalProjectLibraries(project: Project): MutableSet { - return project.moveProjects - .allProjects - .flatMap { it.ideaLibraries } - .toMutableSet() - } - - override fun getRootsToWatch(project: Project): List { - return getAdditionalProjectLibraries(project).flatMap { it.sourceRoots } - } -} diff --git a/src/main/kotlin/org/move/cli/sentryReporter/SentryContextsProjectsListener.kt b/src/main/kotlin/org/move/cli/sentryReporter/SentryContextsProjectsListener.kt new file mode 100644 index 000000000..6cb4c4329 --- /dev/null +++ b/src/main/kotlin/org/move/cli/sentryReporter/SentryContextsProjectsListener.kt @@ -0,0 +1,58 @@ +package org.move.cli.sentryReporter + +import io.sentry.Sentry +import org.move.cli.MoveProject +import org.move.cli.MoveProjectsService +import org.move.cli.MoveProjectsService.MoveProjectsListener +import org.move.cli.manifest.TomlDependency +import org.move.openapiext.getTable +import org.move.openapiext.getTablesByFirstSegment +import org.move.openapiext.syntheticLibraries + +@Suppress("PropertyName") +private data class MoveTomlContext( + val name: String, + val dependencies_parsed: List, + val dependencies_raw: List, +) + +private data class SyntheticLibraryContext(val roots: List) + +private data class MoveProjectContext( + val moveToml: MoveTomlContext, + val syntheticLibraries: List +) + +class SentryContextsProjectsListener : MoveProjectsListener { + override fun moveProjectsUpdated(service: MoveProjectsService, projects: Collection) { + Sentry.configureScope { + it.setContexts( + "projects", + projects.mapNotNull { moveProject -> getMoveProjectContext(moveProject) } + ) + } + } + + private fun getMoveProjectContext(moveProject: MoveProject): MoveProjectContext? { + val tomlFile = moveProject.currentPackage.moveToml.tomlFile ?: return null + + val rawDeps = mutableListOf() + val depsTable = tomlFile.getTable("dependencies") + if (depsTable != null) { + rawDeps.add(depsTable.text) + } + for (depInlineTable in tomlFile.getTablesByFirstSegment("dependencies")) { + rawDeps.add(depInlineTable.text) + } + + val moveTomt = MoveTomlContext( + name = moveProject.currentPackage.packageName, + dependencies_raw = rawDeps, + dependencies_parsed = moveProject.currentPackage.moveToml.deps.map { it.first }, + ) + val syntheticLibraries = + moveProject.project.syntheticLibraries + .map { SyntheticLibraryContext(it.allRoots.map { it.path }) } + return MoveProjectContext(moveTomt, syntheticLibraries) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/move/cli/PontemSentryErrorReporter.kt b/src/main/kotlin/org/move/cli/sentryReporter/SentryErrorReporter.kt similarity index 93% rename from src/main/kotlin/org/move/cli/PontemSentryErrorReporter.kt rename to src/main/kotlin/org/move/cli/sentryReporter/SentryErrorReporter.kt index c150816a4..913fb6769 100644 --- a/src/main/kotlin/org/move/cli/PontemSentryErrorReporter.kt +++ b/src/main/kotlin/org/move/cli/sentryReporter/SentryErrorReporter.kt @@ -1,4 +1,4 @@ -package org.move.cli +package org.move.cli.sentryReporter import com.intellij.diagnostic.DiagnosticBundle import com.intellij.diagnostic.IdeErrorsDialog @@ -20,17 +20,16 @@ import io.sentry.UserFeedback import io.sentry.protocol.Message import io.sentry.protocol.SentryId import org.move.cli.settings.moveSettings -import org.move.cli.settings.pluginDebugMode import org.move.openapiext.project import org.move.stdext.asMap import java.awt.Component -class PontemSentryErrorReporter : ErrorReportSubmitter() { +class SentryErrorReporter : ErrorReportSubmitter() { init { Sentry.init { options -> options.dsn = "https://a3153f348f8d43f189c4228db47cfc0d@sentry.pontem.network/6" - options.enableUncaughtExceptionHandler = false + options.isEnableUncaughtExceptionHandler = false } } @@ -85,10 +84,8 @@ class PontemSentryErrorReporter : ErrorReportSubmitter() { settings.remove("aptosPath") sentryEvent.contexts["Settings"] = settings } - sentryEvent.contexts["Stacktrace"] = - mapOf( - "Value" to event.throwableText - ) + // IdeaLoggingEvent only provides text stacktrace + sentryEvent.contexts["Stacktrace"] = mapOf("Value" to event.throwableText) val sentryMessage = Message() sentryMessage.formatted = event.errorMessage diff --git a/src/main/kotlin/org/move/cli/settings/MoveSettingsPanel.kt b/src/main/kotlin/org/move/cli/settings/MoveSettingsPanel.kt index 30659121a..72a33be6c 100644 --- a/src/main/kotlin/org/move/cli/settings/MoveSettingsPanel.kt +++ b/src/main/kotlin/org/move/cli/settings/MoveSettingsPanel.kt @@ -7,6 +7,7 @@ import com.intellij.openapi.util.Disposer import com.intellij.ui.dsl.builder.Panel import com.intellij.ui.dsl.gridLayout.HorizontalAlign import org.move.cli.runConfigurations.aptos.Aptos +import org.move.ide.actions.download.ui.DownloadAptosDialog import org.move.openapiext.UiDebouncer import org.move.openapiext.pathField import org.move.openapiext.showSettings @@ -55,10 +56,23 @@ class MoveSettingsPanel( comment("(required)") } row("Version") { cell(versionLabel) } - row(" ") { + row { + button("Download Aptos CLI") { + val dialog = DownloadAptosDialog(parentComponent = aptosPathField) + dialog.show() + + val newAptosPath = dialog.outPath + if (dialog.isOK && newAptosPath != null) { + data = Data(newAptosPath) + } + } + } + row { link("Set default project settings") { ProjectManager.getInstance().defaultProject.showSettings() - }.visible(showDefaultSettingsLink) + } + .visible(showDefaultSettingsLink) + .horizontalAlign(HorizontalAlign.RIGHT) } } diff --git a/src/main/kotlin/org/move/cli/settings/PerProjectMoveConfigurable.kt b/src/main/kotlin/org/move/cli/settings/PerProjectMoveConfigurable.kt index 51bbaab5b..fefa6bcb9 100644 --- a/src/main/kotlin/org/move/cli/settings/PerProjectMoveConfigurable.kt +++ b/src/main/kotlin/org/move/cli/settings/PerProjectMoveConfigurable.kt @@ -33,15 +33,14 @@ class PerProjectMoveConfigurable(val project: Project) : BoundConfigurable("Move checkBox("Enable debug mode") .bindSelected(state::debugMode) comment( - "Enables some explicit crashes in the different parts of code. " + - "Useful for bug reporting to help the development of the plugin." + "Enables some explicit crashes in the plugin code. Useful for the error reporting." ) } row { checkBox("Skip fetching latest git dependencies for tests") .bindSelected(state::skipFetchLatestGitDeps) comment( - "Adds --skip-fetch-latest-git-deps to generated test runs." + "Adds --skip-fetch-latest-git-deps to the test runs." ) } } diff --git a/src/main/kotlin/org/move/ide/actions/download/AptosDownloader.kt b/src/main/kotlin/org/move/ide/actions/download/AptosDownloader.kt new file mode 100644 index 000000000..a81feaa5c --- /dev/null +++ b/src/main/kotlin/org/move/ide/actions/download/AptosDownloader.kt @@ -0,0 +1,184 @@ +package org.move.ide.actions.download + +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.Task +import com.intellij.openapi.util.SystemInfo +import java.io.File +import java.io.IOException +import java.net.HttpURLConnection +import java.net.MalformedURLException +import java.net.URL +import java.net.URLEncoder +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import java.util.zip.ZipFile +import javax.swing.JComponent +import javax.swing.SwingUtilities + +class DownloadAptosTask( + parentComponent: JComponent, + private val aptosVersion: String, + private val destDir: String, +) : + Task.WithResult(null, parentComponent, "Downloading Aptos CLI", true) { + + @Suppress("PrivatePropertyName") + private val LOG = logger() + + override fun compute(indicator: ProgressIndicator): String? { + val os = os() ?: return null + val downloadUrl = "https://github.com/aptos-labs/aptos-core" + + "/releases/download/aptos-cli-v$aptosVersion/aptos-cli-$aptosVersion-$os-x86_64.zip" + + LOG.debug("Downloading '$downloadUrl' to '$destDir'") + + return download(downloadUrl, File(destDir)) { + when (it) { + is DownloadStatus.Downloading -> + SwingUtilities.invokeLater { + indicator.text = it.report + } + + is DownloadStatus.Failed -> { + // TODO: show error + } + + is DownloadStatus.Finished -> { + // TODO: show report + } + } + } + } + + private fun download( + aptosUrl: String, + destinationDir: File, + statusListener: (DownloadStatus) -> Unit + ): String? { + if (!destinationDir.isDirectory) { + statusListener( + DownloadStatus.Failed( + "${destinationDir.absolutePath} is not a directory", + aptosUrl, + destinationDir + ) + ) + return null + } + if (aptosUrl.startsWith("http")) { + val url = try { + URL(aptosUrl) + } catch (e: MalformedURLException) { + statusListener(DownloadStatus.Failed("Bad Aptos HTTP url", aptosUrl, destinationDir)) + return null + } + val destinationFile = File(destinationDir, aptosUrl.toFileName()) + + val httpConn = url.openConnection() as HttpURLConnection + try { + httpConn.getHeaderField("Content-Type") + } catch (e: IllegalStateException) { + statusListener(DownloadStatus.Failed("Nothing to download", aptosUrl, destinationDir)) + return null + } + +// val fraction = 0.0 + statusListener(DownloadStatus.Downloading("Fetching $aptosUrl")) + + val inputStream = try { + url.openStream() + } catch (e: IOException) { + statusListener(DownloadStatus.Failed("Can't connect", aptosUrl, destinationDir)) + return null + } + + Files.copy(inputStream, destinationFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + + // unzip + val outFile = File(destinationDir, "aptos-$aptosVersion") + statusListener( + DownloadStatus.Downloading( + "Unzipping ${destinationFile.absoluteFile} to the ${outFile.name}" + ) + ) + ZipFile(destinationFile).use { zip -> + zip.entries().asSequence().forEach { entry -> + zip.getInputStream(entry).use { input -> + if (entry.name == "aptos") { + outFile.outputStream().use { output -> + input.copyTo(output) + } + outFile.setExecutable(true) + } + } + } + } + // remove zip archive + statusListener(DownloadStatus.Downloading("Removing zip archive")) + destinationFile.delete() + + statusListener(DownloadStatus.Finished(outFile.absolutePath, aptosUrl, destinationDir)) + return outFile.absolutePath + } else { + statusListener(DownloadStatus.Failed("Can't understand link type", aptosUrl, destinationDir)) + return null + } + } + + private fun String.toFileName(): String { + return if (this.lastIndexOf("/") != this.length && "/" in this) { + URLEncoder.encode(this.substringAfterLast("/"), Charsets.UTF_8.toString()) + } else { + URLEncoder.encode(this, Charsets.UTF_8.toString()) + } + } + + private fun os(): String? { + return when { + SystemInfo.isWindows -> "Windows" + SystemInfo.isLinux -> "Ubuntu" + SystemInfo.isMac -> "MacOSX" + else -> { + // TODO: show error somewhere + return null + } + } + } +} + +//class AptosDownloader( +// private val parentComponent: JComponent, +// private val aptosVersion: String, +// private val destDir: String, +//) { +// private val LOG = logger() +// +// +// fun start() { +// object : Task.Modal(null, parentComponent, "Downloading Aptos CLI", true) { +// override fun run(indicator: ProgressIndicator) { +// +// val aptosUrl = aptosUrl() ?: return +// LOG.debug("Downloading '$aptosUrl' to '$destDir'") +// +// download(aptosUrl, File(destDir)) { +// when (it) { +// is DownloadStatus.Downloading -> +// SwingUtilities.invokeLater { +// indicator.text = it.report +// } +// +// is DownloadStatus.Failed -> { +// // TODO: show error +// } +// +// is DownloadStatus.Finished -> { +// // TODO: show report +// } +// } +// } +// } +// }.queue() +// } +//} diff --git a/src/main/kotlin/org/move/ide/actions/download/DownloadFinishNotifier.kt b/src/main/kotlin/org/move/ide/actions/download/DownloadFinishNotifier.kt new file mode 100644 index 000000000..4f57ad7f2 --- /dev/null +++ b/src/main/kotlin/org/move/ide/actions/download/DownloadFinishNotifier.kt @@ -0,0 +1,41 @@ +/* + * MIT License + * + * Copyright (c) 2020-2020 JetBrains s.r.o. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.move.ide.actions.download + +import com.intellij.notification.Notification +import com.intellij.notification.NotificationDisplayType +import com.intellij.notification.NotificationGroup +import com.intellij.notification.NotificationType +import com.intellij.openapi.project.Project + +object DownloadFinishNotifier { + + fun notify(project: Project, title: String, content: String): Notification { + val notification: Notification = NOTIFICATION_GROUP.createNotification(title, content, NotificationType.INFORMATION) + notification.notify(project) + return notification + } + + private val NOTIFICATION_GROUP = NotificationGroup("Download This", NotificationDisplayType.BALLOON, true) +} diff --git a/src/main/kotlin/org/move/ide/actions/download/DownloadStatus.kt b/src/main/kotlin/org/move/ide/actions/download/DownloadStatus.kt new file mode 100644 index 000000000..d24843e02 --- /dev/null +++ b/src/main/kotlin/org/move/ide/actions/download/DownloadStatus.kt @@ -0,0 +1,22 @@ +package org.move.ide.actions.download + +import java.io.File + +sealed class DownloadStatus { + class Downloading(val report: String) : DownloadStatus() + + class Finished(val destinationFile: String, query: String, destinationDir: File) : DownloadStatus() + + class Failed(val reason: String, query: String, destinationDir: File) : DownloadStatus() { + override fun toString() = "Failed[$reason]" + } +} + +// : +// DownloadStatus(query, destinationDir) + +//class Finished(val destinationFile: String, query: String, destinationDir: File) : DownloadStatus(query, destinationDir) +// +//class Failed(val reason: String, query: String, destinationDir: File) : DownloadStatus(query, destinationDir) { + +//} diff --git a/src/main/kotlin/org/move/ide/actions/download/PathSelector.kt b/src/main/kotlin/org/move/ide/actions/download/PathSelector.kt new file mode 100644 index 000000000..761db2636 --- /dev/null +++ b/src/main/kotlin/org/move/ide/actions/download/PathSelector.kt @@ -0,0 +1,37 @@ +/* + * MIT License + * + * Copyright (c) 2020-2020 JetBrains s.r.o. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.move.ide.actions.download + +import com.intellij.execution.Platform + +object PathSelector { + + fun getHomeAptosFolder(): String { + val userHome = System.getProperty("user.home") + return joinPath(userHome, "aptos-cli") + } + + private fun joinPath(vararg parts: String) = + parts.joinTo(StringBuilder(), Platform.current().fileSeparator.toString()).toString() +} diff --git a/src/main/kotlin/org/move/ide/actions/download/ui/DownloadAptosDialog.kt b/src/main/kotlin/org/move/ide/actions/download/ui/DownloadAptosDialog.kt new file mode 100644 index 000000000..ac388ac82 --- /dev/null +++ b/src/main/kotlin/org/move/ide/actions/download/ui/DownloadAptosDialog.kt @@ -0,0 +1,125 @@ +/* + * MIT License + * + * Copyright (c) 2020-2020 JetBrains s.r.o. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.move.ide.actions.download.ui + +import com.intellij.execution.Platform +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.dsl.builder.bindSelected +import com.intellij.ui.dsl.builder.bindText +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.selected +import org.move.ide.actions.download.DownloadAptosTask +import org.move.ide.actions.download.PathSelector +import java.io.File +import javax.swing.JComponent + +class DownloadAptosDialog( + private val parentComponent: JComponent, +) : DialogWrapper(null, parentComponent, true, IdeModalityType.IDE) { + + var outPath: String? = null + + private var version = "2.0.2" + private var downloadToProjectRoot: Boolean = true + private var customDestinationDirectory: String = "" + + init { + init() + title = "Download Aptos" + setOKButtonText("Download") + } + + override fun createCenterPanel(): JComponent { + return panel { + row("Version") { + textField() + .bindText( + { version }, + { version = it } + ) + } + buttonsGroup("Download to") { + row { + radioButton("Home directory") + .bindSelected( + { downloadToProjectRoot }, + { downloadToProjectRoot = it } + ) + val sep = fileSeparator() + comment("Download to the \$HOME\$${sep}aptos-cli${sep} directory") + } + row { + val radio = radioButton("Other") + radio + .bindSelected( + { !downloadToProjectRoot }, + { downloadToProjectRoot = !it } + ) + textFieldWithBrowseButton("Choose Directory") + .enabledIf(radio.selected) + .bindText( + { customDestinationDirectory }, + { customDestinationDirectory = it } + ) + } + } + } + } + + override fun getDimensionServiceKey() = DownloadAptosDialog::class.simpleName + + override fun doOKAction() { + super.doOKAction() + + val destinationDir = when (downloadToProjectRoot) { + true -> { + val dir = PathSelector.getHomeAptosFolder() + val file = File(dir) + if (!file.exists()) { + if (!file.mkdir()) { + // TODO: show error +// SwingUtilities.invokeLater { +// Messages.showErrorDialog( +// project, +// "Cannot create $dir directory", +// "IO Error", +// ) +// } + return + } + } + dir + } + false -> customDestinationDirectory + } + val downloadTask = DownloadAptosTask(parentComponent, version, destinationDir) + downloadTask.queue() + + outPath = downloadTask.result + } + + companion object { + fun fileSeparator(): String = Platform.current().fileSeparator.toString() + } +} diff --git a/src/main/kotlin/org/move/ide/annotator/HighlightingAnnotator.kt b/src/main/kotlin/org/move/ide/annotator/HighlightingAnnotator.kt index b1ea8c04b..3ce082164 100644 --- a/src/main/kotlin/org/move/ide/annotator/HighlightingAnnotator.kt +++ b/src/main/kotlin/org/move/ide/annotator/HighlightingAnnotator.kt @@ -8,6 +8,10 @@ import org.move.lang.MvElementTypes.HEX_INTEGER_LITERAL import org.move.lang.MvElementTypes.IDENTIFIER import org.move.lang.core.psi.* import org.move.lang.core.psi.ext.* +import org.move.lang.core.types.infer.inference +import org.move.lang.core.types.ty.Ty +import org.move.lang.core.types.ty.TyReference +import org.move.lang.core.types.ty.TyStruct val INTEGER_TYPE_IDENTIFIERS = setOf("u8", "u16", "u32", "u64", "u128", "u256") val SPEC_INTEGER_TYPE_IDENTIFIERS = INTEGER_TYPE_IDENTIFIERS + setOf("num") @@ -74,16 +78,30 @@ class HighlightingAnnotator : MvAnnotatorBase() { if (element is MvModule) return MvColor.MODULE if (element is MvVectorLitExpr) return MvColor.VECTOR_LITERAL - val path = element as? MvPath ?: return null - // any qual :: access is not highlighted - if (path.isQualPath) return null + return when (element) { + is MvPath -> highlightPathElement(element) + is MvBindingPat -> highlightBindingPat(element) + else -> null + } + } - return highlightPathElement(path) + private fun highlightBindingPat(bindingPat: MvBindingPat): MvColor { + val msl = bindingPat.isMsl() + val itemTy = bindingPat.inference(msl)?.getPatType(bindingPat) + return if (itemTy != null) { + highlightVariableByType(itemTy) + } else { + MvColor.VARIABLE + } } private fun highlightPathElement(path: MvPath): MvColor? { + // any qual :: access is not highlighted + if (path.isQualPath) return null + val identifierName = path.identifierName - return when (path.parent) { + val pathOwner = path.parent + return when (pathOwner) { is MvPathType -> { when { identifierName in PRIMITIVE_TYPE_IDENTIFIERS -> MvColor.PRIMITIVE_TYPE @@ -121,10 +139,37 @@ class HighlightingAnnotator : MvAnnotatorBase() { if (item is MvConst) { MvColor.CONSTANT } else { - MvColor.VARIABLE + val msl = pathOwner.isMsl() + val itemTy = pathOwner.inference(msl)?.getExprType(pathOwner) + if (itemTy != null) { + highlightVariableByType(itemTy) + } else { + MvColor.VARIABLE + } } } else -> null } } + + private fun highlightVariableByType(itemTy: Ty): MvColor = + when { + itemTy is TyReference -> { + val referenced = itemTy.referenced + when { + referenced is TyStruct && referenced.item.hasKey -> + if (itemTy.isMut) MvColor.MUT_REF_TO_KEY_OBJECT else MvColor.REF_TO_KEY_OBJECT + referenced is TyStruct && referenced.item.hasStore && !referenced.item.hasDrop -> + if (itemTy.isMut) MvColor.MUT_REF_TO_STORE_NO_DROP_OBJECT else MvColor.REF_TO_STORE_NO_DROP_OBJECT + referenced is TyStruct && referenced.item.hasStore && referenced.item.hasDrop -> + if (itemTy.isMut) MvColor.MUT_REF_TO_STORE_OBJECT else MvColor.REF_TO_STORE_OBJECT + else -> + if (itemTy.isMut) MvColor.MUT_REF else MvColor.REF + } + } + itemTy is TyStruct && itemTy.item.hasStore && !itemTy.item.hasDrop -> MvColor.STORE_NO_DROP_OBJECT + itemTy is TyStruct && itemTy.item.hasStore -> MvColor.STORE_OBJECT + itemTy is TyStruct && itemTy.item.hasKey -> MvColor.KEY_OBJECT + else -> MvColor.VARIABLE + } } diff --git a/src/main/kotlin/org/move/ide/annotator/MvSyntaxErrorAnnotator.kt b/src/main/kotlin/org/move/ide/annotator/MvSyntaxErrorAnnotator.kt index 93a49114c..92a83c362 100644 --- a/src/main/kotlin/org/move/ide/annotator/MvSyntaxErrorAnnotator.kt +++ b/src/main/kotlin/org/move/ide/annotator/MvSyntaxErrorAnnotator.kt @@ -3,13 +3,8 @@ package org.move.ide.annotator import com.intellij.lang.annotation.AnnotationHolder import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement -import org.move.lang.core.psi.MvCastExpr -import org.move.lang.core.psi.MvLitExpr -import org.move.lang.core.psi.MvParensExpr -import org.move.lang.core.psi.MvVisitor -import org.move.lang.core.psi.ext.Literal -import org.move.lang.core.psi.ext.literal -import org.move.lang.core.psi.ext.startOffset +import org.move.lang.core.psi.* +import org.move.lang.core.psi.ext.* import org.move.lang.utils.Diagnostic import org.move.lang.utils.addToHolder @@ -22,6 +17,7 @@ class MvSyntaxErrorAnnotator : MvAnnotatorBase() { val visitor = object : MvVisitor() { override fun visitLitExpr(expr: MvLitExpr) = checkLitExpr(moveHolder, expr) override fun visitCastExpr(expr: MvCastExpr) = checkCastExpr(moveHolder, expr) + override fun visitStruct(s: MvStruct) = checkStruct(moveHolder, s) } element.accept(visitor) } @@ -35,6 +31,13 @@ class MvSyntaxErrorAnnotator : MvAnnotatorBase() { } } + private fun checkStruct(holder: MvAnnotationHolder, struct: MvStruct) { + val native = struct.native ?: return + val errorRange = TextRange.create(native.startOffset, struct.structKw.endOffset) + Diagnostic.NativeStructNotSupported(struct, errorRange) + .addToHolder(holder) + } + private fun checkLitExpr(holder: MvAnnotationHolder, litExpr: MvLitExpr) { val lit = litExpr.literal when (lit) { @@ -59,7 +62,7 @@ class MvSyntaxErrorAnnotator : MvAnnotatorBase() { } } for ((i, char) in actualLitValue.toList().withIndex()) { - if (char !in ACCEPTABLE_HEX_SYMBOLS) { + if (char !in ACCEPTABLE_HEX_INTEGER_SYMBOLS) { val offset = actualLitOffset + i holder.createErrorAnnotation( TextRange.from(offset, 1), @@ -128,18 +131,15 @@ class MvSyntaxErrorAnnotator : MvAnnotatorBase() { companion object { private val INTEGER_WITH_SUFFIX_REGEX = - Regex("([0-9a-zA-Z]+)(u[0-9]{1,4})") + Regex("([0-9a-zA-Z_]+)(u[0-9]{1,4})") private val ACCEPTABLE_INTEGER_SYMBOLS = - setOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9') + setOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_') private val ACCEPTABLE_INTEGER_SUFFIXES = setOf("u8", "u16", "u32", "u64", "u128", "u256") private val HEX_INTEGER_WITH_SUFFIX_REGEX = - Regex("([0-9a-zA-Z]+)*(u[0-9]{1,4})") + Regex("([0-9a-zA-Z_]+)*(u[0-9]{1,4})") private val ACCEPTABLE_HEX_SYMBOLS = setOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') -// private val INTEGER_WITH_SUFFIX_REGEX = -// Regex("([0-9a-zA-Z]+)(u(8)|(16)|(32)|(64)|(128)|(256))") - -// private val INTEGER_REGEX = Regex("[0-9]+(u(8)|(16)|(32)|(64)|(128)|(256))?") + private val ACCEPTABLE_HEX_INTEGER_SYMBOLS = ACCEPTABLE_HEX_SYMBOLS + setOf('_') } } diff --git a/src/main/kotlin/org/move/ide/colors/MvColor.kt b/src/main/kotlin/org/move/ide/colors/MvColor.kt index 9953969dd..0eed6ae28 100644 --- a/src/main/kotlin/org/move/ide/colors/MvColor.kt +++ b/src/main/kotlin/org/move/ide/colors/MvColor.kt @@ -9,6 +9,20 @@ enum class MvColor(humanName: String, default: TextAttributesKey? = null) { VARIABLE("Variables//Default", Default.IDENTIFIER), FIELD("Variables//Field", Default.INSTANCE_FIELD), + KEY_OBJECT("Variables//Object with 'key'", Default.IDENTIFIER), + STORE_OBJECT("Variables//Object with 'store'", Default.IDENTIFIER), + STORE_NO_DROP_OBJECT("Variables//Object with 'store' but without 'drop'", Default.IDENTIFIER), + + REF("Variables//Reference", Default.IDENTIFIER), + REF_TO_KEY_OBJECT("Variables//Reference to object with 'key'", Default.IDENTIFIER), + REF_TO_STORE_OBJECT("Variables//Reference to object with 'store'", Default.IDENTIFIER), + REF_TO_STORE_NO_DROP_OBJECT("Variables//Reference to object with 'store' but without 'drop'", Default.IDENTIFIER), + + MUT_REF("Variables//Mutable reference", Default.IDENTIFIER), + MUT_REF_TO_KEY_OBJECT("Variables//Mutable reference to object with 'key' ability", Default.IDENTIFIER), + MUT_REF_TO_STORE_OBJECT("Variables//Mutable reference to object with 'store' ability", Default.IDENTIFIER), + MUT_REF_TO_STORE_NO_DROP_OBJECT("Variables//Mutable reference to object with 'store' ability but without 'drop'", Default.IDENTIFIER), + MODULE("Modules//Module definition", Default.IDENTIFIER), CONSTANT("Variables//Constant", Default.CONSTANT), diff --git a/src/main/kotlin/org/move/ide/inspections/MvNamingInspection.kt b/src/main/kotlin/org/move/ide/inspections/MvNamingInspection.kt index e4da959b4..f59be8c8d 100644 --- a/src/main/kotlin/org/move/ide/inspections/MvNamingInspection.kt +++ b/src/main/kotlin/org/move/ide/inspections/MvNamingInspection.kt @@ -1,10 +1,7 @@ package org.move.ide.inspections import com.intellij.codeInspection.ProblemsHolder -import org.move.lang.core.psi.MvBindingPat -import org.move.lang.core.psi.MvConst -import org.move.lang.core.psi.MvStruct -import org.move.lang.core.psi.MvVisitor +import org.move.lang.core.psi.* abstract class MvNamingInspection(private val elementTitle: String) : MvLocalInspectionTool() { @@ -28,6 +25,26 @@ class MvConstNamingInspection : MvNamingInspection("Constant") { } } +class MvFunctionNamingInspection : MvNamingInspection("Function") { + override fun buildMvVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : MvVisitor() { + + override fun visitFunction(o: MvFunction) = checkFunctionName(o) + + override fun visitSpecFunction(o: MvSpecFunction) = checkFunctionName(o) + + private fun checkFunctionName(o: MvFunctionLike) { + val ident = o.nameIdentifier ?: return + val name = ident.text + if (name.startsWithUnderscore()) { + holder.registerProblem( + ident, + "Invalid function name '$name'. Function names cannot start with '_'" + ) + } + } + } +} + class MvStructNamingInspection : MvNamingInspection("Struct") { override fun buildMvVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : MvVisitor() { override fun visitStruct(o: MvStruct) { @@ -65,3 +82,5 @@ class MvLocalBindingNamingInspection : MvNamingInspection("Local variable") { fun String.startsWithUpperCaseLetter(): Boolean = this[0].isUpperCase() fun String.startsWithLowerCaseLetter(): Boolean = this[0].isLowerCase() + +fun String.startsWithUnderscore(): Boolean = this[0] == '_' diff --git a/src/main/kotlin/org/move/ide/typing/MvQuoteHandler.kt b/src/main/kotlin/org/move/ide/typing/MvQuoteHandler.kt index 8bb345550..4000a8861 100644 --- a/src/main/kotlin/org/move/ide/typing/MvQuoteHandler.kt +++ b/src/main/kotlin/org/move/ide/typing/MvQuoteHandler.kt @@ -12,15 +12,19 @@ class MvQuoteHandler : SimpleTokenSetQuoteHandler(BYTE_STRING_LITERAL, HEX_STRIN override fun isOpeningQuote(iterator: HighlighterIterator, offset: Int): Boolean { val elementType = iterator.tokenType val start = iterator.start - return when (elementType) { - BYTE_STRING_LITERAL, HEX_STRING_LITERAL -> offset - start <= 1 + val result = when (elementType) { + BYTE_STRING_LITERAL, HEX_STRING_LITERAL -> start == (offset + 1) else -> super.isOpeningQuote(iterator, offset) } + return result } override fun isNonClosedLiteral(iterator: HighlighterIterator, chars: CharSequence): Boolean { - if (iterator.tokenType == HEX_STRING_LITERAL) - return iterator.end - iterator.start == 2 + if (iterator.tokenType == HEX_STRING_LITERAL + || iterator.tokenType == BYTE_STRING_LITERAL + ) { + return (iterator.end - iterator.start) == 2 + } return super.isNonClosedLiteral(iterator, chars) } diff --git a/src/main/kotlin/org/move/ide/wordSelection/MvListSelectionHandler.kt b/src/main/kotlin/org/move/ide/wordSelection/MvListSelectionHandler.kt index 0989eacba..150f257c1 100644 --- a/src/main/kotlin/org/move/ide/wordSelection/MvListSelectionHandler.kt +++ b/src/main/kotlin/org/move/ide/wordSelection/MvListSelectionHandler.kt @@ -22,7 +22,7 @@ class MvListSelectionHandler : ExtendWordSelectionHandlerBase() { cursorOffset: Int, editor: Editor ): List? { - val node = e.node!! + val node = e.node ?: return null val startNode = node.findChildByType(LIST_OPEN_SYMBOLS) ?: return null val endNode = node.findChildByType(LIST_CLOSE_SYMBOLS) ?: return null val range = TextRange(startNode.startOffset + 1, endNode.startOffset) diff --git a/src/main/kotlin/org/move/ide/wordSelection/MvStringSelectionHandler.kt b/src/main/kotlin/org/move/ide/wordSelection/MvStringSelectionHandler.kt new file mode 100644 index 000000000..c52be2d01 --- /dev/null +++ b/src/main/kotlin/org/move/ide/wordSelection/MvStringSelectionHandler.kt @@ -0,0 +1,29 @@ +package org.move.ide.wordSelection + +import com.intellij.codeInsight.editorActions.ExtendWordSelectionHandlerBase +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import org.move.lang.MvElementTypes.BYTE_STRING_LITERAL +import org.move.lang.MvElementTypes.HEX_STRING_LITERAL +import org.move.lang.core.psi.ext.elementType +import org.move.lang.core.psi.ext.startOffset + +class MvStringSelectionHandler : ExtendWordSelectionHandlerBase() { + override fun canSelect(e: PsiElement): Boolean = + e.elementType == BYTE_STRING_LITERAL || e.elementType == HEX_STRING_LITERAL + + override fun select( + e: PsiElement, + editorText: CharSequence, + cursorOffset: Int, + editor: Editor + ): List? { + val startQuote = e.text.indexOf('"').takeIf { it != -1 } ?: return null + val endQuote = + e.text.indexOf('"', startIndex = startQuote + 1) + .takeIf { it != -1 } ?: return null + val range = TextRange(e.startOffset + startQuote + 1, e.startOffset + endQuote) + return listOf(range) + } +} diff --git a/src/main/kotlin/org/move/lang/core/psi/MvPsiManager.kt b/src/main/kotlin/org/move/lang/core/psi/MvPsiManager.kt index 1478a1278..2cc5fe740 100644 --- a/src/main/kotlin/org/move/lang/core/psi/MvPsiManager.kt +++ b/src/main/kotlin/org/move/lang/core/psi/MvPsiManager.kt @@ -22,6 +22,7 @@ import com.intellij.testFramework.LightVirtualFile import com.intellij.util.messages.MessageBusConnection import com.intellij.util.messages.Topic import org.move.cli.MoveProjectsService +import org.move.cli.MoveProjectsService.MoveProjectsListener import org.move.cli.moveProjects import org.move.lang.MoveFile import org.move.lang.MoveFileType @@ -101,14 +102,14 @@ class MvPsiManagerImpl(val project: Project) : MvPsiManager, Disposable { init { PsiManager.getInstance(project).addPsiTreeChangeListener(CacheInvalidator(), this) - project.messageBus.connect().subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener { - override fun rootsChanged(event: ModuleRootEvent) { - incStructureModificationCount() - } - }) - project.messageBus.connect().subscribe( - MoveProjectsService.MOVE_PROJECTS_TOPIC, - MoveProjectsService.MoveProjectsListener { _, _ -> + project.messageBus.connect() + .subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener { + override fun rootsChanged(event: ModuleRootEvent) { + incStructureModificationCount() + } + }) + project.messageBus.connect() + .subscribe(MoveProjectsService.MOVE_PROJECTS_TOPIC, MoveProjectsListener { _, _ -> incStructureModificationCount() }) } diff --git a/src/main/kotlin/org/move/lang/core/psi/ext/MvStruct.kt b/src/main/kotlin/org/move/lang/core/psi/ext/MvStruct.kt index 852db5096..713efb5c8 100644 --- a/src/main/kotlin/org/move/lang/core/psi/ext/MvStruct.kt +++ b/src/main/kotlin/org/move/lang/core/psi/ext/MvStruct.kt @@ -53,6 +53,11 @@ val MvStruct.psiAbilities: List val MvStruct.abilities: Set get() = this.psiAbilities.mapNotNull { it.ability }.toSet() +val MvStruct.hasKey: Boolean get() = Ability.KEY in abilities +val MvStruct.hasStore: Boolean get() = Ability.STORE in abilities +val MvStruct.hasCopy: Boolean get() = Ability.COPY in abilities +val MvStruct.hasDrop: Boolean get() = Ability.DROP in abilities + val MvStruct.requiredAbilitiesForTypeParam: Set get() = this.abilities.map { it.requires() }.toSet() diff --git a/src/main/kotlin/org/move/lang/core/resolve/NameResolution.kt b/src/main/kotlin/org/move/lang/core/resolve/NameResolution.kt index 6f42e04d1..4cd37ca98 100644 --- a/src/main/kotlin/org/move/lang/core/resolve/NameResolution.kt +++ b/src/main/kotlin/org/move/lang/core/resolve/NameResolution.kt @@ -1,18 +1,17 @@ package org.move.lang.core.resolve -import com.intellij.codeInsight.completion.CompletionUtil import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil import org.move.lang.MoveFile import org.move.lang.core.completion.getOriginalOrSelf -import org.move.lang.core.completion.safeGetOriginalElement -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.ref.MvReferenceElement import org.move.lang.core.resolve.ref.Namespace import org.move.lang.core.resolve.ref.Visibility import org.move.lang.core.types.address +import org.move.lang.core.types.infer.MvInferenceContextOwner +import org.move.lang.core.types.infer.inferTypesIn import org.move.lang.core.types.infer.inference import org.move.lang.core.types.ty.TyReference import org.move.lang.core.types.ty.TyStruct @@ -238,7 +237,18 @@ fun processLexicalDeclarations( val msl = dotExpr.isMsl() val receiverExpr = dotExpr.expr val inference = receiverExpr.inference(msl) ?: return false - val receiverTy = inference.getExprType(receiverExpr) + val receiverTy = + inference.getExprTypeOrNull(receiverExpr) ?: run { + // Sometimes in the completion, tree drastically changes between + // `s.` and `s.IntellijIdeaRulezzz`, in that case inference cache value is inapplicable. + // We don't want to drop the cache in that case, so to mitigate stale cache problems + // - we just create another inference context (but only in case of an error). + // Should happen pretty rarely, so hopefully won't affect performance much. + val inferenceOwner = + receiverExpr.ancestorOrSelf() ?: return false + val noCacheInference = inferTypesIn(inferenceOwner, msl) + noCacheInference.getExprType(receiverExpr) + } val innerTy = when (receiverTy) { is TyReference -> receiverTy.innerTy() as? TyStruct ?: TyUnknown 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 5f1c8864b..a9e0ef173 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 @@ -2,8 +2,8 @@ package org.move.lang.core.types.infer import com.intellij.openapi.util.Key import com.intellij.psi.PsiElement +import com.intellij.psi.impl.DebugUtil import com.intellij.psi.util.CachedValue -import com.jetbrains.rd.util.concurrentMapOf import org.jetbrains.annotations.TestOnly import org.move.cli.settings.pluginDebugMode import org.move.ide.formatter.impl.location @@ -97,6 +97,7 @@ data class InferenceResult( /// Explicitly allow uninferred expr fun getExprTypeOrUnknown(expr: MvExpr): Ty = exprTypes[expr] ?: TyUnknown + fun getExprTypeOrNull(expr: MvExpr): Ty? = exprTypes[expr] fun getExpectedType(expr: MvExpr): Ty = exprExpectedTypes[expr] ?: TyUnknown fun getCallExprType(expr: MvCallExpr): Ty? = callExprTypes[expr] @@ -112,18 +113,20 @@ internal val MvElement.typeErrorText: String text += "\nFile: ${file.toNioPathOrNull()} at ($line, $col)" } } - - val context = when (this) { - is MvExpr -> this.ancestorStrict()?.text - is MvPat -> { - val inferenceOwner = this.ancestorStrict() - inferenceOwner?.text ?: this.parent?.text + when (this) { + is MvExpr -> { + val stmt = this.ancestorStrict(); + if (stmt != null) { + val psiString = DebugUtil.psiToString(stmt, true) + text += "\n" + text += psiString + // print next stmt too + val nextPsiContext = stmt.getNextNonCommentSibling() as? MvStmt + if (nextPsiContext != null) { + text += DebugUtil.psiToString(nextPsiContext, true) + } + } } - else -> null - } - if (context != null) { - if (!context.endsWith('\n')) text += "\n" - text += "Context: \n${context.trimIndent()}\n" } return text } diff --git a/src/main/kotlin/org/move/lang/utils/Diagnostic.kt b/src/main/kotlin/org/move/lang/utils/Diagnostic.kt index 750a98c82..f7dd479f8 100644 --- a/src/main/kotlin/org/move/lang/utils/Diagnostic.kt +++ b/src/main/kotlin/org/move/lang/utils/Diagnostic.kt @@ -16,6 +16,7 @@ import org.move.ide.annotator.pluralise import org.move.lang.core.psi.MvCastExpr import org.move.lang.core.psi.MvItemSpec import org.move.lang.core.psi.MvPath +import org.move.lang.core.psi.MvStruct import org.move.lang.core.psi.ext.* import org.move.lang.utils.Severity.* @@ -142,6 +143,15 @@ sealed class Diagnostic( ) } } + + class NativeStructNotSupported(struct: MvStruct, errorRange: TextRange): Diagnostic(struct, errorRange) { + override fun prepare(): PreparedAnnotation { + return PreparedAnnotation( + ERROR, + "Native structs aren't supported by the Move VM anymore" + ) + } + } } enum class Severity { diff --git a/src/main/kotlin/org/move/openapiext/utils.kt b/src/main/kotlin/org/move/openapiext/utils.kt index 32ff07e75..feb12da34 100644 --- a/src/main/kotlin/org/move/openapiext/utils.kt +++ b/src/main/kotlin/org/move/openapiext/utils.kt @@ -22,7 +22,11 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.AdditionalLibraryRootsProvider import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.roots.SyntheticLibrary +import com.intellij.openapi.roots.libraries.LibraryTable +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar import com.intellij.openapi.util.Computable import com.intellij.openapi.util.JDOMUtil import com.intellij.openapi.util.NlsContexts @@ -117,6 +121,13 @@ val Project.contentRoots: Sequence get() = this.modules.asSequence() .flatMap { ModuleRootManager.getInstance(it).contentRoots.asSequence() } +val Project.syntheticLibraries: Collection get() { + val libraries = AdditionalLibraryRootsProvider.EP_NAME + .extensionList + .flatMap { it.getAdditionalProjectLibraries(this) } + return libraries +} + val Project.root: Path? get() = contentRoots.firstOrNull()?.toNioPathOrNull() val Project.contentRoot: VirtualFile? get() = contentRoots.firstOrNull() diff --git a/src/main/kotlin/org/move/toml/MoveTomlErrorAnnotator.kt b/src/main/kotlin/org/move/toml/MoveTomlErrorAnnotator.kt new file mode 100644 index 000000000..b6c5534e5 --- /dev/null +++ b/src/main/kotlin/org/move/toml/MoveTomlErrorAnnotator.kt @@ -0,0 +1,54 @@ +package org.move.toml + +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.psi.PsiElement +import com.intellij.util.text.findTextRange +import org.move.ide.annotator.MvAnnotatorBase +import org.move.openapiext.stringValue +import org.toml.lang.psi.TomlTable + +class MoveTomlErrorAnnotator : MvAnnotatorBase() { + override fun annotateInternal(element: PsiElement, holder: AnnotationHolder) { + val file = element.containingFile ?: return + if (file.name != "Move.toml") { + return + } + if (element !is TomlTable) return + + val tableKey = element.header.key ?: return + if (!tableKey.textMatches("addresses")) return + + for (tomlKeyValue in element.entries) { + val tomlValue = tomlKeyValue.value ?: continue + val rawStringValue = tomlValue.stringValue() ?: continue + if (rawStringValue == "_") continue + val tomlString = rawStringValue.removePrefix("0x") + val stringRange = + tomlValue.text.findTextRange(tomlString)?.shiftRight(tomlValue.textOffset) + ?: tomlValue.textRange + if (tomlString.length > 64) { + holder.newAnnotation( + HighlightSeverity.ERROR, + "Invalid address: no more than 64 symbols allowed" + ) + .range(stringRange) + .create() + return + } + if (DIEM_ADDRESS_REGEX.matchEntire(tomlString) == null) { + holder.newAnnotation( + HighlightSeverity.ERROR, + "Invalid address: only hex symbols are allowed" + ) + .range(stringRange) + .create() + return + } + } + } + + companion object { + private val DIEM_ADDRESS_REGEX = Regex("[0-9a-fA-F]{1,64}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/move/utils/tests/MvSelectionHandlerTestBase.kt b/src/main/kotlin/org/move/utils/tests/MvSelectionHandlerTestBase.kt index 2efbf80b0..9f712714e 100644 --- a/src/main/kotlin/org/move/utils/tests/MvSelectionHandlerTestBase.kt +++ b/src/main/kotlin/org/move/utils/tests/MvSelectionHandlerTestBase.kt @@ -10,7 +10,7 @@ import com.intellij.ide.DataManager import org.intellij.lang.annotations.Language abstract class MvSelectionHandlerTestBase : MvTestBase() { - fun doTest(@Language("Move") before: String, @Language("Move") vararg after: String) { + fun doTest(@Language("Move") before: String, @Language("Move") vararg after: String) { doTestInner(before, after.toList()) } diff --git a/src/main/kotlin/org/move/utils/tests/annotation/MvAnnotationTestCase.kt b/src/main/kotlin/org/move/utils/tests/annotation/MvAnnotationTestCase.kt index 068b90bb0..f37065fb4 100644 --- a/src/main/kotlin/org/move/utils/tests/annotation/MvAnnotationTestCase.kt +++ b/src/main/kotlin/org/move/utils/tests/annotation/MvAnnotationTestCase.kt @@ -23,6 +23,13 @@ abstract class MvAnnotationTestCase : MvTestBase() { fun checkErrors(@Language("Move") text: String) = annotationFixture.checkErrors(text) fun checkWarnings(@Language("Move") text: String) = annotationFixture.checkWarnings(text) + fun checkMoveTomlWarnings(@Language("TOML") text: String) = + annotationFixture.check(text, + configure = { tomlText -> + annotationFixture.codeInsightFixture + .configureByText("Move.toml", tomlText) + }) + protected fun checkByText( @Language("Move") text: String, checkWarn: Boolean = true, diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8b3e362f5..5e6ac377b 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -137,6 +137,9 @@ + + + + @@ -322,7 +330,7 @@ Move - + @@ -330,6 +338,9 @@ + @@ -339,6 +350,12 @@ description="Create new Move file"> + + + + + + + + + + diff --git a/src/main/resources/colors/MoveDefault.xml b/src/main/resources/colors/MoveDefault.xml index 58a405c53..1b56b9341 100644 --- a/src/main/resources/colors/MoveDefault.xml +++ b/src/main/resources/colors/MoveDefault.xml @@ -20,4 +20,28 @@ + + + + diff --git a/src/test/kotlin/org/move/ide/annotator/HighlightingAnnotatorTest.kt b/src/test/kotlin/org/move/ide/annotator/HighlightingAnnotatorTest.kt index 0a24e4ff3..f656f07ec 100644 --- a/src/test/kotlin/org/move/ide/annotator/HighlightingAnnotatorTest.kt +++ b/src/test/kotlin/org/move/ide/annotator/HighlightingAnnotatorTest.kt @@ -228,4 +228,54 @@ class HighlightingAnnotatorTest : AnnotatorTestCase(HighlightingAnnotator::class } } """) + + fun `test highlight objects`() = checkHighlighting(""" + module 0x1::m { + struct Res {} + struct ResKey has key {} + struct ResStoreDrop has store, drop {} + struct ResStore has store {} + fun objects( + binding: u8, + res_key: ResKey, + res_store_drop: ResStoreDrop, + res_store: ResStore, + ) { + res_key; + res_store_drop; + res_store; + } + } + """) + + fun `test highlight references`() = checkHighlighting(""" + module 0x1::m { + struct Res {} + struct ResKey has key {} + struct ResStoreDrop has store, drop {} + struct ResStoreNoDrop has store {} + fun refs(ref_res: &Res, mut_ref_res: &mut Res) { + ref_res; + mut_ref_res; + } + fun ref_to_object( + ref_res_key: &ResKey, + ref_res_store_drop: &ResStoreDrop, + ref_res_store_no_drop: &ResStoreNoDrop, + ) { + ref_res_key; + ref_res_store_drop; + ref_res_store_no_drop; + } + fun mut_ref_to_object( + ref_res_key: &mut ResKey, + ref_res_store_drop: &mut ResStoreDrop, + ref_res_store_no_drop: &mut ResStoreNoDrop, + ) { + ref_res_key; + ref_res_store_drop; + ref_res_store_no_drop; + } + } + """) } diff --git a/src/test/kotlin/org/move/ide/annotator/syntaxErrors/InvalidIntegerTest.kt b/src/test/kotlin/org/move/ide/annotator/syntaxErrors/InvalidIntegerTest.kt index 808122232..b51323065 100644 --- a/src/test/kotlin/org/move/ide/annotator/syntaxErrors/InvalidIntegerTest.kt +++ b/src/test/kotlin/org/move/ide/annotator/syntaxErrors/InvalidIntegerTest.kt @@ -14,6 +14,9 @@ class InvalidIntegerTest : AnnotatorTestCase(MvSyntaxErrorAnnotator::class) { 0x1; 0xff; 0xFFF; 0xACACAFFF; 0x1f1fu128; 0011; + + 0x1111_1111; + 1_000; } } """ diff --git a/src/test/kotlin/org/move/ide/annotator/syntaxErrors/NativeStructNotSupportedTest.kt b/src/test/kotlin/org/move/ide/annotator/syntaxErrors/NativeStructNotSupportedTest.kt new file mode 100644 index 000000000..a7c2d32ea --- /dev/null +++ b/src/test/kotlin/org/move/ide/annotator/syntaxErrors/NativeStructNotSupportedTest.kt @@ -0,0 +1,12 @@ +package org.move.ide.annotator.syntaxErrors + +import org.move.ide.annotator.MvSyntaxErrorAnnotator +import org.move.utils.tests.annotation.AnnotatorTestCase + +class NativeStructNotSupportedTest: AnnotatorTestCase(MvSyntaxErrorAnnotator::class) { + fun `test native struct is not supported by the vm`() = checkWarnings(""" + module 0x1::m { + native struct S; + } + """) +} \ No newline at end of file diff --git a/src/test/kotlin/org/move/ide/inspections/naming_inspections.kt b/src/test/kotlin/org/move/ide/inspections/naming_inspections.kt index 438a70dba..c90f9d782 100644 --- a/src/test/kotlin/org/move/ide/inspections/naming_inspections.kt +++ b/src/test/kotlin/org/move/ide/inspections/naming_inspections.kt @@ -13,6 +13,32 @@ module 0x1::M { ) } +class MvFunctionNamingInspectionTest : InspectionTestBase(MvFunctionNamingInspection::class) { + fun `test function name cannot start with _`() = checkByText( + """ +module 0x1::m { + fun _main() {} +} + """ + ) + + fun `test spec function name cannot start with _`() = checkByText( + """ +module 0x1::m { + spec fun _main(): u8 { 1 } +} + """ + ) + + fun `test native function name cannot start with _`() = checkByText( + """ +module 0x1::m { + native fun _main(): u8; +} + """ + ) +} + class MvStructNamingInspectionTest : InspectionTestBase(MvStructNamingInspection::class) { fun `test structs`() = checkByText( """ diff --git a/src/test/kotlin/org/move/ide/typing/QuotesHandlerTest.kt b/src/test/kotlin/org/move/ide/typing/QuotesHandlerTest.kt index 628a4cdd9..345d3e074 100644 --- a/src/test/kotlin/org/move/ide/typing/QuotesHandlerTest.kt +++ b/src/test/kotlin/org/move/ide/typing/QuotesHandlerTest.kt @@ -17,21 +17,21 @@ class QuotesHandlerTest: MvTypingTestCase() { } """, '"') - fun `test complete byte string quotes`() = doTestByText(""" + fun `test complete byte string quotes no semi`() = doTestByText(""" script { fun m() { - b/*caret*/; + b } } """, """ script { fun m() { - b"/*caret*/"; + b"" } } """, '"') - fun `test complete hex string quotes`() = doTestByText(""" + fun `test complete hex string quotes semi`() = doTestByText(""" script { fun m() { x/*caret*/; @@ -44,4 +44,18 @@ class QuotesHandlerTest: MvTypingTestCase() { } } """, '"') + + fun `test complete hex string quotes no semi`() = doTestByText(""" + script { + fun m() { + x/*caret*/ + } + } + """, """ + script { + fun m() { + x"/*caret*/" + } + } + """, '"') } diff --git a/src/test/kotlin/org/move/ide/wordSelection/MvStringSelectionHandlerTest.kt b/src/test/kotlin/org/move/ide/wordSelection/MvStringSelectionHandlerTest.kt new file mode 100644 index 000000000..f11cdc840 --- /dev/null +++ b/src/test/kotlin/org/move/ide/wordSelection/MvStringSelectionHandlerTest.kt @@ -0,0 +1,51 @@ +package org.move.ide.wordSelection + +import org.move.utils.tests.MvSelectionHandlerTestBase + +class MvStringSelectionHandlerTest: MvSelectionHandlerTestBase() { + fun `test byte string`() = doTest(""" + module 0x1::m { + fun main() { + b"hello, world"; + } + } + """, """ + module 0x1::m { + fun main() { + b"hello, world"; + } + } + """, """ + module 0x1::m { + fun main() { + b"hello, world"; + } + } + """, """ + module 0x1::m { + fun main() { + b"hello, world"; + } + } + """) + + fun `test hex string`() = doTest(""" + module 0x1::m { + fun main() { + x"afffaa"; + } + } + """, """ + module 0x1::m { + fun main() { + x"afffaa"; + } + } + """, """ + module 0x1::m { + fun main() { + x"afffaa"; + } + } + """) +} \ No newline at end of file diff --git a/src/test/kotlin/org/move/lang/completion/names/DotAccessCompletionTest.kt b/src/test/kotlin/org/move/lang/completion/names/DotAccessCompletionTest.kt index e2e642e16..96602522c 100644 --- a/src/test/kotlin/org/move/lang/completion/names/DotAccessCompletionTest.kt +++ b/src/test/kotlin/org/move/lang/completion/names/DotAccessCompletionTest.kt @@ -55,4 +55,22 @@ module 0x1::M { } """ ) + + fun `test chained dot access`() = doSingleCompletion(""" + module 0x1::m { + struct Pool { field: u8 } + fun main(pool: &mut Pool) { + pool./*caret*/ + pool.field + } + } + """, """ + module 0x1::m { + struct Pool { field: u8 } + fun main(pool: &mut Pool) { + pool.field/*caret*/ + pool.field + } + } + """) } diff --git a/src/test/kotlin/org/move/lang/parser/CompleteParsingTest.kt b/src/test/kotlin/org/move/lang/parser/CompleteParsingTest.kt index f8f165d26..24453d5a5 100644 --- a/src/test/kotlin/org/move/lang/parser/CompleteParsingTest.kt +++ b/src/test/kotlin/org/move/lang/parser/CompleteParsingTest.kt @@ -3,40 +3,44 @@ package org.move.lang.parser import org.move.utils.tests.parser.MvParsingTestCase class CompleteParsingTest : MvParsingTestCase("complete") { - fun `test comments`() = doTest(true) - fun `test addresses`() = doTest(true) - fun `test attributes`() = doTest(true) + fun `test comments`() = doTest() + fun `test addresses`() = doTest() + fun `test attributes`() = doTest() // functions - fun `test function declarations`() = doTest(true) - fun `test function calls`() = doTest(true) + fun `test function declarations`() = doTest() + fun `test function calls`() = doTest() // expressions - fun `test strings`() = doTest(true) - fun `test vectors`() = doTest(true) - fun `test expressions`() = doTest(true) - fun `test expressions assignments`() = doTest(true) - fun `test expressions if else as`() = doTest(true) - fun `test expressions angle brackets`() = doTest(true) - fun `test expressions specs`() = doTest(true) + fun `test strings`() = doTest() + fun `test vectors`() = doTest() + fun `test expressions`() = doTest() + fun `test expressions assignments`() = doTest() + fun `test expressions if else as`() = doTest() + fun `test expressions angle brackets`() = doTest() + fun `test expressions specs`() = doTest() // use - fun `test use`() = doTest(true) - fun `test friend`() = doTest(true) + fun `test use`() = doTest() + fun `test friend`() = doTest() // assignments - fun `test let patterns`() = doTest(true) - fun `test assignments`() = doTest(true) + fun `test let patterns`() = doTest() + fun `test assignments`() = doTest() // structs - fun `test struct declarations`() = doTest(true) - fun `test struct literals`() = doTest(true) + fun `test struct declarations`() = doTest() + fun `test struct literals`() = doTest() // misc - fun `test while loop inline assignment`() = doTest(true) - fun `test contextual token operators`() = doTest(true) - fun `test generics`() = doTest(true) - fun `test annotated literals`() = doTest(true) + fun `test while loop inline assignment`() = doTest() + fun `test contextual token operators`() = doTest() + fun `test generics`() = doTest() + fun `test annotated literals`() = doTest() - fun `test macros`() = doTest(true) + fun `test macros`() = doTest() + + fun doTest() { + super.doTest(true, true) + } } diff --git a/src/test/kotlin/org/move/lang/parser/PartialParsingTest.kt b/src/test/kotlin/org/move/lang/parser/PartialParsingTest.kt index 89292b143..fa4b56d50 100644 --- a/src/test/kotlin/org/move/lang/parser/PartialParsingTest.kt +++ b/src/test/kotlin/org/move/lang/parser/PartialParsingTest.kt @@ -21,4 +21,5 @@ class PartialParsingTest: MvParsingTestCase("partial") { // expressions fun `test expressions`() = doTest(true) fun `test assignments`() = doTest(true) + fun `test dot exprs`() = doTest(true) } diff --git a/src/test/kotlin/org/move/lang/parser/SpecsParsingTest.kt b/src/test/kotlin/org/move/lang/parser/SpecsParsingTest.kt index 43441f284..51e0d3cfb 100644 --- a/src/test/kotlin/org/move/lang/parser/SpecsParsingTest.kt +++ b/src/test/kotlin/org/move/lang/parser/SpecsParsingTest.kt @@ -3,14 +3,18 @@ package org.move.lang.parser import org.move.utils.tests.parser.MvParsingTestCase class SpecsParsingTest: MvParsingTestCase("specs") { - fun `test scopes`() = doTest(true) - fun `test conditions`() = doTest(true) - fun `test forall exists`() = doTest(true) - fun `test pragma`() = doTest(true) + fun `test scopes`() = doTest() + fun `test conditions`() = doTest() + fun `test forall exists`() = doTest() + fun `test pragma`() = doTest() - fun `test apply`() = doTest(true) - fun `test spec statements`() = doTest(true) - fun `test spec file`() = doTest(true) - fun `test spec properties`() = doTest(true) - fun `test spec variables`() = doTest(true) + fun `test apply`() = doTest() + fun `test spec statements`() = doTest(true, false) + fun `test spec file`() = doTest() + fun `test spec properties`() = doTest() + fun `test spec variables`() = doTest() + + fun doTest() { + super.doTest(true, true) + } } diff --git a/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt b/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt index 81105ccc1..091a9f6e3 100644 --- a/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt +++ b/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt @@ -1650,4 +1650,36 @@ module 0x1::main { } """) + fun `test dot expr chained`() = testExpr(""" + module 0x1::m { + struct Pool { field: u8 } + fun main(pool: &mut Pool) { + pool. + //^ &mut 0x1::m::Pool + pool.field; + } + } + """) + + fun `test dot expr with dot expr incomplete 1`() = testExpr(""" + module 0x1::m { + struct Pool { field: u8 } + fun main(pool: &mut Pool) { + pool.field + //^ &mut 0x1::m::Pool + pool.field + } + } + """) + + fun `test dot expr with dot expr incomplete 2`() = testExpr(""" + module 0x1::m { + struct Pool { field: u8 } + fun main(pool: &mut Pool) { + pool.unknown + pool.field + //^ u8 + } + } + """) } diff --git a/src/test/kotlin/org/move/toml/MoveTomlErrorAnnotatorTest.kt b/src/test/kotlin/org/move/toml/MoveTomlErrorAnnotatorTest.kt new file mode 100644 index 000000000..b141d01fd --- /dev/null +++ b/src/test/kotlin/org/move/toml/MoveTomlErrorAnnotatorTest.kt @@ -0,0 +1,24 @@ +package org.move.toml + +import org.move.utils.tests.annotation.AnnotatorTestCase + +class MoveTomlErrorAnnotatorTest : AnnotatorTestCase(MoveTomlErrorAnnotator::class) { + fun `test valid addresses`() = checkMoveTomlWarnings(""" + [addresses] + addr0 = "_" + addr1 = "0x1" + addr2 = "0x42" + addr3 = "0x4242424242424242424242424242424242424242424242424242420000000000" + addr4 = "4242424242424242424242424242424242424242424242424242420000000000" + """) + + fun `test invalid symbols in address`() = checkMoveTomlWarnings(""" + [addresses] + addr2 = "0xhelloworld" + """) + + fun `test address is too long`() = checkMoveTomlWarnings(""" + [addresses] + addr3 = "0x424242424242424242424242424242424242424242424242424242000000000011122" + """) +} \ No newline at end of file diff --git a/src/test/resources/org/move/lang/parser/complete/assignments.txt b/src/test/resources/org/move/lang/parser/complete/assignments.txt index dbcb7442e..634ba076c 100644 --- a/src/test/resources/org/move/lang/parser/complete/assignments.txt +++ b/src/test/resources/org/move/lang/parser/complete/assignments.txt @@ -205,4 +205,4 @@ FILE PsiWhiteSpace('\n ') PsiElement(})('}') PsiWhiteSpace('\n') - PsiElement(})('}') + PsiElement(})('}') \ No newline at end of file diff --git a/src/test/resources/org/move/lang/parser/complete/expressions.move b/src/test/resources/org/move/lang/parser/complete/expressions.move index 48cc1f93c..36681b4f8 100644 --- a/src/test/resources/org/move/lang/parser/complete/expressions.move +++ b/src/test/resources/org/move/lang/parser/complete/expressions.move @@ -9,6 +9,9 @@ module M { 0xRR; // invalid 0xFFFu128; + 0x1111_1111; + 1_000; + (1 + 1) * (1 + 1); (!!true + !!true) * !!false; 1 % 2; @@ -35,4 +38,11 @@ module M { fun m() { return 1 } + fun dot() { + bin.field1.field2; + bin.field < 1; + } + spec dot { + bin.field[1]; + } } diff --git a/src/test/resources/org/move/lang/parser/complete/expressions.txt b/src/test/resources/org/move/lang/parser/complete/expressions.txt index 6fc6491e8..2c3f812bf 100644 --- a/src/test/resources/org/move/lang/parser/complete/expressions.txt +++ b/src/test/resources/org/move/lang/parser/complete/expressions.txt @@ -82,6 +82,16 @@ FILE PsiElement(HEX_INTEGER_LITERAL)('0xFFFu128') PsiElement(;)(';') PsiWhiteSpace('\n\n ') + MvExprStmtImpl(EXPR_STMT) + MvLitExprImpl(LIT_EXPR) + PsiElement(HEX_INTEGER_LITERAL)('0x1111_1111') + PsiElement(;)(';') + PsiWhiteSpace('\n ') + MvExprStmtImpl(EXPR_STMT) + MvLitExprImpl(LIT_EXPR) + PsiElement(INTEGER_LITERAL)('1_000') + PsiElement(;)(';') + PsiWhiteSpace('\n\n ') MvExprStmtImpl(EXPR_STMT) MvBinaryExprImpl(BINARY_EXPR[*]) MvParensExprImpl(PARENS_EXPR) @@ -377,5 +387,75 @@ FILE PsiElement(INTEGER_LITERAL)('1') PsiWhiteSpace('\n ') PsiElement(})('}') + PsiWhiteSpace('\n ') + MvFunctionImpl(FUNCTION) + PsiElement(fun)('fun') + PsiWhiteSpace(' ') + PsiElement(IDENTIFIER)('dot') + MvFunctionParameterListImpl(FUNCTION_PARAMETER_LIST) + PsiElement(()('(') + PsiElement())(')') + PsiWhiteSpace(' ') + MvCodeBlockImpl(CODE_BLOCK) + PsiElement({)('{') + PsiWhiteSpace('\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + MvStructDotFieldImpl(STRUCT_DOT_FIELD) + PsiElement(IDENTIFIER)('field1') + PsiElement(.)('.') + MvStructDotFieldImpl(STRUCT_DOT_FIELD) + PsiElement(IDENTIFIER)('field2') + PsiElement(;)(';') + PsiWhiteSpace('\n ') + MvExprStmtImpl(EXPR_STMT) + MvBinaryExprImpl(BINARY_EXPR[<]) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + MvStructDotFieldImpl(STRUCT_DOT_FIELD) + PsiElement(IDENTIFIER)('field') + PsiWhiteSpace(' ') + MvBinaryOpImpl(BINARY_OP) + PsiElement(<)('<') + PsiWhiteSpace(' ') + MvLitExprImpl(LIT_EXPR) + PsiElement(INTEGER_LITERAL)('1') + PsiElement(;)(';') + PsiWhiteSpace('\n ') + PsiElement(})('}') + PsiWhiteSpace('\n ') + MvItemSpecImpl(ITEM_SPEC) + PsiElement(spec)('spec') + PsiWhiteSpace(' ') + MvItemSpecRefImpl(ITEM_SPEC_REF) + PsiElement(IDENTIFIER)('dot') + PsiWhiteSpace(' ') + MvSpecCodeBlockImpl(SPEC_CODE_BLOCK) + PsiElement({)('{') + PsiWhiteSpace('\n ') + MvSpecExprStmtImpl(SPEC_EXPR_STMT) + MvIndexExprImpl(INDEX_EXPR) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + MvStructDotFieldImpl(STRUCT_DOT_FIELD) + PsiElement(IDENTIFIER)('field') + PsiElement([)('[') + MvLitExprImpl(LIT_EXPR) + PsiElement(INTEGER_LITERAL)('1') + PsiElement(])(']') + PsiElement(;)(';') + PsiWhiteSpace('\n ') + PsiElement(})('}') PsiWhiteSpace('\n') PsiElement(})('}') \ No newline at end of file diff --git a/src/test/resources/org/move/lang/parser/partial/dot_exprs.move b/src/test/resources/org/move/lang/parser/partial/dot_exprs.move new file mode 100644 index 000000000..f5d26a82f --- /dev/null +++ b/src/test/resources/org/move/lang/parser/partial/dot_exprs.move @@ -0,0 +1,21 @@ +module 0x1::dot_exprs { + fun m() { + bin.field. call(); + + bin. call(); + + bin. assert!(true, 1); + + bin. S { field: 1 }; + + bin. vector[]; + + bin.field bin.field + + bin. aptos_token::get_token(); + + bin. b" + bin. x" + bin. return + } +} diff --git a/src/test/resources/org/move/lang/parser/partial/dot_exprs.txt b/src/test/resources/org/move/lang/parser/partial/dot_exprs.txt new file mode 100644 index 000000000..a11ef9e66 --- /dev/null +++ b/src/test/resources/org/move/lang/parser/partial/dot_exprs.txt @@ -0,0 +1,222 @@ +FILE + MvModuleImpl(MODULE) + PsiElement(module)('module') + PsiWhiteSpace(' ') + MvAddressRefImpl(ADDRESS_REF) + PsiElement(DIEM_ADDRESS)('0x1') + PsiElement(::)('::') + PsiElement(IDENTIFIER)('dot_exprs') + PsiWhiteSpace(' ') + MvModuleBlockImpl(MODULE_BLOCK) + PsiElement({)('{') + PsiWhiteSpace('\n ') + MvFunctionImpl(FUNCTION) + PsiElement(fun)('fun') + PsiWhiteSpace(' ') + PsiElement(IDENTIFIER)('m') + MvFunctionParameterListImpl(FUNCTION_PARAMETER_LIST) + PsiElement(()('(') + PsiElement())(')') + PsiWhiteSpace(' ') + MvCodeBlockImpl(CODE_BLOCK) + PsiElement({)('{') + PsiWhiteSpace('\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + MvStructDotFieldImpl(STRUCT_DOT_FIELD) + PsiElement(IDENTIFIER)('field') + PsiElement(.)('.') + PsiErrorElement:IDENTIFIER expected, got 'call' + + PsiWhiteSpace(' ') + MvExprStmtImpl(EXPR_STMT) + MvCallExprImpl(CALL_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('call') + MvValueArgumentListImpl(VALUE_ARGUMENT_LIST) + PsiElement(()('(') + PsiElement())(')') + PsiElement(;)(';') + PsiWhiteSpace('\n\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + PsiErrorElement:IDENTIFIER expected, got 'call' + + PsiWhiteSpace(' ') + MvExprStmtImpl(EXPR_STMT) + MvCallExprImpl(CALL_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('call') + MvValueArgumentListImpl(VALUE_ARGUMENT_LIST) + PsiElement(()('(') + PsiElement())(')') + PsiElement(;)(';') + PsiWhiteSpace('\n\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + PsiErrorElement:IDENTIFIER expected, got 'assert' + + PsiWhiteSpace(' ') + MvExprStmtImpl(EXPR_STMT) + MvMacroCallExprImpl(MACRO_CALL_EXPR) + MvMacroIdentImpl(MACRO_IDENT) + PsiElement(IDENTIFIER)('assert') + PsiElement(!)('!') + MvValueArgumentListImpl(VALUE_ARGUMENT_LIST) + PsiElement(()('(') + MvValueArgumentImpl(VALUE_ARGUMENT) + MvLitExprImpl(LIT_EXPR) + PsiElement(BOOL_LITERAL)('true') + PsiElement(,)(',') + PsiWhiteSpace(' ') + MvValueArgumentImpl(VALUE_ARGUMENT) + MvLitExprImpl(LIT_EXPR) + PsiElement(INTEGER_LITERAL)('1') + PsiElement())(')') + PsiElement(;)(';') + PsiWhiteSpace('\n\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + PsiErrorElement:IDENTIFIER expected, got 'S' + + PsiWhiteSpace(' ') + MvExprStmtImpl(EXPR_STMT) + MvStructLitExprImpl(STRUCT_LIT_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('S') + PsiWhiteSpace(' ') + MvStructLitFieldsBlockImpl(STRUCT_LIT_FIELDS_BLOCK) + PsiElement({)('{') + PsiWhiteSpace(' ') + MvStructLitFieldImpl(STRUCT_LIT_FIELD) + PsiElement(IDENTIFIER)('field') + PsiElement(:)(':') + PsiWhiteSpace(' ') + MvLitExprImpl(LIT_EXPR) + PsiElement(INTEGER_LITERAL)('1') + PsiWhiteSpace(' ') + PsiElement(})('}') + PsiElement(;)(';') + PsiWhiteSpace('\n\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + PsiErrorElement:';' expected, got 'vector' + + PsiWhiteSpace(' ') + MvExprStmtImpl(EXPR_STMT) + MvVectorLitExprImpl(VECTOR_LIT_EXPR) + PsiElement(IDENTIFIER)('vector') + MvVectorLitItemsImpl(VECTOR_LIT_ITEMS) + PsiElement([)('[') + PsiElement(])(']') + PsiElement(;)(';') + PsiWhiteSpace('\n\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + MvStructDotFieldImpl(STRUCT_DOT_FIELD) + PsiElement(IDENTIFIER)('field') + PsiErrorElement:';' expected, got 'bin' + + PsiWhiteSpace(' ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + MvStructDotFieldImpl(STRUCT_DOT_FIELD) + PsiElement(IDENTIFIER)('field') + PsiErrorElement:';' expected, got 'bin' + + PsiWhiteSpace('\n\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + PsiErrorElement:IDENTIFIER expected, got 'aptos_token' + + PsiWhiteSpace(' ') + MvExprStmtImpl(EXPR_STMT) + MvCallExprImpl(CALL_EXPR) + MvPathImpl(PATH) + MvModuleRefImpl(MODULE_REF) + PsiElement(IDENTIFIER)('aptos_token') + PsiElement(::)('::') + PsiElement(IDENTIFIER)('get_token') + MvValueArgumentListImpl(VALUE_ARGUMENT_LIST) + PsiElement(()('(') + PsiElement())(')') + PsiElement(;)(';') + PsiWhiteSpace('\n\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + PsiErrorElement:IDENTIFIER expected, got 'b"' + + PsiWhiteSpace(' ') + MvExprStmtImpl(EXPR_STMT) + MvLitExprImpl(LIT_EXPR) + PsiElement(BYTE_STRING_LITERAL)('b"') + PsiErrorElement:';' expected, got 'bin' + + PsiWhiteSpace('\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + PsiErrorElement:IDENTIFIER expected, got 'x"' + + PsiWhiteSpace(' ') + MvExprStmtImpl(EXPR_STMT) + MvLitExprImpl(LIT_EXPR) + PsiElement(HEX_STRING_LITERAL)('x"') + PsiErrorElement:';' expected, got 'bin' + + PsiWhiteSpace('\n ') + MvExprStmtImpl(EXPR_STMT) + MvDotExprImpl(DOT_EXPR) + MvRefExprImpl(REF_EXPR) + MvPathImpl(PATH) + PsiElement(IDENTIFIER)('bin') + PsiElement(.)('.') + PsiErrorElement:IDENTIFIER expected, got 'return' + + PsiWhiteSpace(' ') + MvReturnExprImpl(RETURN_EXPR) + PsiElement(return)('return') + PsiWhiteSpace('\n ') + PsiElement(})('}') + PsiWhiteSpace('\n') + PsiElement(})('}') \ No newline at end of file diff --git a/src/test/resources/org/move/lang/parser/specs/spec_properties.move b/src/test/resources/org/move/lang/parser/specs/spec_properties.move index b50d20477..274577850 100644 --- a/src/test/resources/org/move/lang/parser/specs/spec_properties.move +++ b/src/test/resources/org/move/lang/parser/specs/spec_properties.move @@ -3,6 +3,6 @@ module 0x1::spec_properties { invariant [] true; invariant [seed = 1] true; invariant [abstract, concrete, verify = true] 1 == 1; - invariant [seed] + invariant [seed] true } } diff --git a/src/test/resources/org/move/lang/parser/specs/spec_properties.txt b/src/test/resources/org/move/lang/parser/specs/spec_properties.txt index 378ca94e3..34a526dc9 100644 --- a/src/test/resources/org/move/lang/parser/specs/spec_properties.txt +++ b/src/test/resources/org/move/lang/parser/specs/spec_properties.txt @@ -91,8 +91,9 @@ FILE MvSpecPropertyImpl(SPEC_PROPERTY) PsiElement(IDENTIFIER)('seed') PsiElement(])(']') - PsiErrorElement: expected, got '}' - + PsiWhiteSpace(' ') + MvLitExprImpl(LIT_EXPR) + PsiElement(BOOL_LITERAL)('true') PsiWhiteSpace('\n ') PsiElement(})('}') PsiWhiteSpace('\n')