diff --git a/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala b/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala index 9226f663cff..2f9b1aa188d 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/BuildTargets.scala @@ -434,10 +434,14 @@ final class BuildTargets private ( * @return path to the source jar for that jar */ private def sourceJarPathFallback( - sourceJarPath: AbsolutePath + sourceJarPath: AbsolutePath, + inputIsSourceJar: Boolean = false, ): Option[AbsolutePath] = { + val (from, to) = + if (inputIsSourceJar) ("-sources.jar", ".jar") + else (".jar", "-sources.jar") val fallback = sourceJarPath.parent.resolve( - sourceJarPath.filename.replace(".jar", "-sources.jar") + sourceJarPath.filename.replace(from, to) ) if (fallback.exists) Some(fallback) else None @@ -578,7 +582,7 @@ final class BuildTargets private ( jar: AbsolutePath, ): Option[AbsolutePath] = { data - .fromOptions(_.findSourceJarOf(jar, Some(id))) + .fromOptions(_.findConnectedArtifact(jar, Some(id))) .orElse(sourceJarPathFallback(jar)) } @@ -586,10 +590,21 @@ final class BuildTargets private ( jar: AbsolutePath ): Option[AbsolutePath] = { data - .fromOptions(_.findSourceJarOf(jar, targetId = None)) + .fromOptions(_.findConnectedArtifact(jar, targetId = None)) .orElse(sourceJarPathFallback(jar)) } + def findJarFor( + id: BuildTargetIdentifier, + sourceJar: AbsolutePath, + ): Option[AbsolutePath] = { + data + .fromOptions( + _.findConnectedArtifact(sourceJar, Some(id), classifier = null) + ) + .orElse(sourceJarPathFallback(sourceJar, inputIsSourceJar = true)) + } + def inverseDependencySource( sourceJar: AbsolutePath ): collection.Set[BuildTargetIdentifier] = { diff --git a/metals/src/main/scala/scala/meta/internal/metals/ScalaVersions.scala b/metals/src/main/scala/scala/meta/internal/metals/ScalaVersions.scala index 4eec0386ef2..c3477fa3977 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/ScalaVersions.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/ScalaVersions.scala @@ -1,7 +1,13 @@ package scala.meta.internal.metals +import java.io.UncheckedIOException + +import scala.collection.concurrent.TrieMap + import scala.meta.Dialect import scala.meta.dialects._ +import scala.meta.internal.io.FileIO +import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.semver.SemVer import scala.meta.io.AbsolutePath @@ -14,6 +20,8 @@ class ScalaVersions( scala3: String, ) { + private val jarScalaVersionIndex = TrieMap[String, String]() + def isScala3Milestone(version: String): Boolean = version.startsWith("3.0.0-M") || version.startsWith("3.0.0-RC") @@ -148,7 +156,9 @@ class ScalaVersions( } private val scalaVersionRegex = - raw"(_)(\d)(\.\d{1,2})?(\.\d(-(RC|M)\d)?)?".r + raw"_(\d)(\.\d{1,2})?(\.\d(-(RC|M)\d)?)?".r + private val scalaLibraryRegex = + raw"scala-library-(\d)(\.\d{1,2})(\.\d(-(RC|M)\d)?)".r /** * Extract scala binary version from dependency jar name. @@ -162,40 +172,59 @@ class ScalaVersions( val dropEnding = filename .stripSuffix(".jar") - scalaVersionRegex - .findAllMatchIn(dropEnding) - .toList + List(scalaLibraryRegex, scalaVersionRegex) + .flatMap(_.findAllMatchIn(dropEnding).toList) .flatMap { m => - val hasUnderscorePrefix = Option(m.group(1)).isDefined - val major = m.group(2) - val minor = Option(m.group(3)).getOrElse("") - val ending = Option(m.group(4)).getOrElse("") + val major = m.group(1) + val minor = Option(m.group(2)).getOrElse("") + val ending = Option(m.group(3)).getOrElse("") val version = s"$major$minor$ending" if (isSupportedScalaBinaryVersion(version)) - Some(version -> hasUnderscorePrefix) + Some(version) else None } - .sortBy(_._2)(Ordering.Boolean.reverse) .headOption - .map { case (version, _) => scalaBinaryVersionFromFullVersion(version) } + .map(scalaBinaryVersionFromFullVersion) } def dialectForDependencyJar( jar: AbsolutePath, buildTargets: BuildTargets, ): Dialect = { + lazy val buildTargetAndScalaVersion = + buildTargets + .inverseDependencySource(jar) + .flatMap(id => buildTargets.scalaTarget(id)) + .map(target => (target.scalaBinaryVersion, target.id)) + .toList + .sortBy(_._1) + .headOption + + def fromTastyExistance = { + val fromTasty = buildTargetAndScalaVersion + .flatMap { case (_, id) => buildTargets.findJarFor(id, jar) } + .flatMap( + FileIO.withJarFileSystem(_, create = false) { root => + try { + root.listRecursive + .find(f => f.isFile && f.filename.endsWith(".tasty")) + .map(_ => "3") + .orElse(Some("2.13")) + } catch { + case _: UncheckedIOException => None + } + } + ) + fromTasty.foreach(jarScalaVersionIndex.put(jar.toURI.toString(), _)) + fromTasty + } + val scalaVersion = scalaBinaryVersionFromJarName(jar.toNIO.getFileName().toString()) - .orElse( - buildTargets - .inverseDependencySource(jar) - .flatMap(id => buildTargets.scalaTarget(id)) - .map(_.scalaBinaryVersion) - .toList - .sorted - .headOption - ) + .orElse(jarScalaVersionIndex.get(jar.toURI.toString())) + .orElse(fromTastyExistance) + .orElse(buildTargetAndScalaVersion.map(_._1)) .getOrElse("2.13") dialectForScalaVersion(scalaVersion, includeSource3 = true) } diff --git a/metals/src/main/scala/scala/meta/internal/metals/TargetData.scala b/metals/src/main/scala/scala/meta/internal/metals/TargetData.scala index b3ee4f757f5..bb868f60409 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/TargetData.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/TargetData.scala @@ -206,9 +206,10 @@ final class TargetData { } } - def findSourceJarOf( + def findConnectedArtifact( jar: AbsolutePath, targetId: Option[BuildTargetIdentifier], + classifier: String = "sources", ): Option[AbsolutePath] = { val jarUri = jar.toURI.toString() def depModules: Iterator[MavenDependencyModule] = targetId match { @@ -228,10 +229,10 @@ final class TargetData { module <- depModules artifacts = module.getArtifacts().asScala if artifacts.exists(artifact => isUriEqual(artifact.getUri(), jarUri)) - sourceJar <- artifacts.find(_.getClassifier() == "sources") - sourceJarPath = sourceJar.getUri().toAbsolutePath - if sourceJarPath.exists - } yield sourceJarPath + foundJar <- artifacts.find(_.getClassifier() == classifier) + foundJarPath = foundJar.getUri().toAbsolutePath + if foundJarPath.exists + } yield foundJarPath allFound.headOption }