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