Skip to content

Commit f6f178b

Browse files
committed
Refactor handling of applications
Simplify code that handles applications, avoiding adding pieces of mutable state.
1 parent e1b31c1 commit f6f178b

File tree

3 files changed

+52
-63
lines changed

3 files changed

+52
-63
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,11 @@ extension (tp: Type)
273273
case _ =>
274274
tp
275275

276+
/** The first element of this path type */
277+
final def pathRoot(using Context): Type = tp.dealias match
278+
case tp1: NamedType if tp1.symbol.owner.isClass => tp1.prefix.pathRoot
279+
case _ => tp
280+
276281
/** If this is a unboxed capturing type with nonempty capture set, its boxed version.
277282
* Or, if type is a TypeBounds of capturing types, the version where the bounds are boxed.
278283
* The identity for all other types.

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

Lines changed: 44 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -424,61 +424,28 @@ class CheckCaptures extends Recheck, SymTransformer:
424424
end markFree
425425

426426
/** Include references captured by the called method in the current environment stack */
427-
def includeCallCaptures(sym: Symbol, pos: SrcPos)(using Context): Unit =
428-
if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos)
429-
430-
private val prefixCalls = util.EqHashSet[GenericApply]()
431-
private val unboxedArgs = util.EqHashSet[Tree]()
432-
433-
def handleCall(meth: Symbol, call: GenericApply, eval: () => Type)(using Context): Type =
434-
if prefixCalls.remove(call) then return eval()
435-
436-
val unboxedParamNames =
437-
meth.rawParamss.flatMap: params =>
438-
params.collect:
439-
case param if param.hasAnnotation(defn.UnboxAnnot) =>
440-
param.name
441-
.toSet
442-
443-
def markUnboxedArgs(call: GenericApply): Unit = call.fun.tpe.widen match
444-
case MethodType(pnames) =>
445-
for (pname, arg) <- pnames.lazyZip(call.args) do
446-
if unboxedParamNames.contains(pname) then
447-
unboxedArgs.add(arg)
448-
case _ =>
449-
450-
def markPrefixCalls(tree: Tree): Unit = tree match
451-
case tree: GenericApply =>
452-
prefixCalls.add(tree)
453-
markUnboxedArgs(tree)
454-
markPrefixCalls(tree.fun)
455-
case _ =>
456-
457-
markUnboxedArgs(call)
458-
markPrefixCalls(call.fun)
459-
val res = eval()
460-
includeCallCaptures(meth, call.srcPos)
461-
res
462-
end handleCall
427+
def includeCallCaptures(sym: Symbol, resType: Type, pos: SrcPos)(using Context): Unit = resType match
428+
case _: MethodOrPoly => // wait until method is fully applied
429+
case _ =>
430+
if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos)
463431

464432
override def recheckIdent(tree: Ident, pt: Type)(using Context): Type =
465-
if tree.symbol.is(Method) then
466-
if tree.symbol.info.isParameterless then
467-
// there won't be an apply; need to include call captures now
468-
includeCallCaptures(tree.symbol, tree.srcPos)
469-
else if !tree.symbol.isStatic then
433+
val sym = tree.symbol
434+
if sym.is(Method) then
435+
includeCallCaptures(sym, sym.info, tree.srcPos)
436+
else if !sym.isStatic then
470437
//debugShowEnvs()
471438
def addSelects(ref: TermRef, pt: Type): TermRef = pt match
472439
case pt: PathSelectionProto if ref.isTracked =>
473440
// if `ref` is not tracked then the selection could not give anything new
474441
// class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters.
475442
addSelects(ref.select(pt.sym).asInstanceOf[TermRef], pt.pt)
476443
case _ => ref
477-
val ref = tree.symbol.termRef
444+
val ref = sym.termRef
478445
val pathRef = addSelects(ref, pt)
479446
//if pathRef ne ref then
480447
// println(i"add selects $ref --> $pathRef")
481-
markFree(tree.symbol, if false then ref else pathRef, tree.srcPos)
448+
markFree(sym, if false then ref else pathRef, tree.srcPos)
482449
super.recheckIdent(tree, pt)
483450

