Skip to content

Commit 3e31b3e

Browse files
committed
[compiler, cbh] Put the in-memory document compiler behind a registry key
#SCL-22338 #SCL-22736 fixed - The physical temporary file document compiler (previous implementation) is used as the default. - This is done due to issues with macro expansion and in-memory files in MUnit and possibly other libraries. - scala/scala3#20591
1 parent d1b94b0 commit 3e31b3e

File tree

2 files changed

+122
-41
lines changed

2 files changed

+122
-41
lines changed

scala/compiler-integration/resources/META-INF/compiler-integration.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
<registryKey key="scala.erase.compiler.process.jdk.once" defaultValue="true" restartRequired="false"
3636
description="Erase compiler.process.jdk value before compilation. A dirty hack needed because of removal of SetSameJdkToBuildProcessAsInCompileServer #SCL-17676"/>
3737

38+
<registryKey key="scala.compiler.highlighting.document.use.in.memory.file" defaultValue="false" restartRequired="false"
39+
description="When enabled, uses an in-memory file for document compilation"/>
40+
3841
<buildProcess.parametersProvider implementation="org.jetbrains.plugins.scala.compiler.references.CompilerIndicesBuildProcessParametersProvider"/>
3942

4043
<projectConfigurable id="Bytecode Indices"

scala/compiler-integration/src/org/jetbrains/plugins/scala/compiler/highlighting/DocumentCompiler.scala

Lines changed: 119 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ import com.intellij.openapi.editor.Document
77
import com.intellij.openapi.module.Module
88
import com.intellij.openapi.project.Project
99
import com.intellij.openapi.util.io.FileUtil
10+
import com.intellij.openapi.util.registry.Registry
1011
import com.intellij.openapi.vfs.VirtualFile
11-
import org.jetbrains.jps.incremental.scala.Client
1212
import org.jetbrains.jps.incremental.scala.remote.{CommandIds, SourceScope}
13+
import org.jetbrains.jps.incremental.scala.{Client, DelegateClient}
1314
import org.jetbrains.plugins.scala.compiler.data.{CompilerData, CompilerJarsFactory, DocumentCompilationArguments, DocumentCompilationData, IncrementalityType}
1415
import org.jetbrains.plugins.scala.compiler.{RemoteServerConnectorBase, RemoteServerRunner}
1516
import org.jetbrains.plugins.scala.editor.DocumentExt
16-
import org.jetbrains.plugins.scala.project.{ModuleExt, ScalaLanguageLevel}
17+
import org.jetbrains.plugins.scala.project.{ModuleExt, ScalaLanguageLevel, VirtualFileExt}
1718

1819
import java.io.File
19-
import java.nio.file.Path
20+
import java.nio.file.{Files, Path}
2021

