Skip to content

Commit 555089a

Browse files
committed
Open expected and actual existentials (draft)
1 parent 322501b commit 555089a

File tree

9 files changed

+132
-30
lines changed

9 files changed

+132
-30
lines changed

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

+22-12
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,13 @@ class CheckCaptures extends Recheck, SymTransformer:
696696
interpolateVarsIn(tree.tpt)
697697
curEnv = saved
698698

699+
override def recheckRHS(rhs: Tree, pt: Type)(using Context): Type =
700+
def avoidMap = new TypeOps.AvoidMap:
701+
def toAvoid(tp: NamedType) =
702+
tp.isTerm && tp.symbol.owner == ctx.owner && !tp.symbol.is(Param)
703+
val tp = recheck(rhs, pt)
704+
if ctx.owner.is(Method) then avoidMap(tp) else tp
705+
699706
/** If val or def definition with inferred (result) type is visible
700707
* in other compilation units, check that the actual inferred type
701708
* conforms to the expected type where all inferred capture sets are dropped.
@@ -812,7 +819,8 @@ class CheckCaptures extends Recheck, SymTransformer:
812819
* Otherwise, if the result type is boxed, simulate an unboxing by
813820
* adding all references in the boxed capture set to the current environment.
814821
*/
815-
override def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type =
822+
override def recheck(tree: Tree, pt0: Type = WildcardType)(using Context): Type =
823+
val pt = Existential.openExpected(pt0)
816824
val saved = curEnv
817825
tree match
818826
case _: RefTree | closureDef(_) if pt.isBoxedCapturing =>
@@ -878,23 +886,24 @@ class CheckCaptures extends Recheck, SymTransformer:
878886
* where local capture roots are instantiated to root variables.
879887
*/
880888
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type =
881-
var expected1 = alignDependentFunction(expected, actual.stripCapturing)
882-
val actualBoxed = adaptBoxed(actual, expected1, tree.srcPos)
889+
val actualUnpacked = Existential.skolemize(actual)
890+
var expected1 = alignDependentFunction(expected, actualUnpacked.stripCapturing)
891+
val actualBoxed = adaptBoxed(actualUnpacked, expected1, tree.srcPos)
883892
//println(i"check conforms $actualBoxed <<< $expected1")
884893

885-
if actualBoxed eq actual then
894+
if actualBoxed eq actualUnpacked then
886895
// Only `addOuterRefs` when there is no box adaptation
887-
expected1 = addOuterRefs(expected1, actual)
896+
expected1 = addOuterRefs(expected1, actualUnpacked)
888897
if isCompatible(actualBoxed, expected1) then
889898
if debugSuccesses then tree match
890899
case Ident(_) =>
891-
println(i"SUCCESS $tree:\n${TypeComparer.explained(_.isSubType(actual, expected))}")
900+
println(i"SUCCESS $tree:\n${TypeComparer.explained(_.isSubType(actualUnpacked, expected))}")
892901
case _ =>
893902
actualBoxed
894903
else
895904
capt.println(i"conforms failed for ${tree}: $actual vs $expected")
896905
err.typeMismatch(tree.withType(actualBoxed), expected1, addenda ++ CaptureSet.levelErrors)
897-
actual
906+
actualUnpacked
898907
end checkConformsExpr
899908

900909
/** Turn `expected` into a dependent function when `actual` is dependent. */
@@ -1121,7 +1130,7 @@ class CheckCaptures extends Recheck, SymTransformer:
11211130
/** If result derives from caps.Capability, yet is not a capturing type itself,
11221131
* make its capture set explicit.
11231132
*/
1124-
def makeCaptureSetExplicit(result: Type) = result match
1133+
def makeCaptureSetExplicit(actual: Type, result: Type) = result match
11251134
case CapturingType(_, _) => result
11261135
case _ =>
11271136
if result.derivesFromCapability then
@@ -1147,16 +1156,17 @@ class CheckCaptures extends Recheck, SymTransformer:
11471156
case _ =>
11481157
case _ =>
11491158
val adapted = adapt(actualw.withReachCaptures(actual), expected, covariant = true)
1150-
makeCaptureSetExplicit:
1159+
makeCaptureSetExplicit(actual,
11511160
if adapted ne actualw then
11521161
capt.println(i"adapt boxed $actual vs $expected ===> $adapted")
11531162
adapted
11541163
else
1155-
actual
1164+
actual)
11561165
end adaptBoxed
11571166