484451
override def selectionProto(tree: Select, pt: Type)(using Context): Type =
@@ -536,6 +503,16 @@ class CheckCaptures extends Recheck, SymTransformer:
536503
selType
537504
}//.showing(i"recheck sel $tree, $qualType = $result")
538505

506+
/** Copy all @use annotations on method parameter symbols to the corresponding paramInfo types.
507+
*/
508+
override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType =
509+
val paramInfosWithUses = funtpe.paramInfos.zipWithConserve(funtpe.paramNames): (formal, pname) =>
510+
val paramOpt = meth.rawParamss.nestedFind(_.name == pname)
511+
paramOpt.flatMap(_.getAnnotation(defn.UnboxAnnot)) match
512+
case Some(ann) => AnnotatedType(formal, ann)
513+
case _ => formal
514+
funtpe.derivedLambdaType(paramInfos = paramInfosWithUses)
515+
539516
override def recheckApply(tree: Apply, pt: Type)(using Context): Type =
540517
val meth = tree.fun.symbol
541518

@@ -570,15 +547,19 @@ class CheckCaptures extends Recheck, SymTransformer:
570547
tp.derivedCapturingType(forceBox(parent), refs)
571548
mapArgUsing(forceBox)
572549
else
573-
handleCall(meth, tree, () => super.recheckApply(tree, pt))
550+
val res = super.recheckApply(tree, pt)
551+
includeCallCaptures(meth, res, tree.srcPos)
552+
res
574553
end recheckApply
575554

576555
protected override
577556
def recheckArg(arg: Tree, formal: Type)(using Context): Type =
578557
val argType = recheck(arg, formal)
579-
if unboxedArgs.contains(arg) then
580-
capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}")
581-
markFree(argType.deepCaptureSet, arg.srcPos)
558+
formal match
559+
case AnnotatedType(formal1, ann) if ann.symbol == defn.UnboxAnnot =>
560+
capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}")
561+
markFree(argType.deepCaptureSet, arg.srcPos)
562+
case _ =>
582563
argType
583564

