Skip to content

Commit 4705a47

Browse files
committed
More detailed explanations where Fresh instances come from
1 parent 11ca325 commit 4705a47

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+566
-534
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,9 @@ extension (tp: Type)
462462
if args.forall(_.isAlwaysPure) then
463463
// Also map existentials in results to reach capabilities if all
464464
// preceding arguments are known to be always pure
465-
t.derivedFunctionOrMethod(args, apply(root.resultToFresh(res)))
465+
t.derivedFunctionOrMethod(
466+
args,
467+
apply(root.resultToFresh(res, i"when instantiating $t")))
466468
else
467469
t
468470
case _ =>
@@ -795,7 +797,8 @@ abstract class DeepTypeAccumulator[T](using Context) extends TypeAccumulator[T]:
795797
case AnnotatedType(parent, _) =>
796798
this(acc, parent)
797799
case t @ FunctionOrMethod(args, res) =>
798-
if args.forall(_.isAlwaysPure) then this(acc, root.resultToFresh(res))
800+
if args.forall(_.isAlwaysPure) then
801+
this(acc, root.resultToFresh(res, i" when instantiating $t"))
799802
else acc
800803
case _ =>
801804
foldOver(acc, t)

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,8 @@ object CaptureSet:
423423
def universalImpliedByCapability(using Context) =
424424
defn.universalCSImpliedByCapability
425425

426-
def fresh()(using Context): CaptureSet =
427-
root.Fresh().singletonCaptureSet
426+
def fresh(purpose: => String)(using Context): CaptureSet =
427+
root.Fresh(purpose).singletonCaptureSet
428428

