Skip to content

Commit 8610cdb

Browse files
authored
Merge pull request #5377 from dotty-staging/fix/5372
Fix #5372: Use `applyDynamic` in structural calls
2 parents d13a467 + b3d392d commit 8610cdb

File tree

11 files changed

+87
-105
lines changed

11 files changed

+87
-105
lines changed

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,17 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
737737
case _ => This(ctx.owner.enclosingClass.asClass)
738738
}
739739

740+
/** Is this an application on a structural selection?
741+
*
742+
* @see isStructuralTermSelect
743+
*/
744+
def isStructuralTermApply(tree: Tree)(implicit ctx: Context): Boolean = tree match {
745+
case Apply(fun, _) =>
746+
isStructuralTermSelect(fun)
747+
case _ =>
748+
false
749+
}
750+
740751
/** Is this a selection of a member of a structural type that is not a member
741752
* of an underlying class or trait?
742753
*/

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ object Definitions {
2424
* else without affecting the set of programs that can be compiled.
2525
*/
2626
val MaxImplementedFunctionArity: Int = 22
27-
28-
/** The maximal arity of a function that can be accessed as member of a structural type */
29-
val MaxStructuralMethodArity: Int = 7
3027
}
3128

3229
/** A class defining symbols and types of standard definitions

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,6 @@ object StdNames {
501501
val scala_ : N = "scala"
502502
val scalaShadowing : N = "scalaShadowing"
503503
val selectDynamic: N = "selectDynamic"
504-
val selectDynamicMethod: N = "selectDynamicMethod"
505504
val selectOverloadedMethod: N = "selectOverloadedMethod"
506505
val selectTerm: N = "selectTerm"
507506
val selectType: N = "selectType"

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
490490
}
491491

492492
def tryDefault(n: Int, args1: List[Arg]): Unit = {
493-
val getter = findDefaultGetter(n + numArgs(normalizedFun))
493+
val getter =
494+
// `methRef.symbol` doesn't exist for structural calls
495+
if (methRef.symbol.exists) findDefaultGetter(n + numArgs(normalizedFun))
496+
else EmptyTree
494497
if (getter.isEmpty) missingArg(n)
495498
else {
496499
val substParam = addTyped(

compiler/src/dotty/tools/dotc/typer/Dynamic.scala

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ object Dynamic {
3535
* The first matching rule of is applied.
3636
*
3737
* 2. Translates member selections on structural types to calls of `selectDynamic`
38-
* or `selectDynamicMethod` on a `Selectable` instance. @See handleStructural.
38+
* or `applyDynamic` on a `Selectable` instance. @See handleStructural.
3939
*
4040
*/
4141
trait Dynamic { self: Typer with Applications =>
@@ -113,53 +113,65 @@ trait Dynamic { self: Typer with Applications =>
113113
}
114114

115115
/** Handle reflection-based dispatch for members of structural types.
116-
* Given `x.a`, where `x` is of (widened) type `T` and `x.a` is of type `U`:
117116
*
118-
* If `U` is a value type, map `x.a` to the equivalent of:
117+
* Given `x.a`, where `x` is of (widened) type `T` (a value type or a nullary method type),
118+
* and `x.a` is of type `U`, map `x.a` to the equivalent of:
119119
*
120-
* (x: Selectable).selectDynamic("a").asInstanceOf[U]
120+
* (x: Selectable).selectDynamic("a").asInstanceOf[U]
121121
*
122-
* If `U` is a method type (T1,...,Tn)R, map `x.a` to the equivalent of:
122+
* Given `x.a(arg1, ..., argn)`, where `x.a` is of (widened) type (T1, ..., Tn)R,
123+
* map `x.a(arg1, ..., argn)` to the equivalent of:
123124
*
124-
* (x: Selectable).selectDynamicMethod("a", CT1, ..., CTn).asInstanceOf[(T1,...,Tn) => R]
125+
* (x:selectable).applyDynamic("a", CT1, ..., CTn)(arg1, ..., argn).asInstanceOf[R]
125126
*
126-
* where CT1,...,CTn are the class tags representing the erasure of T1,...,Tn.
127+
* where CT1, ..., CTn are the class tags representing the erasure of T1, ..., Tn.
127128
*
128129
* It's an error if U is neither a value nor a method type, or a dependent method
129-
* type, or of too large arity (limit is Definitions.MaxStructuralMethodArity).
130+
* type.
130131
*/
131132
def handleStructural(tree: Tree)(implicit ctx: Context): Tree = {
132-
val Select(qual, name) = tree
133133

134-
def structuralCall(selectorName: TermName, formals: List[Tree]) = {
134+
def structuralCall(qual: Tree, name: Name, selectorName: TermName, ctags: List[Tree], args: Option[List[Tree]]) = {
135135
val selectable = adapt(qual, defn.SelectableType)
136-
val scall = untpd.Apply(
137-
untpd.TypedSplice(selectable.select(selectorName)).withPos(tree.pos),
138-
(Literal(Constant(name.toString)) :: formals).map(untpd.TypedSplice(_)))
136+
137+
// ($qual: Selectable).$selectorName("$name", ..$ctags)
138+
val base =
139+
untpd.Apply(
140+
untpd.TypedSplice(selectable.select(selectorName)).withPos(tree.pos),
141+
(Literal(Constant(name.toString)) :: ctags).map(untpd.TypedSplice(_)))
142+
143+
val scall = args match {
144+
case None => base
145+
case Some(args) => untpd.Apply(base, args)
146+
}
147+
139148
typed(scall)
140149
}
141150

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

145-
tree.tpe.widen match {
146-
case tpe: MethodType =>
147-
if (tpe.isParamDependent)
148-
fail(i"has a method type with inter-parameter dependencies")
149-
else if (tpe.paramNames.length > Definitions.MaxStructuralMethodArity)
150-
fail(i"""takes too many parameters.
151-
|Structural types only support methods taking up to ${Definitions.MaxStructuralMethodArity} arguments""")
154+
val tpe = tree.tpe.widen
155+
tree match {
156+
case Apply(fun @ Select(qual, name), args) =>
157+
val funTpe = fun.tpe.widen.asInstanceOf[MethodType]
158+
if (funTpe.isParamDependent)
159+
fail(name, i"has a method type with inter-parameter dependencies")
152160
else {
153-
val ctags = tpe.paramInfos.map(pt =>
161+
val ctags = funTpe.paramInfos.map(pt =>
154162
implicitArgTree(defn.ClassTagType.appliedTo(pt :: Nil), tree.pos.endPos))
155-
structuralCall(nme.selectDynamicMethod, ctags).asInstance(tpe.toFunctionType())
163+
structuralCall(qual, name, nme.applyDynamic, ctags, Some(args)).asInstance(tpe.resultType)
156164
}
157-
case tpe: ValueType =>
158-
structuralCall(nme.selectDynamic, Nil).asInstance(tpe)
159-
case tpe: PolyType =>
160-
fail("is polymorphic")
161-
case tpe =>
162-
fail(i"has an unsupported type: $tpe")
165+
case Select(qual, name) if tpe.isValueType =>
166+
structuralCall(qual, name, nme.selectDynamic, Nil, None).asInstance(tpe)
167+
case Select(_, _) if !tpe.isParameterless =>
168+
// We return the tree unchanged; The structural call will be handled when we take care of the
169+
// enclosing application.
170+
tree
171+
case Select(_, name) if tpe.isInstanceOf[PolyType] =>
172+
fail(name, "is polymorphic")
173+
case Select(_, name) =>
174+
fail(name, i"has an unsupported type: ${tree.tpe.widen}")
163175
}
164176
}
165177
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2638,7 +2638,10 @@ class Typer extends Namer
26382638
convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs)))
26392639
}
26402640
case wtp =>
2641-
if (isStructuralTermSelect(tree)) readaptSimplified(handleStructural(tree))
2641+
if (isStructuralTermApply(tree))
2642+
readaptSimplified(handleStructural(tree))
2643+
else if (isStructuralTermSelect(tree) && tree.tpe.widen.isValueType)
2644+
readaptSimplified(handleStructural(tree))
26422645
else pt match {
26432646
case pt: FunProto =>
26442647
adaptToArgs(wtp, pt)

docs/docs/reference/changed/structural-types-spec.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ The standard library defines a trait `Selectable` in the package
2020
```scala
2121
trait Selectable extends Any {
2222
def selectDynamic(name: String): Any
23-
def selectDynamicMethod(name: String, paramClasses: ClassTag[_]*): Any =
24-
new UnsupportedOperationException("selectDynamicMethod")
23+
def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any =
24+
new UnsupportedOperationException("applyDynamic")
2525
}
2626
```
2727

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

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

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

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

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

7071
## Limitations of structural types
7172

72-
- Methods with more than 7 formal parameters cannot be called via
73-
structural call.
7473
- Dependent methods cannot be called via structural call.
7574
- Overloaded methods cannot be called via structural call.
7675
- Refinements do not handle polymorphic methods.

docs/docs/reference/changed/structural-types.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,9 @@ differences.
6969
how to define reflective access operations. By contrast
7070
`Selectable` is a trait which declares the access operations.
7171

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

7977
[More details](structural-types-spec.html)

library/src/scala/Selectable.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ import scala.reflect.ClassTag
33

44
trait Selectable extends Any {
55
def selectDynamic(name: String): Any
6-
def selectDynamicMethod(name: String, paramClasses: ClassTag[_]*): Any =
7-
new UnsupportedOperationException("selectDynamicMethod")
6+
def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any =
7+
new UnsupportedOperationException("applyDynamic")
88
}

library/src/scala/reflect/Selectable.scala

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,60 +10,16 @@ class Selectable(val receiver: Any) extends AnyVal with scala.Selectable {
1010
}
1111
catch {
1212
case ex: NoSuchFieldException =>
13-
selectDynamicMethod(name).asInstanceOf[() => Any]()
13+
applyDynamic(name)()
1414
}
1515
}
1616

