Skip to content

Commit e570650

Browse files
committed
Show level errors before type mismatches
1 parent ec74abb commit e570650

28 files changed

+168
-115
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ object Capabilities:
899899
case _ => c1
900900

901901
def showAsCapability(using Context) =
902-
i"capability ${ctx.printer.toTextCapability(this).show}"
902+
i"${ctx.printer.toTextCapability(this).show}"
903903

904904
def toText(printer: Printer): Text = printer.toTextCapability(this)
905905
end Capability

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import CaptureSet.VarState
1515
import Capabilities.*
1616
import StdNames.nme
1717
import config.Feature
18+
import dotty.tools.dotc.core.NameKinds.TryOwnerName
1819

1920
/** Attachment key for capturing type trees */
2021
private val Captures: Key[CaptureSet] = Key()
@@ -624,9 +625,13 @@ extension (sym: Symbol)
624625
|| sym.info.hasAnnotation(defn.ConsumeAnnot)
625626

626627
def qualString(prefix: String)(using Context): String =
628+
if !sym.exists then "" else i" $prefix ${sym.ownerString}"
629+
630+
def ownerString(using Context): String =
627631
if !sym.exists then ""
628-
else if sym.isAnonymousFunction then i" $prefix enclosing function"
629-
else i" $prefix $sym"
632+
else if sym.isAnonymousFunction then i"an enclosing function"
633+
else if sym.name.is(TryOwnerName) then i"an enclosing try expression"
634+
else sym.show
630635

631636
extension (tp: AnnotatedType)
632637
/** Is this a boxed capturing type? */

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

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,12 +1324,25 @@ object CaptureSet:
13241324
res.myTrace = cs1 :: this.myTrace
13251325
res
13261326

1327-
def render(using Context) =
1327+
override def prefix(using Context) = cs match
1328+
case cs: Var =>
1329+
!cs.levelOK(elem)
1330+
|| cs.isBadRoot(elem) && elem.isInstanceOf[FreshCap]
1331+
case _ =>
1332+
false
1333+
1334+
def trailing(msg: String)(using Context): String =
13281335
i"""
13291336
|
1330-
|Note that $description."""
1337+
|Note that $msg."""
13311338

1332-
private def description(using Context): String =
1339+
def leading(msg: String)(using Context): String =
1340+
i"""$msg.
1341+
|The leakage occurred when trying to match the following types:
1342+
|
1343+
|"""
1344+
1345+
def render(using Context): String =
13331346
def why =
13341347
val reasons = cs.elems.toList.collect:
13351348
case c: FreshCap if !c.acceptsLevelOf(elem) =>
@@ -1340,27 +1353,33 @@ object CaptureSet:
13401353
else reasons.mkString("\nbecause ", "\nand ", "")
13411354
cs match
13421355
case cs: Var =>
1356+
def ownerStr =
1357+
if !cs.description.isEmpty then "" else cs.owner.qualString("which is owned by")
13431358
if !cs.levelOK(elem) then
1344-
val levelStr = elem match
1345-
case ref: TermRef => i", defined in ${ref.symbol.maybeOwner}\n"
1346-
case _ => " "
1347-
val ownerStr =
1348-
if cs.owner.exists then s" which is owned by ${cs.owner}" else ""
1349-
i"""${elem.showAsCapability}${levelStr}cannot be included in outer capture set $cs$ownerStr"""
1359+
val outlivesStr = elem match
1360+
case ref: TermRef => i"${ref.symbol.maybeOwner.qualString("defined in")} outlives its scope:\n"
1361+
case _ => " outlives its scope: "
1362+
leading:
1363+
i"""Capability ${elem.showAsCapability}${outlivesStr}it leaks into outer capture set $cs$ownerStr"""
13501364
else if !elem.tryClassifyAs(cs.classifier) then
1351-
i"""${elem.showAsCapability} is not classified as ${cs.classifier}, therefore it
1365+
trailing:
1366+
i"""capability ${elem.showAsCapability} is not classified as ${cs.classifier}, therefore it
13521367
|cannot be included in capture set $cs of ${cs.classifier.name} elements"""
13531368
else if cs.isBadRoot(elem) then
13541369
elem match
13551370
case elem: FreshCap =>
1356-
i"""local ${elem.showAsCapability} created in ${elem.ccOwner}
1357-
|cannot be included in outer capture set $cs"""
1371+
leading:
1372+
i"""Local capability ${elem.showAsCapability} created in ${elem.ccOwner} outlives its scope:
1373+
|It leaks into outer capture set $cs$ownerStr"""
13581374
case _ =>
1359-
i"universal ${elem.showAsCapability} cannot be included in capture set $cs"
1375+
trailing:
1376+
i"universal capability ${elem.showAsCapability} cannot be included in capture set $cs"
13601377
else
1361-
i"${elem.showAsCapability} cannot be included in capture set $cs"
1378+
trailing:
1379+
i"capability ${elem.showAsCapability} cannot be included in capture set $cs"
13621380
case _ =>
1363-
i"${elem.showAsCapability} is not included in capture set $cs$why"
1381+
trailing:
1382+
i"capability ${elem.showAsCapability} is not included in capture set $cs$why"
13641383