429429
/** The shared capture set `{cap.rd}` */
430430
def shared(using Context): CaptureSet =
@@ -685,7 +685,7 @@ object CaptureSet:
685685
def solve()(using Context): Unit =
686686
CCState.withCapAsRoot: // // OK here since we infer parameter types that get checked later
687687
val approx = upperApprox(empty)
688-
.map(root.CapToFresh().inverse) // Fresh --> cap
688+
.map(root.CapToFresh("").inverse) // Fresh --> cap
689689
.showing(i"solve $this = $result", capt)
690690
//println(i"solving var $this $approx ${approx.isConst} deps = ${deps.toList}")
691691
val newElems = approx.elems -- elems
@@ -1388,7 +1388,7 @@ object CaptureSet:
13881388
def capturingCase(acc: CaptureSet, parent: Type, refs: CaptureSet) =
13891389
this(acc, parent) ++ refs
13901390
def abstractTypeCase(acc: CaptureSet, t: TypeRef, upperBound: Type) =
1391-
if includeTypevars && upperBound.isExactlyAny then CaptureSet.fresh()
1391+
if includeTypevars && upperBound.isExactlyAny then CaptureSet.fresh(i" when computing deep capture set of $t")
13921392
else this(acc, upperBound)
13931393
collect(CaptureSet.empty, tp)
13941394

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

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ class CheckCaptures extends Recheck, SymTransformer:
714714
val meth = tree.fun.symbol
715715
if meth == defn.Caps_unsafeAssumePure then
716716
val arg :: Nil = tree.args: @unchecked
717-
val argType0 = recheck(arg, pt.stripCapturing.capturing(root.Fresh()))
717+
val argType0 = recheck(arg, pt.stripCapturing.capturing(root.Fresh(i" when instantiating argument of unsafeAssumePure")))
718718
val argType =
719719
if argType0.captureSet.isAlwaysEmpty then argType0
720720
else argType0.widen.stripCapturing
@@ -729,12 +729,16 @@ class CheckCaptures extends Recheck, SymTransformer:
729729
* occurrences are replaced by `Fresh` instances. Also, if formal parameter carries a `@use`,
730730
* charge the deep capture set of the actual argument to the environment.
731731
*/
732-
protected override def recheckArg(arg: Tree, formal: Type)(using Context): Type =
733-
val freshenedFormal = root.capToFresh(formal)
732+
protected override def recheckArg(arg: Tree, formal: Type, pref: ParamRef, app: Apply)(using Context): Type =
733+
val fn = app.symbol
734+
def purpose =
735+
if fn.exists then i" when checking argument to parameter ${pref.paramName} of $fn"
736+
else ""
737+
val freshenedFormal = root.capToFresh(formal, purpose)
734738
val argType = recheck(arg, freshenedFormal)
735739
.showing(i"recheck arg $arg vs $freshenedFormal = $result", capt)
736740
if formal.hasAnnotation(defn.UseAnnot) || formal.hasAnnotation(defn.ConsumeAnnot) then
737-
// The @use and/or @consume annotation is added to `formal` by `prepareFunction`
741+
// The @use and/or @consume annotation is added to `formal` by `prep areFunction`
738742
capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}")
739743
markFree(argType.deepCaptureSet, arg)
740744
if formal.containsCap then
@@ -764,7 +768,11 @@ class CheckCaptures extends Recheck, SymTransformer:
764768
*/
765769
protected override
766770
def recheckApplication(tree: Apply, qualType: Type, funType: MethodType, argTypes: List[Type])(using Context): Type =
767-
val appType = root.resultToFresh(super.recheckApplication(tree, qualType, funType, argTypes))
771+
val fn = tree.symbol
772+
def methDescr = if fn.exists then i"$fn's type " else ""
773+
val appType = root.resultToFresh(
774+
super.recheckApplication(tree, qualType, funType, argTypes),
775+
i" when instantiating $methDescr$funType")
768776
val qualCaptures = qualType.captureSet
769777
val argCaptures =
770778
for (argType, formal) <- argTypes.lazyZip(funType.paramInfos) yield
@@ -823,8 +831,10 @@ class CheckCaptures extends Recheck, SymTransformer:
823831
def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) =
824832
var refined: Type = core
825833
var allCaptures: CaptureSet =
826-
if core.derivesFromMutable then initCs ++ CaptureSet.fresh()
827-
else if core.derivesFromCapability then initCs ++ root.Fresh().readOnly.singletonCaptureSet
834+
if core.derivesFromMutable then
835+
initCs ++ CaptureSet.fresh(i" when constructing mutable $core")
836+
else if core.derivesFromCapability then
837+
initCs ++ root.Fresh(i" when constructing Capability instance $core").readOnly.singletonCaptureSet
828838
else initCs
829839
for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do
830840
val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol
@@ -864,8 +874,10 @@ class CheckCaptures extends Recheck, SymTransformer:
864874
val meth = tree.fun match
865875
case fun @ Select(qual, nme.apply) => qual.symbol.orElse(fun.symbol)
866876
case fun => fun.symbol
877+
def methDescr = if meth.exists then i"$meth's type " else ""
867878
disallowCapInTypeArgs(tree.fun, meth, tree.args)
868-
val res = root.resultToFresh(super.recheckTypeApply(tree, pt))
879+
val funType = super.recheckTypeApply(tree, pt)
880+
val res = root.resultToFresh(funType, i" when instantiating $methDescr$funType")
869881
includeCallCaptures(tree.symbol, res, tree)
870882
checkContains(tree)
871883
res
@@ -925,14 +937,16 @@ class CheckCaptures extends Recheck, SymTransformer:
925937
// neg-custom-args/captures/vars.scala. That's why this code is conditioned.
926938
// to apply only to closures that are not eta expansions.
927939
assert(paramss1.isEmpty)
928-
val respt = root.resultToFresh:
929-
pt match
930-
case defn.RefinedFunctionOf(rinfo) =>
931-
val paramTypes = params.map(_.asInstanceOf[ValDef].tpt.nuType)
932-
rinfo.instantiate(paramTypes)
933-
case _ =>
934-
resType
935-
val res = root.resultToFresh(mdef.tpt.nuType)
940+
val respt0 = pt match
941+
case defn.RefinedFunctionOf(rinfo) =>
942+
val paramTypes = params.map(_.asInstanceOf[ValDef].tpt.nuType)
943+
rinfo.instantiate(paramTypes)
944+
case _ =>
945+
resType
946+
val respt = root.resultToFresh(respt0,
947+
i" when instantiating expected result type $respt0 of lambda")
948+
val res = root.resultToFresh(mdef.tpt.nuType,
949+
i" when instantiating result type ${mdef.tpt.nuType} of lambda")
936950
// We need to open existentials here in order not to get vars mixed up in them
937951
// We do the proper check with existentials when we are finished with the closure block.
938952
capt.println(i"pre-check closure $expr of type $res against $respt")
@@ -1632,7 +1646,9 @@ class CheckCaptures extends Recheck, SymTransformer:
16321646
memberTp,
16331647
otherTp.derivedTypeBounds(
16341648
otherTp.lo,
1635-
hi.derivedCapturingType(parent, root.Fresh().singletonCaptureSet))))
1649+
hi.derivedCapturingType(parent,
1650+
root.Fresh(i" when instantiating upper bound of member overridden by $member")
1651+
.singletonCaptureSet))))
16361652
case _ => None
16371653
case _ => None
16381654
case _ => None

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
472472
val tp3 =
473473
if sym.isType then stripImpliedCaptureSet(tp2)
474474
else tp2
475-
if freshen then root.capToFresh(tp3).tap(addOwnerAsHidden(_, sym))
475+
if freshen then
476+
root.capToFresh(tp3,
477+
if sym.is(Method) then i" in the result type of $sym"
478+
else if sym.exists then i" in the type of $sym"
479+
else "")
480+
.tap(addOwnerAsHidden(_, sym))
476481
else tp3
477482
end transformExplicitType
478483

