aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorodersky <odersky@gmail.com>2017-02-01 18:21:58 +1100
committerGitHub <noreply@github.com>2017-02-01 18:21:58 +1100
commitaf7fdb32df34b352bf39f01a26653b169e0d55cf (patch)
tree4307381bb799db513dd07a0f40aec968ae99e877
parentbb2e99cdfa9876561df912d26e9870526de3dd5d (diff)
parent678e8e47b630786df7548c1be5bee744342f826c (diff)
downloaddotty-af7fdb32df34b352bf39f01a26653b169e0d55cf.tar.gz
dotty-af7fdb32df34b352bf39f01a26653b169e0d55cf.tar.bz2
dotty-af7fdb32df34b352bf39f01a26653b169e0d55cf.zip
Merge pull request #1881 from dotty-staging/add-structural-select
Implement structural type member access
-rw-r--r--compiler/src/dotty/tools/dotc/ast/TreeInfo.scala22
-rw-r--r--compiler/src/dotty/tools/dotc/core/Definitions.scala4
-rw-r--r--compiler/src/dotty/tools/dotc/core/StdNames.scala1
-rw-r--r--compiler/src/dotty/tools/dotc/typer/Dynamic.scala66
-rw-r--r--compiler/src/dotty/tools/dotc/typer/Typer.scala8
-rw-r--r--library/src/scala/Selectable.scala8
-rw-r--r--library/src/scala/reflect/Selectable.scala73
-rw-r--r--tests/neg/structural.scala11
-rw-r--r--tests/neg/zoo.scala14
-rw-r--r--tests/pos/i1866.scala5
-rw-r--r--tests/pos/zoo2.scala45
-rw-r--r--tests/run/structural.scala25
-rw-r--r--tests/run/structuralNoSuchMethod.check1
-rw-r--r--tests/run/structuralNoSuchMethod.scala23
14 files changed, 293 insertions, 13 deletions
diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
index da83d0644..bcda4b92f 100644
--- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
+++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
@@ -629,6 +629,28 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case nil =>
Nil
}
+
+ /** Is this a selection of a member of a structural type that is not a member
+ * of an underlying class or trait?
+ */
+ def isStructuralTermSelect(tree: Tree)(implicit ctx: Context) = tree match {
+ case tree: Select =>
+ def hasRefinement(qualtpe: Type): Boolean = qualtpe.dealias match {
+ case RefinedType(parent, rname, rinfo) =>
+ rname == tree.name || hasRefinement(parent)
+ case tp: TypeProxy =>
+ hasRefinement(tp.underlying)
+ case tp: OrType =>
+ hasRefinement(tp.tp1) || hasRefinement(tp.tp2)
+ case tp: AndType =>
+ hasRefinement(tp.tp1) && hasRefinement(tp.tp2)
+ case _ =>
+ false
+ }
+ !tree.symbol.exists && tree.isTerm && hasRefinement(tree.qualifier.tpe)
+ case _ =>
+ false
+ }
}
object TreeInfo {
diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala
index 0aeb28d36..716c9ef23 100644
--- a/compiler/src/dotty/tools/dotc/core/Definitions.scala
+++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala
@@ -26,6 +26,9 @@ object Definitions {
* else without affecting the set of programs that can be compiled.
*/
val MaxImplementedFunctionArity = 22
+
+ /** The maximal arity of a function that can be accessed as member of a structural type */
+ val MaxStructuralMethodArity = 7
}
/** A class defining symbols and types of standard definitions
@@ -515,6 +518,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 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 716959648..4a9c50dad 100644
--- a/compiler/src/dotty/tools/dotc/core/StdNames.scala
+++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala
@@ -481,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 4039c8b81..000cfd026 100644
--- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala
@@ -7,10 +7,13 @@ import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.untpd
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts.Context
-import dotty.tools.dotc.core.Names.Name
+import dotty.tools.dotc.core.Names.{Name, TermName}
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.core.Decorators._
+import core.Symbols._
+import core.Definitions
+import Inferencing._
import ErrorReporting._
object Dynamic {
@@ -18,7 +21,10 @@ object Dynamic {
name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed
}
-/** Translates selection that does not typecheck according to the scala.Dynamic rules:
+/** Handles programmable member selections of `Dynamic` instances and values
+ * with structural types. Two functionalities:
+ *
+ * 1. Translates selection that does not typecheck according to the scala.Dynamic rules:
* foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux)
* foo.bar = baz ~~> foo.updateDynamic("bar")(baz)
* foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
@@ -26,6 +32,10 @@ object Dynamic {
* foo.bar ~~> foo.selectDynamic(bar)
*
* The first matching rule of is applied.
+ *
+ * 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 =>
import Dynamic._
@@ -100,4 +110,56 @@ trait Dynamic { self: Typer with Applications =>
else untpd.TypeApply(select, targs)
untpd.Apply(selectWithTypes, Literal(Constant(name.toString)))
}
+
+ /** Handle reflection-based dispatch for members of structural types.
+ * Given `x.a`, where `x` is of (widened) type `T` and `x.a` is of type `U`:
+ *
+ * If `U` is a value type, map `x.a` to the equivalent of:
+ *
+ * (x: Selectable).selectDynamic(x, "a").asInstanceOf[U]
+ *
+ * If `U` is a method type (T1,...,Tn)R, map `x.a` to the equivalent of:
+ *
+ * (x: Selectable).selectDynamicMethod("a", CT1, ..., CTn).asInstanceOf[(T1,...,Tn) => R]
+ *
+ * where CT1,...,CTn are the class tags representing the erasure of T1,...,Tn.
+ *
+ * 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 structuralCall(selectorName: TermName, formals: List[Tree]) = {
+ val selectable = adapt(qual, defn.SelectableType)
+ val scall = untpd.Apply(
+ 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")
+
+ 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.
+ |Structural types only support methods taking up to ${Definitions.MaxStructuralMethodArity} arguments""")
+ 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.selectDynamic, Nil).asInstance(tpe)
+ case tpe: PolyType =>
+ fail("is polymorphic")
+ case tpe =>
+ fail(i"has an unsupported type: $tpe")
+ }
+ }
}
diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala
index d05a0aaa7..59df98a93 100644
--- a/compiler/src/dotty/tools/dotc/typer/Typer.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala
@@ -1040,9 +1040,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
typr.println(s"adding refinement $refinement")
checkRefinementNonCyclic(refinement, refineCls, seen)
val rsym = refinement.symbol
- if (rsym.is(Method) && rsym.allOverriddenSymbols.isEmpty)
- ctx.error(i"refinement $rsym without matching type in parent $tpt1", refinement.pos)
- }
+ 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)
}
@@ -2067,7 +2066,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
adaptInterpolated(tree.appliedToTypeTrees(typeArgs), pt, original))
}
case wtp =>
- pt match {
+ if (isStructuralTermSelect(tree)) adapt(handleStructural(tree), pt)
+ else pt match {
case pt: FunProto =>
adaptToArgs(wtp, pt)
case pt: PolyProto =>
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/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/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
+
+}
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/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]) = {}
+}
diff --git a/tests/pos/zoo2.scala b/tests/pos/zoo2.scala
new file mode 100644
index 000000000..06210fe67
--- /dev/null
+++ b/tests/pos/zoo2.scala
@@ -0,0 +1,45 @@
+import scala.reflect.Selectable.reflectiveSelectable
+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..0f18f4579
--- /dev/null
+++ b/tests/run/structural.scala
@@ -0,0 +1,25 @@
+case class Record(elems: (String, Any)*) extends Selectable {
+ def selectDynamic(name: String): Any = elems.find(_._1 == name).get._2
+}
+
+object Test {
+ import scala.reflect.Selectable.reflectiveSelectable
+
+ def f(closeable: { def close(): Unit }) =
+ closeable.close()
+
+ type RN = Record { val name: String; val age: Int }
+
+ 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..476d7ed82
--- /dev/null
+++ b/tests/run/structuralNoSuchMethod.scala
@@ -0,0 +1,23 @@
+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;
+ * 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")
+ }
+
+}