584565
/** A specialized implementation of the apply rule.
@@ -606,10 +587,10 @@ class CheckCaptures extends Recheck, SymTransformer:
606587
val appType = Existential.toCap(super.recheckApplication(tree, qualType, funType, argTypes))
607588
val qualCaptures = qualType.captureSet
608589
val argCaptures =
609-
for (arg, argType) <- tree.args.lazyZip(argTypes) yield
610-
if unboxedArgs.remove(arg) // need to ensure the remove happens, that's why argCaptures is computed even if not needed.
611-
then argType.deepCaptureSet
612-
else argType.captureSet
590+
for (argType, formal) <- argTypes.lazyZip(funType.paramInfos) yield
591+
formal match
592+
case AnnotatedType(_, ann) if ann.symbol == defn.UnboxAnnot => argType.deepCaptureSet
593+
case _ => argType.captureSet
613594
appType match
614595
case appType @ CapturingType(appType1, refs)
615596
if qualType.exists
@@ -704,8 +685,10 @@ class CheckCaptures extends Recheck, SymTransformer:
704685
i"Sealed type variable $pname", "be instantiated to",
705686
i"This is often caused by a local capability$where\nleaking as part of its result.",
706687
tree.srcPos)
707-
try handleCall(meth, tree, () => Existential.toCap(super.recheckTypeApply(tree, pt)))
708-
finally checkContains(tree)
688+
val res = Existential.toCap(super.recheckTypeApply(tree, pt))
689+
includeCallCaptures(meth, res, tree.srcPos)
690+
checkContains(tree)
691+
res
709692
end recheckTypeApply
710693

711694
/** Faced with a tree of form `caps.contansImpl[CS, r.type]`, check that `R` is a tracked
@@ -1156,12 +1139,7 @@ class CheckCaptures extends Recheck, SymTransformer:
11561139
(erefs /: erefs.elems): (erefs, eref) =>
11571140
eref match
11581141
case eref: ThisType if isPureContext(ctx.owner, eref.cls) =>
1159-
1160-
def pathRoot(aref: Type): Type = aref match
1161-
case aref: NamedType if aref.symbol.owner.isClass => pathRoot(aref.prefix)
1162-
case _ => aref
1163-
1164-
def isOuterRef(aref: Type): Boolean = pathRoot(aref) match
1142+
def isOuterRef(aref: Type): Boolean = aref.pathRoot match
11651143
case aref: NamedType => eref.cls.isProperlyContainedIn(aref.symbol.owner)
11661144
case aref: ThisType => eref.cls.isProperlyContainedIn(aref.cls)
11671145
case _ => false
@@ -1171,7 +1149,7 @@ class CheckCaptures extends Recheck, SymTransformer:
11711149
// Include implicitly added outer references in the capture set of the class of `eref`.
11721150
for outerRef <- outerRefs.elems do
11731151
if !erefs.elems.contains(outerRef)
1174-
&& !pathRoot(outerRef).isInstanceOf[ThisType]
1152+
&& !outerRef.pathRoot.isInstanceOf[ThisType]
11751153
// we don't need to add outer ThisTypes as these are anyway added as path
11761154
// prefixes at the use site. And this exemption is required since capture sets
11771155
// of non-local classes are always empty, so we can't add an outer this to them.
@@ -1328,6 +1306,12 @@ class CheckCaptures extends Recheck, SymTransformer:
13281306

13291307
/** If actual is a tracked CaptureRef `a` and widened is a capturing type T^C,
13301308
* improve `T^C` to `T^{a}`, following the VAR rule of CC.
1309+
* TODO: We probably should do this also for other top-level occurrences of captures
1310+
* E.g.
1311+
* class Foo { def a: C^{io}; val def: C^{async} }
1312+
* val foo: Foo^{io, async}
1313+
* Then
1314+
* foo: Foo { def a: C^{foo}; def b: C^{foo} }^{foo}
13311315
*/
13321316
private def improveCaptures(widened: Type, actual: Type)(using Context): Type = actual match
13331317
case ref: CaptureRef if ref.isTracked =>

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ object Recheck:
9999
* - in function and method parameter types
100100
* - under annotations
101101
*/
102-
def normalizeByName(tp: Type)(using Context): Type = tp.dealias match
102+
def normalizeByName(tp: Type)(using Context): Type = tp.dealiasKeepAnnots match
103103
case tp: ExprType =>
104104
mapExprType(tp)
105105
case tp: PolyType =>
@@ -291,7 +291,7 @@ abstract class Recheck extends Phase, SymTransformer:
291291
protected def instantiate(mt: MethodType, argTypes: List[Type], sym: Symbol)(using Context): Type =
292292
mt.instantiate(argTypes)
293293

294-
/** A hook to massage the type of an applied method; currently not overridden */
294+
/** A hook to massage the type of an applied method */
295295
protected def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = funtpe
296296

297297
protected def recheckArg(arg: Tree, formal: Type)(using Context): Type =
@@ -336,7 +336,7 @@ abstract class Recheck extends Phase, SymTransformer:
336336
assert(formals.isEmpty)
337337
Nil
338338
val argTypes = recheckArgs(tree.args, formals, fntpe.paramRefs)
339-
recheckApplication(tree, qualType, fntpe1, argTypes)
339+
recheckApplication(tree, qualType, fntpe, argTypes)
340340
//.showing(i"typed app $tree : $fntpe with ${tree.args}%, % : $argTypes%, % = $result")
341341
case tp =>
342342
assert(false, i"unexpected type of ${tree.fun}: $tp")

0 commit comments

Comments
 (0)