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. --- tests/neg/zoo.scala | 14 +++++------ tests/pos/zoo2.scala | 44 ++++++++++++++++++++++++++++++++++ tests/run/structural.scala | 33 +++++++++++++++++++++++++ tests/run/structuralNoSuchMethod.check | 1 + tests/run/structuralNoSuchMethod.scala | 23 ++++++++++++++++++ 5 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 tests/pos/zoo2.scala create mode 100644 tests/run/structural.scala create mode 100644 tests/run/structuralNoSuchMethod.check create mode 100644 tests/run/structuralNoSuchMethod.scala (limited to 'tests') diff --git a/tests/neg/zoo.scala b/tests/neg/zoo.scala index 19efcc1d7..1674548e8 100644 --- a/tests/neg/zoo.scala +++ b/tests/neg/zoo.scala @@ -7,19 +7,19 @@ type Grass = { } type Animal = { type Food - def eats(food: Food): Unit // error - def gets: Food // error + def eats(food: Food): Unit + def gets: Food } type Cow = { type IsMeat = Any type Food <: Grass - def eats(food: Grass): Unit // error - def gets: Grass // error + def eats(food: Grass): Unit + def gets: Grass } type Lion = { type Food = Meat - def eats(food: Meat): Unit // error - def gets: Meat // error + def eats(food: Meat): Unit + def gets: Meat } def newMeat: Meat = new { type IsMeat = Any @@ -40,5 +40,5 @@ def newLion: Lion = new { } val milka = newCow val leo = newLion -leo.eats(milka) // structural select not supported +leo.eats(milka) // error: no projector found } diff --git a/tests/pos/zoo2.scala b/tests/pos/zoo2.scala new file mode 100644 index 000000000..9911416d3 --- /dev/null +++ b/tests/pos/zoo2.scala @@ -0,0 +1,44 @@ +object Test { +type Meat = { + type IsMeat = Any +} +type Grass = { + type IsGrass = Any +} +type Animal = { + type Food + def eats(food: Food): Unit + def gets: Food +} +type Cow = { + type IsMeat = Any + type Food <: Grass + def eats(food: Grass): Unit + def gets: Grass +} +type Lion = { + type Food = Meat + def eats(food: Meat): Unit + def gets: Meat +} +def newMeat: Meat = new { + type IsMeat = Any +} +def newGrass: Grass = new { + type IsGrass = Any +} +def newCow: Cow = new { + type IsMeat = Any + type Food = Grass + def eats(food: Grass) = () + def gets = newGrass +} +def newLion: Lion = new { + type Food = Meat + def eats(food: Meat) = () + def gets = newMeat +} +val milka = newCow +val leo = newLion +leo.eats(milka) +} diff --git a/tests/run/structural.scala b/tests/run/structural.scala new file mode 100644 index 000000000..43f008b5f --- /dev/null +++ b/tests/run/structural.scala @@ -0,0 +1,33 @@ +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 + } + +} + +object Test { + import scala.reflect.Projector.reflectiveProjector + import Record.projector + + def f(closeable: { def close(): Unit }) = + closeable.close() + + type RN = Record { val name: String } + + def g(r: RN) = r.name + + val rr: RN = Record("name" -> "Bob", "age" -> 42).asInstanceOf[RN] + + def main(args: Array[String]): Unit = { + f(new java.io.PrintStream("foo")) + assert(g(rr) == "Bob") + + val s: { def concat(s: String): String } = "abc" + assert(s.concat("def") == "abcdef") + } +} + diff --git a/tests/run/structuralNoSuchMethod.check b/tests/run/structuralNoSuchMethod.check new file mode 100644 index 000000000..20576fc9c --- /dev/null +++ b/tests/run/structuralNoSuchMethod.check @@ -0,0 +1 @@ +no such method diff --git a/tests/run/structuralNoSuchMethod.scala b/tests/run/structuralNoSuchMethod.scala new file mode 100644 index 000000000..3d33c9d8b --- /dev/null +++ b/tests/run/structuralNoSuchMethod.scala @@ -0,0 +1,23 @@ +import scala.reflect.Projector.reflectiveProjector + +/** 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; + * Generic instantiation is not possible. + */ +object Test { + type T = { def f(x: String, y: String): String } + + class C[X] { + def f(x: X, y: String): String = "f1" + } + + val x: T = new C[String] + + def main(args: Array[String]) = + try println(x.f("", "")) // throws NoSuchMethodException + catch { + case ex: NoSuchMethodException => + println("no such method") + } + +} -- 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 'tests') 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 From bb81d5d54bf95a417d555586cff403c4bb76455f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 10 Jan 2017 18:05:00 +0700 Subject: Add test case for #1866 The PR also fixes #1866, as shown by this test case. --- tests/pos/i1866.scala | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/pos/i1866.scala (limited to 'tests') diff --git a/tests/pos/i1866.scala b/tests/pos/i1866.scala new file mode 100644 index 000000000..918d2e182 --- /dev/null +++ b/tests/pos/i1866.scala @@ -0,0 +1,5 @@ +import scala.reflect.Selectable.reflectiveSelectable +object Test { + def f(g: { val update: Unit }) = g.update + def main(update: Array[String]) = {} +} -- cgit v1.2.3 From 678e8e47b630786df7548c1be5bee744342f826c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 28 Jan 2017 17:11:56 +1100 Subject: Disallow polymorphic refinements in stuctural types. We can't handle them with the proposed scheme. I made a note in #1886. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 +++- tests/neg/structural.scala | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/neg/structural.scala (limited to 'tests') diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ec4c135f7..48636c27c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1039,7 +1039,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit for (refinement <- refinements1) { // TODO: get clarity whether we want to enforce these conditions typr.println(s"adding refinement $refinement") checkRefinementNonCyclic(refinement, refineCls, seen) - } + val rsym = refinement.symbol + if (rsym.info.isInstanceOf[PolyType] && rsym.allOverriddenSymbols.isEmpty) + ctx.error(i"polymorphic refinement $rsym without matching type in parent $tpt1 is no longer allowed", refinement.pos) } assignType(cpy.RefinedTypeTree(tree)(tpt1, refinements1), tpt1, refinements1, refineCls) } diff --git a/tests/neg/structural.scala b/tests/neg/structural.scala new file mode 100644 index 000000000..aab52b2cb --- /dev/null +++ b/tests/neg/structural.scala @@ -0,0 +1,11 @@ +object Test3 { + import scala.reflect.Selectable.reflectiveSelectable + def g(x: { type T ; def t: T ; def f(a: T): Boolean }) = x.f(x.t) // error: no ClassTag for x.T + g(new { type T = Int; def t = 4; def f(a:T) = true }) + g(new { type T = Any; def t = 4; def f(a:T) = true }) + val y: { type T = Int; def t = 4; def f(a:T) = true } + = new { type T = Int; def t = 4; def f(a:T) = true } + + def h(x: { def f[T](a: T): Int }) = x.f[Int](4) // error: polymorphic refinement method ... no longer allowed + +} -- cgit v1.2.3