Skip to content

Commit 5abcecd

Browse files
committed
Improve error message
1 parent 76178ed commit 5abcecd

File tree

7 files changed

+34
-27
lines changed

7 files changed

+34
-27
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,10 @@ extension (tp: Type)
422422
mapOver(t)
423423
tm(tp)
424424

425+
def hasUseAnnot(using Context): Boolean = tp match
426+
case AnnotatedType(_, ann) => ann.symbol == defn.UseAnnot
427+
case _ => false
428+
425429
/** If `x` is a capture ref, its maybe capability `x?`, represented internally
426430
* as `x @maybeCapability`. `x?` stands for a capability `x` that might or might
427431
* not be part of a capture set. We have `{} <: {x?} <: {x}`. Maybe capabilities

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

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,13 @@ object CheckCaptures:
245245
* a separation check?
246246
*/
247247
def needsSepCheck: Boolean
248+
249+
/** If a tree is an argument for which needsSepCheck is true,
250+
* the actual type of the argument before it was widened to formal.
251+
* The nuType of this argument is the formal parameter type in this case,
252+
* but for error diagnosis it's important to know what the actual type was.
253+
*/
254+
def actualType: Type
248255
end CheckerAPI
249256

250257
class CheckCaptures extends Recheck, SymTransformer:
@@ -285,11 +292,14 @@ class CheckCaptures extends Recheck, SymTransformer:
285292
*/
286293
private val todoAtPostCheck = new mutable.ListBuffer[() => Unit]
287294

288-
/** Trees that will need a separation check because they contain cap */
289-
private val sepCheckable = util.EqHashSet[Tree]()
295+
/** Maps trees that will need a separation check because they contain cap
296+
* to the actual, non-widened type.
297+
*/
298+
private val sepCheckable = util.EqHashMap[Tree, Type]()
290299

291300
extension [T <: Tree](tree: T)
292301
def needsSepCheck: Boolean = sepCheckable.contains(tree)
302+
def actualType: Type = sepCheckable.getOrElse(tree, NoType)
293303