@@ -568,7 +573,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
568573
traverse(fn)
569574
for case arg: TypeTree <- args do
570575
if defn.isTypeTestOrCast(fn.symbol) then
571-
arg.setNuType(root.capToFresh(arg.tpe))
576+
arg.setNuType(
577+
root.capToFresh(arg.tpe, i" of type argument ${arg.tpe}"))
572578
else
573579
transformTT(arg, NoSymbol, boxed = true) // type arguments in type applications are boxed
574580

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ object Synthetics:
116116
def transformUnapplyCaptures(info: Type)(using Context): Type = info match
117117
case info: MethodType =>
118118
val paramInfo :: Nil = info.paramInfos: @unchecked
119-
val newParamInfo = CapturingType(paramInfo, CaptureSet.fresh())
119+
val newParamInfo = CapturingType(paramInfo,
120+
CaptureSet.fresh(i" when instantiating argument of unapply with type $info"))
120121
val trackedParam = info.paramRefs.head
121122
def newResult(tp: Type): Type = tp match
122123
case tp: MethodOrPoly =>

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ object root:
6161

6262
enum Kind:
6363
case Result(binder: MethodType)
64-
case Fresh(hidden: CaptureSet.HiddenSet)
64+
case Fresh(hidden: CaptureSet.HiddenSet)(val purpose: () => String)
6565
case Global
6666

6767
override def equals(other: Any): Boolean =
@@ -129,10 +129,10 @@ object root:
129129

130130
/** Constructor and extractor methods for "fresh" capabilities */
131131
object Fresh:
132-
def apply(using Context)(owner: Symbol = ctx.owner): CaptureRef =
132+
def apply(using Context)(purpose: => String, owner: Symbol = ctx.owner): CaptureRef =
133133
if ccConfig.useSepChecks then
134134
val hiddenSet = CaptureSet.HiddenSet(owner)
135-
val res = AnnotatedType(cap, Annot(Kind.Fresh(hiddenSet)))
135+
val res = AnnotatedType(cap, Annot(Kind.Fresh(hiddenSet)(() => purpose)))
136136
hiddenSet.owningCap = res
137137
//assert(hiddenSet.id != 3)
138138
res
@@ -167,14 +167,14 @@ object root:
167167
/** Map each occurrence of cap to a different Fresh instance
168168
* Exception: CapSet^ stays as it is.
169169
*/
170-
class CapToFresh()(using Context) extends BiTypeMap, FollowAliasesMap:
170+
class CapToFresh(purpose: => String)(using Context) extends BiTypeMap, FollowAliasesMap:
171171
thisMap =>
172172

173173
override def apply(t: Type) =
174174
if variance <= 0 then t
175175
else t match
176176
case t: CaptureRef if t.isCap =>
177-
Fresh()
177+
Fresh(purpose)
178178
case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet =>
179179
t
180180
case t @ CapturingType(_, _) =>
@@ -212,23 +212,23 @@ object root:
212212
end CapToFresh
213213

214214
/** Maps cap to fresh */
215-
def capToFresh(tp: Type)(using Context): Type =
216-
if ccConfig.useSepChecks then CapToFresh()(tp) else tp
215+
def capToFresh(tp: Type, purpose: => String)(using Context): Type =
216+
if ccConfig.useSepChecks then CapToFresh(purpose)(tp) else tp
217217

218218
/** Maps fresh to cap */
219219
def freshToCap(tp: Type)(using Context): Type =
220-
if ccConfig.useSepChecks then CapToFresh().inverse(tp) else tp
220+
if ccConfig.useSepChecks then CapToFresh("").inverse(tp) else tp
221221

222222
/** Map top-level free existential variables one-to-one to Fresh instances */
223-
def resultToFresh(tp: Type)(using Context): Type =
223+
def resultToFresh(tp: Type, purpose: => String)(using Context): Type =
224224
val subst = new TypeMap:
225225
val seen = EqHashMap[Annotation, CaptureRef]()
226226
var localBinders: SimpleIdentitySet[MethodType] = SimpleIdentitySet.empty
227227

