diff --git a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 41ab7412d748..6c0ff2ffffbe 100644 --- a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -27,7 +27,7 @@ object SyntaxHighlighting { val CommentColor: String = Console.BLUE val KeywordColor: String = Console.YELLOW val ValDefColor: String = Console.CYAN - val LiteralColor: String = Console.RED + val LiteralColor: String = Console.GREEN val StringColor: String = Console.GREEN val TypeColor: String = Console.MAGENTA val AnnotationColor: String = Console.MAGENTA @@ -80,6 +80,9 @@ object SyntaxHighlighting { case IDENTIFIER if name == nme.??? => highlightRange(start, end, Console.RED_B) + case IDENTIFIER if name.head.isUpper && name.exists(!_.isUpper) => + highlightRange(start, end, KeywordColor) + case _ => } } diff --git a/compiler/src/dotty/tools/repl/Main.scala b/compiler/src/dotty/tools/repl/Main.scala index 7eb906edc586..f4d6b505d430 100644 --- a/compiler/src/dotty/tools/repl/Main.scala +++ b/compiler/src/dotty/tools/repl/Main.scala @@ -4,5 +4,5 @@ package dotty.tools.repl // To test, run bin/scala object Main { def main(args: Array[String]): Unit = - new ReplDriver(args).tryRunning + new ReplDriver(args, extraPredef = ReplDriver.pprintImport).tryRunning } diff --git a/compiler/src/dotty/tools/repl/Rendering.scala b/compiler/src/dotty/tools/repl/Rendering.scala index c127cc959e25..238353867516 100644 --- a/compiler/src/dotty/tools/repl/Rendering.scala +++ b/compiler/src/dotty/tools/repl/Rendering.scala @@ -26,8 +26,6 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None): var myClassLoader: AbstractFileClassLoader = uninitialized - /** (value, maxElements, maxCharacters) => String */ - var myReplStringOf: (Object, Int, Int) => String = uninitialized /** Class loader used to load compiled code */ private[repl] def classLoader()(using Context) = @@ -46,45 +44,6 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None): } myClassLoader = new AbstractFileClassLoader(ctx.settings.outputDir.value, parent) - myReplStringOf = { - // We need to use the ScalaRunTime class coming from the scala-library - // on the user classpath, and not the one available in the current - // classloader, so we use reflection instead of simply calling - // `ScalaRunTime.stringOf`. Also probe for new stringOf that does string quoting, etc. - val scalaRuntime = Class.forName("scala.runtime.ScalaRunTime", true, myClassLoader) - val renderer = "stringOf" - val stringOfInvoker: (Object, Int) => String = - def richStringOf: (Object, Int) => String = - val method = scalaRuntime.getMethod(renderer, classOf[Object], classOf[Int], classOf[Boolean]) - val richly = java.lang.Boolean.TRUE // add a repl option for enriched output - (value, maxElements) => method.invoke(null, value, maxElements, richly).asInstanceOf[String] - def poorStringOf: (Object, Int) => String = - try - val method = scalaRuntime.getMethod(renderer, classOf[Object], classOf[Int]) - (value, maxElements) => method.invoke(null, value, maxElements).asInstanceOf[String] - catch case _: NoSuchMethodException => (value, maxElements) => String.valueOf(value).take(maxElements) - try richStringOf - catch case _: NoSuchMethodException => poorStringOf - def stringOfMaybeTruncated(value: Object, maxElements: Int): String = stringOfInvoker(value, maxElements) - - // require value != null - // `ScalaRuntime.stringOf` returns null iff value.toString == null, let caller handle that. - // `ScalaRuntime.stringOf` may truncate the output, in which case we want to indicate that fact to the user - // In order to figure out if it did get truncated, we invoke it twice - once with the `maxElements` that we - // want to print, and once without a limit. If the first is shorter, truncation did occur. - // Note that `stringOf` has new API in flight to handle truncation, see stringOfMaybeTruncated. - (value: Object, maxElements: Int, maxCharacters: Int) => - stringOfMaybeTruncated(value, Int.MaxValue) match - case null => null - case notTruncated => - val maybeTruncated = - val maybeTruncatedByElementCount = stringOfMaybeTruncated(value, maxElements) - truncate(maybeTruncatedByElementCount, maxCharacters) - // our string representation may have been truncated by element and/or character count - // if so, append an info string - but only once - if notTruncated.length == maybeTruncated.length then maybeTruncated - else s"$maybeTruncated ... large output truncated, print value to show all" - } myClassLoader } @@ -94,18 +53,10 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None): else str.substring(0, str.offsetByCodePoints(0, maxPrintCharacters - 1)) /** Return a String representation of a value we got from `classLoader()`. */ - private[repl] def replStringOf(sym: Symbol, value: Object)(using Context): String = - assert(myReplStringOf != null, - "replStringOf should only be called on values creating using `classLoader()`, but `classLoader()` has not been called so far") - val maxPrintElements = ctx.settings.VreplMaxPrintElements.valueIn(ctx.settingsState) - val maxPrintCharacters = ctx.settings.VreplMaxPrintCharacters.valueIn(ctx.settingsState) - // stringOf returns null if value.toString returns null. Show some text as a fallback. - def fallback = s"""null // result of "${sym.name}.toString" is null""" - if value == null then "null" else - myReplStringOf(value, maxPrintElements, maxPrintCharacters) match - case null => fallback - case res => res - end if + private[repl] def replStringOf(sym: Symbol, value: Object)(using Context): String = { + // pretty-print things with 100 cols 50 rows by default, + dotty.shaded.pprint.PPrinter.BlackWhite.apply(value, width = 100, height = 50).plainText + } /** Load the value of the symbol using reflection. * diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 0b67c36492ae..befb3de9a941 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -77,7 +77,8 @@ case class State(objectIndex: Int, /** Main REPL instance, orchestrating input, compilation and presentation */ class ReplDriver(settings: Array[String], out: PrintStream = Console.out, - classLoader: Option[ClassLoader] = None) extends Driver: + classLoader: Option[ClassLoader] = None, + extraPredef: String = "") extends Driver: /** Overridden to `false` in order to not have to give sources on the * commandline @@ -122,9 +123,10 @@ class ReplDriver(settings: Array[String], final def initialState: State = val emptyState = State(0, 0, Map.empty, Set.empty, false, rootCtx) val initScript = rootCtx.settings.replInitScript.value(using rootCtx) - initScript.trim() match - case "" => emptyState - case script => run(script)(using emptyState) + val combinedScript = initScript.trim() match + case "" => extraPredef + case script => s"$extraPredef\n$script" + run(combinedScript)(using emptyState) /** Reset state of repl to the initial state * @@ -638,3 +640,5 @@ class ReplDriver(settings: Array[String], case _ => ReplConsoleReporter.doReport(dia)(using state.context) end ReplDriver +object ReplDriver: + def pprintImport = "import dotty.shaded.pprint.pprintln\n" \ No newline at end of file diff --git a/compiler/test-resources/repl/19184 b/compiler/test-resources/repl/19184 index cf4ce6f1d22f..a0e2bbc515da 100644 --- a/compiler/test-resources/repl/19184 +++ b/compiler/test-resources/repl/19184 @@ -1,5 +1,5 @@ scala> def o(s: String) = "o"; def oo(s: String) = "oo"; val o = "o"; val oo = "oo" def o(s: String): String def oo(s: String): String -val o: String = o -val oo: String = oo +val o: String = "o" +val oo: String = "oo" diff --git a/compiler/test-resources/repl/defaultClassloader b/compiler/test-resources/repl/defaultClassloader index bd9a955409b9..df2c5b40d05b 100644 --- a/compiler/test-resources/repl/defaultClassloader +++ b/compiler/test-resources/repl/defaultClassloader @@ -1,2 +1,2 @@ scala> val d: Long = (new java.sql.Date(100L)).getTime -val d: Long = 100 +val d: Long = 100L diff --git a/compiler/test-resources/repl/i13181 b/compiler/test-resources/repl/i13181 index c2c48f9d4214..f1d298174aee 100644 --- a/compiler/test-resources/repl/i13181 +++ b/compiler/test-resources/repl/i13181 @@ -1,2 +1,2 @@ scala> scala.compiletime.codeOf(1+2) -val res0: String = 1 + 2 +val res0: String = "1 + 2" diff --git a/compiler/test-resources/repl/i1369 b/compiler/test-resources/repl/i1369 index ced0af6aea36..971d10839f03 100644 --- a/compiler/test-resources/repl/i1369 +++ b/compiler/test-resources/repl/i1369 @@ -1,4 +1,4 @@ scala> print("foo") foo scala> "Hello" -val res0: String = Hello +val res0: String = "Hello" diff --git a/compiler/test-resources/repl/i15493 b/compiler/test-resources/repl/i15493 index 063f7edfaca4..060b5c84597d 100644 --- a/compiler/test-resources/repl/i15493 +++ b/compiler/test-resources/repl/i15493 @@ -5,7 +5,7 @@ scala> NInt(23) val res0: NInt = NInt@17 scala> res0.toString -val res1: String = NInt@17 +val res1: String = "rs$line$1$NInt@17" scala> 23 val res2: Int = 23 @@ -17,7 +17,7 @@ scala> NBoolean(true) val res3: NBoolean = NBoolean@4cf scala> res3.toString -val res4: String = NBoolean@4cf +val res4: String = "rs$line$5$NBoolean@4cf" scala> true val res5: Boolean = true @@ -29,7 +29,7 @@ scala> NByte(1) val res6: NByte = NByte@1 scala> res6.toString -val res7: String = NByte@1 +val res7: String = "rs$line$9$NByte@1" scala> val res8: Byte = 1 val res8: Byte = 1 @@ -41,7 +41,7 @@ scala> NShort(1) val res9: NShort = NShort@1 scala> res9.toString -val res10: String = NShort@1 +val res10: String = "rs$line$13$NShort@1" scala> val res11: Short = 1 val res11: Short = 1 @@ -53,10 +53,10 @@ scala> NLong(1) val res12: NLong = NLong@1 scala> res12.toString -val res13: String = NLong@1 +val res13: String = "rs$line$17$NLong@1" scala> 1L -val res14: Long = 1 +val res14: Long = 1L scala> class NFloat(val x: Float) extends AnyVal // defined class NFloat @@ -65,10 +65,10 @@ scala> NFloat(1L) val res15: NFloat = NFloat@3f800000 scala> res15.toString -val res16: String = NFloat@3f800000 +val res16: String = "rs$line$21$NFloat@3f800000" scala> 1.0F -val res17: Float = 1.0 +val res17: Float = 1.0F scala> class NDouble(val x: Double) extends AnyVal // defined class NDouble @@ -77,7 +77,7 @@ scala> NDouble(1D) val res18: NDouble = NDouble@3ff00000 scala> res18.toString -val res19: String = NDouble@3ff00000 +val res19: String = "rs$line$25$NDouble@3ff00000" scala> 1.0D val res20: Double = 1.0 @@ -89,10 +89,10 @@ scala> NChar('a') val res21: NChar = NChar@61 scala> res21.toString -val res22: String = NChar@61 +val res22: String = "rs$line$29$NChar@61" scala> 'a' -val res23: Char = a +val res23: Char = 'a' scala> class NString(val x: String) extends AnyVal // defined class NString @@ -101,10 +101,10 @@ scala> NString("test") val res24: NString = NString@364492 scala> res24.toString -val res25: String = NString@364492 +val res25: String = "rs$line$33$NString@364492" scala> "test" -val res26: String = test +val res26: String = "test" scala> class CustomToString(val x: Int) extends AnyVal { override def toString(): String = s"Test$x" } // defined class CustomToString @@ -113,7 +113,7 @@ scala> CustomToString(23) val res27: CustomToString = Test23 scala> res27.toString -val res28: String = Test23 +val res28: String = "Test23" scala> class `<>`(x: Int) extends AnyVal // defined class <> @@ -122,7 +122,7 @@ scala> `<>`(23) val res29: <> = less$greater@17 scala> res29.toString -val res30: String = less$greater@17 +val res30: String = "rs$line$40$$less$greater@17" scala> class `🤪`(x: Int) extends AnyVal // defined class 🤪 @@ -131,7 +131,7 @@ scala> `🤪`(23) val res31: 🤪 = uD83E$uDD2A@17 scala> res31.toString -val res32: String = uD83E$uDD2A@17 +val res32: String = "rs$line$43$$uD83E$uDD2A@17" scala> object Outer { class Foo(x: Int) extends AnyVal } // defined object Outer @@ -140,7 +140,7 @@ scala> Outer.Foo(23) val res33: Outer.Foo = Outer$Foo@17 scala> res33.toString -val res34: String = Outer$Foo@17 +val res34: String = "rs$line$46$Outer$Foo@17" scala> Vector.unapplySeq(Vector(2)) val res35: scala.collection.SeqFactory.UnapplySeqWrapper[Int] = scala.collection.SeqFactory$UnapplySeqWrapper@df507bfd diff --git a/compiler/test-resources/repl/i18383 b/compiler/test-resources/repl/i18383 index 563495e2e999..b7e1c8d261a1 100644 --- a/compiler/test-resources/repl/i18383 +++ b/compiler/test-resources/repl/i18383 @@ -11,4 +11,4 @@ scala> class Foo { import scala.util.*; println("foo") } // defined class Foo scala> { import scala.util.*; "foo" } -val res0: String = foo +val res0: String = "foo" diff --git a/compiler/test-resources/repl/i3388 b/compiler/test-resources/repl/i3388 index 2477a5bf7c23..8a8f5780f2f4 100644 --- a/compiler/test-resources/repl/i3388 +++ b/compiler/test-resources/repl/i3388 @@ -1,3 +1,3 @@ scala> val foo = "1"; foo.toInt -val foo: String = 1 +val foo: String = "1" val res0: Int = 1 diff --git a/compiler/test-resources/repl/jar-multiple b/compiler/test-resources/repl/jar-multiple index 453ccc40dbf6..cc2a7ec6dfbb 100644 --- a/compiler/test-resources/repl/jar-multiple +++ b/compiler/test-resources/repl/jar-multiple @@ -7,7 +7,7 @@ Added 'compiler/test-resources/jars/mylibrary.jar' to classpath. scala> import mylibrary.Utils scala> Utils.greet("Alice") -val res0: String = Hello, Alice! +val res0: String = "Hello, Alice!" scala>:jar compiler/test-resources/jars/mylibrary2.jar Added 'compiler/test-resources/jars/mylibrary2.jar' to classpath. @@ -15,18 +15,18 @@ Added 'compiler/test-resources/jars/mylibrary2.jar' to classpath. scala> import mylibrary2.Utils2 scala> Utils2.greet("Alice") -val res1: String = Greetings, Alice! +val res1: String = "Greetings, Alice!" scala> Utils.greet("Alice") -val res2: String = Hello, Alice! +val res2: String = "Hello, Alice!" scala> import mylibrary.Utils.greet scala> greet("Tom") -val res3: String = Hello, Tom! +val res3: String = "Hello, Tom!" scala> Utils.greet("Alice") -val res4: String = Hello, Alice! +val res4: String = "Hello, Alice!" scala> z val res5: Int = 1 diff --git a/compiler/test-resources/repl/settings-repl-max-print-both-truncation-settings b/compiler/test-resources/repl/settings-repl-max-print-both-truncation-settings index a7f7d6c10dd6..fc49e92756c4 100644 --- a/compiler/test-resources/repl/settings-repl-max-print-both-truncation-settings +++ b/compiler/test-resources/repl/settings-repl-max-print-both-truncation-settings @@ -6,5 +6,5 @@ scala>:settings -Vrepl-max-print-elements:2 scala>:settings -Vrepl-max-print-characters:50 scala> Seq(1,2,3) -val res1: Seq[Int] = List(1, 2) ... large output truncated, print value to show all +val res1: Seq[Int] = List(1, 2, 3) diff --git a/compiler/test-resources/repl/settings-repl-max-print-characters b/compiler/test-resources/repl/settings-repl-max-print-characters index 9263680b95cc..a549c924bd9e 100644 --- a/compiler/test-resources/repl/settings-repl-max-print-characters +++ b/compiler/test-resources/repl/settings-repl-max-print-characters @@ -1,7 +1,7 @@ scala> 1.to(10).mkString -val res0: String = 12345678910 +val res0: String = "12345678910" scala>:settings -Vrepl-max-print-characters:10 scala> 1.to(10).mkString -val res1: String = 123456789 ... large output truncated, print value to show all +val res1: String = "12345678910" diff --git a/compiler/test-resources/repl/settings-repl-max-print-elements b/compiler/test-resources/repl/settings-repl-max-print-elements index b203e689f020..8afb244e1cf9 100644 --- a/compiler/test-resources/repl/settings-repl-max-print-elements +++ b/compiler/test-resources/repl/settings-repl-max-print-elements @@ -4,4 +4,5 @@ val res0: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 scala>:settings -Vrepl-max-print-elements:20 scala> 1.to(300).toList -val res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) ... large output truncated, print value to show all +val res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300) + diff --git a/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala b/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala index 2e4b7bf1bb3f..2a2510a95394 100644 --- a/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala +++ b/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala @@ -95,7 +95,7 @@ class SyntaxHighlightingTests extends DottyTest { test("val foo = 123", " = ") test( "val foo: List[List[Int]] = List(List(1))", - " : [[]] = List(List())" + " : [[]] = (())" ) test("var", "") diff --git a/compiler/test/dotty/tools/repl/LoadTests.scala b/compiler/test/dotty/tools/repl/LoadTests.scala index 9b4fb8a44149..309d6d05ecc4 100644 --- a/compiler/test/dotty/tools/repl/LoadTests.scala +++ b/compiler/test/dotty/tools/repl/LoadTests.scala @@ -20,7 +20,7 @@ class LoadTests extends ReplTest { |def helloWorld: String |""".stripMargin, runCode = "helloWorld", - output = """|val res0: String = Hello, World! + output = """|val res0: String = "Hello, World!" |""".stripMargin ) diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index 0ca29fe6e15e..84610e9ee410 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -262,7 +262,7 @@ class ReplCompilerTests extends ReplTest: @Test def testSingletonPrint = initially { run("""val a = "hello"; val x: a.type = a""") - assertMultiLineEquals("val a: String = hello\nval x: a.type = hello", storedOutput().trim) + assertMultiLineEquals("val a: String = \"hello\"\nval x: a.type = \"hello\"", storedOutput().trim) } @Test def i6574 = initially { @@ -445,7 +445,6 @@ class ReplCompilerTests extends ReplTest: .andThen: val last = lines().last assertTrue(last, last.startsWith("val tpolecat: Object = null")) - assertTrue(last, last.endsWith("""// result of "tpolecat.toString" is null""")) @Test def `i17333 print toplevel object with null toString`: Unit = initially: @@ -454,7 +453,6 @@ class ReplCompilerTests extends ReplTest: run("tpolecat") val last = lines().last assertTrue(last, last.startsWith("val res0: tpolecat.type = null")) - assertTrue(last, last.endsWith("""// result of "res0.toString" is null""")) @Test def `i21431 filter out best effort options`: Unit = initially: diff --git a/compiler/test/dotty/tools/repl/ShadowingTests.scala b/compiler/test/dotty/tools/repl/ShadowingTests.scala index 98aa58a62a15..a0c0210fe3b5 100644 --- a/compiler/test/dotty/tools/repl/ShadowingTests.scala +++ b/compiler/test/dotty/tools/repl/ShadowingTests.scala @@ -131,7 +131,7 @@ class ShadowingTests extends ReplTest(options = ShadowingTests.options): testScript(name = "", """|scala> val (x, y) = (42, "foo") |val x: Int = 42 - |val y: String = foo + |val y: String = "foo" | |scala> if (true) x else y |val res0: Int | String = 42 diff --git a/language-server/test/dotty/tools/languageserver/WorksheetTest.scala b/language-server/test/dotty/tools/languageserver/WorksheetTest.scala index da7c7bceefd6..b73337142774 100644 --- a/language-server/test/dotty/tools/languageserver/WorksheetTest.scala +++ b/language-server/test/dotty/tools/languageserver/WorksheetTest.scala @@ -90,7 +90,7 @@ class WorksheetTest { case _ => "odd" }${m2}""" .run(m1, - ((m1 to m2), "val res0: String = odd")) + ((m1 to m2), "val res0: String = \"odd\"")) } @Test def evaluationException: Unit = { diff --git a/project/Build.scala b/project/Build.scala index 24526b5cac00..af6517111fae 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -721,6 +721,8 @@ object Build { ("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), ), + (Compile / sourceGenerators) += ShadedSourceGenerator.task.taskValue, + // For convenience, change the baseDirectory when running the compiler Compile / forkOptions := (Compile / forkOptions).value.withWorkingDirectory((ThisBuild / baseDirectory).value), Compile / run / forkOptions := (Compile / run / forkOptions).value.withWorkingDirectory((ThisBuild / baseDirectory).value), @@ -2141,6 +2143,7 @@ object Build { Seq(file) }.taskValue, + (Compile / sourceGenerators) += ShadedSourceGenerator.task.taskValue, // sbt adds all the projects to scala-tool config which breaks building the scalaInstance // as a workaround, I build it manually by only adding the compiler scalaInstance := { @@ -2330,6 +2333,7 @@ object Build { sjsSources } (Set(scalaJSIRSourcesJar)).toSeq }.taskValue, + (Compile / sourceGenerators) += ShadedSourceGenerator.task.taskValue ) // ============================================================================================== diff --git a/project/ShadedSourceGenerator.scala b/project/ShadedSourceGenerator.scala new file mode 100644 index 000000000000..e3a1c9680db9 --- /dev/null +++ b/project/ShadedSourceGenerator.scala @@ -0,0 +1,110 @@ +import sbt._ +import sbt.Keys._ + +/** + * ShadedSourceGenerator - A build plugin for creating shaded versions of external dependencies + * + * This generator downloads source JARs for specified dependencies (currently pprint, fansi, and sourcecode), + * extracts them, and applies patches to: + * 1. Add the dotty.shaded package prefix + * 2. Rewrite imports to use _root_ to avoid conflicts + * 3. Apply Scala 3 compatibility fixes (mostly due to enforcing null safety in scala/scala3) + * + * The shaded sources are placed in the managed source directory and included in compilation. + * This allows the Scala 3 compiler to bundle these utilities without external dependencies. + */ +object ShadedSourceGenerator { + + val task = Def.task { + val s = streams.value + val cacheDir = s.cacheDirectory + val dest = (Compile / sourceManaged).value / "downloaded" + val lm = dependencyResolution.value + + val dependencies = Seq( + ("com.lihaoyi", "pprint_3", "0.9.3"), + ("com.lihaoyi", "fansi_3", "0.5.1"), + ("com.lihaoyi", "sourcecode_3", "0.4.4"), + ) + + // Create a marker file that tracks the dependencies for cache invalidation + val markerFile = cacheDir / "shaded-sources-marker" + val markerContent = dependencies.map { case (org, name, version) => s"$org:$name:$version:sources" }.mkString("\n") + if (!markerFile.exists || IO.read(markerFile) != markerContent) { + IO.write(markerFile, markerContent) + } + + FileFunction.cached(cacheDir / "fetchShadedSources", + FilesInfo.lastModified, FilesInfo.exists) { _ => + s.log.info(s"Downloading and processing shaded sources to $dest...") + + if (dest.exists) { + IO.delete(dest) + } + IO.createDirectory(dest) + + for((org, name, version) <- dependencies) { + import sbt.librarymanagement._ + + val moduleId = ModuleID(org, name, version).sources() + val retrieveDir = cacheDir / "retrieved" / s"$org-$name-$version-sources" + + s.log.info(s"Retrieving $org:$name:$version:sources...") + val retrieved = lm.retrieve(moduleId, scalaModuleInfo = None, retrieveDir, s.log) + val jarFiles = retrieved.fold( + w => throw w.resolveException, + files => files.filter(_.getName.contains("-sources.jar")) + ) + + jarFiles.foreach { jarFile => + s.log.info(s"Extracting ${jarFile.getName}...") + IO.unzip(jarFile, dest) + } + } + + val scalaFiles = (dest ** "*.scala").get + + // Define patches as a map from search text to replacement text + val patches = Map( + "import scala" -> "import _root_.scala", + " scala.collection." -> " _root_.scala.collection.", + "def apply(c: Char): Trie[T]" -> "def apply(c: Char): Trie[T] | Null", + "var head: Iterator[T] = null" -> "var head: Iterator[T] | Null = null", + "if (head != null && head.hasNext) true" -> "if (head != null && head.nn.hasNext) true", + "head.next()" -> "head.nn.next()", + "abstract class Walker" -> "@scala.annotation.nowarn abstract class Walker", + "object TPrintLowPri" -> "@scala.annotation.nowarn object TPrintLowPri", + "x.toString match{" -> "scala.runtime.ScalaRunTime.stringOf(x) match{" + ) + + val patchUsageCounter = scala.collection.mutable.Map(patches.keys.map(_ -> 0).toSeq: _*) + + scalaFiles.foreach { file => + val text = IO.read(file) + if (!file.getName.equals("CollectionName.scala")) { + var processedText = "package dotty.shaded\n" + text + + // Apply patches and count usage + patches.foreach { case (search, replacement) => + if (processedText.contains(search)) { + processedText = processedText.replace(search, replacement) + patchUsageCounter(search) += 1 + } + } + + IO.write(file, processedText) + } + } + + // Assert that all patches were applied at least once + val unappliedPatches = patchUsageCounter.filter(_._2 == 0).keys + if (unappliedPatches.nonEmpty) { + throw new RuntimeException(s"Patches were not applied: ${unappliedPatches.mkString(", ")}") + } + + scalaFiles.toSet + } (Set(markerFile)).toSeq + + } + +} \ No newline at end of file diff --git a/sbt-bridge/src/xsbt/ConsoleInterface.java b/sbt-bridge/src/xsbt/ConsoleInterface.java index 3467e6abc226..49194fdf53d2 100644 --- a/sbt-bridge/src/xsbt/ConsoleInterface.java +++ b/sbt-bridge/src/xsbt/ConsoleInterface.java @@ -40,7 +40,7 @@ public void run( completeArgsList.add(classpathString); String[] completeArgs = completeArgsList.toArray(args); - ReplDriver driver = new ReplDriver(completeArgs, System.out, Some.apply(loader)); + ReplDriver driver = new ReplDriver(completeArgs, System.out, Some.apply(loader), ReplDriver.pprintImport()); State state = driver.initialState(); assert bindNames.length == bindValues.length; diff --git a/staging/test-resources/repl-staging/i6007 b/staging/test-resources/repl-staging/i6007 index dcb99cc47c67..525166135b5d 100644 --- a/staging/test-resources/repl-staging/i6007 +++ b/staging/test-resources/repl-staging/i6007 @@ -5,6 +5,6 @@ def compiler: scala.quoted.staging.Compiler scala> def v(using Quotes) = '{ (if true then Some(1) else None).map(v => v+1) } def v(using x$1: scala.quoted.Quotes): scala.quoted.Expr[Option[Int]] scala> scala.quoted.staging.withQuotes(v.show) -val res0: String = (if (true) scala.Some.apply[scala.Int](1) else scala.None).map[scala.Int](((v: scala.Int) => v.+(1))) +val res0: String = "(if (true) scala.Some.apply[scala.Int](1) else scala.None).map[scala.Int](((v: scala.Int) => v.+(1)))" scala> scala.quoted.staging.run(v) val res1: Option[Int] = Some(2)