Skip to content

Commit 322501b

Browse files
committed
Implement subtyping between existential types
1 parent 917d576 commit 322501b

File tree

7 files changed

+167
-83
lines changed

7 files changed

+167
-83
lines changed

compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import printing.{Showable, Printer}
1414
import printing.Texts.*
1515
import util.{SimpleIdentitySet, Property}
1616
import typer.ErrorReporting.Addenda
17+
import TypeComparer.linkOK
1718
import util.common.alwaysTrue
1819
import scala.collection.mutable
1920

@@ -159,6 +160,7 @@ sealed abstract class CaptureSet extends Showable:
159160
|| y.info.match
160161
case y1: CaptureRef => x.subsumes(y1)
161162
case _ => false
163+
case y: TermParamRef => linkOK(y, x)
162164
case MaybeCapability(y1) => x.stripMaybe.subsumes(y1)
163165
case _ => false
164166
|| x.match
@@ -167,6 +169,7 @@ sealed abstract class CaptureSet extends Showable:
167169
x.info match
168170
case x1: CaptureRef => x1.subsumes(y)
169171
case _ => false
172+
case x: TermParamRef => linkOK(x, y)
170173
case _ => false
171174

172175
/** {x} <:< this where <:< is subcapturing, but treating all variables

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -907,8 +907,7 @@ class CheckCaptures extends Recheck, SymTransformer:
907907
case expected @ defn.FunctionOf(args, resultType, isContextual)
908908
if defn.isNonRefinedFunction(expected) =>
909909
actual match
910-
case RefinedType(parent, nme.apply, rinfo: MethodType)
911-
if defn.isFunctionNType(actual) =>
910+
case defn.RefinedFunctionOf(rinfo: MethodType) =>
912911
depFun(args, resultType, isContextual, rinfo.paramNames)
913912
case _ => expected
914913
case _ => expected
@@ -999,7 +998,7 @@ class CheckCaptures extends Recheck, SymTransformer:
999998
val (eargs, eres) = expected.dealias.stripCapturing match
1000999
case defn.FunctionOf(eargs, eres, _) => (eargs, eres)
10011000
case expected: MethodType => (expected.paramInfos, expected.resType)
1002-
case expected @ RefinedType(_, _, rinfo: MethodType) if defn.isFunctionNType(expected) => (rinfo.paramInfos, rinfo.resType)
1001+
case defn.RefinedFunctionOf(rinfo: MethodType) => (rinfo.paramInfos, rinfo.resType)
10031002
case _ => (aargs.map(_ => WildcardType), WildcardType)
10041003
val aargs1 = aargs.zipWithConserve(eargs) { (aarg, earg) => adapt(aarg, earg, !covariant) }
10051004
val ares1 = adapt(ares, eres, covariant)

compiler/src/dotty/tools/dotc/cc/Existential.scala

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import core.*
66
import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.*
77
import CaptureSet.IdempotentCaptRefMap
88
import StdNames.nme
9+
import ast.tpd.*
10+
import Decorators.*
11+
import typer.ErrorReporting.errorType
912

1013
/**
1114
@@ -77,20 +80,29 @@ Subtype rules
7780
7881
Representation:
7982
80-
EX a.T[a] is represented as
83+
EX a.T[a] is represented as a dependent function type
8184
82-
r @ RecType(T[TermRef[r.recThis, caps.cap]]
85+
(a: Exists) => T[a]]
86+
87+
where Exists is defined in caps like this:
88+
89+
sealed trait Exists extends Capability
90+
91+
The defn.RefinedFunctionOf extractor will exclude existential types from
92+
its results, so only normal refined functions match.
93+
94+
Let `boundvar(ex)` be the TermParamRef defined by the extistential type `ex`.
8395
8496
Subtype checking algorithm, general scheme:
8597
8698
Maintain two structures in TypeComparer:
8799
88-
openExistentials: List[RecThis]
89-
assocExistentials: Map[RecThis, List[RecThis]]
100+
openExistentials: List[TermParamRef]
101+
assocExistentials: Map[TermParamRef, List[TermParamRef]]
90102
91103
`openExistentials` corresponds to the list of existential variables stored in the environment.
92104
`assocExistentials` maps existential variables bound by existentials appearing on the right
93-
of a subtype judgement to a list of possible associations. Initally this is openExistentials
105+
of a subtype judgement to a list of possible associations. Initially this is openExistentials
94106
at the time when the existential on the right was dropped. It can become a single existential
95107
when the existentially bound key variable is unified with one of the variables in the
96108
environment.
@@ -100,38 +112,31 @@ Subtype checking algorithm, steps to add for tp1 <:< tp2:
100112
If tp1 is an existential EX a.tp1a:
101113
102114
val saved = openExistentials
103-
openExistentials = tp1.recThis :: openExistentials
115+
openExistentials = boundvar(tp1) :: openExistentials
104116
try tp1a <:< tp2
105117
finally openExistentials = saved
106118
107119
If tp2 is an existential EX a.tp2a:
108120
109121
val saved = assocExistentials
110-
assocExistentials = assocExistentials + (tp2.recThis -> openExistentials
122+
assocExistentials = assocExistentials + (boundvar(tp2) -> openExistentials
111123
try tp1 <:< tp2a
112124
finally assocExistentials = saved
113125
114-
If tp1 and tp2 are existentially bound variables `TermRef(pre1/pre2: RecThis, cap)`:
126+
If tp1 and tp2 are existentially bound variables:
115127
116-
assocExistentials(pre2).contains(pre1) &&
117-
{ assocExistentials(pre2) = List(pre1); true }
128+
assocExistentials(tpi).contains(tpj) &&
129+
{ assocExistentials(tpi) = List(tpj); true }
118130
119131
Existential source syntax:
120132
121133
Existential types are ususally not written in source, since we still allow the `^`
122134
syntax that can express most of them more concesely (see below for translation rules).
123135
But we should also allow to write existential types explicity, even if it ends up mainly
124-
for debugging. To express them, we add the following trait definition in the caps object:
125-
126-
trait Exists[X]
127-
128-
A typical expression of an existential is then
136+
for debugging. To express them, we use the encoding with `Exists`, so a typical
137+
expression of an existential would be
129138
130-
Exists[(x: Capability) => A ->{x} B]
131-
132-
Existential types are expanded at Typer to the RecType syntax presented above. It is checked
133-
that the type argument is a dependent function type with one argument of type `Capability` and
134-
that this argument is used only in capture sets of the result type.
139+
(x: Exists) => A ->{x} B
135140
136141
Existential types can only appear at the top-level of _legal existential scopes_. These are:
137142
@@ -183,6 +188,23 @@ Expansion of ^:
183188
*/
184189
object Existential:
185190