2122
@Service(Array(Service.Level.PROJECT))
2223
private final class DocumentCompiler(project: Project) {
@@ -42,21 +43,64 @@ private final class DocumentCompiler(project: Project) {
4243
virtualFile: VirtualFile,
4344
client: Client
4445
): Unit = {
45-
compileDocumentContent(
46-
sourcePath = virtualFile.toNioPath,
47-
sourceContent = document.textWithConvertedSeparators(virtualFile),
48-
module = module,
49-
sourceScope = sourceScope,
50-
client = client
51-
)
46+
val useInMemoryFile = Registry.is("scala.compiler.highlighting.document.use.in.memory.file")
47+
val originalSourceFile = virtualFile.toFile
48+
val sourceContent = document.textWithConvertedSeparators(virtualFile)
49+
50+
if (useInMemoryFile) {
51+
compileInMemoryFile(
52+
sourcePath = originalSourceFile.toPath,
53+
sourceContent = sourceContent,
54+
module = module,
55+
sourceScope = sourceScope,
56+
client = client
57+
)
58+
} else {
59+
compilePhysicalFile(
60+
originalSourceFile = originalSourceFile,
61+
content = sourceContent,
62+
module = module,
63+
sourceScope = sourceScope,
64+
client = client
65+
)
66+
}
5267
}
5368

54-
private def compileDocumentContent(sourcePath: Path,
55-
sourceContent: String,
56-
module: Module,
57-
sourceScope: SourceScope,
58-
client: Client): Unit = {
59-
val connector = new RemoteServerConnector(sourcePath, sourceContent, module, sourceScope)
69+
private def compilePhysicalFile(originalSourceFile: File,
70+
content: String,
71+
module: Module,
72+
sourceScope: SourceScope,
73+
client: Client): Unit = {
74+
val tempSourceFile = workingDirectory.resolve("tempSourceFile").toFile
75+
Files.writeString(tempSourceFile.toPath, content)
76+
val connector =
77+
try new PhysicalFileConnector(tempSourceFile, module, sourceScope)
78+
catch {
79+
case t: Throwable =>
80+
// Remove the temporary source file if creating the connector failed.
81+
FileUtil.delete(tempSourceFile)
82+
throw t
83+
}
84+
85+
try connector.compile(originalSourceFile, client)
86+
finally {
87+
if (connector.requiresCleanup) {
88+
val files = workingDirectory.toFile.listFiles()
89+
if (files ne null) {
90+
files.foreach(FileUtil.delete)
91+
}
92+
} else {
93+
FileUtil.delete(tempSourceFile)
94+
}
95+
}
96+
}
97+
98+
private def compileInMemoryFile(sourcePath: Path,
99+
sourceContent: String,
100+
module: Module,
101+
sourceScope: SourceScope,
102+
client: Client): Unit = {
103+
val connector = new InMemoryFileConnector(sourcePath, sourceContent, module, sourceScope)
60104
try connector.compile(client)
61105
finally {
62106
if (connector.requiresCleanup) {
@@ -68,8 +112,64 @@ private final class DocumentCompiler(project: Project) {
68112
}
69113
}
70114

71-
private class RemoteServerConnector(sourcePath: Path, sourceContent: String, module: Module, sourceScope: SourceScope)
72-
extends RemoteServerConnectorBase(module, None, workingDirectory.toFile) {
115+
private final class PhysicalFileConnector(tempSourceFile: File, module: Module, sourceScope: SourceScope)
116+
extends AbstractRemoteServerConnector(Some(Seq(tempSourceFile)), module, sourceScope) {
117+
118+
def compile(originalSourceFile: File, client: Client): Unit = {
119+
val fixedClient = new DelegateClient(client) {
120+
override def message(msg: Client.ClientMsg): Unit = {
121+
/**
122+
* NOTE: some compiler errors can be with empty `source`<br>
123+
* Example: `bad option '-g:vars' was ignored`<br>
124+
* We do not want to loose such message.
125+
* We rely that they will be reported in the beginning of the file<br>
126+
* see [[org.jetbrains.plugins.scala.compiler.highlighting.ExternalHighlightersService.toHighlightInfo]]
127+
* (we assume that `from` and `to` are also empty for such files)
128+
*/
129+
val fixedSource = Some(originalSourceFile) //msg.source.map(_ => originalSourceFile)
130+
val fixedMsg = msg.copy(source = fixedSource)
131+
client.message(fixedMsg)
132+
}
133+
134+
override def compilationEnd(sources: Set[File]): Unit = {
135+
val fixedSources = Set(originalSourceFile)
136+
client.compilationEnd(fixedSources)
137+
}
138+
}
139+
new RemoteServerRunner()
140+
.buildProcess(CommandIds.Compile, arguments.asStrings, fixedClient)
141+
.runSync()
142+
}
143+
}
144+
145+
private final class InMemoryFileConnector(sourcePath: Path, sourceContent: String, module: Module, sourceScope: SourceScope)
146+
extends AbstractRemoteServerConnector(None, module, sourceScope) {
147+
148+
def compile(client: Client): Unit = {
149+
val arguments = DocumentCompilationArguments(
150+
sbtData = sbtData,
151+
compilerData = CompilerData(
152+
compilerJars = CompilerJarsFactory.fromFiles(compilerClasspath, module.customScalaCompilerBridgeJar).toOption,
153+
javaHome = Some(findJdk),
154+
incrementalType = IncrementalityType.IDEA
155+
),
156+
compilationData = DocumentCompilationData(
157+
sourcePath = sourcePath,
158+
sourceContent = sourceContent,
159+
output = workingDirectory,
160+
classpath = runtimeClasspath.map(_.toPath),
161+
scalacOptions = scalaParameters
162+
)
163+
)
164+
165+
new RemoteServerRunner()
166+
.buildProcess(CommandIds.CompileDocument, DocumentCompilationArguments.serialize(arguments), client)
167+
.runSync()
168+
}
169+
}
170+
171+
private abstract class AbstractRemoteServerConnector(filesToCompile: Option[Seq[File]], module: Module, sourceScope: SourceScope)
172+
extends RemoteServerConnectorBase(module, filesToCompile, workingDirectory.toFile) {
73173

74174
var requiresCleanup: Boolean = false
75175

@@ -112,32 +212,10 @@ private final class DocumentCompiler(project: Project) {
112212
.map(Path.of(_).toFile)
113213
(fromSuper ++ outputDir).distinct
114214
}
115-
116-
def compile(client: Client): Unit = {
117-
val arguments = DocumentCompilationArguments(
118-
sbtData = sbtData,
119-
compilerData = CompilerData(
120-
compilerJars = CompilerJarsFactory.fromFiles(compilerClasspath, module.customScalaCompilerBridgeJar).toOption,
121-
javaHome = Some(findJdk),
122-
incrementalType = IncrementalityType.IDEA
123-
),
124-
compilationData = DocumentCompilationData(
125-
sourcePath = sourcePath,
126-
sourceContent = sourceContent,
127-
output = workingDirectory,
128-
classpath = runtimeClasspath.map(_.toPath),
129-
scalacOptions = scalaParameters
130-
)
131-
)
132-
133-
new RemoteServerRunner()
134-
.buildProcess(CommandIds.CompileDocument, DocumentCompilationArguments.serialize(arguments), client)
135-
.runSync()
136-
}
137215
}
138216
}
139217

140218
private object DocumentCompiler {
141219
def get(project: Project): DocumentCompiler =
142220
project.getService(classOf[DocumentCompiler])
143-
}
221+
}

0 commit comments

Comments
 (0)