Skip to content

Fix #5372: Use applyDynamic in structural calls #5377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,17 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case _ => This(ctx.owner.enclosingClass.asClass)
}

/** Is this an application on a structural selection?
*
* @see isStructuralTermSelect
*/
def isStructuralTermApply(tree: Tree)(implicit ctx: Context): Boolean = tree match {
case Apply(fun, _) =>
isStructuralTermSelect(fun)
case _ =>
false
}

/** Is this a selection of a member of a structural type that is not a member
* of an underlying class or trait?
*/
Expand Down
3 changes: 0 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ object Definitions {
* else without affecting the set of programs that can be compiled.
*/
val MaxImplementedFunctionArity: Int = 22

/** The maximal arity of a function that can be accessed as member of a structural type */
val MaxStructuralMethodArity: Int = 7
}

/** A class defining symbols and types of standard definitions
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,6 @@ object StdNames {
val scala_ : N = "scala"
val scalaShadowing : N = "scalaShadowing"
val selectDynamic: N = "selectDynamic"
val selectDynamicMethod: N = "selectDynamicMethod"
val selectOverloadedMethod: N = "selectOverloadedMethod"
val selectTerm: N = "selectTerm"
val selectType: N = "selectType"
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
}

def tryDefault(n: Int, args1: List[Arg]): Unit = {
val getter = findDefaultGetter(n + numArgs(normalizedFun))
val getter =
// `methRef.symbol` doesn't exist for structural calls
if (methRef.symbol.exists) findDefaultGetter(n + numArgs(normalizedFun))
else EmptyTree
if (getter.isEmpty) missingArg(n)
else {
val substParam = addTyped(
Expand Down
70 changes: 41 additions & 29 deletions compiler/src/dotty/tools/dotc/typer/Dynamic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object Dynamic {
* 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.
* or `applyDynamic` on a `Selectable` instance. @See handleStructural.
*
*/
trait Dynamic { self: Typer with Applications =>
Expand Down Expand Up @@ -113,53 +113,65 @@ trait Dynamic { self: Typer with Applications =>
}

/** 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:
* Given `x.a`, where `x` is of (widened) type `T` (a value type or a nullary method type),
* and `x.a` is of type `U`, map `x.a` to the equivalent of:
*
* (x: Selectable).selectDynamic("a").asInstanceOf[U]
* (x: Selectable).selectDynamic("a").asInstanceOf[U]
*
* If `U` is a method type (T1,...,Tn)R, map `x.a` to the equivalent of:
* Given `x.a(arg1, ..., argn)`, where `x.a` is of (widened) type (T1, ..., Tn)R,
* map `x.a(arg1, ..., argn)` to the equivalent of:
*
* (x: Selectable).selectDynamicMethod("a", CT1, ..., CTn).asInstanceOf[(T1,...,Tn) => R]
* (x:selectable).applyDynamic("a", CT1, ..., CTn)(arg1, ..., argn).asInstanceOf[R]
*
* where CT1,...,CTn are the class tags representing the erasure of T1,...,Tn.
* 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).
* type.
*/
def handleStructural(tree: Tree)(implicit ctx: Context): Tree = {
val Select(qual, name) = tree

def structuralCall(selectorName: TermName, formals: List[Tree]) = {
def structuralCall(qual: Tree, name: Name, selectorName: TermName, ctags: List[Tree], args: Option[List[Tree]]) = {
val selectable = adapt(qual, defn.SelectableType)
val scall = untpd.Apply(
untpd.TypedSplice(selectable.select(selectorName)).withPos(tree.pos),
(Literal(Constant(name.toString)) :: formals).map(untpd.TypedSplice(_)))

// ($qual: Selectable).$selectorName("$name", ..$ctags)
val base =
untpd.Apply(
untpd.TypedSplice(selectable.select(selectorName)).withPos(tree.pos),
(Literal(Constant(name.toString)) :: ctags).map(untpd.TypedSplice(_)))

val scall = args match {
case None => base
case Some(args) => untpd.Apply(base, args)
}

typed(scall)
}

def fail(reason: String) =
def fail(name: Name, reason: String) =
errorTree(tree, em"Structural access not allowed on method $name because it $reason")

tree.tpe.widen match {
case tpe: MethodType =>
if (tpe.isParamDependent)
fail(i"has a method type with inter-parameter dependencies")
else if (tpe.paramNames.length > Definitions.MaxStructuralMethodArity)
fail(i"""takes too many parameters.
|Structural types only support methods taking up to ${Definitions.MaxStructuralMethodArity} arguments""")
val tpe = tree.tpe.widen
tree match {
case Apply(fun @ Select(qual, name), args) =>
val funTpe = fun.tpe.widen.asInstanceOf[MethodType]
if (funTpe.isParamDependent)
fail(name, i"has a method type with inter-parameter dependencies")
else {
val ctags = tpe.paramInfos.map(pt =>
val ctags = funTpe.paramInfos.map(pt =>
implicitArgTree(defn.ClassTagType.appliedTo(pt :: Nil), tree.pos.endPos))
structuralCall(nme.selectDynamicMethod, ctags).asInstance(tpe.toFunctionType())
structuralCall(qual, name, nme.applyDynamic, ctags, Some(args)).asInstance(tpe.resultType)
}
case tpe: ValueType =>
structuralCall(nme.selectDynamic, Nil).asInstance(tpe)
case tpe: PolyType =>
fail("is polymorphic")
case tpe =>
fail(i"has an unsupported type: $tpe")
case Select(qual, name) if tpe.isValueType =>
structuralCall(qual, name, nme.selectDynamic, Nil, None).asInstance(tpe)
case Select(_, _) if !tpe.isParameterless =>
// We return the tree unchanged; The structural call will be handled when we take care of the
// enclosing application.
tree
case Select(_, name) if tpe.isInstanceOf[PolyType] =>
fail(name, "is polymorphic")
case Select(_, name) =>
fail(name, i"has an unsupported type: ${tree.tpe.widen}")
}
}
}
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2638,7 +2638,10 @@ class Typer extends Namer
convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs)))
}
case wtp =>
if (isStructuralTermSelect(tree)) readaptSimplified(handleStructural(tree))
if (isStructuralTermApply(tree))
readaptSimplified(handleStructural(tree))
else if (isStructuralTermSelect(tree) && tree.tpe.widen.isValueType)
readaptSimplified(handleStructural(tree))
else pt match {
case pt: FunProto =>
adaptToArgs(wtp, pt)
Expand Down
27 changes: 13 additions & 14 deletions docs/docs/reference/changed/structural-types-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ The standard library defines a trait `Selectable` in the package
```scala
trait Selectable extends Any {
def selectDynamic(name: String): Any
def selectDynamicMethod(name: String, paramClasses: ClassTag[_]*): Any =
new UnsupportedOperationException("selectDynamicMethod")
def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any =
new UnsupportedOperationException("applyDynamic")
}
```

Expand All @@ -31,10 +31,10 @@ implementations can be envisioned for platforms where Java reflection
is not available.

`selectDynamic` takes a field name and returns the value associated
with that name in the `Selectable`. Similarly, `selectDynamicMethod`
with that name in the `Selectable`. Similarly, `applyDynamic`
takes a method name, `ClassTag`s representing its parameters types and
will return the function that matches this
name and parameter types.
the arguments to pass to the function. It will return the result of
calling this function with the given arguments.

Given a value `v` of type `C { Rs }`, where `C` is a class reference
and `Rs` are refinement declarations, and given `v.a` of type `U`, we
Expand All @@ -51,26 +51,25 @@ consider three distinct cases:
parameters and it is not a dependent method type, we map `v.a` to
the equivalent of:
```scala
v.a
v.a(arg1, ..., argn)
--->
(v: Selectable).selectDynamic("a", CT1, ..., CTn).asInstanceOf[(T1, ..., Tn) => R]
(v: Selectable).applyDynamic("a", CT1, ..., CTn)(arg1, ..., argn).asInstanceOf[R]
```

- If `U` is neither a value nor a method type, or a dependent method
type, or has more than 7 parameters, an error is emitted.

We make sure that `r` conforms to type `Selectable`, potentially by
introducing an implicit conversion, and then call either
`selectDynamic` or `selectMethodDynamic`, passing the name of the
member to access and the class tags of the formal parameters, in the
case of a method call. These parameters could be used to disambiguate
one of several overload variants in the future, but overloads are not
supported in structural types at the moment.
`selectDynamic` or `applyDynamic`, passing the name of the
member to access, along with the class tags of the formal parameters
and the arguments in the case of a method call. These parameters
could be used to disambiguate one of several overload variants in the
future, but overloads are not supported in structural types at the
moment.

## Limitations of structural types

- Methods with more than 7 formal parameters cannot be called via
structural call.
- Dependent methods cannot be called via structural call.
- Overloaded methods cannot be called via structural call.
- Refinements do not handle polymorphic methods.
Expand Down
10 changes: 4 additions & 6 deletions docs/docs/reference/changed/structural-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,9 @@ differences.
how to define reflective access operations. By contrast
`Selectable` is a trait which declares the access operations.

- One access operation, `selectDynamic` is shared between both
approaches, but the other access operations are
different. `Selectable` defines a `selectDynamicMethod`, which
takes class tags indicating the method's formal parameter types as
additional argument. `Dynamic` comes with `applyDynamic` and
`updateDynamic` methods, which take actual argument values.
- Two access operations, `selectDynamic` and `applyDynamic` are shared
between both approches. In `Selectable`, `applyDynamic` also takes
`ClassTag` indicating the method's formal parameter types. `Dynamic`
comes with `updateDynamic`.

[More details](structural-types-spec.html)
4 changes: 2 additions & 2 deletions library/src/scala/Selectable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import scala.reflect.ClassTag

trait Selectable extends Any {
def selectDynamic(name: String): Any
def selectDynamicMethod(name: String, paramClasses: ClassTag[_]*): Any =
new UnsupportedOperationException("selectDynamicMethod")
def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any =
new UnsupportedOperationException("applyDynamic")
}
50 changes: 3 additions & 47 deletions library/src/scala/reflect/Selectable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,60 +10,16 @@ class Selectable(val receiver: Any) extends AnyVal with scala.Selectable {
}
catch {
case ex: NoSuchFieldException =>
selectDynamicMethod(name).asInstanceOf[() => Any]()
applyDynamic(name)()
}
}

override def selectDynamicMethod(name: String, paramTypes: ClassTag[_]*): Any = {
override def applyDynamic(name: String, paramTypes: ClassTag[_]*)(args: Any*): Any = {
val rcls = receiver.getClass
val paramClasses = paramTypes.map(_.runtimeClass)
val mth = rcls.getMethod(name, paramClasses: _*)
ensureAccessible(mth)
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])
}
mth.invoke(receiver, args.map(_.asInstanceOf[AnyRef]): _*)
}
}

Expand Down
6 changes: 5 additions & 1 deletion tests/neg/structural.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ object Test3 {

trait Entry { type Key; val key: Key }
type D = { def foo(e: Entry, k: e.Key): Unit }
def i(x: D) = x.foo(???) // error: foo has dependent params
val e = new Entry { type Key = Int; val key = 0 }
def i(x: D) = x.foo(e, 1) // error: foo has dependent params

type G = { def foo(x: Int, y: Int): Unit }
def j(x: G) = x.foo(???) // error: missing argument
}