From 628c0265aaeaab8ef84dfc623d45119972f83656 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 20 Jul 2011 16:39:39 +0000 Subject: Fleshed out reflection that now also correctly ... Fleshed out reflection that now also correctly interpretes Java classes. We are getting there! No review; let's wait until it is complete. --- .../scala/reflect/runtime/ConversionUtil.scala | 29 ++- .../scala/reflect/runtime/JavaToScala.scala | 202 ++++++++++++++++++--- src/compiler/scala/reflect/runtime/Loaders.scala | 16 +- src/compiler/scala/reflect/runtime/Universe.scala | 3 + 4 files changed, 213 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/reflect/runtime/ConversionUtil.scala b/src/compiler/scala/reflect/runtime/ConversionUtil.scala index e406f99cdf..4cebcb8ed6 100644 --- a/src/compiler/scala/reflect/runtime/ConversionUtil.scala +++ b/src/compiler/scala/reflect/runtime/ConversionUtil.scala @@ -4,7 +4,7 @@ package runtime import java.lang.{Class => jClass, Package => jPackage} import java.lang.reflect.{ Method => jMethod, Constructor => jConstructor, Modifier => jModifier, Field => jField, - Member => jMember, Type => jType, GenericDeclaration} + Member => jMember, Type => jType, TypeVariable => jTypeVariable, GenericDeclaration} import collection.mutable.HashMap trait ConversionUtil extends internal.transform.Transforms { self: Universe => @@ -13,17 +13,37 @@ trait ConversionUtil extends internal.transform.Transforms { self: Universe => * and Scala reflection type `S`. */ protected class TwoWayCache[J, S] { + private val toScalaMap = new HashMap[J, S] private val toJavaMap = new HashMap[S, J] - def toScala(key: J)(body: => S): S = toScalaMap.getOrElseUpdate(key, body) + def enter(j: J, s: S) = { + toScalaMap(j) = s + toJavaMap(s) = j + } - def toJava(key: S)(body: => J): J = toJavaMap.getOrElseUpdate(key, body) + def toScala(key: J)(body: => S): S = toScalaMap get key match { + case Some(v) => + v + case none => + val result = body + enter(key, result) + result + } + + def toJava(key: S)(body: => J): J = toJavaMap get key match { + case Some(v) => + v + case none => + val result = body + enter(result, key) + result + } def toJavaOption(key: S)(body: => Option[J]): Option[J] = toJavaMap get key match { case None => val result = body - for (value <- result) toJavaMap(key) = value + for (value <- result) enter(value, key) result case some => some } @@ -34,6 +54,7 @@ trait ConversionUtil extends internal.transform.Transforms { self: Universe => protected val methodCache = new TwoWayCache[jMethod, Symbol] protected val constructorCache = new TwoWayCache[jConstructor[_], Symbol] protected val fieldCache = new TwoWayCache[jField, Symbol] + protected val tparamCache = new TwoWayCache[jTypeVariable[_], Symbol] def typeToJavaClass(tpe: Type): jClass[_] diff --git a/src/compiler/scala/reflect/runtime/JavaToScala.scala b/src/compiler/scala/reflect/runtime/JavaToScala.scala index e67dbd3ce4..833b42529d 100644 --- a/src/compiler/scala/reflect/runtime/JavaToScala.scala +++ b/src/compiler/scala/reflect/runtime/JavaToScala.scala @@ -4,19 +4,22 @@ package runtime import java.lang.{Class => jClass, Package => jPackage} import java.lang.reflect.{ Method => jMethod, Constructor => jConstructor, Modifier => jModifier, Field => jField, - Member => jMember, Type => jType, GenericDeclaration} + Member => jMember, Type => jType, TypeVariable => jTypeVariable, + GenericDeclaration, ParameterizedType, WildcardType, AnnotatedElement} import internal.pickling.ByteCodecs import internal.ClassfileConstants._ import internal.pickling.UnPickler -import collection.mutable.HashMap +import collection.mutable.{HashMap, ListBuffer} +import internal.Flags._ trait JavaToScala extends ConversionUtil { self: Universe => + import definitions._ + private object unpickler extends UnPickler { val global: JavaToScala.this.type = self } - /** Generate types for top-level Scala root class and root companion object * from the pickled information stored in a corresponding Java class * @param clazz The top-level Scala class for which info is unpickled @@ -46,23 +49,80 @@ trait JavaToScala extends ConversionUtil { self: Universe => unpickler.unpickle(bytes, 0, clazz, module, jclazz.getName) } else { // class does not have a Scala signature; it's a Java class println("no sig found for "+jclazz) - copyMembers(clazz, module, jclazz) + initClassModule(clazz, module, new FromJavaClassCompleter(clazz, module, jclazz)) } } } - /** Generate types for top-level Scala root class and root companion object - * by copying corresponding types from a Java class. This method used + /** A fresh Scala type parameter that corresponds to a Java type variable. + * The association between Scala type parameter and Java type variable is entered in the cache. + * @param jtvar The Java type variable + */ + private def createTypeParameter(jtvar: jTypeVariable[_ <: GenericDeclaration]): Symbol = { + val tparam = sOwner(jtvar).newTypeParameter(NoPosition, newTypeName(jtvar.getName)) + .setInfo(new TypeParamCompleter(jtvar)) + tparamCache enter (jtvar, tparam) + tparam + } + + /** A completer that fills in the type of a Scala type parameter from the bounds of a Java type variable. + * @param jtvar The Java type variable + */ + private class TypeParamCompleter(jtvar: jTypeVariable[_ <: GenericDeclaration]) extends LazyType { + override def complete(sym: Symbol) = { + sym setInfo TypeBounds(NothingClass.tpe, lub(jtvar.getBounds.toList map typeToScala)) + } + } + + /** Copy all annotations of Java annotated element `jann` over to Scala symbol `sym`. + * Pre: `sym` is already initialized with a concrete type. + * Note: If `sym` is a method or constructor, its parameter annotations are copied as well. + */ + private def copyAnnotations(sym: Symbol, jann: AnnotatedElement) { + // to do: implement + } + + /** A completer that fills in the types of a Scala class and its companion object + * by copying corresponding type info from a Java class. This completer is used * to reflect classes in Scala that do not have a Scala pickle info, be it * because they are local classes or have been compiled from Java sources. - * @param clazz The top-level Scala class for which info is copied - * @param module The top-level Scala companion object for which info is copied + * @param clazz The Scala class for which info is copied + * @param module The Scala companion object for which info is copied * @param jclazz The Java class */ - def copyMembers(clazz: Symbol, module: Symbol, jclazz: jClass[_]) { - clazz setInfo new ClassInfoType(List(), newScope, clazz) - module.moduleClass setInfo new ClassInfoType(List(), newScope, module.moduleClass) - module setInfo module.moduleClass.tpe + private class FromJavaClassCompleter(clazz: Symbol, module: Symbol, jclazz: jClass[_]) extends LazyType { + override def complete(sym: Symbol) = { + println("completing from Java "+sym+"/"+clazz.fullName) + assert(sym == clazz || sym == module || sym == module.moduleClass, sym) + val flags = toScalaFlags(jclazz.getModifiers, isClass = true) + clazz setFlag (flags | JAVA) + module setFlag (flags & PRIVATE | JAVA) + module.moduleClass setFlag (flags & PRIVATE | JAVA) + + copyAnnotations(clazz, jclazz) + // to do: annotations to set also for module? + + val tparams = jclazz.getTypeParameters.toList map createTypeParameter + + val jsuperclazz = jclazz.getGenericSuperclass + val superclazz = if (jsuperclazz == null) AnyClass.tpe else typeToScala(jsuperclazz) + val parents = superclazz :: (jclazz.getGenericInterfaces.toList map typeToScala) + clazz setInfo polyType(tparams, new ClassInfoType(parents, newScope, clazz)) + module.moduleClass setInfo new ClassInfoType(List(), newScope, module.moduleClass) + module setInfo module.moduleClass.tpe + + def enter(sym: Symbol) = + (if (sym.isStatic) module.moduleClass else clazz).info.decls enter sym + + for (jfield <- jclazz.getDeclaredFields) + enter(jfieldAsScala(jfield)) + + for (jmeth <- jclazz.getDeclaredMethods) + enter(jmethodAsScala(jmeth)) + + for (jconstr <- jclazz.getConstructors) + enter(jconstrAsScala(jconstr)) + } } /** If Java modifiers `mods` contain STATIC, return the module class @@ -78,8 +138,10 @@ trait JavaToScala extends ConversionUtil { self: Universe => followStatic(classToScala(jclazz.getEnclosingClass), jclazz.getModifiers) else if (jclazz.isLocalClass) methodToScala(jclazz.getEnclosingMethod) orElse constrToScala(jclazz.getEnclosingConstructor) - else + else { + assert(jclazz.getPackage != null, jclazz) packageToScala(jclazz.getPackage) + } } /** The Scala owner of the Scala symbol corresponding to the Java member `jmember` @@ -88,6 +150,11 @@ trait JavaToScala extends ConversionUtil { self: Universe => followStatic(classToScala(jmember.getDeclaringClass), jmember.getModifiers) } + /** The Scala owner of the Scala type parameter corresponding to the Java type variable `jtvar` + */ + private def sOwner(jtvar: jTypeVariable[_ <: GenericDeclaration]): Symbol = + genericDeclarationToScala(jtvar.getGenericDeclaration) + /** Returns `true` if Scala name `name` equals Java name `jstr`, possibly after * make-not-private expansion. */ @@ -145,7 +212,7 @@ trait JavaToScala extends ConversionUtil { self: Universe => */ private def makeScalaPackage(fullname: String): Symbol = { val split = fullname lastIndexOf '.' - val owner = if (split > 0) packageNameToScala(fullname take split) else definitions.RootClass + val owner = if (split > 0) packageNameToScala(fullname take split) else RootClass assert(owner.isModuleClass) val name = fullname drop (split + 1) val pkg = owner.info decl newTermName(name) @@ -164,14 +231,77 @@ trait JavaToScala extends ConversionUtil { self: Universe => } else if (jclazz.isLocalClass) { // local classes not preserved by unpickling - treat as Java jclassAsScala(jclazz) } else { // jclazz is top-level - get signature - val (clazz, module) = createClassModule(sOwner(jclazz), newTypeName(jclazz.getSimpleName)) + val (clazz, module) = createClassModule( + sOwner(jclazz), newTypeName(jclazz.getSimpleName), new TopClassCompleter(_, _)) clazz } } + /** The Scala type parameter that corresponds to a given Java type parameter. + * @param jparam The Java type parameter + * @return A Scala type parameter symbol that has the same owner and name as the Java type parameter + */ + def tparamToScala(jparam: jTypeVariable[_ <: GenericDeclaration]): Symbol = tparamCache.toScala(jparam) { + val owner = genericDeclarationToScala(jparam.getGenericDeclaration) + owner.info match { + case PolyType(tparams, _) => tparams.find(_.name.toString == jparam.getName).get + } + } + + /** The Scala symbol that corresponds to a given Java generic declaration (class, method, or constructor) + */ + def genericDeclarationToScala(jdecl: GenericDeclaration) = jdecl match { + case jclazz: jClass[_] => classToScala(jclazz) + case jmeth: jMethod => methodToScala(jmeth) + case jconstr: jConstructor[_] => constrToScala(jconstr) + } + + /** Given some Java type arguments, a corresponding list of Scala types, plus potentially + * some existentially bound type variables that represent wildcard arguments. + */ + private def targsToScala(owner: Symbol, args: List[jType]): (List[Type], List[Symbol]) = { + val tparams = new ListBuffer[Symbol] + def targToScala(arg: jType): Type = arg match { + case jwild: WildcardType => + val tparam = owner.newExistential(NoPosition, newTypeName("T$"+tparams.length)) + .setInfo(TypeBounds( + lub(jwild.getLowerBounds.toList map typeToScala), + glb(jwild.getUpperBounds.toList map typeToScala))) + tparams += tparam + typeRef(NoPrefix, tparam, List()) + case _ => + typeToScala(arg) + } + (args map targToScala, tparams.toList) + } + /** The Scala type that corresponds to given Java type (to be done) */ - def jtypeToScala(tpe: jType): Type = NoType + def typeToScala(jtpe: jType): Type = jtpe match { + case java.lang.Void.TYPE => UnitClass.tpe + case java.lang.Byte.TYPE => ByteClass.tpe + case java.lang.Character.TYPE => CharClass.tpe + case java.lang.Short.TYPE => ShortClass.tpe + case java.lang.Integer.TYPE => IntClass.tpe + case java.lang.Long.TYPE => LongClass.tpe + case java.lang.Float.TYPE => FloatClass.tpe + case java.lang.Double.TYPE => DoubleClass.tpe + case java.lang.Boolean.TYPE => BooleanClass.tpe + case jclazz: jClass[_] => + val clazz = classToScala(jclazz) + rawToExistential(typeRef(clazz.owner.thisType, clazz, List())) + case japplied: ParameterizedType => + val (pre, sym) = typeToScala(japplied.getRawType) match { + case ExistentialType(tparams, TypeRef(pre, sym, _)) => (pre, sym) + case TypeRef(pre, sym, _) => (pre, sym) + } + val args0 = japplied.getActualTypeArguments + val (args, bounds) = targsToScala(pre.typeSymbol, args0.toList) + ExistentialType(bounds, typeRef(pre, sym, args)) + case jtvar: jTypeVariable[_] => + val tparam = tparamToScala(jtvar) + typeRef(NoPrefix, tparam, List()) + } /** The Scala class that corresponds to given Java class without taking * Scala pickling info into account. @@ -179,9 +309,8 @@ trait JavaToScala extends ConversionUtil { self: Universe => * @return A Scala class symbol that wraps all reflection info of `jclazz` */ private def jclassAsScala(jclazz: jClass[_]): Symbol = { - val (clazz, module) = createClassModule(sOwner(jclazz), newTypeName(jclazz.getSimpleName)) - // fill in clazz, module from jclazz - copyMembers(clazz, module, jclazz) + val (clazz, module) = createClassModule( + sOwner(jclazz), newTypeName(jclazz.getSimpleName), new FromJavaClassCompleter(_, _, jclazz)) clazz } @@ -191,10 +320,11 @@ trait JavaToScala extends ConversionUtil { self: Universe => * @return A Scala value symbol that wraps all reflection info of `jfield` */ private def jfieldAsScala(jfield: jField): Symbol = fieldCache.toScala(jfield) { - sOwner(jfield).newValue(NoPosition, newTermName(jfield.getName)) - .setFlag(toScalaFlags(jfield.getModifiers, isClass = false)) - .setInfo(jtypeToScala(jfield.getGenericType)) - // todo: copy annotations + val field = sOwner(jfield).newValue(NoPosition, newTermName(jfield.getName)) + .setFlag(toScalaFlags(jfield.getModifiers, isClass = false) | JAVA) + .setInfo(typeToScala(jfield.getGenericType)) + copyAnnotations(field, jfield) + field } /** The Scala method that corresponds to given Java method without taking @@ -202,14 +332,32 @@ trait JavaToScala extends ConversionUtil { self: Universe => * @param jmeth The Java method * @return A Scala method symbol that wraps all reflection info of `jmethod` */ - private def jmethodAsScala(jmeth: jMethod): Symbol = NoSymbol // to be done + private def jmethodAsScala(jmeth: jMethod): Symbol = methodCache.toScala(jmeth) { + val clazz = sOwner(jmeth) + val meth = clazz.newMethod(NoPosition, newTermName(jmeth.getName)) + .setFlag(toScalaFlags(jmeth.getModifiers, isClass = false) | JAVA) + val tparams = jmeth.getTypeParameters.toList map createTypeParameter + val paramtpes = jmeth.getGenericParameterTypes.toList map typeToScala + val resulttpe = typeToScala(jmeth.getGenericReturnType) + meth setInfo polyType(tparams, MethodType(clazz.newSyntheticValueParams(paramtpes), resulttpe)) + copyAnnotations(meth, jmeth) + meth + } /** The Scala constructor that corresponds to given Java constructor without taking * Scala pickling info into account. * @param jconstr The Java constructor * @return A Scala constructor symbol that wraps all reflection info of `jconstr` */ - private def jconstrAsScala(jconstr: jConstructor[_]): Symbol = NoSymbol // to be done - - + private def jconstrAsScala(jconstr: jConstructor[_]): Symbol = { + // [Martin] Note: I know there's a lot of duplication wrt jmethodAsScala, but don't think it's worth it to factor this out. + val clazz = sOwner(jconstr) + val meth = clazz.newMethod(NoPosition, nme.CONSTRUCTOR) + .setFlag(toScalaFlags(jconstr.getModifiers, isClass = false) | JAVA) + val tparams = jconstr.getTypeParameters.toList map createTypeParameter + val paramtpes = jconstr.getGenericParameterTypes.toList map typeToScala + meth setInfo polyType(tparams, MethodType(clazz.newSyntheticValueParams(paramtpes), clazz.tpe)) + copyAnnotations(meth, jconstr) + meth + } } \ No newline at end of file diff --git a/src/compiler/scala/reflect/runtime/Loaders.scala b/src/compiler/scala/reflect/runtime/Loaders.scala index cd608cd471..bb2f628041 100644 --- a/src/compiler/scala/reflect/runtime/Loaders.scala +++ b/src/compiler/scala/reflect/runtime/Loaders.scala @@ -51,19 +51,23 @@ trait Loaders { self: Universe => * and initialize with a lazy type completer. * @param owner The owner of the newly created class and object * @param name The simple name of the newly created class + * @param completer The completer to be used to set the info of the class and the module */ - protected def createClassModule(owner: Symbol, name: TypeName) = { + protected def createClassModule(owner: Symbol, name: TypeName, completer: (Symbol, Symbol) => LazyType) = { val clazz = owner.newClass(NoPosition, name) val module = owner.newModule(NoPosition, name.toTermName) owner.info.decls enter clazz owner.info.decls enter module - val classCompleter = new TopClassCompleter(clazz, module) - clazz.setInfo(classCompleter) - module.setInfo(classCompleter) - module.moduleClass.setInfo(classCompleter) + initClassModule(clazz, module, completer(clazz, module)) (clazz, module) } + protected def initClassModule(clazz: Symbol, module: Symbol, completer: LazyType) = { + clazz.setInfo(completer) + module.setInfo(completer) + module.moduleClass.setInfo(completer) + } + /** The type for packages. * Since we cannot search the file system for classes in directories to populate * a package, we do the following instead: For every member that is looked up in @@ -77,7 +81,7 @@ trait Loaders { self: Universe => assert(this eq pkg.info, this+" "+pkg.info) assert(decls eq pkg.info.decls) println("creating "+name+" in "+pkg) - val (clazz, module) = createClassModule(pkg, name.toTypeName) + val (clazz, module) = createClassModule(pkg, name.toTypeName, new TopClassCompleter(_, _)) if (name.isTypeName) clazz else module } override def member(name: Name): Symbol = decl(name) diff --git a/src/compiler/scala/reflect/runtime/Universe.scala b/src/compiler/scala/reflect/runtime/Universe.scala index bc295694be..edabd4eb84 100644 --- a/src/compiler/scala/reflect/runtime/Universe.scala +++ b/src/compiler/scala/reflect/runtime/Universe.scala @@ -36,6 +36,9 @@ class Universe extends internal.SymbolTable with JavaToScala with ScalaToJava wi type Position = String // source file? val NoPosition = "" + + // establish root association to avoid cyclic dependency errors later + classToScala(classOf[java.lang.Object]).initialize } object Universe extends Universe -- cgit v1.2.3