294304
/** Instantiate capture set variables appearing contra-variantly to their
295305
* upper approximation.
@@ -666,15 +676,13 @@ class CheckCaptures extends Recheck, SymTransformer:
666676
val freshenedFormal = Fresh.fromCap(formal)
667677
val argType = recheck(arg, freshenedFormal)
668678
.showing(i"recheck arg $arg vs $freshenedFormal", capt)
669-
formal match
670-
case AnnotatedType(formal1, ann) if ann.symbol == defn.UseAnnot =>
671-
// The UseAnnot is added to `formal` by `prepareFunction`
672-
capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}")
673-
markFree(argType.deepCaptureSet, arg.srcPos)
674-
case _ =>
679+
if formal.hasUseAnnot then
680+
// The @use annotation is added to `formal` by `prepareFunction`
681+
capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}")
682+
markFree(argType.deepCaptureSet, arg.srcPos)
675683
if formal.containsCap then
676684
arg.updNuType(freshenedFormal)
677-
sepCheckable += arg
685+
sepCheckable(arg) = argType
678686
argType
679687

680688
/** Map existential captures in result to `cap` and implement the following
@@ -704,9 +712,7 @@ class CheckCaptures extends Recheck, SymTransformer:
704712
val qualCaptures = qualType.captureSet
705713
val argCaptures =
706714
for (argType, formal) <- argTypes.lazyZip(funType.paramInfos) yield
707-
formal match
708-
case AnnotatedType(_, ann) if ann.symbol == defn.UseAnnot => argType.deepCaptureSet
709-
case _ => argType.captureSet
715+
if formal.hasUseAnnot then argType.deepCaptureSet else argType.captureSet
710716
appType match
711717
case appType @ CapturingType(appType1, refs)
712718
if qualType.exists

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,7 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
6161

6262
def captures(arg: Tree) =
6363
val argType = arg.nuType
64-
argType match
65-
case AnnotatedType(formal1, ann) if ann.symbol == defn.UseAnnot =>
66-
argType.deepCaptureSet
67-
case _ =>
68-
argType.captureSet
64+
if argType.hasUseAnnot then argType.deepCaptureSet else argType.captureSet
6965

7066
val argCaptures = args.map(captures)
7167
capt.println(i"check separate $fn($args), fnCaptures = $fnCaptures, argCaptures = $argCaptures")
@@ -80,13 +76,14 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
8076
//println(i"check sep $arg / $footprint / $hiddenInArg")
8177
val overlap = hiddenInArg.footprint.overlapWith(footprint)
8278
if !overlap.isEmpty then
79+
def formalName = if pname.toString.contains('$') then "" else i"$pname "
8380
def whatStr = if overlap.size == 1 then "this capability" else "these capabilities"
8481
def funStr =
8582
if fn.symbol.exists then i"${fn.symbol}"
8683
else "the function"
8784
report.error(
88-
em"""Separation failure: argument to capture-polymorphic parameter $pname: ${arg.nuType}
89-
|captures ${CaptureSet(overlap)} and also passes $whatStr separately to $funStr""",
85+
em"""Separation failure: argument of type ${arg.actualType} to capture-polymorphic parameter
86+
|${formalName}of type ${arg.nuType} captures ${CaptureSet(overlap)}, and $whatStr is also passed separately to $funStr.""",
9087
arg.srcPos)
9188
footprint ++= hiddenInArg
9289

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-- Error: tests/neg-custom-args/captures/cc-dep-param.scala:8:6 --------------------------------------------------------
22
8 | foo(a, useA) // error: separation failure
33
| ^
4-
| Separation failure: argument to capture-polymorphic parameter x$0: Foo[Int]^
5-
| captures {a} and also passes this capability separately to method foo
4+
| Separation failure: argument of type (a : Foo[Int]^) to capture-polymorphic parameter
5+
| of type Foo[Int]^ captures {a}, and this capability is also passed separately to method foo.

tests/neg-custom-args/captures/filevar-expanded.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
-- Error: tests/neg-custom-args/captures/filevar-expanded.scala:34:19 --------------------------------------------------
22
34 | withFile(io3): f => // error: separation failure
33
| ^
4-
| Separation failure: argument to capture-polymorphic parameter x$1: (f: test2.File^{io3}) => Unit
5-
| captures {io3} and also passes this capability separately to method withFile
4+
|Separation failure: argument of type (f: test2.File^{io3}) ->{io3} Unit to capture-polymorphic parameter
5+
|of type (f: test2.File^{io3}) => Unit captures {io3}, and this capability is also passed separately to method withFile.
66
35 | val o = Service(io3)
77
36 | o.file = f // this is a bit dubious. It's legal since we treat class refinements
88
37 | // as capture set variables that can be made to include refs coming from outside.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-- Error: tests/neg-custom-args/captures/function-combinators.scala:15:22 ----------------------------------------------
22
15 | val b2 = g1.andThen(g1); // error: separation failure
33
| ^^
4-
| Separation failure: argument to capture-polymorphic parameter x$0: Int => Int
5-
| captures {ctx1} and also passes this capability separately to method andThen
4+
| Separation failure: argument of type (g1 : Int ->{ctx1} Int) to capture-polymorphic parameter
5+
| of type Int => Int captures {ctx1}, and this capability is also passed separately to method andThen.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@
2929
-- Error: tests/neg-custom-args/captures/lazyref.scala:24:55 -----------------------------------------------------------
3030
24 | val ref4 = (if cap1 == cap2 then ref1 else ref2).map(g) // error: separation failure
3131
| ^
32-
| Separation failure: argument to capture-polymorphic parameter x$0: Int => Int
33-
| captures {cap2} and also passes this capability separately to method map
32+
| Separation failure: argument of type (x: Int) ->{cap2} Int to capture-polymorphic parameter
33+
| of type Int => Int captures {cap2}, and this capability is also passed separately to method map.

0 commit comments

Comments
 (0)