Skip to content

Commit 8536064

Browse files
authored
Backport "Add Stable Presentation Compiler" to LTS (#19061)
Backports #17528 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents 8af6da5 + 0147f07 commit 8536064

File tree

135 files changed

+29662
-51
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+29662
-51
lines changed

NOTICE.md

+8-4
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,19 @@ major authors were omitted by oversight.
8989
details.
9090

9191
* dotty.tools.dotc.coverage: Coverage instrumentation utilities have been
92-
adapted from the scoverage plugin for scala 2 [5], which is under the
92+
adapted from the scoverage plugin for scala 2 [4], which is under the
9393
Apache 2.0 license.
9494

95+
* dooty.tools.pc: Presentation compiler implementation adapted from
96+
scalameta/metals [5] mtags module, which is under the Apache 2.0 license.
97+
9598
* The Dotty codebase contains parts which are derived from
96-
the ScalaPB protobuf library [4], which is under the Apache 2.0 license.
99+
the ScalaPB protobuf library [6], which is under the Apache 2.0 license.
97100

98101

99102
[1] https://github.com/scala/scala
100103
[2] https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt
101104
[3] https://github.com/sbt/sbt/tree/0.13/compile/interface/src/main/scala/xsbt
102-
[4] https://github.com/lampepfl/dotty/pull/5783/files
103-
[5] https://github.com/scoverage/scalac-scoverage-plugin
105+
[4] https://github.com/scoverage/scalac-scoverage-plugin
106+
[5] https://github.com/scalameta/metals
107+
[6] https://github.com/lampepfl/dotty/pull/5783/files

build.sbt

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ val `scala3-bench-run` = Build.`scala3-bench-run`
2828
val dist = Build.dist
2929
val `community-build` = Build.`community-build`
3030
val `sbt-community-build` = Build.`sbt-community-build`
31+
val `scala3-presentation-compiler` = Build.`scala3-presentation-compiler`
32+
val `scala3-presentation-compiler-bootstrapped` = Build.`scala3-presentation-compiler-bootstrapped`
3133

3234
val sjsSandbox = Build.sjsSandbox
3335
val sjsJUnitTests = Build.sjsJUnitTests

compiler/src/dotty/tools/dotc/CompilationUnit.scala

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import core._
55
import Contexts._
66
import SymDenotations.ClassDenotation
77
import Symbols._
8+
import Comments.Comment
89
import util.{FreshNameCreator, SourceFile, NoSource}
910
import util.Spans.Span
1011
import ast.{tpd, untpd}
@@ -69,6 +70,9 @@ class CompilationUnit protected (val source: SourceFile) {
6970
/** Can this compilation unit be suspended */
7071
def isSuspendable: Boolean = true
7172

73+
/** List of all comments present in this compilation unit */
74+
var comments: List[Comment] = Nil
75+
7276
/** Suspends the compilation unit by thowing a SuspendException
7377
* and recording the suspended compilation unit
7478
*/

compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala

+2-5
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
145145
(fromSource ++ fromClassPath).distinct
146146
}
147147

148-
def run(uri: URI, sourceCode: String): List[Diagnostic] = run(uri, toSource(uri, sourceCode))
148+
def run(uri: URI, sourceCode: String): List[Diagnostic] = run(uri, SourceFile.virtual(uri, sourceCode))
149149

150150
def run(uri: URI, source: SourceFile): List[Diagnostic] = {
151151
import typer.ImportInfo._
@@ -297,9 +297,6 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
297297
cleanupTree(tree)
298298
}
299299

300-
private def toSource(uri: URI, sourceCode: String): SourceFile =
301-
SourceFile.virtual(Paths.get(uri).toString, sourceCode)
302-
303300
/**
304301
* Initialize this driver and compiler.
305302
*
@@ -323,7 +320,7 @@ object InteractiveDriver {
323320
else
324321
try
325322
// We don't use file.file here since it'll be null
326-
// for the VirtualFiles created by InteractiveDriver#toSource
323+
// for the VirtualFiles created by SourceFile#virtual
327324
// TODO: To avoid these round trip conversions, we could add an
328325
// AbstractFile#toUri method and implement it by returning a constant
329326
// passed as a parameter to a constructor of VirtualFile

compiler/src/dotty/tools/dotc/parsing/ParserPhase.scala

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class Parser extends Phase {
3030
val p = new Parsers.Parser(unit.source)
3131
// p.in.debugTokenStream = true
3232
val tree = p.parse()
33+
ctx.compilationUnit.comments = p.in.comments
3334
if (p.firstXmlPos.exists && !firstXmlPos.exists)
3435
firstXmlPos = p.firstXmlPos
3536
tree

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

+11-11
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,11 @@ object Scanners {
227227
*/
228228
private var docstringMap: SortedMap[Int, Comment] = SortedMap.empty
229229

230-
/* A Buffer for comment positions */
231-
private val commentPosBuf = new mutable.ListBuffer[Span]
230+
/* A Buffer for comments */
231+
private val commentBuf = new mutable.ListBuffer[Comment]
232232

233-
/** Return a list of all the comment positions */
234-
def commentSpans: List[Span] = commentPosBuf.toList
233+
/** Return a list of all the comments */
234+
def comments: List[Comment] = commentBuf.toList
235235

236236
private def addComment(comment: Comment): Unit = {
237237
val lookahead = lookaheadReader()
@@ -246,7 +246,7 @@ object Scanners {
246246
def getDocComment(pos: Int): Option[Comment] = docstringMap.get(pos)
247247

248248
/** A buffer for comments */
249-
private val commentBuf = CharBuffer(initialCharBufferSize)
249+
private val currentCommentBuf = CharBuffer(initialCharBufferSize)
250250

251251
def toToken(identifier: SimpleName): Token =
252252
def handleMigration(keyword: Token): Token =
@@ -523,7 +523,7 @@ object Scanners {
523523
*
524524
* The following tokens can start an indentation region:
525525
*
526-
* : = => <- if then else while do try catch
526+
* : = => <- if then else while do try catch
527527
* finally for yield match throw return with
528528
*
529529
* Inserting an INDENT starts a new indentation region with the indentation of the current
@@ -1019,7 +1019,7 @@ object Scanners {
10191019

10201020
private def skipComment(): Boolean = {
10211021
def appendToComment(ch: Char) =
1022-
if (keepComments) commentBuf.append(ch)
1022+
if (keepComments) currentCommentBuf.append(ch)
10231023
def nextChar() = {
10241024
appendToComment(ch)
10251025
Scanner.this.nextChar()
@@ -1047,9 +1047,9 @@ object Scanners {
10471047
def finishComment(): Boolean = {
10481048
if (keepComments) {
10491049
val pos = Span(start, charOffset - 1, start)
1050-
val comment = Comment(pos, commentBuf.toString)
1051-
commentBuf.clear()
1052-
commentPosBuf += pos
1050+
val comment = Comment(pos, currentCommentBuf.toString)
1051+
currentCommentBuf.clear()
1052+
commentBuf += comment
10531053

10541054
if (comment.isDocComment)
10551055
addComment(comment)
@@ -1065,7 +1065,7 @@ object Scanners {
10651065
else if (ch == '*') { nextChar(); skipComment(); finishComment() }
10661066
else {
10671067
// This was not a comment, remove the `/` from the buffer
1068-
commentBuf.clear()
1068+
currentCommentBuf.clear()
10691069
false
10701070
}
10711071
}

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

+11-11
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
179179
if (printWithoutPrefix.contains(tp.symbol))
180180
toText(tp.name)
181181
else
182-
toTextPrefix(tp.prefix) ~ selectionString(tp)
182+
toTextPrefixOf(tp) ~ selectionString(tp)
183183
case tp: TermParamRef =>
184184
ParamRefNameString(tp) ~ lambdaHash(tp.binder) ~ ".type"
185185
case tp: TypeParamRef =>
@@ -353,7 +353,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
353353
def toTextRef(tp: SingletonType): Text = controlled {
354354
tp match {
355355
case tp: TermRef =>
356-
toTextPrefix(tp.prefix) ~ selectionString(tp)
356+
toTextPrefixOf(tp) ~ selectionString(tp)
357357
case tp: ThisType =>
358358
nameString(tp.cls) + ".this"
359359
case SuperType(thistpe: SingletonType, _) =>
@@ -375,15 +375,6 @@ class PlainPrinter(_ctx: Context) extends Printer {
375375
}
376376
}
377377

378-
/** The string representation of this type used as a prefix, including separator */
379-
def toTextPrefix(tp: Type): Text = controlled {
380-
homogenize(tp) match {
381-
case NoPrefix => ""
382-
case tp: SingletonType => toTextRef(tp) ~ "."
383-
case tp => trimPrefix(toTextLocal(tp)) ~ "#"
384-
}
385-
}
386-
387378
def toTextCaptureRef(tp: Type): Text =
388379
homogenize(tp) match
389380
case tp: TermRef if tp.symbol == defn.captureRoot => Str("cap")
@@ -393,6 +384,15 @@ class PlainPrinter(_ctx: Context) extends Printer {
393384
protected def isOmittablePrefix(sym: Symbol): Boolean =
394385
defn.unqualifiedOwnerTypes.exists(_.symbol == sym) || isEmptyPrefix(sym)
395386

387+
/** The string representation of type prefix, including separator */
388+
def toTextPrefixOf(tp: NamedType): Text = controlled {
389+
homogenize(tp.prefix) match {
390+
case NoPrefix => ""
391+
case tp: SingletonType => toTextRef(tp) ~ "."
392+
case tp => trimPrefix(toTextLocal(tp)) ~ "#"
393+
}
394+
}
395+
396396
protected def isEmptyPrefix(sym: Symbol): Boolean =
397397
sym.isEffectiveRoot || sym.isAnonymousClass || sym.name.isReplWrapperName
398398

compiler/src/dotty/tools/dotc/printing/Printer.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package printing
44

55
import core._
66
import Texts._, ast.Trees._
7-
import Types.{Type, SingletonType, LambdaParam},
7+
import Types.{Type, SingletonType, LambdaParam, NamedType},
88
Symbols.Symbol, Scopes.Scope, Constants.Constant,
99
Names.Name, Denotations._, Annotations.Annotation, Contexts.Context
1010
import typer.Implicits.*
@@ -101,7 +101,7 @@ abstract class Printer {
101101
def toTextRef(tp: SingletonType): Text
102102

103103
/** Textual representation of a prefix of some reference, ending in `.` or `#` */
104-
def toTextPrefix(tp: Type): Text
104+
def toTextPrefixOf(tp: NamedType): Text
105105

106106
/** Textual representation of a reference in a capture set */
107107
def toTextCaptureRef(tp: Type): Text

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

+10-10
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,22 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
117117
}
118118
}
119119

120-
override def toTextPrefix(tp: Type): Text = controlled {
120+
override def toTextPrefixOf(tp: NamedType): Text = controlled {
121121
def isOmittable(sym: Symbol) =
122122
if printDebug then false
123123
else if homogenizedView then isEmptyPrefix(sym) // drop <root> and anonymous classes, but not scala, Predef.
124124
else if sym.isPackageObject then isOmittablePrefix(sym.owner)
125125
else isOmittablePrefix(sym)
126-
tp match {
127-
case tp: ThisType if isOmittable(tp.cls) =>
126+
127+
tp.prefix match {
128+
case thisType: ThisType if isOmittable(thisType.cls) =>
128129
""
129-
case tp @ TermRef(pre, _) =>
130-
val sym = tp.symbol
131-
if sym.isPackageObject && !homogenizedView && !printDebug then toTextPrefix(pre)
130+
case termRef @ TermRef(pre, _) =>
131+
val sym = termRef.symbol
132+
if sym.isPackageObject && !homogenizedView && !printDebug then toTextPrefixOf(termRef)
132133
else if (isOmittable(sym)) ""
133-
else super.toTextPrefix(tp)
134-
case _ => super.toTextPrefix(tp)
134+
else super.toTextPrefixOf(tp)
135+
case _ => super.toTextPrefixOf(tp)
135136
}
136137
}
137138

@@ -427,8 +428,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
427428
case id @ Ident(name) =>
428429
val txt = tree.typeOpt match {
429430
case tp: NamedType if name != nme.WILDCARD =>
430-
val pre = if (tp.symbol.is(JavaStatic)) tp.prefix.widen else tp.prefix
431-
toTextPrefix(pre) ~ withPos(selectionString(tp), tree.sourcePos)
431+
toTextPrefixOf(tp) ~ withPos(selectionString(tp), tree.sourcePos)
432432
case _ =>
433433
toText(name)
434434
}

compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ object SyntaxHighlighting {
8383
}
8484
}
8585

86-
for (span <- scanner.commentSpans)
87-
highlightPosition(span, CommentColor)
86+
for (comment <- scanner.comments)
87+
highlightPosition(comment.span, CommentColor)
8888

8989
object TreeHighlighter extends untpd.UntypedTreeTraverser {
9090
import untpd._

compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ trait ImportSuggestions:
330330
def importString(ref: TermRef): String =
331331
val imported =
332332
if ref.symbol.is(ExtensionMethod) then
333-
s"${ctx.printer.toTextPrefix(ref.prefix).show}${ref.symbol.name}"
333+
s"${ctx.printer.toTextPrefixOf(ref).show}${ref.symbol.name}"
334334
else
335335
ctx.printer.toTextRef(ref).show
336336
s" import $imported"

compiler/src/dotty/tools/dotc/util/DiffUtil.scala

+67-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ object DiffUtil {
7070
* differences are highlighted.
7171
*/
7272
def mkColoredLineDiff(expected: Seq[String], actual: Seq[String]): String = {
73-
val expectedSize = EOF.length max expected.maxBy(_.length).length
73+
val longestExpected = expected.map(_.length).maxOption.getOrElse(0)
74+
val longestActual = actual.map(_.length).maxOption.getOrElse(0)
75+
val expectedSize = EOF.length max longestActual max longestExpected
7476
actual.padTo(expected.length, "").zip(expected.padTo(actual.length, "")).map { case (act, exp) =>
7577
mkColoredLineDiff(exp, act, expectedSize)
7678
}.mkString(System.lineSeparator)
@@ -101,11 +103,75 @@ object DiffUtil {
101103
case Deleted(str) => deleted(str)
102104
}.mkString
103105

106+
(expectedDiff, actualDiff)
104107
val pad = " " * 0.max(expectedSize - expected.length)
105108

106109
expectedDiff + pad + " | " + actualDiff
107110
}
108111

112+
private def ensureLineSeparator(str: String): String =
113+
if str.endsWith(System.lineSeparator) then
114+
str
115+
else
116+
str + System.lineSeparator
117+
118+
/**
119+
* Returns a colored diffs by comparison of lines instead of tokens.
120+
* It will automatically group subsequential pairs of `Insert` and `Delete`
121+
* in order to improve the readability
122+
*
123+
* @param expected The expected lines
124+
* @param actual The actual lines
125+
* @return A string with colored diffs between `expected` and `actual` grouped whenever possible
126+
*/
127+
def mkColoredHorizontalLineDiff(expected: String, actual: String): String = {
128+
val indent = 2
129+
val tab = " " * indent
130+
val insertIndent = "+" ++ (" " * (indent - 1))
131+
val deleteIndent = "-" ++ (" " * (indent - 1))
132+
133+
if actual.isEmpty then
134+
(expected.linesIterator.map(line => added(insertIndent + line)).toList :+ deleted("--- EMPTY OUTPUT ---"))
135+
.map(ensureLineSeparator).mkString
136+
else if expected.isEmpty then
137+
(added("--- NO VALUE EXPECTED ---") +: actual.linesIterator.map(line => deleted(deleteIndent + line)).toList)
138+
.map(ensureLineSeparator).mkString
139+
else
140+
lazy val diff = {
141+
val expectedTokens = expected.linesWithSeparators.toArray
142+
val actualTokens = actual.linesWithSeparators.toArray
143+
hirschberg(actualTokens, expectedTokens)
144+
}.toList
145+
146+
val transformedDiff = diff.flatMap {
147+
case Modified(original, str) => Seq(
148+
Inserted(ensureLineSeparator(original)), Deleted(ensureLineSeparator(str))
149+
)
150+
case other => Seq(other)
151+
}
152+
153+
val zipped = transformedDiff zip transformedDiff.drop(1)
154+
155+
val (acc, inserts, deletions) = zipped.foldLeft((Seq[Patch](), Seq[Inserted](), Seq[Deleted]())): (acc, patches) =>
156+
val (currAcc, inserts, deletions) = acc
157+
patches match
158+
case (currentPatch: Inserted, nextPatch: Deleted) =>
159+
(currAcc, inserts :+ currentPatch, deletions)
160+
case (currentPatch: Deleted, nextPatch: Inserted) =>
161+
(currAcc, inserts, deletions :+ currentPatch)
162+
case (currentPatch, nextPatch) =>
163+
(currAcc :++ inserts :++ deletions :+ currentPatch, Seq.empty, Seq.empty)
164+
165+
val stackedDiff = acc :++ inserts :++ deletions :+ diff.last
166+
167+
stackedDiff.collect {
168+
case Unmodified(str) => tab + str
169+
case Inserted(str) => added(insertIndent + str)
170+
case Deleted(str) => deleted(deleteIndent + str)
171+
}.map(ensureLineSeparator).mkString
172+
173+
}
174+
109175
def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = {
110176
val tokens = splitTokens(code, Nil).toArray
111177
val lastTokens = splitTokens(lastCode, Nil).toArray

compiler/src/dotty/tools/dotc/util/SourceFile.scala

+9-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import scala.collection.mutable.ArrayBuffer
1616
import scala.util.chaining.given
1717

1818
import java.io.File.separator
19+
import java.net.URI
1920
import java.nio.charset.StandardCharsets
20-
import java.nio.file.{FileSystemException, NoSuchFileException}
21+
import java.nio.file.{FileSystemException, NoSuchFileException, Paths}
2122
import java.util.Optional
2223
import java.util.concurrent.atomic.AtomicInteger
2324
import java.util.regex.Pattern
@@ -222,6 +223,13 @@ object SourceFile {
222223
SourceFile(new VirtualFile(name.replace(separator, "/"), content.getBytes(StandardCharsets.UTF_8)), content.toCharArray)
223224
.tap(_._maybeInComplete = maybeIncomplete)
224225

226+
/** A helper method to create a virtual source file for given URI.
227+
* It relies on SourceFile#virtual implementation to create the virtual file.
228+
*/
229+
def virtual(uri: URI, content: String): SourceFile =
230+
val path = Paths.get(uri).toString
231+
SourceFile.virtual(path, content)
232+
225233
/** Returns the relative path of `source` within the `reference` path
226234
*
227235
* It returns the absolute path of `source` if it is not contained in `reference`.

0 commit comments

Comments
 (0)