Skip to content

Commit a22cd91

Browse files
committed
Handle structural types with curried methods
We desugar `x.foo(a1)(a2)` (where the type of `x` is a structural type) to: ```scala (x:selectable).applyDynamic("a", CTa1, CTa2)(a1, a2) ```
1 parent 40c714b commit a22cd91

File tree

6 files changed

+100
-53
lines changed

6 files changed

+100
-53
lines changed

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

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -737,22 +737,11 @@ 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-
751-
/** Is this a selection of a member of a structural type that is not a member
752-
* of an underlying class or trait?
740+
/** Is this an application of a selection of a member of a structural type
741+
* that is not a member of an underlying class or trait?
753742
*/
754-
def isStructuralTermSelect(tree: Tree)(implicit ctx: Context): Boolean = tree match {
755-
case tree: Select =>
743+
def isStructuralTermSelectOrApply(tree: Tree)(implicit ctx: Context): Boolean = {
744+
def isStructuralTermSelect(tree: Select) = {
756745
def hasRefinement(qualtpe: Type): Boolean = qualtpe.dealias match {
757746
case RefinedType(parent, rname, rinfo) =>
758747
rname == tree.name || hasRefinement(parent)
@@ -766,8 +755,16 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
766755
false
767756
}
768757
!tree.symbol.exists && tree.isTerm && hasRefinement(tree.qualifier.tpe)
769-
case _ =>
770-
false
758+
}
759+
def loop(tree: Tree): Boolean = tree match {
760+
case Apply(fun, _) =>
761+
loop(fun)
762+
case tree: Select =>
763+
isStructuralTermSelect(tree)
764+
case _ =>
765+
false
766+
}
767+
loop(tree)
771768
}
772769

773770
/** Is this call a call to a method that is marked as Inline */

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

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -117,61 +117,64 @@ trait Dynamic { self: Typer with Applications =>
117117
* Given `x.a`, where `x` is of (widened) type `T` (a value type or a nullary method type),
118118
* and `x.a` is of type `U`, map `x.a` to the equivalent of:
119119
*
120-
* (x: Selectable).selectDynamic("a").asInstanceOf[U]
120+
* ```scala
121+
* (x: Selectable).selectDynamic("a").asInstanceOf[U]
122+
* ```
121123
*
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:
124+
* Given `x.a(a11, ..., a1n)...(aN1, ..., aNn)`, where `x.a` is of (widened) type
125+
* `(T11, ..., T1n)...(TN1, ..., TNn) => R`, it is desugared to:
124126
*
125-
* (x:selectable).applyDynamic("a", CT1, ..., CTn)(arg1, ..., argn).asInstanceOf[R]
127+
* ```scala
128+
* (x:selectable).applyDynamic("a", CT11, ..., CT1n, ..., CTN1, ... CTNn)
129+
* (a11, ..., a1n, ..., aN1, ..., aNn)
130+
* .asInstanceOf[R]
131+
* ```
126132
*
127-
* where CT1, ..., CTn are the class tags representing the erasure of T1, ..., Tn.
133+
* where CT11, ..., CTNn are the class tags representing the erasure of T11, ..., TNn.
128134
*
129135
* It's an error if U is neither a value nor a method type, or a dependent method
130136
* type.
131137
*/
132138
def handleStructural(tree: Tree)(implicit ctx: Context): Tree = {
139+
val (fun @ Select(qual, name), targs, vargss) = decomposeCall(tree)
133140

134-
def structuralCall(qual: Tree, name: Name, selectorName: TermName, ctags: List[Tree], args: Option[List[Tree]]) = {
141+
def structuralCall(selectorName: TermName, ctags: List[Tree]) = {
135142
val selectable = adapt(qual, defn.SelectableType)
136143

137144
// ($qual: Selectable).$selectorName("$name", ..$ctags)
138145
val base =
139146
untpd.Apply(
140-
untpd.TypedSplice(selectable.select(selectorName)).withPos(tree.pos),
147+
untpd.TypedSplice(selectable.select(selectorName)).withPos(fun.pos),
141148
(Literal(Constant(name.toString)) :: ctags).map(untpd.TypedSplice(_)))
142149

143-
val scall = args match {
144-
case None => base
145-
case Some(args) => untpd.Apply(base, args)
146-
}
150+
val scall =
151+
if (vargss.isEmpty) base
152+
else untpd.Apply(base, vargss.flatten)
147153

148154
typed(scall)
149155
}
150156

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

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)
160+
fun.tpe.widen match {
161+
case tpe: ValueType =>
162+
structuralCall(nme.selectDynamic, Nil).asInstance(tpe)
163+
164+
case tpe: MethodType =>
165+
if (tpe.isParamDependent)
159166
fail(name, i"has a method type with inter-parameter dependencies")
160167
else {
161-
val ctags = funTpe.paramInfos.map(pt =>
162-
implicitArgTree(defn.ClassTagType.appliedTo(pt :: Nil), tree.pos.endPos))
163-
structuralCall(qual, name, nme.applyDynamic, ctags, Some(args)).asInstance(tpe.resultType)
168+
val ctags = tpe.paramInfoss.flatten.map(pt =>
169+
implicitArgTree(defn.ClassTagType.appliedTo(pt :: Nil), fun.pos.endPos))
170+
structuralCall(nme.applyDynamic, ctags).asInstance(tpe.finalResultType)
164171
}
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+
173+
// (@allanrenucci) I think everything below is dead code
174+
case _: PolyType =>
172175
fail(name, "is polymorphic")
173-
case Select(_, name) =>
174-
fail(name, i"has an unsupported type: ${tree.tpe.widen}")
176+
case tpe =>
177+
fail(name, i"has an unsupported type: $tpe")
175178
}
176179
}
177180
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2640,9 +2640,8 @@ class Typer extends Namer
26402640
convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs)))
26412641
}
26422642
case wtp =>
2643-
if (isStructuralTermApply(tree))
2644-
readaptSimplified(handleStructural(tree))
2645-
else if (isStructuralTermSelect(tree) && tree.tpe.widen.isValueType)
2643+
val isStructuralCall = wtp.isValueType && isStructuralTermSelectOrApply(tree)
2644+
if (isStructuralCall)
26462645
readaptSimplified(handleStructural(tree))
26472646
else pt match {
26482647
case pt: FunProto =>

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,19 @@ consider three distinct cases:
4747
(v: Selectable).selectDynamic("a").asInstanceOf[U]
4848
```
4949

50-
- If `U` is a method type `(T1, ..., Tn) => R` with at most 7
51-
parameters and it is not a dependent method type, we map `v.a` to
50+
- 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
5252
the equivalent of:
5353
```scala
5454
v.a(arg1, ..., argn)
5555
--->
56-
(v: Selectable).applyDynamic("a", CT1, ..., CTn)(arg1, ..., argn).asInstanceOf[R]
56+
(v: Selectable).applyDynamic("a", CT11, ..., CTn, ..., CTN1, ... CTNn)
57+
(a11, ..., a1n, ..., aN1, ..., aNn)
58+
.asInstanceOf[R]
5759
```
5860

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

6264
We make sure that `r` conforms to type `Selectable`, potentially by
6365
introducing an implicit conversion, and then call either

library/src/scala/reflect/Selectable.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Selectable(val receiver: Any) extends AnyVal with scala.Selectable {
1919
val paramClasses = paramTypes.map(_.runtimeClass)
2020
val mth = rcls.getMethod(name, paramClasses: _*)
2121
ensureAccessible(mth)
22-
mth.invoke(receiver, args.map(_.asInstanceOf[AnyRef]): _*)
22+
mth.invoke(receiver, args.asInstanceOf[Seq[AnyRef]]: _*)
2323
}
2424
}
2525

tests/run/structural.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import scala.reflect.Selectable.reflectiveSelectable
2+
3+
object Test {
4+
type Foo = {
5+
def foo(x: Int)(y: Int): Int
6+
def bar(x: Int): Int => Int
7+
def bat(a1: Int, a2: Int, a3: Int)
8+
(a4: Int, a5: Int, a6: Int)
9+
(a7: Int, a8: Int, a9: Int): Int
10+
def baz(x: Int): Int
11+
}
12+
13+
class FooI {
14+
def foo(x: Int)(y: Int): Int = x + y
15+
def bar(x: Int): Int => Int = y => x * y
16+
def bat(a1: Int, a2: Int, a3: Int)
17+
(a4: Int, a5: Int, a6: Int)
18+
(a7: Int, a8: Int, a9: Int): Int = -1
19+
def baz(x: Int): Int = x
20+
}
21+
22+
def test(x: Foo): Unit = {
23+
assert(x.foo(1)(2) == 3)
24+
assert(x.bar(1)(2) == 2)
25+
assert(x.bat(1, 2, 3)(4, 5, 6)(7, 8, 9) == -1)
26+
27+
val f1 = x.foo(1)(_)
28+
assert(f1(2) == 3)
29+
30+
val f2 = x.foo(1) _
31+
assert(f2(2) == 3)
32+
33+
val f3 = x.foo(1)
34+
assert(f3(2) == 3)
35+
36+
val f4 = x.baz
37+
assert(f4(1) == 1)
38+
39+
val f5 = x.baz _
40+
assert(f5(2) == 2)
41+
}
42+
43+
def main(args: Array[String]): Unit = {
44+
test(new FooI)
45+
}
46+
}

0 commit comments

Comments
 (0)