Skip to content

Commit d43d613

Browse files
committed
Tweak cc error messages
Sometimes we have more than one representation of the same symbol, i.e. both `^` and `cap`, or both `=>` and `cap`. If the cap is rendered first, we expand the following string to `^{cap}` or `->{cap}` in order make it clearer that they are the same. Unfortunately it only works if the cap comes first.
1 parent da9656a commit d43d613

File tree

7 files changed

+137
-112
lines changed

7 files changed

+137
-112
lines changed

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

Lines changed: 62 additions & 66 deletions
Large diffs are not rendered by default.

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

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import printing.Formatting.hl
1010
import config.SourceVersion
1111
import cc.CaptureSet
1212
import cc.Capabilities.*
13+
import util.Property
1314

1415
import scala.annotation.threadUnsafe
1516

@@ -41,6 +42,11 @@ object Message:
4142
i"\n$what can be rewritten automatically under -rewrite $optionStr."
4243
else ""
4344

45+
/** A context property that turns off expansion of `^` or `=>` to explicit cap's.
46+
* Used in the where clauses of error messages.
47+
*/
48+
val NoCapExpansion = new Property.Key[Unit]
49+
4450
/** A note can produce an added string for an error message */
4551
abstract class Note:
4652

@@ -103,9 +109,12 @@ object Message:
103109
* If the entry was not yet recorded, allocate the next superscript corresponding
104110
* to the same string in the same name space. The first recording is the string proper
105111
* and following recordings get consecutive superscripts starting with 2.
112+
* @param capOk if true and there is already a cap recorded that matches the entry,
113+
* then use an expanded representation with explicit `cap` for the
114+
* original string.
106115
* @return The possibly superscripted version of `str`.
107116
*/
108-
def record(str: String, isType: Boolean, entry: Recorded)(using Context): String =
117+
def record(str: String, isType: Boolean, entry: Recorded, capOK: Boolean = false)(using Context): String =
109118
if disambi.recordOK(str) then
110119
//println(s"recording $str, $isType, $entry")
111120

@@ -119,9 +128,6 @@ object Message:
119128
if (underlying.name == e1.name) underlying else e1.namedType.dealias.typeSymbol
120129
case _ => e1
121130
}
122-
val key = SeenKey(str, isType)
123-
val existing = seen(key)
124-
lazy val dealiased = followAlias(entry)
125131

126132
/** All lambda parameters with the same name are given the same superscript as
127133
* long as their corresponding binders have the same parameter name lists.
@@ -138,13 +144,34 @@ object Message:
138144
case _ =>
139145
false
140146

141-
// The length of alts corresponds to the number of superscripts we need to print.
142-
var alts = existing.dropWhile(alt => !sameSuperscript(dealiased, followAlias(alt)))
143-
if alts.isEmpty then
144-
alts = entry :: existing
145-
seen(key) = alts
147+
/** The superscript in `existing` that matches the current entry, using
148+
* `sameSuperscript` on dealiased entries as a test. The superscript is
149+
* the index of the matching entry in existing, counting from the right
150+
* and starting at 1. I.e last entry in the list has superscript 1, next to
151+
* last entry has superscript 2, and so on. A result of 0 means that no
152+
* matching entry exists.
153+
*/
154+
def matchingSuperscript(existing: List[Recorded]): Int =
155+
lazy val dealiased = followAlias(entry)
156+
existing.dropWhile(alt => !sameSuperscript(dealiased, followAlias(alt))).length
157+
158+
if capOK && ctx.property(NoCapExpansion).isEmpty
159+
&& matchingSuperscript(seen(SeenKey("cap", isType = false))) > 0
160+
then
161+
val capStr = record("cap", isType = false, entry)
162+
return str match
163+
case "^" => s"^{$capStr}"
164+
case "=>" => s"->{$capStr}"
165+
case "?=>" => s"?->{$capStr}"
166+
167+
val key = SeenKey(str, isType)
168+
val existing = seen(key)
169+
var sup = matchingSuperscript(existing)
170+
if sup == 0 then
171+
seen(key) = entry :: existing
172+
sup = existing.length + 1
146173

