Repository: intellij-rust/intellij-rust
Issue: Failed to parse the `edition` key

<!--
Hello and thank you for the issue!
If you would like to report a bug, we have added some points below that you can fill out.
Feel free to remove all the irrelevant text to request a new feature.
-->

## Environment

* **IntelliJ Rust plugin version:** 0.4.164.4409-213
* **Rust toolchain version:** 1.60.0-nightly (27f5d830e 2022-02-02) x86_64-unknown-linux-gnu
* **IDE name and version:** CLion 2021.3.3 (CL-213.6777.58)
* **Operating system:** Windows 10 10.0
* **Macro expansion engine:** new
* **Name resolution engine:** new
* **Additional experimental features:** org.rust.cargo.features.settings.gutter, org.rust.cargo.evaluate.build.scripts, org.rust.macros.proc

## Problem description
Project sync fails with this error:

```
Execution failed (exit code 101).
C:\\Windows\\system32\\wsl.exe --distribution ubuntu --exec /bin/sh -c "export PATH=\\"/home/matyas/.cargo/bin:$PATH\\" && export RUST_BACKTRACE=short && export TERM=ansi && cd /home/matyas/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test && /home/matyas/.cargo/bin/cargo vendor /mnt/c/Users/panstromek/AppData/Local/JetBrains/CLion2021.3/intellij-rust/stdlib/1.60.0-nightly-da0947d6a3255f7cde017d47d074aabb849e246a/vendor"
stdout : error: failed to parse manifest at `/home/matyas/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/Cargo.toml`

Caused by:
  failed to parse the `edition` key

Caused by:
  this version of Cargo is older than the `2021` edition, and only supports `2015` and `2018` editions.

stderr : 
```
This project runs in WSL

## Steps to reproduce

I am not sure how, but could it be that the directory override is not used by the sync command? My stable version is 1.49, but the project uses 1.60 nightly via directory override.


Changed files:
src/main/kotlin/org/rust/cargo/toolchain/tools/Cargo.kt, src/main/kotlin/org/rust/cargo/runconfig/command/CargoCommandConfiguration.kt, src/main/kotlin/org/rust/cargo/project/model/impl/CargoSyncTask.kt, src/main/kotlin/org/rust/cargo/util/UnitTestRustcCacheService.kt, src/main/kotlin/org/rust/cargo/toolchain/tools/Rustup.kt, src/main/kotlin/org/rust/cargo/util/TestUnitTestRustcCacheService.kt, src/main/kotlin/org/rust/cargo/project/model/CargoProjectService.kt, src/main/kotlin/org/rust/cargo/project/workspace/StandardLibrary.kt, src/test/kotlin/org/rust/cargo/toolchain/CargoTest.kt

diff --git a/src/main/kotlin/org/rust/cargo/project/model/CargoProjectService.kt b/src/main/kotlin/org/rust/cargo/project/model/CargoProjectService.kt
index 261790b63..7461d0de4 100644
--- a/src/main/kotlin/org/rust/cargo/project/model/CargoProjectService.kt
+++ b/src/main/kotlin/org/rust/cargo/project/model/CargoProjectService.kt
@@ -118,7 +118,12 @@ interface CargoProject : UserDataHolderEx {
     }
 }
 
-data class RustcInfo(val sysroot: String, val version: RustcVersion?, val targets: List<String>? = null)
+data class RustcInfo(
+    val sysroot: String,
+    val version: RustcVersion?,
+    val rustupActiveToolchain: String? = null,
+    val targets: List<String>? = null
+)
 
 fun guessAndSetupRustProject(project: Project, explicitRequest: Boolean = false): Boolean {
     if (!explicitRequest) {
diff --git a/src/main/kotlin/org/rust/cargo/project/model/impl/CargoSyncTask.kt b/src/main/kotlin/org/rust/cargo/project/model/impl/CargoSyncTask.kt
index 169e262dd..b4ae28f95 100644
--- a/src/main/kotlin/org/rust/cargo/project/model/impl/CargoSyncTask.kt
+++ b/src/main/kotlin/org/rust/cargo/project/model/impl/CargoSyncTask.kt
@@ -306,9 +306,12 @@ private fun fetchRustcInfo(context: CargoSyncTask.SyncContext): TaskResult<Rustc
         val rustcVersion = childContext.toolchain.rustc().queryVersion(workingDirectory)
         val sysroot = UnitTestRustcCacheService.cached(rustcVersion) { childContext.toolchain.rustc().getSysroot(workingDirectory) }
             ?: return@runWithChildProgress TaskResult.Err("failed to get project sysroot")
+        val rustupActiveToolchain = UnitTestRustcCacheService.cached(rustcVersion) {
+            childContext.toolchain.rustup(workingDirectory)?.activeToolchainName()
+        }
         val rustcTargets = UnitTestRustcCacheService.cached(rustcVersion) { childContext.toolchain.rustc().getTargets(workingDirectory) }
 
-        TaskResult.Ok(RustcInfo(sysroot, rustcVersion, rustcTargets))
+        TaskResult.Ok(RustcInfo(sysroot, rustcVersion, rustupActiveToolchain, rustcTargets))
     }
 }
 
