Skip to content

Commit

Permalink
[synthetic psi] provide a proper navigation element from sources for …
Browse files Browse the repository at this point in the history
…synthetic elements from scala library #SCL-22167 fixed, #SCL-22350

Should work since Scala 2.13.14
See scala/bug#12958


(cherry picked from commit 981f992)
  • Loading branch information
unkarjedy authored and builduser committed Apr 26, 2024
1 parent b2a1318 commit 8cece08
Show file tree
Hide file tree
Showing 16 changed files with 379 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ abstract class SbtDocumentationProviderTestBase extends DocumentationProviderTes
protected def doGenerateSbtDocDescriptionTest(sbtContent: String, expectedDocShort: String): Unit =
doGenerateDocContentTest(sbtContent, expectedDocShort)

override protected def createFile(fileContent: String): PsiFile = {
override protected def configureFixtureFromText(fileContent: String): Unit = {
val fileText =
s"""import sbt._
|import sbt.KeyRanks._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ abstract class SbtScalacOptionsDocumentationProviderTestBase extends Documentati

override protected def documentationProvider: DocumentationProvider = new SbtScalacOptionsDocumentationProvider

override protected def createFile(fileContent: String): PsiFile =
override protected def configureFixtureFromText(fileContent: String): Unit =
myFixture.configureByText(SbtFileType, fileContent)

override protected def extractReferredAndOriginalElements(editor: Editor, file: PsiFile): (PsiElement, PsiElement) = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ abstract class ScalaCompilerTestBase extends JavaModuleTestCase with ScalaSdkOwn
protected def compilerBridgeBinaryJar: Option[File] = None

override protected def librariesLoaders: Seq[LibraryLoader] = Seq(
ScalaSDKLoader(includeReflectLibrary, includeCompilerAsLibrary, compilerBridgeBinaryJar),
ScalaSDKLoader(includeReflectLibrary, includeCompilerAsLibrary, compilerBridgeBinaryJar = compilerBridgeBinaryJar),
HeavyJDKLoader(testProjectJdkVersion),
SourcesLoader(getSourceRootDir.getCanonicalPath)
) ++ additionalLibraries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import org.apache.ivy.plugins.repository.jar.JarRepository
import org.apache.ivy.plugins.resolver.{ChainResolver, IBiblioResolver, RepositoryResolver, URLResolver}
import org.apache.ivy.util.{DefaultMessageLogger, MessageLogger}
import org.jetbrains.plugins.scala.DependencyManagerBase.DependencyDescription.scalaArtifact
import org.jetbrains.plugins.scala.DependencyManagerBase.IvyResolver
import org.jetbrains.plugins.scala.extensions.IterableOnceExt
import org.jetbrains.plugins.scala.project.Version
import org.jetbrains.plugins.scala.project.template._

import java.io.File
Expand All @@ -28,11 +26,8 @@ object DependencyManager extends DependencyManagerBase
abstract class DependencyManagerBase {
import DependencyManagerBase._

private val homePrefix = sys.props.get("tc.idea.prefix").orElse(Some(SystemProperties.getUserHome)).map(new File(_)).get
private val ivyHome = sys.props.get("sbt.ivy.home").map(new File(_)).orElse(Option(new File(homePrefix, ".ivy2"))).get

protected def useFileSystemResolversOnly: Boolean =
if (ApplicationManager.getApplication == null) //to beable to use DependencyManagerBase outside IntelliJ App
if (ApplicationManager.getApplication == null) //to be able to use DependencyManagerBase outside IntelliJ App
false
else
RegistryManager.getInstance().is("scala.dependency.manager.use.file.system.resolvers.only")
Expand All @@ -44,12 +39,8 @@ abstract class DependencyManagerBase {
protected val logLevel: Int = org.apache.ivy.util.Message.MSG_WARN

protected def resolvers: Seq[Resolver] = defaultResolvers
private final val defaultResolvers: Seq[Resolver] = Seq(
MavenResolver(
"central",
"https://repo1.maven.org/maven2"
)
)

private final val defaultResolvers: Seq[Resolver] = Seq(Resolver.MavenCentral)

private def mkIvyXml(deps: Seq[DependencyDescription]): String = {
s"""
Expand Down Expand Up @@ -248,6 +239,9 @@ abstract class DependencyManagerBase {

object DependencyManagerBase {

private val homePrefix: File = sys.props.get("tc.idea.prefix").orElse(Some(SystemProperties.getUserHome)).map(new File(_)).get
val ivyHome: File = sys.props.get("sbt.ivy.home").map(new File(_)).orElse(Option(new File(homePrefix, ".ivy2"))).get

private val fileSystemOnlyIvySettingsURL: URL = {
val clazz = classOf[DependencyManagerBase]
val resourcePath = "dependencyManager/ivysettings-file-system-resolvers-only.xml"
Expand Down Expand Up @@ -279,7 +273,10 @@ object DependencyManagerBase {
def configuration(conf: String): DependencyDescription = copy(conf = conf)
def transitive(): DependencyDescription = copy(isTransitive = true)
def exclude(patterns: String*): DependencyDescription = copy(excludes = patterns)
override def toString: String = s"$org:$artId:$version"
override def toString: String = {
val kindHint = if (kind == Types.SRC) " (sources)" else ""
s"$org:$artId:$version$kindHint"
}
}
object DependencyDescription {
def fromId(id: ModuleRevisionId): DependencyDescription =
Expand Down Expand Up @@ -310,6 +307,21 @@ object DependencyManagerBase {
case class ResolvedDependency(info: DependencyDescription, file: File) extends Dependency

sealed trait Resolver
object Resolver {
val MavenCentral: MavenResolver = MavenResolver(
"central",
"https://repo1.maven.org/maven2"
)
val TypesafeReleases: IvyResolver = IvyResolver(
"typesafe-releases",
"https://repo.typesafe.com/typesafe/ivy-releases/[organisation]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]"
)
val TypesafeScalaPRValidationSnapshots: MavenResolver = MavenResolver(
"scala-pr-validation-snapshots",
"https://scala-ci.typesafe.com/artifactory/scala-pr-validation-snapshots"
)
}

case class MavenResolver(name: String, root: String) extends Resolver

/** @param pattern same generic pattern for both artifact & ivy files e.g. <br>
Expand All @@ -322,7 +334,6 @@ object DependencyManagerBase {
def scalaLibraryDescription(implicit scalaVersion: ScalaVersion): DependencyDescription = scalaArtifact("library", scalaVersion)
def scalaReflectDescription(implicit scalaVersion: ScalaVersion): DependencyDescription = scalaArtifact("reflect", scalaVersion)


implicit class RichStr(private val org: String) extends AnyVal {

def %(artId: String): DependencyDescription = DependencyDescription(org, artId, "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ object LatestScalaVersions {
//
val Scala_3_RC = new ScalaVersion(ScalaLanguageLevel.Scala_3_4, "2-RC1")

/** Available only when using [[DependencyManagerBase.Resolver.TypesafeScalaPRValidationSnapshots]] */
val Scala_2_13_RC = new ScalaVersion(ScalaLanguageLevel.Scala_2_13, "14-bin-ed3dfc9-SNAPSHOT")

val allScala2: Seq[ScalaVersion] = Seq(
Scala_2_9,
Scala_2_10,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.jetbrains.plugins.scala.lang.psi.api.statements.params.ScParameter
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef._
import org.jetbrains.plugins.scala.lang.psi.api.{ScalaFile, ScalaPsiElement}
import org.jetbrains.plugins.scala.lang.psi.impl.ScalaPsiManager
import org.jetbrains.plugins.scala.lang.psi.impl.toplevel.synthetic.SyntheticNamedElement
import org.jetbrains.plugins.scala.lang.psi.light.{PsiClassWrapper, ScFunctionWrapper}
import org.jetbrains.plugins.scala.lang.scaladoc.psi.api.ScDocComment

Expand Down Expand Up @@ -156,6 +157,8 @@ object ScalaDocumentationProvider {
originalElement match {
case null => null
case ScFunctionWrapper(delegate) => delegate
case synthetic: SyntheticNamedElement =>
synthetic //extra .getNavigationalElement will be called later
case _: ScTypeDefinition |
_: ScTypeAlias |
_: ScValue |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,25 @@ package org.jetbrains.plugins.scala.lang.psi.impl.toplevel.synthetic

import com.intellij.navigation.ItemPresentation
import com.intellij.openapi.components.Service
import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.project.{Project, ProjectManagerListener}
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.startup.StartupActivity
import com.intellij.psi._
import com.intellij.psi.impl.light.LightElement
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.IncorrectOperationException
import com.intellij.util.containers.MultiMap
import org.jetbrains.annotations.TestOnly
import org.jetbrains.plugins.scala.caches.cachedInUserData
import org.jetbrains.plugins.scala.extensions._
import org.jetbrains.plugins.scala.icons.Icons
import org.jetbrains.plugins.scala.lang.psi.adapters.PsiClassAdapter
import org.jetbrains.plugins.scala.lang.psi.api.statements.params.ScTypeParam
import org.jetbrains.plugins.scala.lang.psi.api.statements.{ScFun, ScTypeAlias}
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScObject
import org.jetbrains.plugins.scala.lang.psi.api.statements.{ScFun, ScFunction, ScTypeAlias}
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.{ScObject, ScTemplateDefinition}
import org.jetbrains.plugins.scala.lang.psi.api.{ScalaFile, ScalaPsiElement}
import org.jetbrains.plugins.scala.lang.psi.impl.ScalaPsiElementFactory
import org.jetbrains.plugins.scala.lang.psi.impl.toplevel.PsiClassFake
import org.jetbrains.plugins.scala.lang.psi.impl.{ScalaPsiElementFactory, ScalaPsiManager}
import org.jetbrains.plugins.scala.lang.psi.implicits.ImplicitProcessor
import org.jetbrains.plugins.scala.lang.psi.types._
import org.jetbrains.plugins.scala.lang.psi.types.api._
Expand Down Expand Up @@ -83,14 +86,16 @@ final class ScSyntheticTypeParameter(override val name: String, override val own

// we could try and implement all type system related stuff
// with class types, but it is simpler to indicate types corresponding to synthetic classes explicitly
sealed class ScSyntheticClass(
final class ScSyntheticClass(
val className: String,
val stdType: StdType
)(implicit projectContext: ProjectContext)
extends SyntheticNamedElement(className)
with PsiClassAdapter
with PsiClassFake {

override def getQualifiedName: String = "scala." + className

override def getPresentation: ItemPresentation = {
new ItemPresentation {
val This: ScSyntheticClass = ScSyntheticClass.this
Expand All @@ -102,13 +107,38 @@ sealed class ScSyntheticClass(
}
}

override def getNavigationElement: PsiElement = cachedInUserData("ScSyntheticClass.getNavigationElement", this, ProjectRootManager.getInstance(getProject)) {
val syntheticClassSourceMirror = for {
scalaPackagePsiDirectory <- findScalaPackageSourcesPsiDirectory(this.stdType.projectContext.project)
//class Any -> Any.scala
psiFile <- Option(scalaPackagePsiDirectory.findFile(s"$className.scala")).map(_.asInstanceOf[ScalaFile])
classDef <- psiFile.typeDefinitions.headOption //expecting single class definition in the file
} yield classDef
syntheticClassSourceMirror.getOrElse(super.getNavigationElement)
}

//TODO: current implementation might not work in a project with multiple scala versions. It depends on SCL-22349.
private def findScalaPackageSourcesPsiDirectory(project: Project): Option[PsiDirectory] = cachedInUserData("ScSyntheticClass.findScalaPackageSourcesPsiDirectory", project, ProjectRootManager.getInstance(project)) {
//Get some representative class from Scala standard library
val classFromStdLib = ScalaPsiManager.instance(this.stdType.projectContext).getCachedClass(GlobalSearchScope.allScope(this.stdType.projectContext.project), "scala.Array")
classFromStdLib.map { clazz =>
//.../scala-library-2.13.11-sources.jar!/scala/Array.scala
val navigationFile = clazz.getContainingFile.getNavigationElement.asInstanceOf[ScalaFile]
//.../scala-library-2.13.11-sources.jar!/scala
navigationFile.getParent;
}
}

override def getNameIdentifier: PsiIdentifier = null

override def toString = "Synthetic class"

val syntheticMethods = new MultiMap[String, ScSyntheticFunction]()

def addMethod(method: ScSyntheticFunction): Unit = syntheticMethods.putValue(method.name, method)
def addMethod(method: ScSyntheticFunction): Unit = {
syntheticMethods.putValue(method.name, method)
method.setContainingSyntheticClass(this)
}

override def processDeclarations(
processor: com.intellij.psi.scope.PsiScopeProcessor,
Expand Down Expand Up @@ -152,6 +182,14 @@ sealed class ScSyntheticFunction(
typeParameterNames: Seq[String]
)(implicit projectContext: ProjectContext)
extends SyntheticNamedElement(name) with ScFun {

private var containingSyntheticClass: Option[ScSyntheticClass] = None

def setContainingSyntheticClass(value: ScSyntheticClass): Unit = {
assert(containingSyntheticClass.isEmpty, s"Containing synthetic class was already assigned to method $name")
containingSyntheticClass = Some(value)
}

def isStringPlusMethod: Boolean = {
if (name != "+") return false
retType.extractClass match {
Expand Down Expand Up @@ -186,6 +224,18 @@ sealed class ScSyntheticFunction(
}
null
}

override def getNavigationElement: PsiElement = cachedInUserData("ScSyntheticFunction.getNavigationElement", this, ProjectRootManager.getInstance(retType.projectContext)) {
val syntheticFunctionSourceMirror = containingSyntheticClass.flatMap(_.getNavigationElement match {
case classInSources: ScTemplateDefinition =>
//NOTE: we search for the function with the same name ignoring overloaded functions
// in principle this is not entirely correct, but for the synthetic classes in Scala library
// it should work fine because it's known that there are no overloaded methods in those classes
classInSources.members.filterByType[ScFunction].find(_.name == name)
case _ => None
})
syntheticFunctionSourceMirror.getOrElse(super.getNavigationElement)
}
}

final class ScSyntheticValue(val name: String, val tp: ScType)
Expand All @@ -196,8 +246,6 @@ final class ScSyntheticValue(val name: String, val tp: ScType)
override def toString = "Synthetic value"
}

import com.intellij.openapi.project.Project

@Service(Array(Service.Level.PROJECT))
final class SyntheticClasses(project: Project) {
implicit def ctx: ProjectContext = project
Expand Down Expand Up @@ -327,6 +375,7 @@ final class SyntheticClasses(project: Project) {
}

//register synthetic objects
//TODO: drop it (see https://youtrack.jetbrains.com/issue/SCL-20932)
def registerObject(debugName: String, fileText: String): Unit = {
val dummyFile = createDummyFile(debugName, fileText)
val obj = dummyFile.typeDefinitions.head.asInstanceOf[ScObject]
Expand All @@ -344,7 +393,7 @@ final class SyntheticClasses(project: Project) {
val contextParameters = (1 to n).map(i => s"x$i: T$i").mkString(", ")

registerContextFunctionClass("ContextFunction",
s"""
s"""
|package scala
|
|trait ContextFunction$n[$typeParameters, +R] {
Expand All @@ -355,7 +404,7 @@ final class SyntheticClasses(project: Project) {
}

registerObject("Boolean",
"""
"""
package scala
object Boolean {
Expand All @@ -367,7 +416,7 @@ object Boolean {
)

registerObject("Byte",
"""
"""
package scala
object Byte {
Expand All @@ -383,7 +432,7 @@ object Byte {
)

registerObject("Char",
"""
"""
package scala
object Char {
Expand All @@ -399,7 +448,7 @@ object Char {
)

registerObject("Double",
"""
"""
package scala
object Double {
Expand Down Expand Up @@ -429,7 +478,7 @@ object Double {
)

registerObject("Float",
"""
"""
package scala
object Float {
Expand Down Expand Up @@ -459,7 +508,7 @@ object Float {
)

registerObject("Int",
"""
"""
package scala
object Int {
Expand All @@ -475,7 +524,7 @@ object Int {
)

registerObject("Long",
"""
"""
package scala
object Long {
Expand All @@ -491,7 +540,7 @@ object Long {
)

registerObject("Short",
"""
"""
package scala
object Short {
Expand All @@ -507,7 +556,7 @@ object Short {
)

registerObject("Unit",
"""
"""
package scala
object Unit
Expand Down Expand Up @@ -556,9 +605,7 @@ object Unit
}

def registerClass(t: StdType, name: String, isScala3: Boolean = false): ScSyntheticClass = {
val cls = new ScSyntheticClass(name, t) {
override def getQualifiedName: String = "scala." + name
}
val cls = new ScSyntheticClass(name, t)

if (isScala3)
scala3Classes += ((name, cls))
Expand All @@ -572,6 +619,8 @@ object Unit
def registerNumericClass(clazz : ScSyntheticClass): ScSyntheticClass = {numeric += clazz; clazz}

def all: Iterable[PsiClass] = sharedClasses.values ++ scala3Classes.values
@TestOnly
def getScala3Classes: Iterable[PsiClass] = scala3Classes.values

def sharedClassesOnly: Iterable[PsiClass] = sharedClasses.values

Expand Down

0 comments on commit 8cece08

Please sign in to comment.