Skip to content

Commit bd71c84

Browse files
author
Adriaan Moors
committed
Merge pull request scala#1067 from scalamacros/topic/ultimate-reflection-pull-request
Ultimate reflection pull request #2
2 parents e815dd8 + 73a9e71 commit bd71c84

22 files changed

+2107
-49
lines changed

src/compiler/scala/tools/reflect/ToolBoxFactory.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
256256
// }
257257
val (singleton, jmeth) = compileExpr(expr)
258258
val result = jmeth.invoke(singleton, thunks map (_.asInstanceOf[AnyRef]): _*)
259-
result
259+
if (jmeth.getReturnType == java.lang.Void.TYPE) ()
260+
else result
260261
}
261262

262263
def parseExpr(code: String): Tree = {

src/reflect/scala/reflect/api/Mirrors.scala

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ trait Mirrors { self: Universe =>
3333
/** Reflects against a field symbol and returns a mirror
3434
* that can be used to get and, if appropriate, set the value of the field.
3535
*
36+
* FieldMirrors are the only way to get at private[this] vals and vars and
37+
* might be useful to inspect the data of underlying Java fields.
38+
* For all other uses, it's better to go through the fields accessor.
39+
*
40+
* In particular, there should be no need to ever access a field mirror
41+
* when reflecting on just the public members of a class or trait.
42+
* Note also that only accessor MethodMirrors, but not FieldMirrors will accurately reflect overriding behavior.
43+
*
3644
* To get a field symbol by the name of the field you would like to reflect,
3745
* use `<this mirror>.symbol.typeSignature.member(newTermName(<name of the field>)).asTerm.accessed`.
3846
* For further information about member lookup refer to `Symbol.typeSignature`.
@@ -91,7 +99,7 @@ trait Mirrors { self: Universe =>
9199
trait FieldMirror {
92100

93101
/** The object containing the field */
94-
def receiver: AnyRef
102+
def receiver: Any
95103

96104
/** The field symbol representing the field.
97105
*
@@ -107,6 +115,10 @@ trait Mirrors { self: Universe =>
107115
* Scala reflection uses reflection capabilities of the underlying platform,
108116
* so `FieldMirror.get` might throw platform-specific exceptions associated
109117
* with getting a field or invoking a getter method of the field.
118+
*
119+
* If `symbol` represents a field of a base class with respect to the class of the receiver,
120+
* and this base field is overriden in the class of the receiver, then this method will retrieve
121+
* the value of the base field. To achieve overriding behavior, use reflectMethod on an accessor.
110122
*/
111123
def get: Any
112124

@@ -117,6 +129,10 @@ trait Mirrors { self: Universe =>
117129
* Scala reflection uses reflection capabilities of the underlying platform,
118130
* so `FieldMirror.get` might throw platform-specific exceptions associated
119131
* with setting a field or invoking a setter method of the field.
132+
*
133+
* If `symbol` represents a field of a base class with respect to the class of the receiver,
134+
* and this base field is overriden in the class of the receiver, then this method will set
135+
* the value of the base field. To achieve overriding behavior, use reflectMethod on an accessor.
120136
*/
121137
def set(value: Any): Unit
122138
}
@@ -125,7 +141,7 @@ trait Mirrors { self: Universe =>
125141
trait MethodMirror {
126142

127143
/** The receiver object of the method */
128-
def receiver: AnyRef
144+
def receiver: Any
129145

130146
/** The method symbol representing the method */
131147
def symbol: MethodSymbol
@@ -226,7 +242,9 @@ trait Mirrors { self: Universe =>
226242
* Such a mirror can be used to further reflect against the members of the object
227243
* to get/set fields, invoke methods and inspect inner classes and objects.
228244
*/
229-
def reflect(obj: Any): InstanceMirror
245+
// we need a ClassTag here to preserve boxity of primitives
246+
// the class tag lets us tell apart `mirror.reflect(2)` and `mirror.reflect(new Integer(2))`
247+
def reflect[T: ClassTag](obj: T): InstanceMirror
230248

231249
/** Reflects against a static class symbol and returns a mirror
232250
* that can be used to create instances of the class, inspect its companion object or perform further reflections.

src/reflect/scala/reflect/internal/StdNames.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,12 @@ trait StdNames {
937937
case _ => NO_NAME
938938
}
939939

940+
def primitiveMethodName(name: Name): TermName =
941+
primitiveInfixMethodName(name) match {
942+
case NO_NAME => primitivePostfixMethodName(name)
943+
case name => name
944+
}
945+
940946
/** Translate a String into a list of simple TypeNames and TermNames.
941947
* In all segments before the last, type/term is determined by whether
942948
* the following separator char is '.' or '#'. In the last segment,

src/reflect/scala/reflect/runtime/JavaMirrors.scala

Lines changed: 138 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import internal.Flags._
2020
//import scala.tools.nsc.util.ScalaClassLoader._
2121
import ReflectionUtils.{singletonInstance}
2222
import language.existentials
23+
import scala.runtime.{ScalaRunTime, BoxesRunTime}
24+
import scala.reflect.internal.util.Collections._
2325

2426
trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: SymbolTable =>
2527

@@ -124,15 +126,16 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym
124126
private def ErrorStaticClass(wannabe: Symbol) = throw new ScalaReflectionException(s"$wannabe is a static class, use reflectClass on a RuntimeMirror to obtain its ClassMirror")
125127
private def ErrorStaticModule(wannabe: Symbol) = throw new ScalaReflectionException(s"$wannabe is a static module, use reflectModule on a RuntimeMirror to obtain its ModuleMirror")
126128
private def ErrorNotMember(wannabe: Symbol, owner: Symbol) = throw new ScalaReflectionException(s"expected a member of $owner, you provided ${wannabe.kind} ${wannabe.fullName}")
127-
private def ErrorNotField(wannabe: Symbol) = throw new ScalaReflectionException(s"expected a field or an accessor method symbol, you provided $wannabe}")
129+
private def ErrorNotField(wannabe: Symbol) = throw new ScalaReflectionException(s"expected a field or an accessor method symbol, you provided $wannabe")
128130
private def ErrorNonExistentField(wannabe: Symbol) = throw new ScalaReflectionException(s"""
129131
|Scala field ${wannabe.name} isn't represented as a Java field, neither it has a Java accessor method
130132
|note that private parameters of class constructors don't get mapped onto fields and/or accessors,
131133
|unless they are used outside of their declaring constructors.
132134
""".trim.stripMargin)
133135
private def ErrorSetImmutableField(wannabe: Symbol) = throw new ScalaReflectionException(s"cannot set an immutable field ${wannabe.name}")
136+
private def ErrorNotConstructor(wannabe: Symbol, owner: Symbol) = throw new ScalaReflectionException(s"expected a constructor of $owner, you provided $wannabe")
134137

135-
def reflect(obj: Any): InstanceMirror = new JavaInstanceMirror(obj.asInstanceOf[AnyRef])
138+
def reflect[T: ClassTag](obj: T): InstanceMirror = new JavaInstanceMirror(obj)
136139

137140
def reflectClass(cls: ClassSymbol): ClassMirror = {
138141
if (!cls.isStatic) ErrorInnerClass(cls)
@@ -152,13 +155,25 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym
152155

153156
def moduleSymbol(rtcls: RuntimeClass): ModuleSymbol = classToScala(rtcls).companionModule.asModule
154157

155-
private def checkMemberOf(wannabe: Symbol, owner: Symbol) =
156-
if (!owner.info.member(wannabe.name).alternatives.contains(wannabe)) ErrorNotMember(wannabe, owner)
158+
private def checkMemberOf(wannabe: Symbol, owner: ClassSymbol) {
159+
if (wannabe.owner == AnyClass || wannabe.owner == AnyRefClass || wannabe.owner == ObjectClass) {
160+
// do nothing
161+
} else if (wannabe.owner == AnyValClass) {
162+
if (!owner.isPrimitiveValueClass && !owner.isDerivedValueClass) ErrorNotMember(wannabe, owner)
163+
} else {
164+
if (!(owner.info.baseClasses contains wannabe.owner)) ErrorNotMember(wannabe, owner)
165+
}
166+
}
167+
168+
private def preciseClass[T: ClassTag](instance: T) = {
169+
val staticClazz = classTag[T].runtimeClass
170+
val dynamicClazz = instance.getClass
171+
if (staticClazz.isPrimitive) staticClazz else dynamicClazz
172+
}
157173

158-
private class JavaInstanceMirror(obj: AnyRef)
174+
private class JavaInstanceMirror[T: ClassTag](val instance: T)
159175
extends InstanceMirror {
160-
def instance = obj
161-
def symbol = wholemirror.classSymbol(obj.getClass)
176+
def symbol = wholemirror.classSymbol(preciseClass(instance))
162177
def reflectField(field: TermSymbol): FieldMirror = {
163178
checkMemberOf(field, symbol)
164179
if ((field.isMethod && !field.isAccessor) || field.isModule) ErrorNotField(field)
@@ -171,26 +186,26 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym
171186
catch {
172187
case _: NoSuchFieldException => ErrorNonExistentField(field1)
173188
}
174-
new JavaFieldMirror(obj, field1)
189+
new JavaFieldMirror(instance, field1)
175190
}
176191
def reflectMethod(method: MethodSymbol): MethodMirror = {
177192
checkMemberOf(method, symbol)
178-
new JavaMethodMirror(obj, method)
193+
mkJavaMethodMirror(instance, method)
179194
}
180195
def reflectClass(cls: ClassSymbol): ClassMirror = {
181196
if (cls.isStatic) ErrorStaticClass(cls)
182197
checkMemberOf(cls, symbol)
183-
new JavaClassMirror(instance, cls)
198+
new JavaClassMirror(instance.asInstanceOf[AnyRef], cls)
184199
}
185200
def reflectModule(mod: ModuleSymbol): ModuleMirror = {
186201
if (mod.isStatic) ErrorStaticModule(mod)
187202
checkMemberOf(mod, symbol)
188-
new JavaModuleMirror(instance, mod)
203+
new JavaModuleMirror(instance.asInstanceOf[AnyRef], mod)
189204
}
190-
override def toString = s"instance mirror for $obj"
205+
override def toString = s"instance mirror for $instance"
191206
}
192207

193-
private class JavaFieldMirror(val receiver: AnyRef, val symbol: TermSymbol)
208+
private class JavaFieldMirror(val receiver: Any, val symbol: TermSymbol)
194209
extends FieldMirror {
195210
lazy val jfield = {
196211
val jfield = fieldToJava(symbol)
@@ -230,26 +245,115 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym
230245
sig
231246
}
232247

233-
private class JavaMethodMirror(val receiver: AnyRef, val symbol: MethodSymbol)
248+
// the "symbol == Any_getClass || symbol == Object_getClass" test doesn't cut it
249+
// because both AnyVal and its primitive descendants define their own getClass methods
250+
private def isGetClass(meth: MethodSymbol) = meth.name.toString == "getClass" && meth.params.flatten.isEmpty
251+
private def isMagicPrimitiveMethod(meth: MethodSymbol) = meth.owner.isPrimitiveValueClass
252+
private def isStringConcat(meth: MethodSymbol) = meth == String_+ || (isMagicPrimitiveMethod(meth) && meth.returnType =:= StringClass.toType)
253+
lazy val magicMethodOwners = Set[Symbol](AnyClass, AnyValClass, AnyRefClass, ObjectClass, ArrayClass) ++ ScalaPrimitiveValueClasses
254+
lazy val nonMagicObjectMethods = Set[Symbol](Object_clone, Object_equals, Object_finalize, Object_hashCode, Object_toString,
255+
Object_notify, Object_notifyAll) ++ ObjectClass.info.member(nme.wait_).asTerm.alternatives.map(_.asMethod)
256+
private def isMagicMethod(meth: MethodSymbol): Boolean = {
257+
if (isGetClass(meth) || isStringConcat(meth) || isMagicPrimitiveMethod(meth) || meth == Predef_classOf || meth.isTermMacro) return true
258+
magicMethodOwners(meth.owner) && !nonMagicObjectMethods(meth)
259+
}
260+
261+
// unlike other mirrors, method mirrors are created by a factory
262+
// that's because we want to have decent performance
263+
// therefore we move special cases into separate subclasses
264+
// rather than have them on a hot path them in a unified implementation of the `apply` method
265+
private def mkJavaMethodMirror[T: ClassTag](receiver: T, symbol: MethodSymbol): JavaMethodMirror = {
266+
if (isMagicMethod(symbol)) new JavaMagicMethodMirror(receiver, symbol)
267+
else if (symbol.params.flatten exists (p => isByNameParamType(p.info))) new JavaByNameMethodMirror(receiver, symbol)
268+
else new JavaVanillaMethodMirror(receiver, symbol)
269+
}
270+
271+
private abstract class JavaMethodMirror(val symbol: MethodSymbol)
234272
extends MethodMirror {
235273
lazy val jmeth = {
236274
val jmeth = methodToJava(symbol)
237275
if (!jmeth.isAccessible) jmeth.setAccessible(true)
238276
jmeth
239277
}
240-
def apply(args: Any*): Any =
241-
if (symbol.owner == ArrayClass)
242-
symbol.name match {
243-
case nme.length => jArray.getLength(receiver)
244-
case nme.apply => jArray.get(receiver, args(0).asInstanceOf[Int])
245-
case nme.update => jArray.set(receiver, args(0).asInstanceOf[Int], args(1))
246-
case _ => assert(false, s"unexpected array method: $symbol")
247-
}
248-
else
249-
jmeth.invoke(receiver, args.asInstanceOf[Seq[AnyRef]]: _*)
278+
279+
def jinvoke(jmeth: jMethod, receiver: Any, args: Seq[Any]): Any = {
280+
val result = jmeth.invoke(receiver, args.asInstanceOf[Seq[AnyRef]]: _*)
281+
if (jmeth.getReturnType == java.lang.Void.TYPE) ()
282+
else result
283+
}
284+
250285
override def toString = s"method mirror for ${showMethodSig(symbol)} (bound to $receiver)"
251286
}
252287

288+
private class JavaVanillaMethodMirror(val receiver: Any, symbol: MethodSymbol)
289+
extends JavaMethodMirror(symbol) {
290+
def apply(args: Any*): Any = jinvoke(jmeth, receiver, args)
291+
}
292+
293+
private class JavaByNameMethodMirror(val receiver: Any, symbol: MethodSymbol)
294+
extends JavaMethodMirror(symbol) {
295+
def apply(args: Any*): Any = {
296+
val transformed = map2(args.toList, symbol.params.flatten)((arg, param) => if (isByNameParamType(param.info)) () => arg else arg)
297+
jinvoke(jmeth, receiver, transformed)
298+
}
299+
}
300+
301+
private class JavaMagicMethodMirror[T: ClassTag](val receiver: T, symbol: MethodSymbol)
302+
extends JavaMethodMirror(symbol) {
303+
def apply(args: Any*): Any = {
304+
// checking type conformance is too much of a hassle, so we don't do it here
305+
// actually it's not even necessary, because we manually dispatch arguments to magic methods below
306+
val params = symbol.paramss.flatten
307+
val perfectMatch = args.length == params.length
308+
// todo. this doesn't account for multiple vararg parameter lists
309+
// however those aren't supported by the mirror API: https://issues.scala-lang.org/browse/SI-6182
310+
// hence I leave this code as is, to be fixed when the corresponding bug is fixed
311+
val varargMatch = args.length >= params.length - 1 && isVarArgsList(params)
312+
if (!perfectMatch && !varargMatch) {
313+
val n_arguments = if (isVarArgsList(params)) s"${params.length - 1} or more" else s"${params.length}"
314+
var s_arguments = if (params.length == 1 && !isVarArgsList(params)) "argument" else "arguments"
315+
throw new ScalaReflectionException(s"${showMethodSig(symbol)} takes $n_arguments $s_arguments")
316+
}
317+
318+
def objReceiver = receiver.asInstanceOf[AnyRef]
319+
def objArg0 = args(0).asInstanceOf[AnyRef]
320+
def objArgs = args.asInstanceOf[Seq[AnyRef]]
321+
def fail(msg: String) = throw new ScalaReflectionException(msg + ", it cannot be invoked with mirrors")
322+
323+
def invokeMagicPrimitiveMethod = {
324+
val jmeths = classOf[BoxesRunTime].getDeclaredMethods.filter(_.getName == nme.primitiveMethodName(symbol.name).toString)
325+
assert(jmeths.length == 1, jmeths.toList)
326+
jinvoke(jmeths.head, null, objReceiver +: objArgs)
327+
}
328+
329+
symbol match {
330+
case Any_== | Object_== => ScalaRunTime.inlinedEquals(objReceiver, objArg0)
331+
case Any_!= | Object_!= => !ScalaRunTime.inlinedEquals(objReceiver, objArg0)
332+
case Any_## | Object_## => ScalaRunTime.hash(objReceiver)
333+
case Any_equals => receiver.equals(objArg0)
334+
case Any_hashCode => receiver.hashCode
335+
case Any_toString => receiver.toString
336+
case Object_eq => objReceiver eq objArg0
337+
case Object_ne => objReceiver ne objArg0
338+
case Object_synchronized => objReceiver.synchronized(objArg0)
339+
case sym if isGetClass(sym) => preciseClass(receiver)
340+
case Any_asInstanceOf => fail("Any.asInstanceOf requires a type argument")
341+
case Any_isInstanceOf => fail("Any.isInstanceOf requires a type argument")
342+
case Object_asInstanceOf => fail("AnyRef.$asInstanceOf is an internal method")
343+
case Object_isInstanceOf => fail("AnyRef.$isInstanceOf is an internal method")
344+
case Array_length => ScalaRunTime.array_length(objReceiver)
345+
case Array_apply => ScalaRunTime.array_apply(objReceiver, args(0).asInstanceOf[Int])
346+
case Array_update => ScalaRunTime.array_update(objReceiver, args(0).asInstanceOf[Int], args(1))
347+
case Array_clone => ScalaRunTime.array_clone(objReceiver)
348+
case sym if isStringConcat(sym) => receiver.toString + objArg0
349+
case sym if isMagicPrimitiveMethod(sym) => invokeMagicPrimitiveMethod
350+
case sym if sym == Predef_classOf => fail("Predef.classOf is a compile-time function")
351+
case sym if sym.isTermMacro => fail(s"${symbol.fullName} is a macro, i.e. a compile-time function")
352+
case _ => assert(false, this)
353+
}
354+
}
355+
}
356+
253357
private class JavaConstructorMirror(val outer: AnyRef, val symbol: MethodSymbol)
254358
extends MethodMirror {
255359
override val receiver = outer
@@ -259,6 +363,9 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym
259363
jconstr
260364
}
261365
def apply(args: Any*): Any = {
366+
if (symbol.owner == ArrayClass)
367+
throw new ScalaReflectionException("Cannot instantiate arrays with mirrors. Consider using `scala.reflect.ClassTag(<class of element>).newArray(<length>)` instead")
368+
262369
val effectiveArgs =
263370
if (outer == null) args.asInstanceOf[Seq[AnyRef]]
264371
else outer +: args.asInstanceOf[Seq[AnyRef]]
@@ -279,7 +386,11 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym
279386
extends JavaTemplateMirror with ClassMirror {
280387
def erasure = symbol
281388
def isStatic = false
282-
def reflectConstructor(constructor: MethodSymbol) = new JavaConstructorMirror(outer, constructor)
389+
def reflectConstructor(constructor: MethodSymbol) = {
390+
if (!constructor.isClassConstructor) ErrorNotConstructor(constructor, symbol)
391+
if (!symbol.info.decls.toList.contains(constructor)) ErrorNotConstructor(constructor, symbol)
392+
new JavaConstructorMirror(outer, constructor)
393+
}
283394
def companion: Option[ModuleMirror] = symbol.companionModule match {
284395
case module: ModuleSymbol => Some(new JavaModuleMirror(outer, module))
285396
case _ => None
@@ -1067,7 +1178,7 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym
10671178
mirrors(rootToLoader getOrElseUpdate(root, findLoader)).get.get
10681179
}
10691180

1070-
private lazy val magicSymbols: Map[(String, Name), Symbol] = {
1181+
private lazy val magicClasses: Map[(String, Name), Symbol] = {
10711182
def mapEntry(sym: Symbol): ((String, Name), Symbol) = (sym.owner.fullName, sym.name) -> sym
10721183
Map() ++ (definitions.magicSymbols filter (_.isType) map mapEntry)
10731184
}
@@ -1088,7 +1199,7 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym
10881199
if (name.isTermName && !owner.isEmptyPackageClass)
10891200
return mirror.makeScalaPackage(
10901201
if (owner.isRootSymbol) name.toString else owner.fullName+"."+name)
1091-
magicSymbols get (owner.fullName, name) match {
1202+
magicClasses get (owner.fullName, name) match {
10921203
case Some(tsym) =>
10931204
owner.info.decls enter tsym
10941205
return tsym

0 commit comments

Comments
 (0)