Skip to content

Commit 2d407aa

Browse files
authored
Merge pull request #5391 from dotty-staging/struct-types
Handle structural types with curried methods
2 parents 9a992f0 + 59c90d1 commit 2d407aa

File tree

7 files changed

+185
-53
lines changed

7 files changed

+185
-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 a (potentially applied) 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
/** Structural tree comparison (since == on trees is reference equality).

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

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -117,61 +117,73 @@ 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+
def isDependentMethod(tpe: Type): Boolean = tpe match {
166+
case tpe: MethodType =>
167+
tpe.isParamDependent ||
168+
tpe.isResultDependent ||
169+
isDependentMethod(tpe.resultType)
170+
case _ =>
171+
false
172+
}
173+
174+
if (isDependentMethod(tpe))
159175
fail(name, i"has a method type with inter-parameter dependencies")
160176
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)
177+
val ctags = tpe.paramInfoss.flatten.map(pt =>
178+
implicitArgTree(defn.ClassTagType.appliedTo(pt.widenDealias :: Nil), fun.pos.endPos))
179+
structuralCall(nme.applyDynamic, ctags).asInstance(tpe.finalResultType)
164180
}
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] =>
181+
182+
// (@allanrenucci) I think everything below is dead code
183+
case _: PolyType =>
172184
fail(name, "is polymorphic")
173-
case Select(_, name) =>
174-
fail(name, i"has an unsupported type: ${tree.tpe.widen}")
185+
case tpe =>
186+
fail(name, i"has an unsupported type: $tpe")
175187
}
176188
}
177189
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2647,9 +2647,8 @@ class Typer extends Namer
26472647
convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs)))
26482648
}
26492649
case wtp =>
2650-
if (isStructuralTermApply(tree))
2651-
readaptSimplified(handleStructural(tree))
2652-
else if (isStructuralTermSelect(tree) && tree.tpe.widen.isValueType)
2650+
val isStructuralCall = wtp.isValueType && isStructuralTermSelectOrApply(tree)
2651+
if (isStructuralCall)
26532652
readaptSimplified(handleStructural(tree))
26542653
else pt match {
26552654
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/neg/structural.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,20 @@ object Test3 {
2020

2121
type G = { def foo(x: Int, y: Int): Unit }
2222
def j(x: G) = x.foo(???) // error: missing argument
23+
24+
class H { type S = String; type I }
25+
class I extends H { type I = Int }
26+
type Dep = {
27+
def fun1(x: H, y: x.S): Int
28+
def fun2(x: H, y: x.I): Int
29+
def fun3(y: H): y.S
30+
def fun4(y: H): y.I
31+
}
32+
def k(x: Dep) = {
33+
val y = new I
34+
x.fun1(y, "Hello")
35+
x.fun2(y, 1) // error
36+
x.fun3(y)
37+
x.fun4(y) // error
38+
}
2339
}

tests/run/structural.scala

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import scala.reflect.Selectable.reflectiveSelectable
2+
3+
object Test {
4+
class C { type S = String; type I }
5+
class D extends C { type I = Int }
6+
7+
type Foo = {
8+
def sel0: Int
9+
def sel1: Int => Int
10+
def fun0(x: Int): Int
11+
12+
def fun1(x: Int)(y: Int): Int
13+
def fun2(x: Int): Int => Int
14+
def fun3(a1: Int, a2: Int, a3: Int)
15+
(a4: Int, a5: Int, a6: Int)
16+
(a7: Int, a8: Int, a9: Int): Int
17+
18+
def fun4(implicit x: Int): Int
19+
def fun5(x: Int)(implicit y: Int): Int
20+
21+
def fun6(x: C, y: x.S): Int
22+
def fun7(x: C, y: x.I): Int
23+
def fun8(y: C): y.S
24+
def fun9(y: C): y.I
25+
}
26+
27+
class FooI {
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+
def basic(x: Foo): Unit ={
48+
assert(x.sel0 == 1)
49+
assert(x.sel1(2) == 2)
50+
assert(x.fun0(3) == 3)
51+
52+
val f = x.sel1
53+
assert(f(3) == 3)
54+
}
55+
56+
def currying(x: Foo): Unit = {
57+
assert(x.fun1(1)(2) == 3)
58+
assert(x.fun2(1)(2) == 2)
59+
assert(x.fun3(1, 2, 3)(4, 5, 6)(7, 8, 9) == -1)
60+
}
61+
62+
def etaExpansion(x: Foo): Unit = {
63+
val f0 = x.fun0(_)
64+
assert(f0(2) == 2)
65+
66+
val f1 = x.fun0 _
67+
assert(f1(2) == 2)
68+
69+
val f2 = x.fun1(1)(_)
70+
assert(f2(2) == 3)
71+
72+
val f3 = x.fun1(1) _
73+
assert(f3(2) == 3)
74+
75+
val f4 = x.fun1(1)
76+
assert(f4(2) == 3)
77+
}
78+
79+
def implicits(x: Foo) = {
80+
implicit val y = 2
81+
assert(x.fun4 == 2)
82+
assert(x.fun5(1) == 3)
83+
}
84+
85+
// Limited support for dependant methods
86+
def dependant(x: Foo) = {
87+
val y = new D
88+
89+
assert(x.fun6(y, "Hello") == 1)
90+
// assert(x.fun7(y, 1) == 2) // error: No ClassTag available for x.I
91+
92+
val s = x.fun8(y)
93+
assert((s: String) == "Hello")
94+
95+
// val i = x.fun9(y) // error: rejected (blows up in pickler if not rejected)
96+
// assert((i: String) == "Hello") // error: Type mismatch: found: y.S(i); required: String
97+
}
98+
99+
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)
105+
}
106+
}

0 commit comments

Comments
 (0)