11581167
/** Check overrides again, taking capture sets into account.
11591168
* TODO: Can we avoid doing overrides checks twice?
1169+
*
11601170
* We need to do them here since only at this phase CaptureTypes are relevant
11611171
* But maybe we can then elide the check during the RefChecks phase under captureChecking?
11621172
*/
@@ -1167,12 +1177,12 @@ class CheckCaptures extends Recheck, SymTransformer:
11671177
* @param sym symbol of the field definition that is being checked
11681178
*/
11691179
override def checkSubType(actual: Type, expected: Type)(using Context): Boolean =
1170-
val expected1 = alignDependentFunction(addOuterRefs(expected, actual), actual.stripCapturing)
1180+
val expected1 = alignDependentFunction(addOuterRefs(Existential.strip(expected), actual), actual.stripCapturing)
11711181
val actual1 =
11721182
val saved = curEnv
11731183
try
11741184
curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv)
1175-
val adapted = adaptBoxed(actual, expected1, srcPos, alwaysConst = true)
1185+
val adapted = adaptBoxed(Existential.strip(actual), expected1, srcPos, alwaysConst = true)
11761186
actual match
11771187
case _: MethodType =>
11781188
// We remove the capture set resulted from box adaptation for method types,

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

+68-9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import StdNames.nme
99
import ast.tpd.*
1010
import Decorators.*
1111
import typer.ErrorReporting.errorType
12+
import NameKinds.exSkolemName
1213

1314
/**
1415
@@ -23,7 +24,7 @@ In adapt:
2324
method as owner, and use its termRef
2425
Then,
2526
+ If the EX-bound variable appears only at toplevel, replace it with `x`
26-
+ Otherwise, replace it with a fresh reach capability `x*`.
27+
+ Otherwise, replace it with `x*`.
2728
2829
In avoidance of a type T:
2930
@@ -145,6 +146,21 @@ Existential source syntax:
145146
- The type of a type ascription in an expression or pattern
146147
- The argument and result types of a function.
147148
149+
Restrictions on Existential Types:
150+
151+
- An existential capture ref must be the only member of its set. This is
152+
intended to model the idea that existential variables effectibely range
153+
over capture sets, not capture references. But so far out calculus
154+
and implementation does not yet acoommodate first-class capture sets.
155+
- Existential capture refs must appear co-variantly in their bound type
156+
157+
So the following would all be illegal:
158+
159+
EX x.C^{x, io} // error: multiple members
160+
EX x.() => EX y.C^{x, y} // error: multiple members
161+
EX x.C^{x} ->{x} D // error: contra-variant occurrence
162+
EX x.Set[C^{x}] // error: invariant occurrence
163+
148164
Expansion of ^:
149165
150166
We drop `cap` as a capture reference, but keep the unqualified `^` syntax.
@@ -188,6 +204,55 @@ Expansion of ^:
188204
*/
189205
object Existential:
190206

