Skip to content

Commit 0ac1792

Browse files
committed
Change structural types scheme
Make Selectable a marker trait without any members.
1 parent 348e9db commit 0ac1792

File tree

7 files changed

+86
-26
lines changed

7 files changed

+86
-26
lines changed

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,37 @@ trait Dynamic {
151151
def handleStructural(tree: Tree)(using Context): Tree = {
152152
val (fun @ Select(qual, name), targs, vargss) = decomposeCall(tree)
153153

154-
def structuralCall(selectorName: TermName, ctags: List[Tree]) = {
154+
def structuralCall(selectorName: TermName, ctags: => List[Tree]) = {
155155
val selectable = adapt(qual, defn.SelectableClass.typeRef)
156156

157-
// ($qual: Selectable).$selectorName("$name", ..$ctags)
157+
// ($qual: Selectable).$selectorName("$name")
158158
val base =
159159
untpd.Apply(
160160
untpd.TypedSplice(selectable.select(selectorName)).withSpan(fun.span),
161-
(Literal(Constant(name.toString)) :: ctags).map(untpd.TypedSplice(_)))
161+
(Literal(Constant(name.toString)) :: Nil).map(untpd.TypedSplice(_)))
162162

163163
val scall =
164164
if (vargss.isEmpty) base
165165
else untpd.Apply(base, vargss.flatten)
166166

167-
typed(scall)
167+
// If function is an `applyDynamic` that takes a ClassTag* parameter,
168+
// add `ctags`.
169+
def addClassTags(tree: Tree): Tree = tree match
170+
case Apply(fn: Apply, args) =>
171+
cpy.Apply(tree)(addClassTags(fn), args)
172+
case Apply(fn @ Select(_, nme.applyDynamic), nameArg :: _ :: Nil) =>
173+
fn.tpe.widen match
174+
case mt: MethodType => mt.paramInfos match
175+
case _ :: ctagsParam :: Nil
176+
if ctagsParam.isRepeatedParam
177+
&& ctagsParam.argInfos.head.isRef(defn.ClassTagClass) =>
178+
val ctagType = defn.ClassTagClass.typeRef.appliedTo(TypeBounds.empty)
179+
cpy.Apply(tree)(fn,
180+
nameArg :: seqToRepeated(SeqLiteral(ctags, TypeTree(ctagType))) :: Nil)
181+
case _ => tree
182+
case other => tree
183+
case _ => tree
184+
addClassTags(typed(scall))
168185
}
169186

170187
def fail(name: Name, reason: String) =
@@ -187,7 +204,7 @@ trait Dynamic {
187204
if (isDependentMethod(tpe))
188205
fail(name, i"has a method type with inter-parameter dependencies")
189206
else {
190-
val ctags = tpe.paramInfoss.flatten.map(pt =>
207+
def ctags = tpe.paramInfoss.flatten.map(pt =>
191208
implicitArgTree(defn.ClassTagClass.typeRef.appliedTo(pt.widenDealias :: Nil), fun.span.endPos))
192209
structuralCall(nme.applyDynamic, ctags).cast(tpe.finalResultType)
193210
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ consider three distinct cases:
4848
```
4949

5050
- If `U` is a method type `(T11, ..., T1n)...(TN1, ..., TNn) => R` and it is not
51-
a dependent method type, we map `v.a(a11, ..., a1n)...(aN1, aNn)` to
51+
a dependent method type, we map `v.a(a11, ..., a1n)...(aN1, ..., aNn)` to
5252
the equivalent of:
5353
```scala
5454
v.a(arg1, ..., argn)
@@ -57,6 +57,7 @@ a dependent method type, we map `v.a(a11, ..., a1n)...(aN1, aNn)` to
5757
(a11, ..., a1n, ..., aN1, ..., aNn)
5858
.asInstanceOf[R]
5959
```
60+
where each `CT_ij` is the class tag of the type of the argument `a_ij`.
6061

6162
- If `U` is neither a value nor a method type, or a dependent method
6263
type, an error is emitted.

library/src/scala/Selectable.scala

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
11
package scala
22
import scala.reflect.ClassTag
33

4-
trait Selectable extends Any {
5-
def selectDynamic(name: String): Any
6-
def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any =
7-
new UnsupportedOperationException("applyDynamic")
8-
}
4+
/** A marker trait for objects that support structural selection via
5+
* `selectDynamic` and `applyDynamic`
6+
*
7+
* Implementation classes should define, or make available as extension
8+
* methods, the following two method signatures:
9+
*
10+
* def selectDynamic(name: String): Any
11+
* def applyDynamic(name: String)(args: Any*): Any =
12+
*
13+
* `selectDynamic` is invoked for simple selections `v.m`, whereas
14+
* `applyDynamic` is invoked for selections with arguments `v.m(...)`.
15+
* If there's only one kind of selection, the method supporting the
16+
* other may be omitted. The `applyDynamic` can also have a second parameter
17+
* list of class tag arguments, i.e. it may alternatively have the signature
18+
*
19+
* def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any
20+
*
21+
* In this case the call will synthesize `ClassTag` arguments for all formal parameter
22+
* types of the method in the structural type.
23+
*/
24+
trait Selectable extends Any

library/src/scala/reflect/Selectable.scala

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Selectable(val receiver: Any) extends AnyVal with scala.Selectable {
1414
}
1515
}
1616

17-
override def applyDynamic(name: String, paramTypes: ClassTag[_]*)(args: Any*): Any = {
17+
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: _*)
@@ -24,8 +24,6 @@ class Selectable(val receiver: Any) extends AnyVal with scala.Selectable {
2424
}
2525

2626
object Selectable {
27-
implicit def reflectiveSelectable(receiver: Any): scala.Selectable = receiver match {
28-
case receiver: scala.Selectable => receiver
29-
case _ => new Selectable(receiver)
30-
}
27+
implicit def reflectiveSelectable(receiver: Any): Selectable =
28+
new Selectable(receiver)
3129
}

tests/run-macros/refined-selectable-macro/Macro_1.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import scala.quoted._
22

33
object Macro {
44

5+
trait Selectable extends scala.Selectable:
6+
def selectDynamic(name: String): Any
7+
58
trait SelectableRecord extends Selectable {
69
transparent inline def toTuple: Tuple = ${ toTupleImpl('this)}
710
}

tests/run/structural.scala

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,27 @@ object Test {
2424
def fun9(y: C): y.I
2525
}
2626

27-
class FooI {
27+
class Foo1 {
28+
def sel0: Int = 1
29+
def sel1: Int => Int = x => x
30+
def fun0(x: Int): Int = x
31+
32+
def fun1(x: Int)(y: Int): Int = x + y
33+
def fun2(x: Int): Int => Int = y => x * y
34+
def fun3(a1: Int, a2: Int, a3: Int)
35+
(a4: Int, a5: Int, a6: Int)
36+
(a7: Int, a8: Int, a9: Int): Int = -1
37+
38+
def fun4(implicit x: Int): Int = x
39+
def fun5(x: Int)(implicit y: Int): Int = x + y
40+
41+
def fun6(x: C, y: x.S): Int = 1
42+
def fun7(x: C, y: x.I): Int = 2
43+
def fun8(y: C): y.S = "Hello"
44+
def fun9(y: C): y.I = 1.asInstanceOf[y.I]
45+
}
46+
47+
class Foo2 extends scala.Selectable {
2848
def sel0: Int = 1
2949
def sel1: Int => Int = x => x
3050
def fun0(x: Int): Int = x
@@ -83,7 +103,7 @@ object Test {
83103
}
84104

85105
// Limited support for dependant methods
86-
def dependant(x: Foo) = {
106+
def dependent(x: Foo) = {
87107
val y = new D
88108

89109
assert(x.fun6(y, "Hello") == 1)
@@ -97,10 +117,15 @@ object Test {
97117
}
98118

99119
def main(args: Array[String]): Unit = {
100-
basic(new FooI)
101-
currying(new FooI)
102-
etaExpansion(new FooI)
103-
implicits(new FooI)
104-
dependant(new FooI)
120+
basic(new Foo1)
121+
currying(new Foo1)
122+
etaExpansion(new Foo1)
123+
implicits(new Foo1)
124+
dependent(new Foo1)
125+
basic(new Foo2)
126+
currying(new Foo2)
127+
etaExpansion(new Foo2)
128+
implicits(new Foo2)
129+
dependent(new Foo2)
105130
}
106131
}

tests/semanticdb/metac.expect

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,23 +144,23 @@ Occurrences:
144144
[24:12..24:12): -> scala/reflect/Selectable.reflectiveSelectable().
145145
[24:12..24:13): s -> advanced/Test.s.
146146
[24:14..24:16): s1 -> advanced/Structural#s1().
147-
[24:16..24:16): -> scala/Selectable#selectDynamic().
147+
[24:16..24:16): -> scala/reflect/Selectable#selectDynamic().
148148
[25:6..25:8): s2 <- advanced/Test.s2.
149149
[25:11..25:12): s -> advanced/Test.s.
150150
[25:13..25:15): s2 -> advanced/Structural#s2().
151151
[26:6..26:9): s2x <- advanced/Test.s2x.
152152
[26:12..26:12): -> scala/reflect/Selectable.reflectiveSelectable().
153153
[26:12..26:13): s -> advanced/Test.s.
154154
[26:14..26:16): s2 -> advanced/Structural#s2().
155-
[26:16..26:16): -> scala/Selectable#selectDynamic().
155+
[26:16..26:16): -> scala/reflect/Selectable#selectDynamic().
156156
[27:6..27:8): s3 <- advanced/Test.s3.
157157
[27:11..27:12): s -> advanced/Test.s.
158158
[27:13..27:15): s3 -> advanced/Structural#s3().
159159
[28:6..28:9): s3x <- advanced/Test.s3x.
160160
[28:12..28:12): -> scala/reflect/Selectable.reflectiveSelectable().
161161
[28:12..28:13): s -> advanced/Test.s.
162162
[28:14..28:16): s3 -> advanced/Structural#s3().
163-
[28:16..28:16): -> scala/Selectable#applyDynamic().
163+
[28:16..28:16): -> scala/reflect/Selectable#applyDynamic().
164164
[28:18..28:18): -> scala/reflect/ClassTag.apply().
165165
[28:19..28:22): ??? -> scala/Predef.`???`().
166166
[30:6..30:7): e <- advanced/Test.e.

0 commit comments

Comments
 (0)