Skip to content

Commit 9ed0fd5

Browse files
committed
[synthetic psi] provide a proper navigation element from sources for synthetic elements from scala library #SCL-22167 fixed, #SCL-22350
Should work since Scala 2.13.14 See scala/bug#12958
1 parent bd89e7f commit 9ed0fd5

File tree

16 files changed

+369
-68
lines changed

16 files changed

+369
-68
lines changed

sbt/sbt-impl/test/org/jetbrains/sbt/editor/documentationProvider/SbtDocumentationProviderTestBase.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ abstract class SbtDocumentationProviderTestBase extends DocumentationProviderTes
1616
protected def doGenerateSbtDocDescriptionTest(sbtContent: String, expectedDocShort: String): Unit =
1717
doGenerateDocContentTest(sbtContent, expectedDocShort)
1818

19-
override protected def createFile(fileContent: String): PsiFile = {
19+
override protected def configureFixtureFromText(fileContent: String): Unit = {
2020
val fileText =
2121
s"""import sbt._
2222
|import sbt.KeyRanks._

sbt/sbt-impl/test/org/jetbrains/sbt/editor/documentationProvider/SbtScalacOptionsDocumentationProviderTestBase.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ abstract class SbtScalacOptionsDocumentationProviderTestBase extends Documentati
1414

1515
override protected def documentationProvider: DocumentationProvider = new SbtScalacOptionsDocumentationProvider
1616

17-
override protected def createFile(fileContent: String): PsiFile =
17+
override protected def configureFixtureFromText(fileContent: String): Unit =
1818
myFixture.configureByText(SbtFileType, fileContent)
1919

2020
override protected def extractReferredAndOriginalElements(editor: Editor, file: PsiFile): (PsiElement, PsiElement) = {

scala/compiler-integration/test/org/jetbrains/plugins/scala/compiler/ScalaCompilerTestBase.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ abstract class ScalaCompilerTestBase extends JavaModuleTestCase with ScalaSdkOwn
103103
protected def compilerBridgeBinaryJar: Option[File] = None
104104

105105
override protected def librariesLoaders: Seq[LibraryLoader] = Seq(
106-
ScalaSDKLoader(includeReflectLibrary, includeCompilerAsLibrary, compilerBridgeBinaryJar),
106+
ScalaSDKLoader(includeReflectLibrary, includeCompilerAsLibrary, compilerBridgeBinaryJar = compilerBridgeBinaryJar),
107107
HeavyJDKLoader(testProjectJdkVersion),
108108
SourcesLoader(getSourceRootDir.getCanonicalPath)
109109
) ++ additionalLibraries

scala/scala-impl/src/org/jetbrains/plugins/scala/DependencyManager.scala

+22-11
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ import org.apache.ivy.plugins.repository.jar.JarRepository
1313
import org.apache.ivy.plugins.resolver.{ChainResolver, IBiblioResolver, RepositoryResolver, URLResolver}
1414
import org.apache.ivy.util.{DefaultMessageLogger, MessageLogger}
1515
import org.jetbrains.plugins.scala.DependencyManagerBase.DependencyDescription.scalaArtifact
16-
import org.jetbrains.plugins.scala.DependencyManagerBase.IvyResolver
1716
import org.jetbrains.plugins.scala.extensions.IterableOnceExt
18-
import org.jetbrains.plugins.scala.project.Version
1917
import org.jetbrains.plugins.scala.project.template._
2018

2119
import java.io.File
@@ -32,7 +30,7 @@ abstract class DependencyManagerBase {
3230
private val ivyHome = sys.props.get("sbt.ivy.home").map(new File(_)).orElse(Option(new File(homePrefix, ".ivy2"))).get
3331

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

4644
protected def resolvers: Seq[Resolver] = defaultResolvers
47-
private final val defaultResolvers: Seq[Resolver] = Seq(
48-
MavenResolver(
49-
"central",
50-
"https://repo1.maven.org/maven2"
51-
)
52-
)
45+
46+
private final val defaultResolvers: Seq[Resolver] = Seq(Resolver.MavenCentral)
5347

5448
private def mkIvyXml(deps: Seq[DependencyDescription]): String = {
5549
s"""
@@ -279,7 +273,10 @@ object DependencyManagerBase {
279273
def configuration(conf: String): DependencyDescription = copy(conf = conf)
280274
def transitive(): DependencyDescription = copy(isTransitive = true)
281275
def exclude(patterns: String*): DependencyDescription = copy(excludes = patterns)
282-
override def toString: String = s"$org:$artId:$version"
276+
override def toString: String = {
277+
val kindHint = if (kind == Types.SRC) " (sources)" else ""
278+
s"$org:$artId:$version$kindHint"
279+
}
283280
}
284281
object DependencyDescription {
285282
def fromId(id: ModuleRevisionId): DependencyDescription =
@@ -310,6 +307,21 @@ object DependencyManagerBase {
310307
case class ResolvedDependency(info: DependencyDescription, file: File) extends Dependency
311308

312309
sealed trait Resolver
310+
object Resolver {
311+
val MavenCentral: MavenResolver = MavenResolver(
312+
"central",
313+
"https://repo1.maven.org/maven2"
314+
)
315+
val TypesafeReleases: IvyResolver = IvyResolver(
316+
"typesafe-releases",
317+
"https://repo.typesafe.com/typesafe/ivy-releases/[organisation]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]"
318+
)
319+
val TypesafeScalaPRValidationSnapshots: MavenResolver = MavenResolver(
320+
"scala-pr-validation-snapshots",
321+
"https://scala-ci.typesafe.com/artifactory/scala-pr-validation-snapshots"
322+
)
323+
}
324+
313325
case class MavenResolver(name: String, root: String) extends Resolver
314326

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

325-
326337
implicit class RichStr(private val org: String) extends AnyVal {
327338

328339
def %(artId: String): DependencyDescription = DependencyDescription(org, artId, "")

scala/scala-impl/src/org/jetbrains/plugins/scala/ScalaVersion.scala

+3
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ object LatestScalaVersions {
9797
//
9898
val Scala_3_RC = new ScalaVersion(ScalaLanguageLevel.Scala_3_4, "2-RC1")
9999

100+
/** Available only when using [[DependencyManagerBase.Resolver.TypesafeScalaPRValidationSnapshots]] */
101+
val Scala_2_13_RC = new ScalaVersion(ScalaLanguageLevel.Scala_2_13, "14-bin-ed3dfc9-SNAPSHOT")
102+
100103
val allScala2: Seq[ScalaVersion] = Seq(
101104
Scala_2_9,
102105
Scala_2_10,

scala/scala-impl/src/org/jetbrains/plugins/scala/editor/documentationProvider/ScalaDocumentationProvider.scala

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.jetbrains.plugins.scala.lang.psi.api.statements.params.ScParameter
1616
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef._
1717
import org.jetbrains.plugins.scala.lang.psi.api.{ScalaFile, ScalaPsiElement}
1818
import org.jetbrains.plugins.scala.lang.psi.impl.ScalaPsiManager
19+
import org.jetbrains.plugins.scala.lang.psi.impl.toplevel.synthetic.SyntheticNamedElement
1920
import org.jetbrains.plugins.scala.lang.psi.light.{PsiClassWrapper, ScFunctionWrapper}
2021
import org.jetbrains.plugins.scala.lang.scaladoc.psi.api.ScDocComment
2122

@@ -156,6 +157,8 @@ object ScalaDocumentationProvider {
156157
originalElement match {
157158
case null => null
158159
case ScFunctionWrapper(delegate) => delegate
160+
case synthetic: SyntheticNamedElement =>
161+
synthetic //extra .getNavigationalElement will be called later
159162
case _: ScTypeDefinition |
160163
_: ScTypeAlias |
161164
_: ScValue |

scala/scala-impl/src/org/jetbrains/plugins/scala/lang/psi/impl/toplevel/synthetic/ScSyntheticClass.scala

+70-21
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,25 @@ package org.jetbrains.plugins.scala.lang.psi.impl.toplevel.synthetic
22

33
import com.intellij.navigation.ItemPresentation
44
import com.intellij.openapi.components.Service
5-
import com.intellij.openapi.project.ProjectManagerListener
5+
import com.intellij.openapi.project.{Project, ProjectManagerListener}
6+
import com.intellij.openapi.roots.ProjectRootManager
67
import com.intellij.openapi.startup.StartupActivity
78
import com.intellij.psi._
89
import com.intellij.psi.impl.light.LightElement
910
import com.intellij.psi.search.GlobalSearchScope
1011
import com.intellij.util.IncorrectOperationException
1112
import com.intellij.util.containers.MultiMap
13+
import org.jetbrains.annotations.TestOnly
14+
import org.jetbrains.plugins.scala.caches.cachedInUserData
1215
import org.jetbrains.plugins.scala.extensions._
1316
import org.jetbrains.plugins.scala.icons.Icons
1417
import org.jetbrains.plugins.scala.lang.psi.adapters.PsiClassAdapter
1518
import org.jetbrains.plugins.scala.lang.psi.api.statements.params.ScTypeParam
16-
import org.jetbrains.plugins.scala.lang.psi.api.statements.{ScFun, ScTypeAlias}
17-
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScObject
19+
import org.jetbrains.plugins.scala.lang.psi.api.statements.{ScFun, ScFunction, ScTypeAlias}
20+
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.{ScObject, ScTemplateDefinition}
1821
import org.jetbrains.plugins.scala.lang.psi.api.{ScalaFile, ScalaPsiElement}
19-
import org.jetbrains.plugins.scala.lang.psi.impl.ScalaPsiElementFactory
2022
import org.jetbrains.plugins.scala.lang.psi.impl.toplevel.PsiClassFake
23+
import org.jetbrains.plugins.scala.lang.psi.impl.{ScalaPsiElementFactory, ScalaPsiManager}
2124
import org.jetbrains.plugins.scala.lang.psi.implicits.ImplicitProcessor
2225
import org.jetbrains.plugins.scala.lang.psi.types._
2326
import org.jetbrains.plugins.scala.lang.psi.types.api._
@@ -83,14 +86,16 @@ final class ScSyntheticTypeParameter(override val name: String, override val own
8386

8487
// we could try and implement all type system related stuff
8588
// with class types, but it is simpler to indicate types corresponding to synthetic classes explicitly
86-
sealed class ScSyntheticClass(
89+
final class ScSyntheticClass(
8790
val className: String,
8891
val stdType: StdType
8992
)(implicit projectContext: ProjectContext)
9093
extends SyntheticNamedElement(className)
9194
with PsiClassAdapter
9295
with PsiClassFake {
9396

97+
override def getQualifiedName: String = "scala." + className
98+
9499
override def getPresentation: ItemPresentation = {
95100
new ItemPresentation {
96101
val This: ScSyntheticClass = ScSyntheticClass.this
@@ -102,13 +107,38 @@ sealed class ScSyntheticClass(
102107
}
103108
}
104109

110+
override def getNavigationElement: PsiElement = cachedInUserData("ScSyntheticClass.getNavigationElement", this, ProjectRootManager.getInstance(getProject)) {
111+
val syntheticClassSourceMirror = for {
112+
scalaPackagePsiDirectory <- findScalaPackageSourcesPsiDirectory(this.stdType.projectContext.project)
113+
//class Any -> Any.scala
114+
psiFile <- Option(scalaPackagePsiDirectory.findFile(s"$className.scala")).map(_.asInstanceOf[ScalaFile])
115+
classDef <- psiFile.typeDefinitions.headOption //expecting single class definition in the file
116+
} yield classDef
117+
syntheticClassSourceMirror.getOrElse(super.getNavigationElement)
118+
}
119+
120+
//TODO: current implementation might not work in a project with multiple scala versions. It depends on SCL-22349.
121+
private def findScalaPackageSourcesPsiDirectory(project: Project): Option[PsiDirectory] = cachedInUserData("ScSyntheticClass.findScalaPackageSourcesPsiDirectory", project, ProjectRootManager.getInstance(project)) {
122+
//Get some representative class from Scala standard library
123+
val classFromStdLib = ScalaPsiManager.instance(this.stdType.projectContext).getCachedClass(GlobalSearchScope.allScope(this.stdType.projectContext.project), "scala.Array")
124+
classFromStdLib.map { clazz =>
125+
//.../scala-library-2.13.11-sources.jar!/scala/Array.scala
126+
val navigationFile = clazz.getContainingFile.getNavigationElement.asInstanceOf[ScalaFile]
127+
//.../scala-library-2.13.11-sources.jar!/scala
128+
navigationFile.getParent;
129+
}
130+
}
131+
105132
override def getNameIdentifier: PsiIdentifier = null
106133

107134
override def toString = "Synthetic class"
108135

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

111-
def addMethod(method: ScSyntheticFunction): Unit = syntheticMethods.putValue(method.name, method)
138+
def addMethod(method: ScSyntheticFunction): Unit = {
139+
syntheticMethods.putValue(method.name, method)
140+
method.setContainingSyntheticClass(this)
141+
}
112142

113143
override def processDeclarations(
114144
processor: com.intellij.psi.scope.PsiScopeProcessor,
@@ -152,6 +182,14 @@ sealed class ScSyntheticFunction(
152182
typeParameterNames: Seq[String]
153183
)(implicit projectContext: ProjectContext)
154184
extends SyntheticNamedElement(name) with ScFun {
185+
186+
private var containingSyntheticClass: Option[ScSyntheticClass] = None
187+
188+
def setContainingSyntheticClass(value: ScSyntheticClass): Unit = {
189+
assert(containingSyntheticClass.isEmpty, s"Containing synthetic class was already assigned to method $name")
190+
containingSyntheticClass = Some(value)
191+
}
192+
155193
def isStringPlusMethod: Boolean = {
156194
if (name != "+") return false
157195
retType.extractClass match {
@@ -186,6 +224,18 @@ sealed class ScSyntheticFunction(
186224
}
187225
null
188226
}
227+
228+
override def getNavigationElement: PsiElement = cachedInUserData("ScSyntheticFunction.getNavigationElement", this, ProjectRootManager.getInstance(retType.projectContext)) {
229+
val syntheticFunctionSourceMirror = containingSyntheticClass.flatMap(_.getNavigationElement match {
230+
case classInSources: ScTemplateDefinition =>
231+
//NOTE: we search for the function with the same name ignoring overloaded functions
232+
// in principle this is not entirely correct, but for the synthetic classes in Scala library
233+
// it should work fine because it's known that there are no overloaded methods in those classes
234+
classInSources.members.filterByType[ScFunction].find(_.name == name)
235+
case _ => None
236+
})
237+
syntheticFunctionSourceMirror.getOrElse(super.getNavigationElement)
238+
}
189239
}
190240

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

199-
import com.intellij.openapi.project.Project
200-
201249
@Service(Array(Service.Level.PROJECT))
202250
final class SyntheticClasses(project: Project) {
203251
implicit def ctx: ProjectContext = project
@@ -327,6 +375,7 @@ final class SyntheticClasses(project: Project) {
327375
}
328376

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

346395
registerContextFunctionClass("ContextFunction",
347-
s"""
396+
s"""
348397
|package scala
349398
|
350399
|trait ContextFunction$n[$typeParameters, +R] {
@@ -355,7 +404,7 @@ final class SyntheticClasses(project: Project) {
355404
}
356405

357406
registerObject("Boolean",
358-
"""
407+
"""
359408
package scala
360409
361410
object Boolean {
@@ -367,7 +416,7 @@ object Boolean {
367416
)
368417

369418
registerObject("Byte",
370-
"""
419+
"""
371420
package scala
372421
373422
object Byte {
@@ -383,7 +432,7 @@ object Byte {
383432
)
384433

385434
registerObject("Char",
386-
"""
435+
"""
387436
package scala
388437
389438
object Char {
@@ -399,7 +448,7 @@ object Char {
399448
)
400449

401450
registerObject("Double",
402-
"""
451+
"""
403452
package scala
404453
405454
object Double {
@@ -429,7 +478,7 @@ object Double {
429478
)
430479

431480
registerObject("Float",
432-
"""
481+
"""
433482
package scala
434483
435484
object Float {
@@ -459,7 +508,7 @@ object Float {
459508
)
460509

461510
registerObject("Int",
462-
"""
511+
"""
463512
package scala
464513
465514
object Int {
@@ -475,7 +524,7 @@ object Int {
475524
)
476525

477526
registerObject("Long",
478-
"""
527+
"""
479528
package scala
480529
481530
object Long {
@@ -491,7 +540,7 @@ object Long {
491540
)
492541

493542
registerObject("Short",
494-
"""
543+
"""
495544
package scala
496545
497546
object Short {
@@ -507,7 +556,7 @@ object Short {
507556
)
508557

509558
registerObject("Unit",
510-
"""
559+
"""
511560
package scala
512561
513562
object Unit
@@ -556,9 +605,7 @@ object Unit
556605
}
557606

558607
def registerClass(t: StdType, name: String, isScala3: Boolean = false): ScSyntheticClass = {
559-
val cls = new ScSyntheticClass(name, t) {
560-
override def getQualifiedName: String = "scala." + name
561-
}
608+
val cls = new ScSyntheticClass(name, t)
562609

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

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

576625
def sharedClassesOnly: Iterable[PsiClass] = sharedClasses.values
577626

scala/scala-impl/test/org/jetbrains/plugins/scala/base/ScalaLightCodeInsightFixtureTestCase.scala

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ abstract class ScalaLightCodeInsightFixtureTestCase
144144
protected final def openEditorAtOffset(startOffset: Int): Editor = scalaFixture.openEditorAtOffset(startOffset)
145145

146146
protected final def configureScalaFromFileText(@Language("Scala") fileText: String): PsiFile = scalaFixture.configureFromFileText(fileText)
147+
protected final def configureScala3FromFileText(@Language("Scala 3") fileText: String): PsiFile = scalaFixture.configureFromFileText(fileText)
147148
protected final def addScalaFileToProject(relativePath: String, @Language("Scala") fileText: String): PsiFile = myFixture.addFileToProject(relativePath, fileText)
148149
//end section: helper methods
149150

0 commit comments

Comments
 (0)