Skip to content

Commit 11ca325

Browse files
committed
Explain root capabilities in error messages
1 parent 14e992c commit 11ca325

Some content is hidden

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

67 files changed

+683
-198
lines changed

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@ import CaptureSet.VarState
1515
/** Attachment key for capturing type trees */
1616
private val Captures: Key[CaptureSet] = Key()
1717

18-
/** Context property to print root.Fresh(...) as "fresh" instead of "cap" */
19-
val PrintFresh: Key[Unit] = Key()
20-
2118
/** Are we at checkCaptures phase? */
2219
def isCaptureChecking(using Context): Boolean =
2320
ctx.phaseId == Phases.checkCapturesPhase.id

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,7 @@ class CheckCaptures extends Recheck, SymTransformer:
373373
case added: CaptureRef => target.elems += added
374374
case added: CaptureSet => target.elems ++= added.elems
375375
case _ =>
376-
inContext(root.printContext(added, res.blocking)):
377-
report.error(msg(provisional = false), pos)
376+
report.error(msg(provisional = false), pos)
378377
case _ =>
379378

380379
/** Check subcapturing `{elem} <: cs`, report error on failure */
@@ -1325,9 +1324,8 @@ class CheckCaptures extends Recheck, SymTransformer:
13251324
actualBoxed
13261325
case fail: CompareFailure =>
13271326
capt.println(i"conforms failed for ${tree}: $actual vs $expected")
1328-
inContext(root.printContext(actualBoxed, expected1)):
1329-
err.typeMismatch(tree.withType(actualBoxed), expected1,
1330-
addApproxAddenda(
1327+
err.typeMismatch(tree.withType(actualBoxed), expected1,
1328+
addApproxAddenda(
13311329
addenda ++ errorNotes(fail.errorNotes),
13321330
expected1))
13331331
actual

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

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -328,30 +328,4 @@ object root:
328328
throw ex
329329
end toResultInResults
330330

331-
/** If `refs` contains an occurrence of `cap` or `cap.rd`, the current context
332-
* with an added property PrintFresh. This addition causes all occurrences of
333-
* `Fresh` to be printed as `fresh` instead of `cap`, so that one avoids
334-
* confusion in error messages.
335-
*/
336-
def printContext(refs: (Type | CaptureSet)*)(using Context): Context =
337-
def hasCap = new TypeAccumulator[Boolean]:
338-
def apply(x: Boolean, t: Type) =
339-
x || t.dealiasKeepAnnots.match
340-
case Fresh(_) => false
341-
case t: TermRef => t.isCap || this(x, t.widen)
342-
case CapturingType(t1, refs) => refs.containsCap || this(x, t1)
343-
case x: ThisType => false
344-
case _ => foldOver(x, t)
345-
346-
def containsCap(x: Type | CaptureSet): Boolean = x match
347-
case tp: Type =>
348-
hasCap(false, tp)
349-
case refs: CaptureSet =>
350-
refs.elems.exists(_.stripReadOnly.isCap)
351-
352-
if refs.exists(containsCap) then
353-
ctx.withProperty(PrintFresh, Some(()))
354-
else
355-
ctx
356-
end printContext
357331
end root

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ class PlainPrinter(_ctx: Context) extends Printer {
3131
/** Print Fresh instances as <cap hiding ...> */
3232
protected def ccVerbose = ctx.settings.YccVerbose.value
3333

34-
/** Print Fresh instances as "fresh" */
35-
protected def printFresh = ccVerbose || ctx.property(PrintFresh).isDefined
36-
3734
private var openRecs: List[RecType] = Nil
3835

3936
protected def maxToTextRecursions: Int = 100
@@ -192,7 +189,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
192189
val isUniversal =
193190
refs.elems.size == 1
194191
&& (refs.isUniversal
195-
|| !printDebug && !printFresh && !showUniqueIds && refs.elems.nth(0).match
192+
|| !printDebug && !ccVerbose && !showUniqueIds && refs.elems.nth(0).match
196193
case root.Result(binder) =>
197194
CCState.openExistentialScopes match
198195
case b :: _ => binder eq b
@@ -201,7 +198,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
201198
false
202199
)
203200
isUniversal
204-
|| !refs.elems.isEmpty && refs.elems.forall(_.isCapOrFresh) && !printFresh
201+
|| !refs.elems.isEmpty && refs.elems.forall(_.isCapOrFresh) && !ccVerbose
205202
case (ref: tpd.Tree) :: Nil => ref.symbol == defn.captureRoot
206203
case _ => false
207204

@@ -466,12 +463,12 @@ class PlainPrinter(_ctx: Context) extends Printer {
466463
val vbleText: Text = CCState.openExistentialScopes.indexOf(binder) match
467464
case -1 =>
468465
"<cap of " ~ toText(binder) ~ ">"
469-
case n => "outer_" * n ++ (if printFresh then "localcap" else "cap")
466+
case n => "outer_" * n ++ (if ccVerbose then "localcap" else "cap")
470467
vbleText ~ hashStr(binder) ~ Str(idStr).provided(showUniqueIds)
471468
case tp @ root.Fresh(hidden) =>
472469
val idStr = if showUniqueIds then s"#${tp.rootAnnot.id}" else ""
473470
if ccVerbose then s"<fresh$idStr hiding " ~ toTextCaptureSet(hidden) ~ ">"
474-
else if printFresh then "fresh"
471+
else if ccVerbose then "fresh"
475472
else "cap"
476473
case tp => toText(tp)
477474

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
156156
else simpleNameString(tsym)
157157
}
158158

159-
private def arrow(isGiven: Boolean, isPure: Boolean): String =
159+
protected def arrow(isGiven: Boolean, isPure: Boolean): String =
160160
(if isGiven then "?" else "") + (if isPure then "->" else "=>")
161161

162162
private def toTextFunction(tp: AppliedType, refs: GeneralCaptureSet | Null): Text =
@@ -197,7 +197,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
197197
if cc.isCaptureCheckingOrSetup
198198
&& tp.allParamNamesSynthetic
199199
&& !tp.looksResultDependent && !tp.looksParamDependent
200-
&& !showUniqueIds && !printDebug && !printFresh
200+
&& !showUniqueIds && !printDebug && !ccVerbose
201201
then
202202
// cc.Setup converts all functions to dependent functions. Undo that when printing.
203203
toTextFunction(tp.paramInfos, tp.resType, tp, refs, isContextual, isPure)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ object Texts {
137137
case _ => relems.foldLeft(-1)((acc, relem) => acc max relem.maxLine)
138138
}
139139

140-
def mkString(width: Int, withLineNumbers: Boolean): String = {
140+
def mkString(width: Int = Int.MaxValue, withLineNumbers: Boolean = false): String = {
141141
val sb = new StringBuilder
142142
val numberWidth = if (withLineNumbers) (2 * maxLine.toString.length) + 2 else 0
143143
layout(width - numberWidth).print(sb, numberWidth)

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

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +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}
1112

1213
import scala.language.unsafeNulls
1314
import scala.annotation.threadUnsafe
@@ -40,7 +41,7 @@ object Message:
4041
i"\n$what can be rewritten automatically under -rewrite $optionStr."
4142
else ""
4243

43-
private type Recorded = Symbol | ParamRef | SkolemType
44+
private type Recorded = Symbol | ParamRef | SkolemType | CaptureRef
4445

4546
private case class SeenKey(str: String, isType: Boolean)
4647

@@ -132,7 +133,7 @@ object Message:
132133
end record
133134

134135
/** Create explanation for single `Recorded` type or symbol */
135-
private def explanation(entry: AnyRef)(using Context): String =
136+
private def explanation(entry: AnyRef, key: String)(using Context): String =
136137
def boundStr(bound: Type, default: ClassSymbol, cmp: String) =
137138
if (bound.isRef(default)) "" else i"$cmp $bound"
138139

@@ -152,7 +153,7 @@ object Message:
152153
""
153154
}
154155

155-
entry match {
156+
entry match
156157
case param: TypeParamRef =>
157158
s"is a type variable${addendum("constraint", TypeComparer.bounds(param))}"
158159
case param: TermParamRef =>
@@ -166,7 +167,25 @@ object Message:
166167
s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", info)}"
167168
case tp: SkolemType =>
168169
s"is an unknown value of type ${tp.widen.show}"
169-
}
170+
case ref: CaptureRef =>
171+
val relation =
172+
if List("^", "=>", "?=>").exists(key.startsWith) then "refers to"
173+
else "is"
174+
def ownerStr(owner: Symbol): String =
175+
if owner.isConstructor then
176+
i"constructor of ${ownerStr(owner.owner)}"
177+
else if owner.isAnonymousFunction then
178+
i"anonymous fucntion of type ${owner.info}"
179+
else if owner.name.toString.contains('$') then
180+
ownerStr(owner.owner)
181+
else
182+
owner.show
183+
val descr =
184+
if ref.isCap then "the universal root capability"
185+
else ref match
186+
case root.Fresh(hidden) => i"a fresh root capability created in ${ownerStr(hidden.owner)}"
187+
case root.Result(binder) => i"a root capability associated with the result type of $binder"
188+
s"$relation $descr"
170189
end explanation
171190

172191
/** Produce a where clause with explanations for recorded iterms.
@@ -177,6 +196,7 @@ object Message:
177196
case param: ParamRef => false
178197
case skolem: SkolemType => true
179198
case sym: Symbol => ctx.gadt.contains(sym) && ctx.gadt.fullBounds(sym) != TypeBounds.empty
199+
case ref: CaptureRef => ref.isRootCapability
180200
}
181201

182202
val toExplain: List[(String, Recorded)] = seen.toList.flatMap { kvs =>
@@ -201,7 +221,7 @@ object Message:
201221
}
202222
}
203223

204-
val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) }
224+
val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry, str)) }
205225
val explainLines = columnar(explainParts)
206226
if (explainLines.isEmpty) "" else i"where: $explainLines%\n %\n"
207227
end explanations
@@ -225,6 +245,25 @@ object Message:
225245
case tp: SkolemType => seen.record(tp.repr.toString, isType = true, tp)
226246
case _ => super.toTextRef(tp)
227247

248+
override def toTextCaptureRef(tp: Type): Text = tp match
249+
case tp: CaptureRef if tp.isRootCapability && !tp.isReadOnly =>
250+
seen.record("cap", isType = false, tp)
251+
case _ => super.toTextCaptureRef(tp)
252+
253+
override def toTextCapturing(parent: Type, refs: GeneralCaptureSet, boxText: Text) = refs match
254+
case refs: CaptureSet
255+
if isUniversalCaptureSet(refs) && !defn.isFunctionType(parent) && !printDebug =>
256+
boxText ~ toTextLocal(parent) ~ seen.record("^", isType = true, refs.elems.nth(0))
257+
case _ =>
258+
super.toTextCapturing(parent, refs, boxText)
259+
260+
override def funMiddleText(isContextual: Boolean, isPure: Boolean, refs: GeneralCaptureSet | Null): Text =
261+
refs match
262+
case refs: CaptureSet if isUniversalCaptureSet(refs) =>
263+
seen.record(arrow(isContextual, isPure = false), isType = true, refs.elems.nth(0))
264+
case _ =>
265+
super.funMiddleText(isContextual, isPure, refs)
266+
228267
override def toTextMethodAsFunction(info: Type, isPure: Boolean, refs: GeneralCaptureSet): Text =
229268
info match
230269
case info: LambdaType =>

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,30 @@
33
| ^
44
| Type argument box test.Tree^ does not conform to upper bound test.Tree in inferred type test.C[box test.Tree^]
55
|
6+
| where: ^ refers to the universal root capability
7+
|
68
| longer explanation available when compiling with `-explain`
79
-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:10:11 --------------------------------
810
10 | type T = C[Tree^] // error
911
| ^
1012
| Type argument box test.Tree^ does not conform to upper bound test.Tree in inferred type test.C[box test.Tree^]
1113
|
14+
| where: ^ refers to the universal root capability
15+
|
1216
| longer explanation available when compiling with `-explain`
1317
-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:11:11 --------------------------------
1418
11 | val bar: T -> T = ??? // error
1519
| ^
1620
|Type argument box test.Tree^ does not conform to upper bound test.Tree in subpart test.C[box test.Tree^] of inferred type test.C[box test.Tree^] -> test.C[box test.Tree^]
1721
|
22+
|where: ^ refers to the universal root capability
23+
|
1824
| longer explanation available when compiling with `-explain`
1925
-- [E057] Type Mismatch Error: tests/neg-custom-args/captures/boundschecks3.scala:12:11 --------------------------------
2026
12 | val baz: C[Tree^] -> Unit = ??? // error
2127
| ^
2228
|Type argument box test.Tree^ does not conform to upper bound test.Tree in subpart test.C[box test.Tree^] of inferred type test.C[box test.Tree^] -> Unit
2329
|
30+
|where: ^ refers to the universal root capability
31+
|
2432
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/box-adapt-cases.check

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
8 | x.value(cap => cap.use()) // error, was OK
33
| ^^^^^^^^^^^^^^^^
44
| Found: (cap: box Cap^?) => Int
5-
| Required: (cap: box Cap^) ->{fresh} Int
5+
| Required: (cap: box Cap^) =>² Int
6+
|
7+
| where: => refers to the universal root capability
8+
| =>² refers to a fresh root capability created in method test1
9+
| ^ refers to the universal root capability
610
|
711
| longer explanation available when compiling with `-explain`
812
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/box-adapt-cases.scala:15:10 ------------------------------

tests/neg-custom-args/captures/box-adapt-contra.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
| ^^^^^^^^^^^^^^^^^^^
99
| Cap^{c} => Unit cannot be box-converted to box Cap^{c} ->{cap, c} Unit
1010
| since the additional capture set {c} resulting from box conversion is not allowed in box Cap^{c} => Unit
11+
|
12+
| where: => refers to the universal root capability
13+
| cap is the universal root capability
1114
-- Error: tests/neg-custom-args/captures/box-adapt-contra.scala:19:54 --------------------------------------------------
1215
19 | val f3: (Cap^{c} -> Unit) => Unit = useCap3[Cap^{c}](c) // error
1316
| ^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)