@@ -16,6 +16,7 @@ import transform.Recheck
1616import Recheck .*
1717import scala .collection .mutable
1818import CaptureSet .withCaptureSetsExplained
19+ import StdNames .nme
1920import reporting .trace
2021
2122object CheckCaptures :
@@ -213,22 +214,6 @@ class CheckCaptures extends Recheck:
213214 interpolateVarsIn(tree.tpt)
214215 curEnv = saved
215216
216- override def recheckRHS (tree : Tree , pt : Type , sym : Symbol )(using Context ): Type =
217- val pt1 = pt match
218- case CapturingType (core, refs, _)
219- if sym.owner.isClass && ! sym.owner.isExtensibleClass
220- && refs.elems.contains(sym.owner.thisType) =>
221- val paramCaptures =
222- sym.paramSymss.flatten.foldLeft(CaptureSet .empty) { (cs, p) =>
223- val pcs = p.info.captureSet
224- (cs ++ (if pcs.isConst then pcs else CaptureSet .universal)).asConst
225- }
226- val declaredCaptures = sym.owner.asClass.givenSelfType.captureSet
227- pt.derivedCapturingType(core, refs ++ (declaredCaptures -- paramCaptures))
228- case _ =>
229- pt
230- recheck(tree, pt1)
231-
232217 override def recheckClassDef (tree : TypeDef , impl : Template , cls : ClassSymbol )(using Context ): Type =
233218 for param <- cls.paramGetters do
234219 if param.is(Private ) && ! param.info.captureSet.isAlwaysEmpty then
@@ -237,6 +222,8 @@ class CheckCaptures extends Recheck:
237222 param.srcPos)
238223 val saved = curEnv
239224 val localSet = capturedVars(cls)
225+ for parent <- impl.parents do
226+ checkSubset(capturedVars(parent.tpe.classSymbol), localSet, parent.srcPos)
240227 if ! localSet.isAlwaysEmpty then curEnv = Env (cls, localSet, false , curEnv)
241228 try super .recheckClassDef(tree, impl, cls)
242229 finally curEnv = saved
@@ -289,9 +276,34 @@ class CheckCaptures extends Recheck:
289276 finally curEnv = curEnv.outer
290277 recheckFinish(result, arg, pt)
291278
279+ /** A specialized implementation of the apply rule from https://github.com/lampepfl/dotty/discussions/14387:
280+ *
281+ * E |- f: Cf (Ra -> Cr Rr)
282+ * E |- a: Ra
283+ * ------------------------
284+ * E |- f a: Cr /\ {f} Rr
285+ *
286+ * Specialized for the case where `f` is a tracked and the arguments are pure.
287+ * This replaces the previous rule #13657 while still allowing the code in pos/lazylists1.scala.
288+ * We could consider generalizing to the case where the function arguments have non-empty
289+ * capture sets as suggested in #14387, but that would make capture set computations more complex,
290+ * so we should also evaluate the performance impact.
291+ */
292292 override def recheckApply (tree : Apply , pt : Type )(using Context ): Type =
293293 includeCallCaptures(tree.symbol, tree.srcPos)
294- super .recheckApply(tree, pt)
294+ super .recheckApply(tree, pt) match
295+ case tp @ CapturingType (tp1, refs, kind) =>
296+ tree.fun match
297+ case Select (qual, nme.apply)
298+ if defn.isFunctionType(qual.tpe.widen) =>
299+ qual.tpe match
300+ case ref : CaptureRef
301+ if ref.isTracked && tree.args.forall(_.tpe.captureSet.isAlwaysEmpty) =>
302+ tp.derivedCapturingType(tp1, refs ** ref.singletonCaptureSet)
303+ .showing(i " narrow $tree: $tp --> $result" , capt)
304+ case _ => tp
305+ case _ => tp
306+ case tp => tp
295307
296308 override def recheck (tree : Tree , pt : Type = WildcardType )(using Context ): Type =
297309 val res = super .recheck(tree, pt)
@@ -319,6 +331,42 @@ class CheckCaptures extends Recheck:
319331 case _ =>
320332 super .recheckFinish(tpe, tree, pt)
321333
334+ /** This method implements the rule outlined in #14390:
335+ * When checking an expression `e: Ca Ta` against an expected type `Cx Tx`
336+ * where the capture set of `Cx` contains this and any method inside the class
337+ * `Cls` of `this` that contains `e` has only pure parameters, drop from `Ca`
338+ * all references to variables or this references outside `Cls`. These are all
339+ * accessed through this, so are already accounted for by `Cx`.
340+ */
341+ override def checkConformsExpr (original : Type , actual : Type , expected : Type , tree : Tree )(using Context ): Unit =
342+ def isPure (info : Type ): Boolean = info match
343+ case info : PolyType => isPure(info.resType)
344+ case info : MethodType => info.paramInfos.forall(_.captureSet.isAlwaysEmpty) && isPure(info.resType)
345+ case _ => true
346+ def isPureContext (owner : Symbol , limit : Symbol ): Boolean =
347+ if owner == limit then true
348+ else if ! owner.exists then false
349+ else isPure(owner.info) && isPureContext(owner.owner, limit)
350+ val actual1 = (expected, actual.widen) match
351+ case (CapturingType (ecore, erefs, _), actualw @ CapturingType (acore, arefs, _)) =>
352+ val arefs1 = (arefs /: erefs.elems) { (arefs1, eref) =>
353+ eref match
354+ case eref : ThisType if isPureContext(ctx.owner, eref.cls) =>
355+ arefs1.filter {
356+ case aref1 : TermRef => ! eref.cls.isContainedIn(aref1.symbol.owner)
357+ case aref1 : ThisType => ! eref.cls.isContainedIn(aref1.cls)
358+ case _ => true
359+ }
360+ case _ =>
361+ arefs1
362+ }
363+ if arefs1 eq arefs then actual
364+ else actualw.derivedCapturingType(acore, arefs1)
365+ .showing(i " healing $actual --> $result" , capt)
366+ case _ =>
367+ actual
368+ super .checkConformsExpr(original, actual1, expected, tree)
369+
322370 override def checkUnit (unit : CompilationUnit )(using Context ): Unit =
323371 Setup (preRecheckPhase, thisPhase, recheckDef)
324372 .traverse(ctx.compilationUnit.tpdTree)
0 commit comments