207+
type Carrier = RefinedType
208+
209+
def openExpected(pt: Type)(using Context): Type = pt.dealias match
210+
case Existential(boundVar, unpacked) =>
211+
val tm = new IdempotentCaptRefMap:
212+
val cvar = CaptureSet.Var(ctx.owner)
213+
def apply(t: Type) = mapOver(t) match
214+
case t @ CapturingType(parent, refs) if refs.elems.contains(boundVar) =>
215+
assert(refs.isConst && refs.elems.size == 1, i"malformed existential $t")
216+
t.derivedCapturingType(parent, cvar)
217+
case t =>
218+
t
219+
openExpected(tm(unpacked))
220+
case _ => pt
221+
222+
def strip(tp: Type)(using Context) = tp match
223+
case Existential(_, tpunpacked) => tpunpacked
224+
case _ => tp
225+
226+
def skolemize(tp: Type)(using Context) = tp.widenDealias match
227+
case Existential(boundVar, unpacked) =>
228+
val skolem = tp match
229+
case tp: CaptureRef if tp.isTracked => tp
230+
case _ => newSkolemSym(boundVar.underlying).termRef
231+
val tm = new IdempotentCaptRefMap:
232+
var deep = false
233+
private inline def deepApply(t: Type): Type =
234+
val saved = deep
235+
deep = true
236+
try apply(t) finally deep = saved
237+
def apply(t: Type) =
238+
if t eq boundVar then
239+
if deep then skolem.reach else skolem
240+
else t match
241+
case defn.FunctionOf(args, res, contextual) =>
242+
val res1 = deepApply(res)
243+
if res1 ne res then defn.FunctionOf(args, res1, contextual)
244+
else t
245+
case defn.RefinedFunctionOf(mt) =>
246+
mt.derivedLambdaType(resType = deepApply(mt.resType))
247+
case _ =>
248+
mapOver(t)
249+
tm(unpacked)
250+
case _ => tp
251+
end skolemize
252+
253+
def newSkolemSym(tp: Type)(using Context): TermSymbol =
254+
newSymbol(ctx.owner.enclosingMethodOrClass, exSkolemName.fresh(), Synthetic, tp)
255+
/*
191256
def fromDepFun(arg: Tree)(using Context): Type = arg.tpe match
192257
case RefinedType(parent, nme.apply, info: MethodType) if defn.isNonRefinedFunction(parent) =>
193258
info match
@@ -198,13 +263,7 @@ object Existential:
198263
errorType(em"Malformed existential: dependent function must have a singgle parameter of type caps.Capability", arg.srcPos)
199264
case _ =>
200265
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-
266+
*/
208267
private class PackMap(sym: Symbol, rt: RecType)(using Context) extends DeepTypeMap, IdempotentCaptRefMap:
209268
def apply(tp: Type): Type = tp match
210269
case ref: TermRef if ref.symbol == sym => TermRef(rt.recThis, defn.captureRoot)
@@ -235,7 +294,7 @@ object Existential:
235294
case (info: TypeRef) :: rest => info.symbol == defn.Caps_Exists && rest.isEmpty
236295
case _ => false
237296