191+
def fromDepFun(arg: Tree)(using Context): Type = arg.tpe match
192+
case RefinedType(parent, nme.apply, info: MethodType) if defn.isNonRefinedFunction(parent) =>
193+
info match
194+
case info @ MethodType(_ :: Nil)
195+
if info.paramInfos.head.derivesFrom(defn.Caps_Capability) =>
196+
apply(ref => info.resultType.substParams(info, ref :: Nil))
197+
case _ =>
198+
errorType(em"Malformed existential: dependent function must have a singgle parameter of type caps.Capability", arg.srcPos)
199+
case _ =>
200+
errorType(em"Malformed existential: dependent function type expected", arg.srcPos)
201+
202+
def apply(fn: TermRef => Type)(using Context): RecType =
203+
RecType(rt => fn(exBoundRef(rt)))
204+
205+
def exBoundRef(rt: RecType)(using Context): TermRef =
206+
TermRef(rt.recThis, defn.captureRoot)
207+
186208
private class PackMap(sym: Symbol, rt: RecType)(using Context) extends DeepTypeMap, IdempotentCaptRefMap:
187209
def apply(tp: Type): Type = tp match
188210
case ref: TermRef if ref.symbol == sym => TermRef(rt.recThis, defn.captureRoot)
@@ -209,9 +231,14 @@ object Existential:
209231
def fromSymbol(tp: Type, sym: Symbol)(using Context): RecType =
210232
RecType(PackMap(sym, _)(tp))
211233

212-
def unapply(rt: RecType)(using Context): Option[Type] =
213-
if isCaptureChecking && rt.parent.existsPart(isExBound(_, rt))
214-
then Some(rt.parent)
215-
else None
234+
def isExistentialMethod(mt: MethodType)(using Context): Boolean = mt.paramInfos match
235+
case (info: TypeRef) :: rest => info.symbol == defn.Caps_Exists && rest.isEmpty
236+
case _ => false
216237

238+
def unapply(tp: RefinedType)(using Context): Option[(TermParamRef, Type)] =
239+
tp.refinedInfo match
240+
case mt: MethodType
241+
if isExistentialMethod(mt) && defn.isNonRefinedFunction(tp.parent) =>
242+
Some(mt.paramRefs.head, mt.resultType)
243+
case _ => None
217244
end Existential

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Comments.{Comment, docCtx}
1515
import util.Spans.NoSpan
1616
import config.Feature
1717
import Symbols.requiredModuleRef
18-
import cc.{CaptureSet, RetainingType}
18+
import cc.{CaptureSet, RetainingType, Existential}
1919
import ast.tpd.ref
2020

2121
import scala.annotation.tailrec
@@ -993,6 +993,7 @@ class Definitions {
993993
@tu lazy val captureRoot: TermSymbol = CapsModule.requiredValue("cap")
994994
@tu lazy val Caps_Capability: TypeSymbol = CapsModule.requiredType("Capability")
995995
@tu lazy val Caps_reachCapability: TermSymbol = CapsModule.requiredMethod("reachCapability")
996+
@tu lazy val Caps_Exists = requiredClass("scala.caps.Exists")
996997
@tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe")
997998
@tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure")
998999
@tu lazy val Caps_unsafeBox: Symbol = CapsUnsafeModule.requiredMethod("unsafeBox")
@@ -1189,11 +1190,17 @@ class Definitions {
11891190

11901191
/** Matches a refined `PolyFunction`/`FunctionN[...]`/`ContextFunctionN[...]`.
11911192
* Extracts the method type type and apply info.
1193+
* Will NOT math an existential type encoded as a dependent function.
11921194
*/
11931195
def unapply(tpe: RefinedType)(using Context): Option[MethodOrPoly] =
11941196
tpe.refinedInfo match
1195-
case mt: MethodOrPoly
1196-
if tpe.refinedName == nme.apply && isFunctionType(tpe.parent) => Some(mt)
1197+
case mt: MethodType
1198+
if tpe.refinedName == nme.apply
1199+
&& isFunctionType(tpe.parent)
1200+
&& !Existential.isExistentialMethod(mt) => Some(mt)
1201+
case mt: PolyType
1202+
if tpe.refinedName == nme.apply
1203+
&& isFunctionType(tpe.parent) => Some(mt)
11971204
case _ => None
11981205

11991206
end RefinedFunctionOf

0 commit comments

Comments
 (0)