Skip to content

Commit 231f9ab

Browse files
authored
REPL: Fix crash when printing instances of value classes (#16393)
By fixing `Rendering`'s `rewrapValueClass` to use the fully qualified class name Sequel to #15545 Fixes #16322 Fixes #16387
2 parents 4a3747a + 2deb5f5 commit 231f9ab

File tree

5 files changed

+54
-58
lines changed

5 files changed

+54
-58
lines changed

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

+24
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,30 @@ object SymDenotations {
506506
/** `fullName` where `.' is the separator character */
507507
def fullName(using Context): Name = fullNameSeparated(QualifiedName)
508508

509+
/** The fully qualified name on the JVM of the class corresponding to this symbol. */
510+
def binaryClassName(using Context): String =
511+
val builder = new StringBuilder
512+
val pkg = enclosingPackageClass
513+
if !pkg.isEffectiveRoot then
514+
builder.append(pkg.fullName.mangledString)
515+
builder.append(".")
516+
val flatName = this.flatName
517+
// Some companion objects are fake (that is, they're a compiler fiction
518+
// that doesn't correspond to a class that exists at runtime), this
519+
// can happen in two cases:
520+
// - If a Java class has static members.
521+
// - If we create constructor proxies for a class (see NamerOps#addConstructorProxies).
522+
//
523+
// In both cases it's may be vital that we don't return the object name.
524+
// For instance, sending it to zinc: when sbt is restarted, zinc will inspect the binary
525+
// dependencies to see if they're still on the classpath, if it
526+
// doesn't find them it will invalidate whatever referenced them, so
527+
// any reference to a fake companion will lead to extra recompilations.
528+
// Instead, use the class name since it's guaranteed to exist at runtime.
529+
val clsFlatName = if isOneOf(JavaDefined | ConstructorProxy) then flatName.stripModuleClassSuffix else flatName
530+
builder.append(clsFlatName.mangledString)
531+
builder.toString
532+
509533
private var myTargetName: Name | Null = null
510534

511535
private def computeTargetName(targetNameAnnot: Option[Annotation])(using Context): Name =

compiler/src/dotty/tools/dotc/quoted/Interpreter.scala

+1-6
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,7 @@ abstract class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context)
202202
}
203203
else {
204204
// nested object in an object
205-
val className = {
206-
val pack = sym.topLevelClass.owner
207-
if (pack == defn.RootPackage || pack == defn.EmptyPackageClass) sym.flatName.toString
208-
else pack.showFullName + "." + sym.flatName
209-
}
210-
val clazz = loadClass(className)
205+
val clazz = loadClass(sym.binaryClassName)
211206
clazz.getConstructor().newInstance().asInstanceOf[Object]
212207
}
213208

compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala

+1-28
Original file line numberDiff line numberDiff line change
@@ -143,34 +143,7 @@ class ExtractDependencies extends Phase {
143143
def allowLocal = dep.context == DependencyByInheritance || dep.context == LocalDependencyByInheritance
144144
if (depFile.extension == "class") {
145145
// Dependency is external -- source is undefined
146-
147-
// The fully qualified name on the JVM of the class corresponding to `dep.to`
148-
val binaryClassName = {
149-
val builder = new StringBuilder
150-
val pkg = dep.to.enclosingPackageClass
151-
if (!pkg.isEffectiveRoot) {
152-
builder.append(pkg.fullName.mangledString)
153-
builder.append(".")
154-
}
155-
val flatName = dep.to.flatName
156-
// Some companion objects are fake (that is, they're a compiler fiction
157-
// that doesn't correspond to a class that exists at runtime), this
158-
// can happen in two cases:
159-
// - If a Java class has static members.
160-
// - If we create constructor proxies for a class (see NamerOps#addConstructorProxies).
161-
//
162-
// In both cases it's vital that we don't send the object name to
163-
// zinc: when sbt is restarted, zinc will inspect the binary
164-
// dependencies to see if they're still on the classpath, if it
165-
// doesn't find them it will invalidate whatever referenced them, so
166-
// any reference to a fake companion will lead to extra recompilations.
167-
// Instead, use the class name since it's guaranteed to exist at runtime.
168-
val clsFlatName = if (dep.to.isOneOf(JavaDefined | ConstructorProxy)) flatName.stripModuleClassSuffix else flatName
169-
builder.append(clsFlatName.mangledString)
170-
builder.toString
171-
}
172-
173-
processExternalDependency(depFile, binaryClassName)
146+
processExternalDependency(depFile, dep.to.binaryClassName)
174147
} else if (allowLocal || depFile.file != sourceFile) {
175148
// We cannot ignore dependencies coming from the same source file because
176149
// the dependency info needs to propagate. See source-dependencies/trait-trait-211.

compiler/src/dotty/tools/repl/Rendering.scala

+23-24
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,14 @@ package repl
33

44
import scala.language.unsafeNulls
55

6-
import java.lang.{ ClassLoader, ExceptionInInitializerError }
7-
import java.lang.reflect.InvocationTargetException
8-
9-
import dotc.core.Contexts._
10-
import dotc.core.Denotations.Denotation
11-
import dotc.core.Flags
12-
import dotc.core.Flags._
13-
import dotc.core.Symbols.{Symbol, defn}
14-
import dotc.core.StdNames.{nme, str}
15-
import dotc.printing.ReplPrinter
16-
import dotc.reporting.Diagnostic
17-
import dotc.transform.ValueClasses
6+
import dotc.*, core.*
7+
import Contexts.*, Denotations.*, Flags.*, NameOps.*, StdNames.*, Symbols.*
8+
import printing.ReplPrinter
9+
import reporting.Diagnostic
10+
import transform.ValueClasses
11+
import util.StackTraceOps.*
12+
13+
import scala.util.control.NonFatal
1814

1915
/** This rendering object uses `ClassLoader`s to accomplish crossing the 4th
2016
* wall (i.e. fetching back values from the compiled class files put into a
@@ -131,8 +127,7 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
131127
*/
132128
private def rewrapValueClass(sym: Symbol, value: Object)(using Context): Option[Object] =
133129
if ValueClasses.isDerivedValueClass(sym) then
134-
val valueClassName = sym.flatName.encode.toString
135-
val valueClass = Class.forName(valueClassName, true, classLoader())
130+
val valueClass = Class.forName(sym.binaryClassName, true, classLoader())
136131
valueClass.getConstructors.headOption.map(_.newInstance(value))
137132
else
138133
Some(value)
@@ -148,35 +143,31 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
148143
infoDiagnostic(d.symbol.showUser, d)
149144

150145
/** Render value definition result */
151-
def renderVal(d: Denotation)(using Context): Either[InvocationTargetException, Option[Diagnostic]] =
146+
def renderVal(d: Denotation)(using Context): Either[ReflectiveOperationException, Option[Diagnostic]] =
152147
val dcl = d.symbol.showUser
153148
def msg(s: String) = infoDiagnostic(s, d)
154149
try
155150
Right(
156151
if d.symbol.is(Flags.Lazy) then Some(msg(dcl))
157152
else valueOf(d.symbol).map(value => msg(s"$dcl = $value"))
158153
)
159-
catch case e: InvocationTargetException => Left(e)
154+
catch case e: ReflectiveOperationException => Left(e)
160155
end renderVal
161156

162157
/** Force module initialization in the absence of members. */
163158
def forceModule(sym: Symbol)(using Context): Seq[Diagnostic] =
164-
import scala.util.control.NonFatal
165159
def load() =
166160
val objectName = sym.fullName.encode.toString
167161
Class.forName(objectName, true, classLoader())
168162
Nil
169163
try load()
170164
catch
171165
case e: ExceptionInInitializerError => List(renderError(e, sym.denot))
172-
case NonFatal(e) => List(renderError(InvocationTargetException(e), sym.denot))
166+
case NonFatal(e) => List(renderError(e, sym.denot))
173167

174168
/** Render the stack trace of the underlying exception. */
175-
def renderError(ite: InvocationTargetException | ExceptionInInitializerError, d: Denotation)(using Context): Diagnostic =
176-
import dotty.tools.dotc.util.StackTraceOps._
177-
val cause = ite.getCause match
178-
case e: ExceptionInInitializerError => e.getCause
179-
case e => e
169+
def renderError(thr: Throwable, d: Denotation)(using Context): Diagnostic =
170+
val cause = rootCause(thr)
180171
// detect
181172
//at repl$.rs$line$2$.<clinit>(rs$line$2:1)
182173
//at repl$.rs$line$2.res1(rs$line$2)
@@ -190,7 +181,6 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
190181
private def infoDiagnostic(msg: String, d: Denotation)(using Context): Diagnostic =
191182
new Diagnostic.Info(msg, d.symbol.sourcePos)
192183

193-
194184
object Rendering:
195185
final val REPL_WRAPPER_NAME_PREFIX = str.REPL_SESSION_LINE
196186

@@ -200,3 +190,12 @@ object Rendering:
200190
val text = printer.dclText(s)
201191
text.mkString(ctx.settings.pageWidth.value, ctx.settings.printLines.value)
202192
}
193+
194+
def rootCause(x: Throwable): Throwable = x match
195+
case _: ExceptionInInitializerError |
196+
_: java.lang.reflect.InvocationTargetException |
197+
_: java.lang.reflect.UndeclaredThrowableException |
198+
_: java.util.concurrent.ExecutionException
199+
if x.getCause != null =>
200+
rootCause(x.getCause)
201+
case _ => x

compiler/test-resources/repl/i15493

+5
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,8 @@ val res33: Outer.Foo = Outer$Foo@17
142142
scala> res33.toString
143143
val res34: String = Outer$Foo@17
144144

145+
scala> Vector.unapplySeq(Vector(2))
146+
val res35: scala.collection.SeqFactory.UnapplySeqWrapper[Int] = scala.collection.SeqFactory$UnapplySeqWrapper@df507bfd
147+
148+
scala> new scala.concurrent.duration.DurationInt(5)
149+
val res36: scala.concurrent.duration.package.DurationInt = scala.concurrent.duration.package$DurationInt@5

0 commit comments

Comments
 (0)