238-
def unapply(tp: RefinedType)(using Context): Option[(TermParamRef, Type)] =
297+
def unapply(tp: Carrier)(using Context): Option[(TermParamRef, Type)] =
239298
tp.refinedInfo match
240299
case mt: MethodType
241300
if isExistentialMethod(mt) && defn.isNonRefinedFunction(tp.parent) =>

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
237237
val rinfo1 = apply(rinfo)
238238
if rinfo1 ne rinfo then rinfo1.toFunctionType(alwaysDependent = true)
239239
else tp
240+
case Existential(_, unpacked) =>
241+
apply(unpacked)
240242
case tp: MethodType =>
241243
tp.derivedLambdaType(
242244
paramInfos = mapNested(tp.paramInfos),
@@ -252,7 +254,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
252254
end apply
253255
end mapInferred
254256

255-
mapInferred(refine = true)(tp)
257+
try mapInferred(refine = true)(tp)
258+
catch case ex: AssertionError =>
259+
println(i"error while maop inferred $tp")
260+
throw ex
256261
end transformInferredType
257262

258263
private def transformExplicitType(tp: Type, tptToCheck: Option[Tree] = None)(using Context): Type =

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

+1
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ object NameKinds {
332332
val InlineScrutineeName: UniqueNameKind = new UniqueNameKind("$scrutinee")
333333
val InlineBinderName: UniqueNameKind = new UniqueNameKind("$proxy")
334334
val MacroNames: UniqueNameKind = new UniqueNameKind("$macro$")
335+
val exSkolemName: UniqueNameKind = new UniqueNameKind("$exSkolem")
335336

336337
val UniqueExtMethName: UniqueNameKind = new UniqueNameKindWithUnmangle("$extension")
337338

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -3318,7 +3318,12 @@ object Types extends TypeUtils {
33183318
private def badInst =
33193319
throw new AssertionError(s"bad instantiation: $this")
33203320

3321-
def checkInst(using Context): this.type = this // debug hook
3321+
def checkInst(using Context): this.type =
3322+
if refinedName == nme.apply then
3323+
parent.dealias.stripAnnots match
3324+
case RefinedType(_, nme.apply, _) => assert(false, this) // debug hook
3325+
case _ =>
3326+
this
33223327

33233328
final def derivedRefinedType
33243329
(parent: Type = this.parent, refinedName: Name = this.refinedName, refinedInfo: Type = this.refinedInfo)

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
564564
case SingletonTypeTree(ref) =>
565565
toTextLocal(ref) ~ "." ~ keywordStr("type")
566566
case RefinedTypeTree(tpt, refines) =>
567-
toTextLocal(tpt) ~ " " ~ blockText(refines)
567+
if defn.isFunctionSymbol(tpt.symbol) && tree.hasType && !printDebug
568+
then changePrec(GlobalPrec) { toText(tree.typeOpt) }
569+
else toTextLocal(tpt) ~ blockText(refines)
568570
case AppliedTypeTree(tpt, args) =>
569571
if (tpt.symbol == defn.orType && args.length == 2)
570572
changePrec(OrTypePrec) { toText(args(0)) ~ " | " ~ atPrec(OrTypePrec + 1) { toText(args(1)) } }

compiler/src/dotty/tools/dotc/transform/Recheck.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,16 @@ abstract class Recheck extends Phase, SymTransformer:
249249
def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type =
250250
val resType = recheck(tree.tpt)
251251
if tree.rhs.isEmpty then resType
252-
else recheck(tree.rhs, resType)
252+
else recheckRHS(tree.rhs, resType)
253253

254254
def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type =
255255
inContext(linkConstructorParams(sym).withOwner(sym)):
256256
val resType = recheck(tree.tpt)
257257
if tree.rhs.isEmpty || sym.isInlineMethod || sym.isEffectivelyErased
258258
then resType
259-
else recheck(tree.rhs, resType)
259+
else recheckRHS(tree.rhs, resType)
260+
261+
def recheckRHS(rhs: Tree, pt: Type)(using Context): Type = recheck(rhs, pt)
260262

261263
def recheckTypeDef(tree: TypeDef, sym: Symbol)(using Context): Type =
262264
recheck(tree.rhs)

tests/neg/cc-existentials.scala renamed to tests/neg/cc-ex-conformance.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import caps.{Exists, Capability}
33

44
class C
55

6-
type EX1 = (c: Exists) => (C^{c}, C^{c})
6+
type EX1 = () => (c: Exists) => (C^{c}, C^{c})
77

8-
type EX2 = (c1: Exists) => (c2: Exists) => (C^{c1}, C^{c2})
8+
type EX2 = () => (c1: Exists) => (c2: Exists) => (C^{c1}, C^{c2})
99

10-
type EX3 = (c: Exists) => () => C^{c}
10+
type EX3 = () => (c: Exists) => () => C^{c}
1111

12-
type EX4 = () => (c: Exists) => C^{c}
12+
type EX4 = () => () => (c: Exists) => C^{c}
1313

1414
def Test =
1515
val ex1: EX1 = ???

tests/pos/cc-ex-unpack.scala

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import language.experimental.captureChecking
2+
import caps.{Exists, Capability}
3+
4+
class C
5+
6+
type EX1 = (c: Exists) -> (C^{c}, C^{c})
7+
8+
type EX2 = () -> (c1: Exists) -> (c2: Exists) -> (C^{c1}, C^{c2})
9+
10+
type EX3 = () -> (c: Exists) -> () -> C^{c}
11+
12+
type EX4 = () -> () -> (c: Exists) -> C^{c}
13+
14+
def Test =
15+
def f =
16+
val ex1: EX1 = ???
17+
val c1 = ex1
18+
c1

0 commit comments

Comments
 (0)