diff --git a/src/main/kotlin/org/rust/cargo/project/workspace/StandardLibrary.kt b/src/main/kotlin/org/rust/cargo/project/workspace/StandardLibrary.kt
index d7b027ca9..87a2d74cb 100644
--- a/src/main/kotlin/org/rust/cargo/project/workspace/StandardLibrary.kt
+++ b/src/main/kotlin/org/rust/cargo/project/workspace/StandardLibrary.kt
@@ -80,7 +80,7 @@ data class StandardLibrary(
                     warn("Toolchain version is unknown. Hardcoded stdlib structure will be used")
                     fetchHardcodedStdlib(srcDir)
                 } else {
-                    val result = fetchActualStdlib(project, srcDir, rustcVersion, listener)
+                    val result = fetchActualStdlib(project, srcDir, rustcVersion, rustcInfo.rustupActiveToolchain, listener)
                     if (result == null) {
                         warn("Fetching actual stdlib info failed. Hardcoded stdlib structure will be used")
                     }
@@ -107,18 +107,19 @@ data class StandardLibrary(
             project: Project,
             srcDir: VirtualFile,
             version: RustcVersion,
+            activeToolchain: String?,
             listener: ProcessProgressListener?,
             cleanVendorDir: Boolean = false
         ): StandardLibrary? {
             try {
-                return StdlibDataFetcher.create(project, srcDir, version, listener, cleanVendorDir)?.fetchStdlibData()
+                return StdlibDataFetcher.create(project, srcDir, version, activeToolchain, listener, cleanVendorDir)?.fetchStdlibData()
             } catch (e: Throwable) {
                 if (!isUnitTestMode) {
                     // Logger.error in tests, fail the test
                     LOG.error(e)
                 }
                 if (!cleanVendorDir && e is CargoMetadataException) {
-                    return fetchActualStdlib(project, srcDir, version, listener, cleanVendorDir = true)
+                    return fetchActualStdlib(project, srcDir, version, activeToolchain, listener, cleanVendorDir = true)
                 }
             }
             return null
@@ -186,6 +187,7 @@ class StdlibDataFetcher private constructor(
     private val srcDir: VirtualFile,
     private val testPackageSrcDir: VirtualFile,
     private val stdlibDependenciesDir: Path,
+    private val activeToolchain: String?,
     private val listener: ProcessProgressListener?
 ) {
     private val workspaceMembers = mutableListOf<PackageId>()
@@ -293,7 +295,7 @@ class StdlibDataFetcher private constructor(
             return
         }
 
-        val metadataProject = cargo.fetchMetadata(project, pathAsPath, listener).unwrapOrElse {
+        val metadataProject = cargo.fetchMetadata(project, pathAsPath, activeToolchain, listener).unwrapOrElse {
             listener?.error("Failed to fetch stdlib package info", it.message.orEmpty())
             LOG.error(it)
             return
@@ -310,6 +312,7 @@ class StdlibDataFetcher private constructor(
             project: Project,
             srcDir: VirtualFile,
             version: RustcVersion,
+            activeToolchain: String?,
             listener: ProcessProgressListener?,
             cleanVendorDir: Boolean
         ): StdlibDataFetcher? {
@@ -324,10 +327,19 @@ class StdlibDataFetcher private constructor(
                 srcDir,
                 testPackageSrcDir,
                 version,
+                activeToolchain,
                 listener,
                 cleanVendorDir
             ) ?: return null
-            return StdlibDataFetcher(project, cargo, srcDir, testPackageSrcDir, stdlibDependenciesDir, listener)
+            return StdlibDataFetcher(
+                project,
+                cargo,
+                srcDir,
+                testPackageSrcDir,
+                stdlibDependenciesDir,
+                activeToolchain,
+                listener
+            )
         }
 
         @VisibleForTesting
@@ -342,6 +354,7 @@ class StdlibDataFetcher private constructor(
             srcDir: VirtualFile,
             testPackageSrcDir: VirtualFile,
             version: RustcVersion,
+            activeToolchain: String?,
             listener: ProcessProgressListener?,
             cleanVendorDir: Boolean
         ): Path? {
@@ -360,7 +373,7 @@ class StdlibDataFetcher private constructor(
             if (!stdlibVendorExists) {
                 // `test` package depends on all other stdlib packages,
                 // so it's enough to vendor only its dependencies
-                cargo.vendorDependencies(project, testPackageSrcDir.pathAsPath, stdlibVendor, listener).unwrapOrElse {
+                cargo.vendorDependencies(project, testPackageSrcDir.pathAsPath, stdlibVendor, activeToolchain, listener).unwrapOrElse {
                     listener?.error("Failed to load stdlib dependencies", it.message.orEmpty())
                     LOG.error(it)
                     return null
diff --git a/src/main/kotlin/org/rust/cargo/runconfig/command/CargoCommandConfiguration.kt b/src/main/kotlin/org/rust/cargo/runconfig/command/CargoCommandConfiguration.kt
index 11350f54d..b909eddb2 100644
--- a/src/main/kotlin/org/rust/cargo/runconfig/command/CargoCommandConfiguration.kt
+++ b/src/main/kotlin/org/rust/cargo/runconfig/command/CargoCommandConfiguration.kt
@@ -120,7 +120,7 @@ open class CargoCommandConfiguration(
 
     fun setFromCmd(cmd: CargoCommandLine) {
         channel = cmd.channel
-        command = ParametersListUtil.join(listOfNotNull(cmd.toolchain, cmd.command, *cmd.additionalArguments.toTypedArray()))
+        command = cmd.toRawCommand()
         requiredFeatures = cmd.requiredFeatures
         allFeatures = cmd.allFeatures
         emulateTerminal = cmd.emulateTerminal
@@ -133,7 +133,7 @@ open class CargoCommandConfiguration(
     }
 
     fun canBeFrom(cmd: CargoCommandLine): Boolean =
-        command == ParametersListUtil.join(listOfNotNull(cmd.toolchain, cmd.command, *cmd.additionalArguments.toTypedArray()))
+        command == cmd.toRawCommand()
 
     @Throws(RuntimeConfigurationException::class)
     override fun checkConfiguration() {
@@ -242,6 +242,11 @@ open class CargoCommandConfiguration(
         return CleanConfiguration.Ok(cmd, toolchain)
     }
 
+    private fun CargoCommandLine.toRawCommand(): String {
+        val toolchainOverride = toolchain?.let { "+$it" }
+        return ParametersListUtil.join(listOfNotNull(toolchainOverride, command, *additionalArguments.toTypedArray()))
+    }
+
     companion object {
         fun findCargoProject(project: Project, additionalArgs: List<String>, workingDirectory: Path?): CargoProject? {
             val cargoProjects = project.cargoProjects
@@ -323,7 +328,7 @@ data class ParsedCommand(val command: String, val toolchain: String?, val additi
         fun parse(rawCommand: String): ParsedCommand? {
             val args = ParametersListUtil.parse(rawCommand)
             val command = args.firstOrNull { !it.startsWith("+") } ?: return null
-            val toolchain = args.firstOrNull()?.takeIf { it.startsWith("+") }
+            val toolchain = args.firstOrNull()?.takeIf { it.startsWith("+") }?.removePrefix("+")
             val additionalArguments = args.drop(args.indexOf(command) + 1)
             return ParsedCommand(command, toolchain, additionalArguments)
         }
diff --git a/src/main/kotlin/org/rust/cargo/toolchain/tools/Cargo.kt b/src/main/kotlin/org/rust/cargo/toolchain/tools/Cargo.kt
index b20c5e979..6fcd1df11 100644
--- a/src/main/kotlin/org/rust/cargo/toolchain/tools/Cargo.kt
+++ b/src/main/kotlin/org/rust/cargo/toolchain/tools/Cargo.kt
@@ -141,7 +141,7 @@ class Cargo(
         projectDirectory: Path,
         listenerProvider: (CargoCallType) -> ProcessListener? = { null }
     ): RsResult<ProjectDescription, RsProcessExecutionOrDeserializationException> {
-        val rawData = fetchMetadata(owner, projectDirectory, listenerProvider(CargoCallType.METADATA))
+        val rawData = fetchMetadata(owner, projectDirectory, listener = listenerProvider(CargoCallType.METADATA))
             .unwrapOrElse { return Err(it) }
 
         val buildScriptsInfo = if (isFeatureEnabled(RsExperiments.EVALUATE_BUILD_SCRIPTS)) {
@@ -160,10 +160,11 @@ class Cargo(
     fun fetchMetadata(
         owner: Project,
         projectDirectory: Path,
+        toolchainOverride: String? = null,
         listener: ProcessListener? = null
     ): RsResult<CargoMetadata.Project, RsProcessExecutionOrDeserializationException> {
-        val additionalArgs = mutableListOf("--verbose", "--format-version", "1", "--all-features")
-        val json = CargoCommandLine("metadata", projectDirectory, additionalArgs)
+        val additionalArgs = listOf("--verbose", "--format-version", "1", "--all-features")
+        val json = CargoCommandLine("metadata", projectDirectory, additionalArgs, toolchain = toolchainOverride)
             .execute(owner, listener = listener)
             .unwrapOrElse { return Err(it) }
             .stdout
@@ -181,10 +182,16 @@ class Cargo(
         owner: Project,
         projectDirectory: Path,
         dstPath: Path,
+        toolchainOverride: String? = null,
         listener: ProcessListener? = null
     ): RsProcessResult<Unit> {
         val additionalArgs = listOf("--respect-source-config", dstPath.toString())
-        val commandLine = CargoCommandLine("vendor", projectDirectory, additionalArgs)
+        val commandLine = CargoCommandLine(
+            "vendor",
+            projectDirectory,
+            additionalArgs,
+            toolchain = toolchainOverride
+        )
         commandLine.execute(owner, listener = listener).unwrapOrElse { return Err(it) }
         return Ok(Unit)
     }
@@ -384,7 +391,7 @@ class Cargo(
                 if (channel != RustChannel.DEFAULT) {
                     add("+$channel")
                 } else if (toolchain != null) {
-                    add(toolchain)
+                    add("+$toolchain")
                 }
                 if (project.rustSettings.useOffline) {
                     val cargoProject = findCargoProject(project, additionalArguments, workingDirectory)
diff --git a/src/main/kotlin/org/rust/cargo/toolchain/tools/Rustup.kt b/src/main/kotlin/org/rust/cargo/toolchain/tools/Rustup.kt
index 0d8ab1a17..694fc735a 100644
--- a/src/main/kotlin/org/rust/cargo/toolchain/tools/Rustup.kt
+++ b/src/main/kotlin/org/rust/cargo/toolchain/tools/Rustup.kt
@@ -108,6 +108,17 @@ class Rustup(toolchain: RsToolchainBase, private val projectDirectory: Path) : R
             workingDirectory = projectDirectory
         ).execute(owner).convertResult()
 
+    fun activeToolchainName(): String? {
+        val output = createBaseCommandLine("show", "active-toolchain", workingDirectory = projectDirectory)
+            .execute(toolchain.executionTimeoutInMilliseconds) ?: return null
+        if (!output.isSuccess) return null
+        // Expected outputs:
+        //  1.48.0-x86_64-apple-darwin (default)
+        //  stable-x86_64-apple-darwin (overridden by '/path/to/rust-toolchain.toml')
+        //  nightly-x86_64-apple-darwin (directory override for '/path/to/override/dir')
+        return output.stdout.substringBefore("(").trim()
+    }
+
     private fun RsProcessResult<ProcessOutput>.convertResult() =
         when (this) {
             is RsResult.Ok -> DownloadResult.Ok(Unit)
diff --git a/src/main/kotlin/org/rust/cargo/util/TestUnitTestRustcCacheService.kt b/src/main/kotlin/org/rust/cargo/util/TestUnitTestRustcCacheService.kt
index 01d184531..614d89168 100644
--- a/src/main/kotlin/org/rust/cargo/util/TestUnitTestRustcCacheService.kt
+++ b/src/main/kotlin/org/rust/cargo/util/TestUnitTestRustcCacheService.kt
@@ -18,11 +18,11 @@ class TestUnitTestRustcCacheService : UnitTestRustcCacheService {
     @Suppress("UNCHECKED_CAST")
     override fun <T> cachedInner(
         rustcVersion: RustcVersion?,
-        cls: Class<T>,
         cacheIf: () -> Boolean,
         computation: () -> T
     ): T {
         if (rustcVersion == null || !cacheIf()) return computation()
+        val cls = computation.javaClass
         return cache.getOrPut(rustcVersion to cls) { Optional.ofNullable(computation()) }.orNull() as T
     }
 }
diff --git a/src/main/kotlin/org/rust/cargo/util/UnitTestRustcCacheService.kt b/src/main/kotlin/org/rust/cargo/util/UnitTestRustcCacheService.kt
index aeac3790a..ca50d2ecc 100644
--- a/src/main/kotlin/org/rust/cargo/util/UnitTestRustcCacheService.kt
+++ b/src/main/kotlin/org/rust/cargo/util/UnitTestRustcCacheService.kt
@@ -18,18 +18,17 @@ import org.rust.cargo.toolchain.impl.RustcVersion
 interface UnitTestRustcCacheService {
     fun <T> cachedInner(
         rustcVersion: RustcVersion?,
-        cls: Class<T>,
         cacheIf: () -> Boolean,
         computation: () -> T
     ): T
 
     companion object {
-        inline fun <reified T> cached(
+        fun <T> cached(
             rustcVersion: RustcVersion?,
-            noinline cacheIf: () -> Boolean = { true },
-            noinline computation: () -> T
+            cacheIf: () -> Boolean = { true },
+            computation: () -> T
         ): T {
-            return service<UnitTestRustcCacheService>().cachedInner(rustcVersion, T::class.java, cacheIf, computation)
+            return service<UnitTestRustcCacheService>().cachedInner(rustcVersion, cacheIf, computation)
         }
     }
 }
@@ -37,7 +36,6 @@ interface UnitTestRustcCacheService {
 class UnitTestRustcCacheServiceImpl : UnitTestRustcCacheService {
     override fun <T> cachedInner(
         rustcVersion: RustcVersion?,
-        cls: Class<T>,
         cacheIf: () -> Boolean,
         computation: () -> T
     ): T {
diff --git a/src/test/kotlin/org/rust/cargo/toolchain/CargoTest.kt b/src/test/kotlin/org/rust/cargo/toolchain/CargoTest.kt
index ecd9b691f..5d4dfab6b 100644
--- a/src/test/kotlin/org/rust/cargo/toolchain/CargoTest.kt
+++ b/src/test/kotlin/org/rust/cargo/toolchain/CargoTest.kt
@@ -82,7 +82,7 @@ class CargoTest : RsTestBase() {
     """)
 
     fun `test use +nightly explicitly`() = checkCommandLine(
-        cargo.toColoredCommandLine(project, CargoCommandLine("run", wd, toolchain = "+nightly")), """
+        cargo.toColoredCommandLine(project, CargoCommandLine("run", wd, toolchain = "nightly")), """
         cmd: /usr/bin/cargo +nightly run --color=always
         env: RUSTC=/usr/bin/rustc, RUST_BACKTRACE=short, TERM=ansi
         """, """
@@ -91,7 +91,7 @@ class CargoTest : RsTestBase() {
     """)
 
     fun `test override explicitly used +toolchain flag if channel is set`() = checkCommandLine(
-        cargo.toColoredCommandLine(project, CargoCommandLine("run", wd, toolchain = "+nightly", channel = RustChannel.BETA)), """
+        cargo.toColoredCommandLine(project, CargoCommandLine("run", wd, toolchain = "nightly", channel = RustChannel.BETA)), """
         cmd: /usr/bin/cargo +beta run --color=always
         env: RUSTC=/usr/bin/rustc, RUST_BACKTRACE=short, TERM=ansi
         """, """