147-
val suffix = alts.length match {
174+
val suffix = sup match {
148175
case 1 => ""
149176
case n => n.toString.toCharArray.map {
150177
case '0' => '⁰'
@@ -184,26 +211,27 @@ object Message:
184211
""
185212
}
186213

187-
entry match
188-
case param: TypeParamRef =>
189-
s"is a type variable${addendum("constraint", TypeComparer.bounds(param))}"
190-
case param: TermParamRef =>
191-
s"is a reference to a value parameter"
192-
case sym: Symbol =>
193-
val info =
194-
if (ctx.gadt.contains(sym))
195-
sym.info & ctx.gadt.fullBounds(sym).nn
196-
else
197-
sym.info
198-
s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", info)}"
199-
case tp: SkolemType =>
200-
s"is an unknown value of type ${tp.widen.show}"
201-
case ref: RootCapability =>
202-
val relation =
203-
if keys.length > 1 then "refer to"
204-
else if List("^", "=>", "?=>").exists(keys(0).startsWith) then "refers to"
205-
else "is"
206-
s"$relation ${ref.descr}"
214+
inContext(ctx.withProperty(NoCapExpansion, Some(()))):
215+
entry match
216+
case param: TypeParamRef =>
217+
s"is a type variable${addendum("constraint", TypeComparer.bounds(param))}"
218+
case param: TermParamRef =>
219+
s"is a reference to a value parameter"
220+
case sym: Symbol =>
221+
val info =
222+
if (ctx.gadt.contains(sym))
223+
sym.info & ctx.gadt.fullBounds(sym).nn
224+
else
225+
sym.info
226+
s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", info)}"
227+
case tp: SkolemType =>
228+
s"is an unknown value of type ${tp.widen.show}"
229+
case ref: RootCapability =>
230+
val relation =
231+
if keys.length > 1 then "refer to"
232+
else if List("^", "=>", "?=>").exists(keys(0).startsWith) then "refers to"
233+
else "is"
234+
s"$relation ${ref.descr}"
207235
end explanation
208236

209237
/** Produce a where clause with explanations for recorded iterms.
@@ -276,14 +304,14 @@ object Message:
276304
if isUniversalCaptureSet(refs) && !defn.isFunctionType(parent) && !printDebug && seen.isActive =>
277305
boxText
278306
~ toTextLocal(parent)
279-
~ seen.record("^", isType = true, refs.elems.nth(0).asInstanceOf[RootCapability])
307+
~ seen.record("^", isType = true, refs.elems.nth(0).asInstanceOf[RootCapability], capOK = true)
280308
case _ =>
281309
super.toTextCapturing(parent, refs, boxText)
282310

283311
override def funMiddleText(isContextual: Boolean, isPure: Boolean, refs: GeneralCaptureSet | Null): Text =
284312
refs match
285313
case refs: CaptureSet if isUniversalCaptureSet(refs) && seen.isActive =>
286-
seen.record(arrow(isContextual, isPure = false), isType = true, refs.elems.nth(0).asInstanceOf[RootCapability])
314+
seen.record(arrow(isContextual, isPure = false), isType = true, refs.elems.nth(0).asInstanceOf[RootCapability], capOK = true)
287315
case _ =>
288316
super.funMiddleText(isContextual, isPure, refs)
289317

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,12 +1861,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18611861
* the singleton list consisting of its position in `args`, otherwise `Nil`.
18621862
*/
18631863
def paramIndices(param: untpd.ValDef, args: List[untpd.Tree]): List[Int] = {
1864-
def loop(args: List[untpd.Tree], start: Int): List[Int] = args match {
1864+
1865+
def loop(args: List[untpd.Tree], start: Int): List[Int] = args match
18651866
case arg :: args1 =>
18661867
val others = loop(args1, start + 1)
18671868
if (refersTo(arg, param)) start :: others else others
18681869
case _ => Nil
1869-
}
1870+
18701871
val allIndices = loop(args, 0)
18711872
if (allIndices.length == 1) allIndices else Nil
18721873
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
| The leakage occurred when trying to match the following types:
77
|
88
| Found: scala.util.boundary.Label[Object^'s1]
9-
| Required: scala.util.boundary.Label[Object^]^²
9+
| Required: scala.util.boundary.Label[Object^{cap}]^
1010
|
11-
| where: ^ and cap refer to the universal root capability
12-
| ^² refers to a fresh root capability classified as Control in the type of value local
11+
| where: ^ refers to a fresh root capability classified as Control in the type of value local
12+
| cap is the universal root capability
1313
|
1414
6 | boundary[Unit]: l2 ?=>
1515
7 | boundary.break(l2)(using l1) // error

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
-- Error: tests/neg-custom-args/captures/box-adapt-contra.scala:13:57 --------------------------------------------------
77
13 | val f1: (Cap^{c} => Unit) ->{c} Unit = useCap1[Cap^{c}](c) // error, was ok when cap was a root
88
| ^^^^^^^^^^^^^^^^^^^
9-
| Cap^{c} => Unit cannot be box-converted to Cap^{c} ->{cap, c} Unit
10-
| since the additional capture set {c} resulting from box conversion is not allowed in Cap^{c} => Unit
9+
| Cap^{c} => Unit cannot be box-converted to Cap^{c} ->{cap, c} Unit
10+
| since the additional capture set {c} resulting from box conversion is not allowed in Cap^{c} ->{cap} Unit
1111
|
12-
| where: => and cap refer to the universal root capability
12+
| where: => and cap refer to the universal root capability
1313
-- Error: tests/neg-custom-args/captures/box-adapt-contra.scala:19:54 --------------------------------------------------
1414
19 | val f3: (Cap^{c} -> Unit) => Unit = useCap3[Cap^{c}](c) // error
1515
| ^^^^^^^^^^^^^^^^^^^

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
22 | val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error
77
| ^^^^^^^
88
|C^ => Unit cannot be box-converted to C{val arg: C^²}^{c} ->{cap, c} Unit
9-
|since the additional capture set {c} resulting from box conversion is not allowed in C{val arg: C^²}^{c} => Unit
9+
|since the additional capture set {c} resulting from box conversion is not allowed in C{val arg: C^²}^{c} ->{cap} Unit
1010
|
1111
|where: => and ^ and cap refer to the universal root capability
1212
| ^² refers to a fresh root capability in the type of value arg
1313
-- Error: tests/neg-custom-args/captures/i15772.scala:29:35 ------------------------------------------------------------
1414
29 | val boxed2 : Observe[C^] = box2(c) // error
1515
| ^^^^^^^
1616
|C^ => Unit cannot be box-converted to C{val arg: C^²}^{c} ->{cap, c} Unit
17-
|since the additional capture set {c} resulting from box conversion is not allowed in C{val arg: C^²}^{c} => Unit
17+
|since the additional capture set {c} resulting from box conversion is not allowed in C{val arg: C^²}^{c} ->{cap} Unit
1818
|
1919
|where: => and ^ and cap refer to the universal root capability
2020
| ^² refers to a fresh root capability in the type of value arg

tests/neg-custom-args/captures/ref-with-file.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@
3737
|Capability cap outlives its scope: it leaks into outer capture set 's8 which is owned by method Test.
3838
|The leakage occurred when trying to match the following types:
3939
|
40-
|Found: (f: File^'s9, r: Ref[String]^) ->'s10 Ref[String]^
41-
|Required: (f: File^, r: Ref[String]^) => Ref[String]^'s8
40+
|Found: (f: File^'s9, r: Ref[String]^{cap}) ->'s10 Ref[String]^{cap}
41+
|Required: (f: File^{cap}, r: Ref[String]^{cap}) => Ref[String]^'s8
4242
|
43-
|where: => refers to a fresh root capability created in method Test when checking argument to parameter op of method withFileAndRef
44-
| ^ and cap refer to the universal root capability
43+
|where: => refers to a fresh root capability created in method Test when checking argument to parameter op of method withFileAndRef
44+
| cap is the universal root capability
4545
|
4646
26 | r.put(f.read())
4747
27 | r

0 commit comments

Comments
 (0)