Skip to content

Backport "emit generatedNonLocalClass in backend when callback is not enabled" to 3.5.0 #21209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/backend/jvm/CodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,15 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)(
if (ctx.compilerCallback != null)
ctx.compilerCallback.onClassGenerated(sourceFile, convertAbstractFile(clsFile), className)

if isLocal then
ctx.withIncCallback(_.generatedLocalClass(sourceFile, clsFile.jpath))
ctx.withIncCallback: cb =>
if isLocal then
cb.generatedLocalClass(sourceFile, clsFile.jpath)
else if !cb.enabled() then
// callback is not enabled, so nonLocalClasses were not reported in ExtractAPI
val fullClassName = atPhase(sbtExtractDependenciesPhase) {
ExtractDependencies.classNameAsString(claszSymbol)
}
cb.generatedNonLocalClass(sourceFile, clsFile.jpath, className, fullClassName)
}
}

Expand Down
5 changes: 2 additions & 3 deletions sbt-bridge/test/xsbt/ExtractUsedNamesSpecification.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package xsbt

import xsbti.UseScope
import ScalaCompilerForUnitTesting.Callbacks

import org.junit.{ Test, Ignore }
import org.junit.Assert._
Expand Down Expand Up @@ -227,9 +226,9 @@ class ExtractUsedNamesSpecification {

def findPatMatUsages(in: String): Set[String] = {
val compilerForTesting = new ScalaCompilerForUnitTesting
val (_, Callbacks(callback, _)) =
val output =
compilerForTesting.compileSrcs(List(List(sealedClass, in)))
val clientNames = callback.usedNamesAndScopes.view.filterKeys(!_.startsWith("base."))
val clientNames = output.analysis.usedNamesAndScopes.view.filterKeys(!_.startsWith("base."))

val names: Set[String] = clientNames.flatMap {
case (_, usages) =>
Expand Down
41 changes: 41 additions & 0 deletions sbt-bridge/test/xsbt/ProductsSpecification.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package xsbt

import org.junit.Assert.*
import org.junit.Ignore
import org.junit.Test

import java.io.File
import java.nio.file.Path
import java.nio.file.Paths

class ProductsSpecification {

@Test
def extractNonLocalClassesNoInc = {
val src =
"""package example
|
|class A {
| class B
| def foo =
| class C
|}""".stripMargin
val output = compiler.compileSrcsNoInc(src)
val srcFile = output.srcFiles.head
val (srcNames, binaryNames) = output.analysis.classNames(srcFile).unzip // non local class names

assertFalse(output.analysis.enabled()) // inc phases are disabled
assertTrue(output.analysis.apis.isEmpty) // extract-api did not run
assertTrue(output.analysis.usedNamesAndScopes.isEmpty) // extract-dependencies did not run

// note that local class C is not included, classNames only records non local classes
val expectedBinary = Set("example.A", "example.A$B")
assertEquals(expectedBinary, binaryNames.toSet)

// note that local class C is not included, classNames only records non local classes
val expectedSrc = Set("example.A", "example.A.B")
assertEquals(expectedSrc, srcNames.toSet)
}

private def compiler = new ScalaCompilerForUnitTesting
}
95 changes: 56 additions & 39 deletions sbt-bridge/test/xsbt/ScalaCompilerForUnitTesting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package xsbt

import xsbti.compile.{CompileProgress, SingleOutput}
import java.io.File
import java.nio.file.Path
import xsbti._
import sbt.io.IO
import xsbti.api.{ ClassLike, Def, DependencyContext }
Expand All @@ -15,6 +16,8 @@ import dotty.tools.xsbt.CompilerBridge
import TestCallback.ExtractedClassDependencies
import ScalaCompilerForUnitTesting.Callbacks

case class CompileOutput(srcFiles: Seq[VirtualFileRef], classesOutput: Path, analysis: TestCallback, progress: TestCompileProgress)

object ScalaCompilerForUnitTesting:
case class Callbacks(analysis: TestCallback, progress: TestCompileProgress)

Expand All @@ -25,38 +28,33 @@ object ScalaCompilerForUnitTesting:
class ScalaCompilerForUnitTesting {

def extractEnteredPhases(srcs: String*): Seq[List[String]] = {
val (tempSrcFiles, Callbacks(_, testProgress)) = compileSrcs(srcs*)
val run = testProgress.runs.head
tempSrcFiles.map(src => run.unitPhases(src.id))
val output = compileSrcs(srcs*)
val run = output.progress.runs.head
output.srcFiles.map(src => run.unitPhases(src.id))
}

def extractTotal(srcs: String*)(extraSourcePath: String*): Int = {
val (tempSrcFiles, Callbacks(_, testProgress)) = compileSrcs(List(srcs.toList), extraSourcePath.toList)
val run = testProgress.runs.head
run.total
}
def extractTotal(srcs: String*)(extraSourcePath: String*): Int =
compileSrcs(List(srcs.toList), extraSourcePath.toList).progress.runs.head.total

def extractProgressPhases(srcs: String*): List[String] = {
val (_, Callbacks(_, testProgress)) = compileSrcs(srcs*)
testProgress.runs.head.phases
}
def extractProgressPhases(srcs: String*): List[String] =
compileSrcs(srcs*).progress.runs.head.phases

/**
* Compiles given source code using Scala compiler and returns API representation
* extracted by ExtractAPI class.
*/
def extractApiFromSrc(src: String): Seq[ClassLike] = {
val (Seq(tempSrcFile), Callbacks(analysisCallback, _)) = compileSrcs(src)
analysisCallback.apis(tempSrcFile)
val output = compileSrcs(src)
output.analysis.apis(output.srcFiles.head)
}

/**
* Compiles given source code using Scala compiler and returns API representation
* extracted by ExtractAPI class.
*/
def extractApisFromSrcs(srcs: List[String]*): Seq[Seq[ClassLike]] = {
val (tempSrcFiles, Callbacks(analysisCallback, _)) = compileSrcs(srcs.toList)
tempSrcFiles.map(analysisCallback.apis)
val output = compileSrcs(srcs.toList)
output.srcFiles.map(output.analysis.apis)
}

/**
Expand All @@ -73,15 +71,16 @@ class ScalaCompilerForUnitTesting {
assertDefaultScope: Boolean = true
): Map[String, Set[String]] = {
// we drop temp src file corresponding to the definition src file
val (Seq(_, tempSrcFile), Callbacks(analysisCallback, _)) = compileSrcs(definitionSrc, actualSrc)
val output = compileSrcs(definitionSrc, actualSrc)
val analysis = output.analysis

if (assertDefaultScope) for {
(className, used) <- analysisCallback.usedNamesAndScopes
analysisCallback.TestUsedName(name, scopes) <- used
(className, used) <- analysis.usedNamesAndScopes
analysis.TestUsedName(name, scopes) <- used
} assert(scopes.size() == 1 && scopes.contains(UseScope.Default), s"$className uses $name in $scopes")

val classesInActualSrc = analysisCallback.classNames(tempSrcFile).map(_._1)
classesInActualSrc.map(className => className -> analysisCallback.usedNames(className)).toMap
val classesInActualSrc = analysis.classNames(output.srcFiles.head).map(_._1)
classesInActualSrc.map(className => className -> analysis.usedNames(className)).toMap
}

/**
Expand All @@ -91,11 +90,11 @@ class ScalaCompilerForUnitTesting {
* Only the names used in the last src file are returned.
*/
def extractUsedNamesFromSrc(sources: String*): Map[String, Set[String]] = {
val (srcFiles, Callbacks(analysisCallback, _)) = compileSrcs(sources*)
srcFiles
val output = compileSrcs(sources*)
output.srcFiles
.map { srcFile =>
val classesInSrc = analysisCallback.classNames(srcFile).map(_._1)
classesInSrc.map(className => className -> analysisCallback.usedNames(className)).toMap
val classesInSrc = output.analysis.classNames(srcFile).map(_._1)
classesInSrc.map(className => className -> output.analysis.usedNames(className)).toMap
}
.reduce(_ ++ _)
}
Expand All @@ -113,15 +112,15 @@ class ScalaCompilerForUnitTesting {
* file system-independent way of testing dependencies between source code "files".
*/
def extractDependenciesFromSrcs(srcs: List[List[String]]): ExtractedClassDependencies = {
val (_, Callbacks(testCallback, _)) = compileSrcs(srcs)
val analysis = compileSrcs(srcs).analysis

val memberRefDeps = testCallback.classDependencies collect {
val memberRefDeps = analysis.classDependencies collect {
case (target, src, DependencyByMemberRef) => (src, target)
}
val inheritanceDeps = testCallback.classDependencies collect {
val inheritanceDeps = analysis.classDependencies collect {
case (target, src, DependencyByInheritance) => (src, target)
}
val localInheritanceDeps = testCallback.classDependencies collect {
val localInheritanceDeps = analysis.classDependencies collect {
case (target, src, LocalDependencyByInheritance) => (src, target)
}
ExtractedClassDependencies.fromPairs(memberRefDeps, inheritanceDeps, localInheritanceDeps)
Expand All @@ -142,12 +141,24 @@ class ScalaCompilerForUnitTesting {
* The sequence of temporary files corresponding to passed snippets and analysis
* callback is returned as a result.
*/
def compileSrcs(groupedSrcs: List[List[String]], sourcePath: List[String] = Nil): (Seq[VirtualFile], Callbacks) = {
def compileSrcs(groupedSrcs: List[List[String]], sourcePath: List[String] = Nil, compileToJar: Boolean = false, incEnabled: Boolean = true): CompileOutput = {
val temp = IO.createTemporaryDirectory
val analysisCallback = new TestCallback
val (forceSbtArgs, analysisCallback) =
if (incEnabled)
(Seq("-Yforce-sbt-phases"), new TestCallback)
else
(Seq.empty, new TestCallbackNoInc)
val testProgress = new TestCompileProgress
val classesDir = new File(temp, "classes")
classesDir.mkdir()
val classesOutput =
if (compileToJar) {
val jar = new File(temp, "classes.jar")
jar.createNewFile()
jar
} else {
val dir = new File(temp, "classes")
dir.mkdir()
dir
}

val bridge = new CompilerBridge

Expand All @@ -164,16 +175,16 @@ class ScalaCompilerForUnitTesting {
}

val virtualSrcFiles = srcFiles.toArray
val classesDirPath = classesDir.getAbsolutePath.toString
val classesOutputPath = classesOutput.getAbsolutePath()
val output = new SingleOutput:
def getOutputDirectory() = classesDir
def getOutputDirectory() = classesOutput

val maybeSourcePath = if extraFiles.isEmpty then Nil else List("-sourcepath", temp.getAbsolutePath.toString)

bridge.run(
virtualSrcFiles,
new TestDependencyChanges,
Array("-Yforce-sbt-phases", "-classpath", classesDirPath, "-usejavacp", "-d", classesDirPath) ++ maybeSourcePath,
(forceSbtArgs ++: Array("-classpath", classesOutputPath, "-usejavacp", "-d", classesOutputPath)) ++ maybeSourcePath,
output,
analysisCallback,
new TestReporter,
Expand All @@ -185,17 +196,23 @@ class ScalaCompilerForUnitTesting {

srcFiles
}
(files.flatten.toSeq, Callbacks(analysisCallback, testProgress))
CompileOutput(files.flatten.toSeq, classesOutput.toPath, analysisCallback, testProgress)
}

def compileSrcs(srcs: String*): (Seq[VirtualFile], Callbacks) = {
def compileSrcs(srcs: String*): CompileOutput = {
compileSrcs(List(srcs.toList))
}

def compileSrcsNoInc(srcs: String*): CompileOutput = {
compileSrcs(List(srcs.toList), incEnabled = false)
}

def compileSrcsToJar(srcs: String*): CompileOutput =
compileSrcs(List(srcs.toList), compileToJar = true)

private def prepareSrcFile(baseDir: File, fileName: String, src: String): VirtualFile = {
val srcFile = new File(baseDir, fileName)
IO.write(srcFile, src)
new TestVirtualFile(srcFile.toPath)
}
}

4 changes: 4 additions & 0 deletions sbt-bridge/test/xsbti/TestCallback.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import DependencyContext._
import java.{util => ju}
import ju.Optional

class TestCallbackNoInc extends TestCallback {
override def enabled(): Boolean = false
}

class TestCallback extends AnalysisCallback2 {
case class TestUsedName(name: String, scopes: ju.EnumSet[UseScope])

Expand Down
Loading