13651384
override def toText(printer: Printer): Text =
13661385
inContext(printer.printerContext):

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,6 @@ object CheckCaptures:
201201
&& !sym.isOneOf(DeferredOrTermParamOrAccessor)
202202
&& !sym.hasAnnotation(defn.UntrackedCapturesAnnot)
203203

204-
private def ownerStr(owner: Symbol)(using Context): String =
205-
if owner.isAnonymousFunction then "enclosing function" else owner.show
206-
207204
trait CheckerAPI:
208205
/** Complete symbol info of a val or a def */
209206
def completeDef(tree: ValOrDefDef, sym: Symbol, completer: LazyType)(using Context): Type

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,18 @@ object Message:
4242
else ""
4343

4444
/** A note can produce an added string for an error message */
45-
abstract class Note(val prefix: Boolean = false):
45+
abstract class Note:
46+
47+
/** Should the note be shown before the actual message or after?
48+
* Default is after.
49+
*/
50+
def prefix(using Context): Boolean = false
51+
52+
/** The note rendered as part of an error message */
4653
def render(using Context): String
4754

4855
object Note:
49-
def apply(msg: Context ?=> String, prefix: Boolean = false) = new Note(prefix):
56+
def apply(msg: Context ?=> String) = new Note:
5057
def render(using Context) = msg
5158

