From aa6ebe938639f07dd6f5612e645f1449f37a86eb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Jan 2017 16:02:09 +0700 Subject: Implement structural type member access New scheme for implementing structural type member access. --- library/src/scala/Projector.scala | 10 +++++ library/src/scala/reflect/Projector.scala | 71 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 library/src/scala/Projector.scala create mode 100644 library/src/scala/reflect/Projector.scala (limited to 'library') diff --git a/library/src/scala/Projector.scala b/library/src/scala/Projector.scala new file mode 100644 index 000000000..cff73f84d --- /dev/null +++ b/library/src/scala/Projector.scala @@ -0,0 +1,10 @@ +package scala +import scala.reflect.ClassTag +import scala.annotation.implicitNotFound + +@implicitNotFound("no projector instance found to implement reflective access to structural type ${T}") +trait Projector[-T] extends Any { + def get(receiver: T, name: String): Any + def getMethod(receiver: T, name: String, paramClasses: ClassTag[_]*): Any = + new UnsupportedOperationException("getMethod") +} diff --git a/library/src/scala/reflect/Projector.scala b/library/src/scala/reflect/Projector.scala new file mode 100644 index 000000000..68240da76 --- /dev/null +++ b/library/src/scala/reflect/Projector.scala @@ -0,0 +1,71 @@ +package scala.reflect + +class Projector extends scala.Projector[Any] { + import Projector._ + def get(receiver: Any, name: String): Any = { + val rcls = receiver.getClass + try { + val fld = rcls.getField(name) + fld.get(receiver) + } + catch { + case ex: NoSuchFieldError => + getMethod(receiver, name).asInstanceOf[() => Any]() + } + } + + override def getMethod(receiver: Any, name: String, paramTypes: ClassTag[_]*): Any = { + val rcls = receiver.getClass + val paramClasses = paramTypes.map(_.runtimeClass) + val mth = rcls.getMethod(name, paramClasses: _*) + paramTypes.length match { + case 0 => () => + mth.invoke(receiver) + case 1 => (x0: Any) => + mth.invoke(receiver, x0.asInstanceOf[Object]) + case 2 => (x0: Any, x1: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object]) + case 3 => (x0: Any, x1: Any, x2: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object], + x2.asInstanceOf[Object]) + case 4 => (x0: Any, x1: Any, x2: Any, x3: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object], + x2.asInstanceOf[Object], + x3.asInstanceOf[Object]) + case 5 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object], + x2.asInstanceOf[Object], + x3.asInstanceOf[Object], + x4.asInstanceOf[Object]) + case 6 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any, x5: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object], + x2.asInstanceOf[Object], + x3.asInstanceOf[Object], + x4.asInstanceOf[Object], + x5.asInstanceOf[Object]) + case 7 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any, x5: Any, x6: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object], + x2.asInstanceOf[Object], + x3.asInstanceOf[Object], + x4.asInstanceOf[Object], + x5.asInstanceOf[Object], + x6.asInstanceOf[Object]) + } + } +} + +object Projector { + implicit def reflectiveProjector: scala.Projector[Any] = new Projector +} \ No newline at end of file -- cgit v1.2.3 From 7e3f69ace983adfbc9bbf44954f9a5845add3d43 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Jan 2017 18:06:41 +0700 Subject: Cleanups --- compiler/src/dotty/tools/dotc/typer/Dynamic.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 1 - library/src/scala/reflect/Projector.scala | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) (limited to 'library') diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 7cfd6327b..4b7584559 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -116,11 +116,11 @@ trait Dynamic { self: Typer with Applications => * * If `U` is a value type, map `x.a` to the equivalent of: * - * implicitly[Projection[T]].get(x, "a").asInstanceOf[U] + * implicitly[Projector[T]].get(x, "a").asInstanceOf[U] * * If `U` is a method type (T1,...,Tn)R, map `x.a` to the equivalent of: * - * implicitly[Projection[T]].getMethod(x, "a")(CT1, ..., CTn).asInstanceOf[(T1,...,Tn) => R] + * implicitly[Projector[T]].getMethod(x, "a")(CT1, ..., CTn).asInstanceOf[(T1,...,Tn) => R] * * where CT1,...,CTn are the classtags representing the erasure of T1,...,Tn. * diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4c238518e..ec4c135f7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1805,7 +1805,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case Apply(_, _) => " more" case _ => "" } - println(i"tree = $tree, pt = $pt") errorTree(tree, em"$methodStr does not take$more parameters") } } diff --git a/library/src/scala/reflect/Projector.scala b/library/src/scala/reflect/Projector.scala index 68240da76..b5732ee17 100644 --- a/library/src/scala/reflect/Projector.scala +++ b/library/src/scala/reflect/Projector.scala @@ -67,5 +67,5 @@ class Projector extends scala.Projector[Any] { } object Projector { - implicit def reflectiveProjector: scala.Projector[Any] = new Projector + implicit val reflectiveProjector: scala.Projector[Any] = new Projector } \ No newline at end of file -- cgit v1.2.3 From 69feaa89167ebeb708535dd4abf3a79a410130f0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 7 Jan 2017 14:47:18 +0700 Subject: Change scheme to use Selectable Use base types instead of implicits. This is more robust in the presence of type abstraction. --- .../src/dotty/tools/dotc/core/Definitions.scala | 3 +- compiler/src/dotty/tools/dotc/core/StdNames.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Dynamic.scala | 41 ++++++------ library/src/scala/Projector.scala | 10 --- library/src/scala/Selectable.scala | 8 +++ library/src/scala/reflect/Projector.scala | 71 --------------------- library/src/scala/reflect/Selectable.scala | 73 ++++++++++++++++++++++ tests/pos/zoo2.scala | 1 + tests/run/structural.scala | 16 ++--- tests/run/structuralNoSuchMethod.scala | 2 +- 10 files changed, 110 insertions(+), 117 deletions(-) delete mode 100644 library/src/scala/Projector.scala create mode 100644 library/src/scala/Selectable.scala delete mode 100644 library/src/scala/reflect/Projector.scala create mode 100644 library/src/scala/reflect/Selectable.scala (limited to 'library') diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index c900f0b64..6e67087a3 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -508,8 +508,7 @@ class Definitions { lazy val LanguageModuleRef = ctx.requiredModule("scala.language") def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl") - lazy val ProjectorType: TypeRef = ctx.requiredClassRef("scala.Projector") - def ProjectorClass(implicit ctx: Context) = ProjectorType.symbol.asClass + lazy val SelectableType: TypeRef = ctx.requiredClassRef("scala.Selectable") lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag") def ClassTagClass(implicit ctx: Context) = ClassTagType.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index f53571bdc..4a9c50dad 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -414,7 +414,6 @@ object StdNames { val genericArrayOps: N = "genericArrayOps" val get: N = "get" val getClass_ : N = "getClass" - val getMethod : N = "getMethod" val getOrElse: N = "getOrElse" val hasNext: N = "hasNext" val hashCode_ : N = "hashCode" @@ -482,6 +481,7 @@ object StdNames { val sameElements: N = "sameElements" val scala_ : N = "scala" val selectDynamic: N = "selectDynamic" + val selectDynamicMethod: N = "selectDynamicMethod" val selectOverloadedMethod: N = "selectOverloadedMethod" val selectTerm: N = "selectTerm" val selectType: N = "selectType" diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 4b7584559..49dd98523 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -33,8 +33,8 @@ object Dynamic { * * The first matching rule of is applied. * - * 2. Translates member seclections on structural types by means of an implicit - * Projector instance. @See handleStructural. + * 2. Translates member selections on structural types to calls of `selectDynamic` + * or `selectDynamicMethod` on a `Selectable` instance. @See handleStructural. * */ trait Dynamic { self: Typer with Applications => @@ -116,44 +116,45 @@ trait Dynamic { self: Typer with Applications => * * If `U` is a value type, map `x.a` to the equivalent of: * - * implicitly[Projector[T]].get(x, "a").asInstanceOf[U] + * (x: Selectable).selectDynamic(x, "a").asInstanceOf[U] * * If `U` is a method type (T1,...,Tn)R, map `x.a` to the equivalent of: * - * implicitly[Projector[T]].getMethod(x, "a")(CT1, ..., CTn).asInstanceOf[(T1,...,Tn) => R] + * (x: Selectable).selectDynamicMethod(x, "a")(CT1, ..., CTn).asInstanceOf[(T1,...,Tn) => R] * - * where CT1,...,CTn are the classtags representing the erasure of T1,...,Tn. + * where CT1,...,CTn are the class tags representing the erasure of T1,...,Tn. * - * The small print: (1) T is forced to be fully defined. (2) It's an error if - * U is neither a value nor a method type, or a dependent method type, or of too - * large arity (limit is Definitions.MaxStructuralMethodArity). + * It's an error if U is neither a value nor a method type, or a dependent method + * type, or of too large arity (limit is Definitions.MaxStructuralMethodArity). */ def handleStructural(tree: Tree)(implicit ctx: Context): Tree = { val Select(qual, name) = tree - def issueError(msgFn: String => String): Unit = ctx.error(msgFn("reflective call"), tree.pos) - def implicitArg(tpe: Type) = inferImplicitArg(tpe, issueError, tree.pos.endPos) - val projector = implicitArg(defn.ProjectorType.appliedTo(qual.tpe.widen)) - - def structuralCall(getterName: TermName, formals: List[Tree]) = { + def structuralCall(selectorName: TermName, formals: List[Tree]) = { + val selectable = adapt(qual, defn.SelectableType) val scall = untpd.Apply( - untpd.TypedSplice(projector.select(getterName)), - (qual :: Literal(Constant(name.toString)) :: formals).map(untpd.TypedSplice(_))) + untpd.TypedSplice(selectable.select(selectorName)), + (Literal(Constant(name.toString)) :: formals).map(untpd.TypedSplice(_))) typed(scall) } + def fail(reason: String) = errorTree(tree, em"Structural access not allowed on method $name because it $reason") - fullyDefinedType(tree.tpe.widen, "structural access", tree.pos) match { + + tree.tpe.widen match { case tpe: MethodType => if (tpe.isDependent) fail(i"has a dependent method type") else if (tpe.paramNames.length > Definitions.MaxStructuralMethodArity) fail(i"takes too many parameters") - val ctags = tpe.paramTypes.map(pt => - implicitArg(defn.ClassTagType.appliedTo(pt :: Nil))) - structuralCall(nme.getMethod, ctags).asInstance(tpe.toFunctionType()) + else { + def issueError(msgFn: String => String): Unit = ctx.error(msgFn(""), tree.pos) + val ctags = tpe.paramTypes.map(pt => + inferImplicitArg(defn.ClassTagType.appliedTo(pt :: Nil), issueError, tree.pos.endPos)) + structuralCall(nme.selectDynamicMethod, ctags).asInstance(tpe.toFunctionType()) + } case tpe: ValueType => - structuralCall(nme.get, Nil).asInstance(tpe) + structuralCall(nme.selectDynamic, Nil).asInstance(tpe) case tpe: PolyType => fail("is polymorphic") case tpe => diff --git a/library/src/scala/Projector.scala b/library/src/scala/Projector.scala deleted file mode 100644 index cff73f84d..000000000 --- a/library/src/scala/Projector.scala +++ /dev/null @@ -1,10 +0,0 @@ -package scala -import scala.reflect.ClassTag -import scala.annotation.implicitNotFound - -@implicitNotFound("no projector instance found to implement reflective access to structural type ${T}") -trait Projector[-T] extends Any { - def get(receiver: T, name: String): Any - def getMethod(receiver: T, name: String, paramClasses: ClassTag[_]*): Any = - new UnsupportedOperationException("getMethod") -} diff --git a/library/src/scala/Selectable.scala b/library/src/scala/Selectable.scala new file mode 100644 index 000000000..c5c714ca9 --- /dev/null +++ b/library/src/scala/Selectable.scala @@ -0,0 +1,8 @@ +package scala +import scala.reflect.ClassTag + +trait Selectable extends Any { + def selectDynamic(name: String): Any + def selectDynamicMethod(name: String, paramClasses: ClassTag[_]*): Any = + new UnsupportedOperationException("selectDynamicMethod") +} diff --git a/library/src/scala/reflect/Projector.scala b/library/src/scala/reflect/Projector.scala deleted file mode 100644 index b5732ee17..000000000 --- a/library/src/scala/reflect/Projector.scala +++ /dev/null @@ -1,71 +0,0 @@ -package scala.reflect - -class Projector extends scala.Projector[Any] { - import Projector._ - def get(receiver: Any, name: String): Any = { - val rcls = receiver.getClass - try { - val fld = rcls.getField(name) - fld.get(receiver) - } - catch { - case ex: NoSuchFieldError => - getMethod(receiver, name).asInstanceOf[() => Any]() - } - } - - override def getMethod(receiver: Any, name: String, paramTypes: ClassTag[_]*): Any = { - val rcls = receiver.getClass - val paramClasses = paramTypes.map(_.runtimeClass) - val mth = rcls.getMethod(name, paramClasses: _*) - paramTypes.length match { - case 0 => () => - mth.invoke(receiver) - case 1 => (x0: Any) => - mth.invoke(receiver, x0.asInstanceOf[Object]) - case 2 => (x0: Any, x1: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object]) - case 3 => (x0: Any, x1: Any, x2: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object], - x2.asInstanceOf[Object]) - case 4 => (x0: Any, x1: Any, x2: Any, x3: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object], - x2.asInstanceOf[Object], - x3.asInstanceOf[Object]) - case 5 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object], - x2.asInstanceOf[Object], - x3.asInstanceOf[Object], - x4.asInstanceOf[Object]) - case 6 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any, x5: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object], - x2.asInstanceOf[Object], - x3.asInstanceOf[Object], - x4.asInstanceOf[Object], - x5.asInstanceOf[Object]) - case 7 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any, x5: Any, x6: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object], - x2.asInstanceOf[Object], - x3.asInstanceOf[Object], - x4.asInstanceOf[Object], - x5.asInstanceOf[Object], - x6.asInstanceOf[Object]) - } - } -} - -object Projector { - implicit val reflectiveProjector: scala.Projector[Any] = new Projector -} \ No newline at end of file diff --git a/library/src/scala/reflect/Selectable.scala b/library/src/scala/reflect/Selectable.scala new file mode 100644 index 000000000..0dbdbc293 --- /dev/null +++ b/library/src/scala/reflect/Selectable.scala @@ -0,0 +1,73 @@ +package scala.reflect + +class Selectable(val receiver: Any) extends AnyVal with scala.Selectable { + def selectDynamic(name: String): Any = { + val rcls = receiver.getClass + try { + val fld = rcls.getField(name) + fld.get(receiver) + } + catch { + case ex: NoSuchFieldError => + selectDynamicMethod(name).asInstanceOf[() => Any]() + } + } + + override def selectDynamicMethod(name: String, paramTypes: ClassTag[_]*): Any = { + val rcls = receiver.getClass + val paramClasses = paramTypes.map(_.runtimeClass) + val mth = rcls.getMethod(name, paramClasses: _*) + paramTypes.length match { + case 0 => () => + mth.invoke(receiver) + case 1 => (x0: Any) => + mth.invoke(receiver, x0.asInstanceOf[Object]) + case 2 => (x0: Any, x1: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object]) + case 3 => (x0: Any, x1: Any, x2: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object], + x2.asInstanceOf[Object]) + case 4 => (x0: Any, x1: Any, x2: Any, x3: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object], + x2.asInstanceOf[Object], + x3.asInstanceOf[Object]) + case 5 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object], + x2.asInstanceOf[Object], + x3.asInstanceOf[Object], + x4.asInstanceOf[Object]) + case 6 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any, x5: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object], + x2.asInstanceOf[Object], + x3.asInstanceOf[Object], + x4.asInstanceOf[Object], + x5.asInstanceOf[Object]) + case 7 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any, x5: Any, x6: Any) => + mth.invoke(receiver, + x0.asInstanceOf[Object], + x1.asInstanceOf[Object], + x2.asInstanceOf[Object], + x3.asInstanceOf[Object], + x4.asInstanceOf[Object], + x5.asInstanceOf[Object], + x6.asInstanceOf[Object]) + } + } +} + +object Selectable { + implicit def reflectiveSelectable(receiver: Any): scala.Selectable = receiver match { + case receiver: scala.Selectable => receiver + case _ => new Selectable(receiver) + } +} diff --git a/tests/pos/zoo2.scala b/tests/pos/zoo2.scala index 9911416d3..06210fe67 100644 --- a/tests/pos/zoo2.scala +++ b/tests/pos/zoo2.scala @@ -1,3 +1,4 @@ +import scala.reflect.Selectable.reflectiveSelectable object Test { type Meat = { type IsMeat = Any diff --git a/tests/run/structural.scala b/tests/run/structural.scala index 43f008b5f..0f18f4579 100644 --- a/tests/run/structural.scala +++ b/tests/run/structural.scala @@ -1,22 +1,14 @@ -case class Record(elems: (String, Any)*) - -object Record { - - implicit def projector: Projector[Record] = new Projector[Record] { - def get(receiver: Record, name: String): Any = - receiver.elems.find(_._1 == name).get._2 - } - +case class Record(elems: (String, Any)*) extends Selectable { + def selectDynamic(name: String): Any = elems.find(_._1 == name).get._2 } object Test { - import scala.reflect.Projector.reflectiveProjector - import Record.projector + import scala.reflect.Selectable.reflectiveSelectable def f(closeable: { def close(): Unit }) = closeable.close() - type RN = Record { val name: String } + type RN = Record { val name: String; val age: Int } def g(r: RN) = r.name diff --git a/tests/run/structuralNoSuchMethod.scala b/tests/run/structuralNoSuchMethod.scala index 3d33c9d8b..476d7ed82 100644 --- a/tests/run/structuralNoSuchMethod.scala +++ b/tests/run/structuralNoSuchMethod.scala @@ -1,4 +1,4 @@ -import scala.reflect.Projector.reflectiveProjector +import scala.reflect.Selectable.reflectiveSelectable /** Demonstrates limitation of structural method dispatch (in Scala 2.x and dotty). * The method must be defined at exactly the argument types given in the structural type; -- cgit v1.2.3