228228
def apply(t: Type): Type = t match
229229
case t @ Result(binder) =>
230230
if localBinders.contains(binder) then t // keep bound references
231-
else seen.getOrElseUpdate(t.annot, Fresh()) // map free references to Fresh()
231+
else seen.getOrElseUpdate(t.annot, Fresh(purpose)) // map free references to Fresh()
232232
case t: MethodType =>
233233
// skip parameters
234234
val saved = localBinders
@@ -294,7 +294,7 @@ object root:
294294
val (k, v) = it.next
295295
if v.annot eq t.annot then ref = k
296296
if ref == null then
297-
ref = Fresh()
297+
ref = Fresh("")
298298
seen(ref) = t
299299
ref
300300
case _ => mapOver(t)

compiler/src/dotty/tools/dotc/reporting/Message.scala

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import printing.{RefinedPrinter, MessageLimiter, ErrorMessageLimiter}
88
import printing.Texts.Text
99
import printing.Formatting.hl
1010
import config.SourceVersion
11-
import cc.{CaptureRef, CaptureSet, root}
11+
import cc.{CaptureRef, CaptureSet, root, rootAnnot}
1212

1313
import scala.language.unsafeNulls
1414
import scala.annotation.threadUnsafe
@@ -175,15 +175,21 @@ object Message:
175175
if owner.isConstructor then
176176
i"constructor of ${ownerStr(owner.owner)}"
177177
else if owner.isAnonymousFunction then
178-
i"anonymous fucntion of type ${owner.info}"
178+
i"anonymous function of type ${owner.info}"
179179
else if owner.name.toString.contains('$') then
180180
ownerStr(owner.owner)
181181
else
182182
owner.show
183183
val descr =
184184
if ref.isCap then "the universal root capability"
185185
else ref match
186-
case root.Fresh(hidden) => i"a fresh root capability created in ${ownerStr(hidden.owner)}"
186+
case ref @ root.Fresh(hidden) =>
187+
val (kind: root.Kind.Fresh) = ref.rootAnnot.kind: @unchecked
188+
val purpose = kind.purpose()
189+
val descr =
190+
if purpose.startsWith(" in the ") then purpose
191+
else i" created in ${ownerStr(hidden.owner)}$purpose"
192+
i"a fresh root capability$descr"
187193
case root.Result(binder) => i"a root capability associated with the result type of $binder"
188194
s"$relation $descr"
189195
end explanation

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ abstract class Recheck extends Phase, SymTransformer:
304304
/** A hook to massage the type of an applied method */
305305
protected def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = funtpe
306306

307-
protected def recheckArg(arg: Tree, formal: Type)(using Context): Type =
307+
protected def recheckArg(arg: Tree, formal: Type, pref: ParamRef, app: Apply)(using Context): Type =
308308
recheck(arg, formal)
309309

310310
/** A hook to check all the parts of an application:
@@ -336,7 +336,7 @@ abstract class Recheck extends Phase, SymTransformer:
336336
else fntpe.paramInfos
337337
def recheckArgs(args: List[Tree], formals: List[Type], prefs: List[ParamRef]): List[Type] = args match
338338
case arg :: args1 =>
339-
val argType = recheckArg(arg, normalizeByName(formals.head))
339+
val argType = recheckArg(arg, normalizeByName(formals.head), prefs.head, tree)
340340
val formals1 =
341341
if fntpe.isParamDependent
342342
then formals.tail.map(_.substParam(prefs.head, argType))

tests/neg-custom-args/captures/byname.check

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ----------------------------------------
88
10 | h(f2()) // error
99
| ^^^^
10-
| Found: () ?->{cap1} Int ->{cap1} Int
11-
| Required: () ?=> Int ->{cap2} Int
10+
|Found: () ?->{cap1} Int ->{cap1} Int
11+
|Required: () ?=> Int ->{cap2} Int
1212
|
13-
| where: ?=> refers to a fresh root capability created in method test
13+
|where: ?=> refers to a fresh root capability created in method test when checking argument to parameter ff of method h
1414
|
1515
| longer explanation available when compiling with `-explain`
1616
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:19:5 ----------------------------------------

tests/neg-custom-args/captures/capt-depfun.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
| Found: Str^{} ->{ac, y, z} Str^{y, z}
55
| Required: Str^{y, z} => Str^{y, z}
66
|
7-
| where: => refers to a fresh root capability created in value dc
7+
| where: => refers to a fresh root capability in the type of value dc
88
|
99
| longer explanation available when compiling with `-explain`
1010
-- Error: tests/neg-custom-args/captures/capt-depfun.scala:11:24 -------------------------------------------------------

0 commit comments

Comments
 (0)