17-
override def selectDynamicMethod(name: String, paramTypes: ClassTag[_]*): Any = {
17+
override def applyDynamic(name: String, paramTypes: ClassTag[_]*)(args: Any*): Any = {
1818
val rcls = receiver.getClass
1919
val paramClasses = paramTypes.map(_.runtimeClass)
2020
val mth = rcls.getMethod(name, paramClasses: _*)
2121
ensureAccessible(mth)
22-
paramTypes.length match {
23-
case 0 => () =>
24-
mth.invoke(receiver)
25-
case 1 => (x0: Any) =>
26-
mth.invoke(receiver, x0.asInstanceOf[Object])
27-
case 2 => (x0: Any, x1: Any) =>
28-
mth.invoke(receiver,
29-
x0.asInstanceOf[Object],
30-
x1.asInstanceOf[Object])
31-
case 3 => (x0: Any, x1: Any, x2: Any) =>
32-
mth.invoke(receiver,
33-
x0.asInstanceOf[Object],
34-
x1.asInstanceOf[Object],
35-
x2.asInstanceOf[Object])
36-
case 4 => (x0: Any, x1: Any, x2: Any, x3: Any) =>
37-
mth.invoke(receiver,
38-
x0.asInstanceOf[Object],
39-
x1.asInstanceOf[Object],
40-
x2.asInstanceOf[Object],
41-
x3.asInstanceOf[Object])
42-
case 5 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any) =>
43-
mth.invoke(receiver,
44-
x0.asInstanceOf[Object],
45-
x1.asInstanceOf[Object],
46-
x2.asInstanceOf[Object],
47-
x3.asInstanceOf[Object],
48-
x4.asInstanceOf[Object])
49-
case 6 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any, x5: Any) =>
50-
mth.invoke(receiver,
51-
x0.asInstanceOf[Object],
52-
x1.asInstanceOf[Object],
53-
x2.asInstanceOf[Object],
54-
x3.asInstanceOf[Object],
55-
x4.asInstanceOf[Object],
56-
x5.asInstanceOf[Object])
57-
case 7 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any, x5: Any, x6: Any) =>
58-
mth.invoke(receiver,
59-
x0.asInstanceOf[Object],
60-
x1.asInstanceOf[Object],
61-
x2.asInstanceOf[Object],
62-
x3.asInstanceOf[Object],
63-
x4.asInstanceOf[Object],
64-
x5.asInstanceOf[Object],
65-
x6.asInstanceOf[Object])
66-
}
22+
mth.invoke(receiver, args.map(_.asInstanceOf[AnyRef]): _*)
6723
}
6824
}
6925

tests/neg/structural.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,9 @@ object Test3 {
1515

1616
trait Entry { type Key; val key: Key }
1717
type D = { def foo(e: Entry, k: e.Key): Unit }
18-
def i(x: D) = x.foo(???) // error: foo has dependent params
18+
val e = new Entry { type Key = Int; val key = 0 }
19+
def i(x: D) = x.foo(e, 1) // error: foo has dependent params
20+
21+
type G = { def foo(x: Int, y: Int): Unit }
22+
def j(x: G) = x.foo(???) // error: missing argument
1923
}

0 commit comments

Comments
 (0)