Skip to content

Commit 30f9f48

Browse files
committed
Improve implicit search involving SelectProtos over dependent method types
Refine the isMatchedBy condition of SelectProto to take into account that sometimes we do not know yet what the members of a type could be. In these cases we now err on the side of generosity and assume that the type does match the selection. This enables implicit searches for cases like a.f ==> implicitDeco(a).f where `implicitDeco` is a dependent function, with a result type depending on `a` or an implicit argument of `implicitDeco`. So far neither dotty not scalac supported this pattern. Test case in typeclass-encoding.scala.
1 parent 0353e64 commit 30f9f48

File tree

2 files changed

+114
-1
lines changed

2 files changed

+114
-1
lines changed

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,30 @@ object ProtoTypes {
9797
abstract case class SelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean)
9898
extends CachedProxyType with ProtoType with ValueTypeOrProto {
9999

100+
/** Is the set of members of this type unknown? This is the caes if:
101+
* 1. The type has Nothing or Wildcard as a prefix or underlying type
102+
* 2. The type has an uninstantiated TypeVar as a prefix or underlying type,
103+
* or as an upper bound of a prefix and underlying type.
104+
*/
105+
private def hasUnknownMembers(tp: Type)(implicit ctx: Context): Boolean = tp match {
106+
case tp: TypeVar => !tp.isInstantiated
107+
case tp: WildcardType => true
108+
case tp: TypeRef =>
109+
val sym = tp.symbol
110+
sym == defn.NothingClass ||
111+
!sym.isStatic && {
112+
hasUnknownMembers(tp.prefix) || {
113+
val bound = tp.info.hiBound
114+
bound.isProvisional && hasUnknownMembers(bound)
115+
}
116+
}
117+
case tp: TypeProxy => hasUnknownMembers(tp.superType)
118+
case _ => false
119+
}
120+
100121
override def isMatchedBy(tp1: Type)(implicit ctx: Context) = {
101-
name == nme.WILDCARD || {
122+
name == nme.WILDCARD || hasUnknownMembers(tp1) ||
123+
{
102124
val mbr = if (privateOK) tp1.member(name) else tp1.nonPrivateMember(name)
103125
def qualifies(m: SingleDenotation) =
104126
memberProto.isRef(defn.UnitClass) ||

tests/pos/typeclass-encoding.scala

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/** A possible type class encoding for
2+
3+
trait SemiGroup {
4+
def add(that: This): This
5+
}
6+
7+
trait Monoid extends SemiGroup {
8+
static def unit: This
9+
}
10+
11+
extend Int : Monoid {
12+
def add(that: Int) = this + that
13+
static def unit = 0
14+
}
15+
16+
extend String : Monoid {
17+
def add(that: Int) = this ++ that
18+
static def unit = ""
19+
}
20+
21+
def sum[T: Monoid](xs: List[T]): T =
22+
(instance[T, Monoid].unit /: xs)(_ `add` _)
23+
24+
*/
25+
object runtime {
26+
27+
trait TypeClass {
28+
type This
29+
type StaticPart[This]
30+
}
31+
32+
trait Implementation[From] {
33+
type This = From
34+
type Implemented <: TypeClass
35+
def inject(x: From): Implemented { type This = From }
36+
}
37+
38+
class CompanionOf[T] { type StaticPart[_] }
39+
40+
def instance[From, To <: TypeClass](
41+
implicit ev1: Implementation[From] { type Implemented = To },
42+
ev2: CompanionOf[To]): Implementation[From] { type Implemented = To } & ev2.StaticPart[From] =
43+
ev1.asInstanceOf // can we avoid the cast?
44+
45+
implicit def inject[From](x: From)(
46+
implicit ev1: Implementation[From]): ev1.Implemented { type This = From } =
47+
ev1.inject(x)
48+
}
49+
50+
object semiGroups {
51+
import runtime._
52+
53+
trait SemiGroup extends TypeClass {
54+
def add(that: This): This
55+
}
56+
57+
trait Monoid extends SemiGroup {
58+
type StaticPart[This] <: MonoidStatic[This]
59+
}
60+
abstract class MonoidStatic[This] { def unit: This }
61+
62+
implicit def companionOfMonoid: CompanionOf[Monoid] {
63+
type StaticPart[X] = MonoidStatic[X]
64+
} = new CompanionOf[Monoid] {
65+
type StaticPart[X] = MonoidStatic[X]
66+
}
67+
68+
implicit object extend_Int_Monoid extends MonoidStatic[Int] with Implementation[Int] {
69+
type Implemented = Monoid
70+
def unit: Int = 0
71+
def inject($this: Int) = new Monoid {
72+
type This = Int
73+
def add(that: This): This = $this + that
74+
}
75+
}
76+
77+
implicit object extend_String_Monoid extends MonoidStatic[String] with Implementation[String] {
78+
type Implemented = Monoid
79+
def unit = ""
80+
def inject($this: String): Monoid { type This = String } =
81+
new Monoid {
82+
type This = String
83+
def add(that: This): This = $this ++ that
84+
}
85+
}
86+
87+
def sum[T](xs: List[T])(implicit $ev: Implementation[T] { type Implemented = Monoid } ) = {
88+
(instance[T, Monoid].unit /: xs)((x, y) => inject(x) `add` y)
89+
(instance[T, Monoid].unit /: xs)((x, y) => x `add` y) // fails in scalac and previous dotc.
90+
}
91+
}

0 commit comments

Comments
 (0)