5259
enum Disambiguation:

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,23 +343,23 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre
343343
mapOver(tp)
344344
case _ =>
345345
mapOver(tp)
346-
346+
val preface = notes.filter(_.prefix).map(_.render).mkString
347347
val found1 = reported(found)
348348
reported.setVariance(-1)
349349
val expected1 = reported(expected)
350350
val (found2, expected2) =
351351
if (found1 frozen_<:< expected1) || reported.fbounded then (found, expected)
352352
else (found1, expected1)
353353
val (foundStr, expectedStr) = Formatting.typeDiff(found2.normalized, expected2.normalized)
354-
i"""|Found: $foundStr
354+
i"""|${preface}Found: $foundStr
355355
|Required: $expectedStr${reported.notes}"""
356356
end msg
357357

358358
override def msgPostscript(using Context): String =
359359
def importSuggestions =
360360
if expected.isTopType || found.isBottomType then ""
361361
else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected))
362-
notes.map(_.render).mkString ++ super.msgPostscript ++ importSuggestions
362+
notes.filter(!_.prefix).map(_.render).mkString ++ super.msgPostscript ++ importSuggestions
363363

364364
override def explain(using Context) =
365365
val treeStr = inTree.map(x => s"\nTree:\n\n${x.show}\n").getOrElse("")

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
4 | boundary[AnyRef^]:
33
5 | l1 ?=> // error // error
44
| ^
5+
| Capability cap outlives its scope: it leaks into outer capture set 's1 which is owned by value local.
6+
| The leakage occurred when trying to match the following types:
7+
|
58
| Found: scala.util.boundary.Label[Object^'s1]
69
| Required: scala.util.boundary.Label[Object^]^²
710
|
8-
| Note that capability cap cannot be included in outer capture set 's1 which is owned by val local.
9-
|
1011
| where: ^ and cap refer to the universal root capability
1112
| ^² refers to a fresh root capability classified as Control in the type of value local
1213
6 | boundary[Unit]: l2 ?=>

tests/neg-custom-args/captures/effect-swaps-explicit.check

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:69:10 ------------------------
2020
69 | Future: fut ?=> // error, type mismatch
2121
| ^
22-
|Found: (contextual$9: boundary.Label[Result[Future[T^'s2]^'s3, E^'s4]^'s5]^'s6) ?->{fr, async} Future[T^'s7]^{fr, contextual$9}
23-
|Required: (boundary.Label[Result[Future[T^'s8]^'s9, E^'s10]]^) ?=> Future[T^'s8]^'s9
22+
|Capability contextual$9 outlives its scope: it leaks into outer capture set 's2 which is owned by method fail4.
23+
|The leakage occurred when trying to match the following types:
2424
|
25-
|Note that capability contextual$9 cannot be included in outer capture set 's9 which is owned by method fail4.
25+
|Found: (contextual$9: boundary.Label[Result[Future[T^'s3]^'s4, E^'s5]^'s6]^'s7) ?->{fr, async} Future[T^'s8]^{fr, contextual$9}
26+
|Required: (boundary.Label[Result[Future[T^'s9]^'s2, E^'s10]]^) ?=> Future[T^'s9]^'s2
2627
|
2728
|where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make
2829
| ^ refers to the universal root capability

tests/neg-custom-args/captures/effect-swaps.check

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:69:10 ---------------------------------
2020
69 | Future: fut ?=> // error, type mismatch
2121
| ^
22-
|Found: (contextual$9: boundary.Label[Result[Future[T^'s2]^'s3, E^'s4]^'s5]^'s6) ?->{fr, async} Future[T^'s7]^{fr, contextual$9}
23-
|Required: (boundary.Label[Result[Future[T^'s8]^'s9, E^'s10]]^) ?=> Future[T^'s8]^'s9
22+
|Capability contextual$9 outlives its scope: it leaks into outer capture set 's2 which is owned by method fail4.
23+
|The leakage occurred when trying to match the following types:
2424
|
25-
|Note that capability contextual$9 cannot be included in outer capture set 's9 which is owned by method fail4.
25+
|Found: (contextual$9: boundary.Label[Result[Future[T^'s3]^'s4, E^'s5]^'s6]^'s7) ?->{fr, async} Future[T^'s8]^{fr, contextual$9}
26+
|Required: (boundary.Label[Result[Future[T^'s9]^'s2, E^'s10]]^) ?=> Future[T^'s9]^'s2
2627
|
2728
|where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make
2829
| ^ refers to the universal root capability

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/filevar.scala:15:12 --------------------------------------
22
15 | withFile: f => // error with level checking, was OK under both schemes before
33
| ^
4-
|Found: (l: scala.caps.Capability^) ?->'s1 File^'s2 ->'s3 Unit
5-
|Required: (l: scala.caps.Capability^) ?-> (f: File^{l}) => Unit
4+
|Capability l outlives its scope: it leaks into outer capture set 's1 of parameter f.
5+
|The leakage occurred when trying to match the following types:
66
|
7-
|Note that capability l cannot be included in outer capture set 's4 of parameter f which is owned by method $anonfun.
7+
|Found: (l: scala.caps.Capability^) ?->'s2 File^'s3 ->'s4 Unit
8+
|Required: (l: scala.caps.Capability^) ?-> (f: File^{l}) => Unit
89
|
910
|where: => refers to a root capability associated with the result type of (using l: scala.caps.Capability^): (f: File^{l}) => Unit
1011
| ^ refers to the universal root capability

0 commit comments

Comments
 (0)