@@ -12,6 +12,8 @@ import util.StackTraceOps.*
12
12
13
13
import scala .util .control .NonFatal
14
14
15
+ import java .util .Objects .toIdentityString
16
+
15
17
/** This rendering object uses `ClassLoader`s to accomplish crossing the 4th
16
18
* wall (i.e. fetching back values from the compiled class files put into a
17
19
* specific class loader capable of loading from memory) and rendering them.
@@ -50,39 +52,40 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
50
52
// We need to use the ScalaRunTime class coming from the scala-library
51
53
// on the user classpath, and not the one available in the current
52
54
// classloader, so we use reflection instead of simply calling
53
- // `ScalaRunTime.replStringOf`. Probe for new API without extraneous newlines.
54
- // For old API, try to clean up extraneous newlines by stripping suffix and maybe prefix newline.
55
+ // `ScalaRunTime.stringOf`. Also probe for new stringOf that does string quoting, etc.
55
56
val scalaRuntime = Class .forName(" scala.runtime.ScalaRunTime" , true , myClassLoader)
56
57
val renderer = " stringOf"
57
- def stringOfMaybeTruncated (value : Object , maxElements : Int ): String = {
58
- try {
59
- val meth = scalaRuntime.getMethod(renderer, classOf [Object ], classOf [Int ], classOf [Boolean ])
60
- val truly = java.lang.Boolean .TRUE
61
- meth.invoke(null , value, maxElements, truly).asInstanceOf [String ]
62
- } catch {
63
- case _ : NoSuchMethodException =>
64
- val meth = scalaRuntime.getMethod(renderer, classOf [Object ], classOf [Int ])
65
- meth.invoke(null , value, maxElements).asInstanceOf [String ]
66
- }
67
- }
68
-
69
- (value : Object , maxElements : Int , maxCharacters : Int ) => {
70
- // `ScalaRuntime.stringOf` may truncate the output, in which case we want to indicate that fact to the user
71
- // In order to figure out if it did get truncated, we invoke it twice - once with the `maxElements` that we
72
- // want to print, and once without a limit. If the first is shorter, truncation did occur.
73
- val notTruncated = stringOfMaybeTruncated(value, Int .MaxValue )
74
- if notTruncated == null then null else
75
- val maybeTruncated =
76
- val maybeTruncatedByElementCount = stringOfMaybeTruncated(value, maxElements)
77
- truncate(maybeTruncatedByElementCount, maxCharacters)
78
-
79
- // our string representation may have been truncated by element and/or character count
80
- // if so, append an info string - but only once
81
- if notTruncated.length == maybeTruncated.length then maybeTruncated
82
- else s " $maybeTruncated ... large output truncated, print value to show all "
83
- end if
84
- }
85
-
58
+ val stringOfInvoker : (Object , Int ) => String =
59
+ def richStringOf : (Object , Int ) => String =
60
+ val method = scalaRuntime.getMethod(renderer, classOf [Object ], classOf [Int ], classOf [Boolean ])
61
+ val richly = java.lang.Boolean .TRUE // add a repl option for enriched output
62
+ (value, maxElements) => method.invoke(null , value, maxElements, richly).asInstanceOf [String ]
63
+ def poorStringOf : (Object , Int ) => String =
64
+ try
65
+ val method = scalaRuntime.getMethod(renderer, classOf [Object ], classOf [Int ])
66
+ (value, maxElements) => method.invoke(null , value, maxElements).asInstanceOf [String ]
67
+ catch case _ : NoSuchMethodException => (value, maxElements) => String .valueOf(value).take(maxElements)
68
+ try richStringOf
69
+ catch case _ : NoSuchMethodException => poorStringOf
70
+ def stringOfMaybeTruncated (value : Object , maxElements : Int ): String = stringOfInvoker(value, maxElements)
71
+
72
+ // require value != null
73
+ // `ScalaRuntime.stringOf` returns null iff value.toString == null, let caller handle that.
74
+ // `ScalaRuntime.stringOf` may truncate the output, in which case we want to indicate that fact to the user
75
+ // In order to figure out if it did get truncated, we invoke it twice - once with the `maxElements` that we
76
+ // want to print, and once without a limit. If the first is shorter, truncation did occur.
77
+ // Note that `stringOf` has new API in flight to handle truncation, see stringOfMaybeTruncated.
78
+ (value : Object , maxElements : Int , maxCharacters : Int ) =>
79
+ stringOfMaybeTruncated(value, Int .MaxValue ) match
80
+ case null => null
81
+ case notTruncated =>
82
+ val maybeTruncated =
83
+ val maybeTruncatedByElementCount = stringOfMaybeTruncated(value, maxElements)
84
+ truncate(maybeTruncatedByElementCount, maxCharacters)
85
+ // our string representation may have been truncated by element and/or character count
86
+ // if so, append an info string - but only once
87
+ if notTruncated.length == maybeTruncated.length then maybeTruncated
88
+ else s " $maybeTruncated ... large output truncated, print value to show all "
86
89
}
87
90
myClassLoader
88
91
}
@@ -93,14 +96,18 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
93
96
else str.substring(0 , str.offsetByCodePoints(0 , maxPrintCharacters - 1 ))
94
97
95
98
/** Return a String representation of a value we got from `classLoader()`. */
96
- private [repl] def replStringOf (value : Object )(using Context ): String =
99
+ private [repl] def replStringOf (sym : Symbol , value : Object )(using Context ): String =
97
100
assert(myReplStringOf != null ,
98
101
" replStringOf should only be called on values creating using `classLoader()`, but `classLoader()` has not been called so far" )
99
102
val maxPrintElements = ctx.settings.VreplMaxPrintElements .valueIn(ctx.settingsState)
100
103
val maxPrintCharacters = ctx.settings.VreplMaxPrintCharacters .valueIn(ctx.settingsState)
101
- Option (value)
102
- .flatMap(v => Option (myReplStringOf(v, maxPrintElements, maxPrintCharacters)))
103
- .getOrElse(" null // non-null reference has null-valued toString" )
104
+ // stringOf returns null if value.toString returns null. Show some text as a fallback.
105
+ def fallback = s """ ${toIdentityString(value)} // return value of " ${sym.name}.toString" is null """
106
+ if value == null then " null" else
107
+ myReplStringOf(value, maxPrintElements, maxPrintCharacters) match
108
+ case null => fallback
109
+ case res => res
110
+ end if
104
111
105
112
/** Load the value of the symbol using reflection.
106
113
*
@@ -112,17 +119,15 @@ private[repl] class Rendering(parentClassLoader: Option[ClassLoader] = None):
112
119
val symValue = resObj
113
120
.getDeclaredMethods.find(_.getName == sym.name.encode.toString)
114
121
.flatMap(result => rewrapValueClass(sym.info.classSymbol, result.invoke(null )))
115
- val valueString = symValue.map(replStringOf)
122
+ symValue
123
+ .filter(_ => sym.is(Flags .Method ) || sym.info != defn.UnitType )
124
+ .map(value => stripReplPrefix(replStringOf(sym, value)))
116
125
117
- if (! sym.is(Flags .Method ) && sym.info == defn.UnitType )
118
- None
126
+ private def stripReplPrefix (s : String ): String =
127
+ if (s.startsWith(REPL_WRAPPER_NAME_PREFIX ))
128
+ s.drop(REPL_WRAPPER_NAME_PREFIX .length).dropWhile(c => c.isDigit || c == '$' )
119
129
else
120
- valueString.map { s =>
121
- if (s.startsWith(REPL_WRAPPER_NAME_PREFIX ))
122
- s.drop(REPL_WRAPPER_NAME_PREFIX .length).dropWhile(c => c.isDigit || c == '$' )
123
- else
124
- s
125
- }
130
+ s
126
131
127
132
/** Rewrap value class to their Wrapper class
128
133
*
0 commit comments