From 2e4914d0c5839ae105def9fbcbec7e979100174c Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 1 Nov 2025 13:53:28 +0100 Subject: [PATCH 1/7] Show the owner of the conflicting set in level errors --- compiler/src/dotty/tools/dotc/cc/CaptureSet.scala | 4 +++- tests/neg-custom-args/captures/boundary.check | 2 +- .../captures/effect-swaps-explicit.check | 2 +- tests/neg-custom-args/captures/effect-swaps.check | 2 +- tests/neg-custom-args/captures/filevar.check | 2 +- tests/neg-custom-args/captures/heal-tparam-cs.check | 2 +- tests/neg-custom-args/captures/i15923.check | 2 +- tests/neg-custom-args/captures/i15923a.check | 2 +- tests/neg-custom-args/captures/i15923b.check | 2 +- tests/neg-custom-args/captures/i21920.check | 2 +- .../captures/lazylists-exceptions.check | 2 +- .../neg-custom-args/captures/leaking-iterators.check | 2 +- tests/neg-custom-args/captures/real-try.check | 6 +++--- tests/neg-custom-args/captures/reference-cc.check | 4 ++-- tests/neg-custom-args/captures/sep-curried-par.check | 2 +- tests/neg-custom-args/captures/simple-using.check | 2 +- tests/neg-custom-args/captures/try.check | 4 ++-- tests/neg-custom-args/captures/usingLogFile.check | 8 ++++---- tests/neg-custom-args/captures/vars.check | 12 ++++++------ 19 files changed, 33 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index d3ad7004d55e..4026e6f086ed 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1338,7 +1338,9 @@ object CaptureSet: val levelStr = elem match case ref: TermRef => i", defined in ${ref.symbol.maybeOwner}\n" case _ => " " - i"""${elem.showAsCapability}${levelStr}cannot be included in outer capture set $cs""" + val ownerStr = + if cs.owner.exists then s" which is owned by ${cs.owner}" else "" + i"""${elem.showAsCapability}${levelStr}cannot be included in outer capture set $cs$ownerStr""" else if !elem.tryClassifyAs(cs.classifier) then i"""${elem.showAsCapability} is not classified as ${cs.classifier}, therefore it |cannot be included in capture set $cs of ${cs.classifier.name} elements""" diff --git a/tests/neg-custom-args/captures/boundary.check b/tests/neg-custom-args/captures/boundary.check index b64d1edef8a6..62255507f7bd 100644 --- a/tests/neg-custom-args/captures/boundary.check +++ b/tests/neg-custom-args/captures/boundary.check @@ -5,7 +5,7 @@ | Found: scala.util.boundary.Label[Object^'s1] | Required: scala.util.boundary.Label[Object^]^² | - | Note that capability cap cannot be included in outer capture set 's1. + | Note that capability cap cannot be included in outer capture set 's1 which is owned by val local. | | where: ^ and cap refer to the universal root capability | ^² refers to a fresh root capability classified as Control in the type of value local diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.check b/tests/neg-custom-args/captures/effect-swaps-explicit.check index d46f9d3bf749..82a5a239807d 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.check +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.check @@ -22,7 +22,7 @@ |Found: (contextual$9: boundary.Label[Result[Future[T^'s2]^'s3, E^'s4]^'s5]^'s6) ?->{fr, async} Future[T^'s7]^{fr, contextual$9} |Required: (boundary.Label[Result[Future[T^'s8]^'s9, E^'s10]]^) ?=> Future[T^'s8]^'s9 | - |Note that capability contextual$9 cannot be included in outer capture set 's9. + |Note that capability contextual$9 cannot be included in outer capture set 's9 which is owned by method fail4. | |where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/effect-swaps.check b/tests/neg-custom-args/captures/effect-swaps.check index 2148de702501..4820318a9fb8 100644 --- a/tests/neg-custom-args/captures/effect-swaps.check +++ b/tests/neg-custom-args/captures/effect-swaps.check @@ -22,7 +22,7 @@ |Found: (contextual$9: boundary.Label[Result[Future[T^'s2]^'s3, E^'s4]^'s5]^'s6) ?->{fr, async} Future[T^'s7]^{fr, contextual$9} |Required: (boundary.Label[Result[Future[T^'s8]^'s9, E^'s10]]^) ?=> Future[T^'s8]^'s9 | - |Note that capability contextual$9 cannot be included in outer capture set 's9. + |Note that capability contextual$9 cannot be included in outer capture set 's9 which is owned by method fail4. | |where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/filevar.check b/tests/neg-custom-args/captures/filevar.check index 41d749727a04..08f035cee2d0 100644 --- a/tests/neg-custom-args/captures/filevar.check +++ b/tests/neg-custom-args/captures/filevar.check @@ -4,7 +4,7 @@ |Found: (l: scala.caps.Capability^) ?->'s1 File^'s2 ->'s3 Unit |Required: (l: scala.caps.Capability^) ?-> (f: File^{l}) => Unit | - |Note that capability l cannot be included in outer capture set 's4 of parameter f. + |Note that capability l cannot be included in outer capture set 's4 of parameter f which is owned by method $anonfun. | |where: => refers to a root capability associated with the result type of (using l: scala.caps.Capability^): (f: File^{l}) => Unit | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.check b/tests/neg-custom-args/captures/heal-tparam-cs.check index eb910f007614..1e12956e20c1 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.check +++ b/tests/neg-custom-args/captures/heal-tparam-cs.check @@ -4,7 +4,7 @@ |Found: (c: Capp^'s1) ->'s2 () ->{c} Unit |Required: (c: Capp^) => () ->'s3 Unit | - |Note that capability c cannot be included in outer capture set 's3. + |Note that capability c cannot be included in outer capture set 's3 which is owned by val test1. | |where: => refers to a fresh root capability created in value test1 when checking argument to parameter op of method localCap | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/i15923.check b/tests/neg-custom-args/captures/i15923.check index 6ae4cb76f4ac..d8204d62f836 100644 --- a/tests/neg-custom-args/captures/i15923.check +++ b/tests/neg-custom-args/captures/i15923.check @@ -4,7 +4,7 @@ |Found: (lcap: scala.caps.Capability^) ?->'s1 Cap^'s2 ->'s3 Id[Cap^'s4]^'s5 |Required: (lcap: scala.caps.Capability^) ?-> Cap^{lcap} => Id[Cap^'s6]^'s7 | - |Note that capability cap cannot be included in outer capture set 's6. + |Note that capability cap cannot be included in outer capture set 's6 which is owned by val leak. | |where: => refers to a root capability associated with the result type of (using lcap: scala.caps.Capability^): Cap^{lcap} => Id[Cap^'s6]^'s7 | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/i15923a.check b/tests/neg-custom-args/captures/i15923a.check index bc2495391625..b25395e17ae6 100644 --- a/tests/neg-custom-args/captures/i15923a.check +++ b/tests/neg-custom-args/captures/i15923a.check @@ -4,7 +4,7 @@ |Found: (lcap: Cap^'s1) ->'s2 () ->'s3 Id[Cap^'s4]^'s5 |Required: (lcap: Cap^) => () =>² Id[Cap^'s6]^'s7 | - |Note that capability cap cannot be included in outer capture set 's6. + |Note that capability cap cannot be included in outer capture set 's6 which is owned by val leak. | |where: => refers to a fresh root capability created in value leak when checking argument to parameter op of method withCap | =>² refers to a root capability associated with the result type of (lcap: Cap^): () =>² Id[Cap^'s6]^'s7 diff --git a/tests/neg-custom-args/captures/i15923b.check b/tests/neg-custom-args/captures/i15923b.check index 5078b97a66db..4a47cd8bcc7c 100644 --- a/tests/neg-custom-args/captures/i15923b.check +++ b/tests/neg-custom-args/captures/i15923b.check @@ -4,7 +4,7 @@ |Found: (x$0: Cap^) -> Id[Cap^{x$0}] |Required: (lcap: Cap^) => Id[Cap^'s1]^'s2 | - |Note that capability lcap cannot be included in outer capture set 's1. + |Note that capability lcap cannot be included in outer capture set 's1 which is owned by val leak. | |where: => refers to a fresh root capability created in value leak when checking argument to parameter op of method withCap | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/i21920.check b/tests/neg-custom-args/captures/i21920.check index c9b1aa276d93..aca23e9da213 100644 --- a/tests/neg-custom-args/captures/i21920.check +++ b/tests/neg-custom-args/captures/i21920.check @@ -4,7 +4,7 @@ |Found: (f: File^'s1) ->'s2 Cell[File^'s3]{val head: () ->'s4 IterableOnce[File^'s5]^'s6}^'s7 |Required: File^ => Cell[File^'s8]{val head: () ->'s9 IterableOnce[File^'s10]^'s11}^'s12 | - |Note that capability cap cannot be included in outer capture set 's13. + |Note that capability cap cannot be included in outer capture set 's13 which is owned by method $anonfun. | |where: => refers to a fresh root capability created in value cell when checking argument to parameter f of method open | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.check b/tests/neg-custom-args/captures/lazylists-exceptions.check index 0f31ab58cca6..d74e019c3677 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -5,7 +5,7 @@ | Required: LazyList[Int]^'s1 | | Note that capability canThrow$1, defined in method try$1 - | cannot be included in outer capture set 's1 of method problem. + | cannot be included in outer capture set 's1 of method problem which is owned by method problem. 38 | if i > 9 then throw Ex1() 39 | i * i 40 | } diff --git a/tests/neg-custom-args/captures/leaking-iterators.check b/tests/neg-custom-args/captures/leaking-iterators.check index b3b8e36fef79..e23d7684cac0 100644 --- a/tests/neg-custom-args/captures/leaking-iterators.check +++ b/tests/neg-custom-args/captures/leaking-iterators.check @@ -4,7 +4,7 @@ |Found: (log: java.io.FileOutputStream^'s1) ->'s2 cctest.Iterator[Int]^{log} |Required: java.io.FileOutputStream^ => cctest.Iterator[Int]^'s3 | - |Note that capability log cannot be included in outer capture set 's3. + |Note that capability log cannot be included in outer capture set 's3 which is owned by method test. | |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index 684dcb7c982d..d58adb1c71e4 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -23,7 +23,7 @@ | Required: () ->'s1 Unit | | Note that capability canThrow$2, defined in method try$2 - | cannot be included in outer capture set 's1 of value x. + | cannot be included in outer capture set 's1 of value x which is owned by val x. | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/real-try.scala:27:4 -------------------------------------- @@ -33,7 +33,7 @@ | Required: () ->'s3 Cell[Unit]^'s4 | | Note that capability canThrow$3, defined in method try$3 - | cannot be included in outer capture set 's3 of value y. + | cannot be included in outer capture set 's3 of value y which is owned by val y. | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/real-try.scala:33:8 -------------------------------------- @@ -43,6 +43,6 @@ | Required: Cell[() ->'s5 Unit]^'s6 | | Note that capability canThrow$4, defined in method try$4 - | cannot be included in outer capture set 's5 of value b. + | cannot be included in outer capture set 's5 of value b which is owned by val b. | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/reference-cc.check b/tests/neg-custom-args/captures/reference-cc.check index 476cb2a09ceb..108d40ea1e37 100644 --- a/tests/neg-custom-args/captures/reference-cc.check +++ b/tests/neg-custom-args/captures/reference-cc.check @@ -4,7 +4,7 @@ |Found: (file: java.io.FileOutputStream^'s1) ->'s2 () ->{file} Unit |Required: java.io.FileOutputStream^ => () ->'s3 Unit | - |Note that capability file cannot be included in outer capture set 's3. + |Note that capability file cannot be included in outer capture set 's3 which is owned by val later. | |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability @@ -16,7 +16,7 @@ |Found: (f: java.io.FileOutputStream^'s4) ->'s5 LzyList[Int]^{f} |Required: java.io.FileOutputStream^ => LzyList[Int]^'s6 | - |Note that capability f cannot be included in outer capture set 's6. + |Note that capability f cannot be included in outer capture set 's6 which is owned by val xs. | |where: => refers to a fresh root capability created in value xs when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/sep-curried-par.check b/tests/neg-custom-args/captures/sep-curried-par.check index 04c052f9c7cc..be6f0e493adf 100644 --- a/tests/neg-custom-args/captures/sep-curried-par.check +++ b/tests/neg-custom-args/captures/sep-curried-par.check @@ -5,7 +5,7 @@ | Required: (() ->'s1 Unit) ->'s2 Unit | | Note that capability p1, defined in method $anonfun - | cannot be included in outer capture set 's2. + | cannot be included in outer capture set 's2 which is owned by val bar. | | where: cap is the universal root capability | diff --git a/tests/neg-custom-args/captures/simple-using.check b/tests/neg-custom-args/captures/simple-using.check index 54aa8a464ae4..e8e2948d349d 100644 --- a/tests/neg-custom-args/captures/simple-using.check +++ b/tests/neg-custom-args/captures/simple-using.check @@ -4,7 +4,7 @@ |Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit |Required: java.io.FileOutputStream^ => () ->'s3 Unit | - |Note that capability f cannot be included in outer capture set 's3. + |Note that capability f cannot be included in outer capture set 's3 which is owned by method test. | |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index aa0137e1a08a..bfa2cafce460 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -37,7 +37,7 @@ |Found: (x: CT[Exception]^) ->'s3 () ->{x} Int |Required: CT[Exception]^ => () ->'s4 Int | - |Note that capability x cannot be included in outer capture set 's4. + |Note that capability x cannot be included in outer capture set 's4 which is owned by val xx. | |where: => refers to a fresh root capability created in value xx when checking argument to parameter op of method handle | ^ refers to the universal root capability @@ -52,7 +52,7 @@ |Found: (x: CT[Exception]^) ->'s5 () ->{x} Int |Required: CT[Exception]^ => () ->'s6 Int | - |Note that capability x cannot be included in outer capture set 's6. + |Note that capability x cannot be included in outer capture set 's6 which is owned by val global. | |where: => refers to a fresh root capability created in value global when checking argument to parameter op of method handle | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index b4ef40afd587..71750865a9e7 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -4,7 +4,7 @@ |Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit |Required: java.io.FileOutputStream^ => () ->'s3 Unit | - |Note that capability f cannot be included in outer capture set 's3. + |Note that capability f cannot be included in outer capture set 's3 which is owned by val later. | |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability @@ -16,7 +16,7 @@ |Found: (f: java.io.FileOutputStream^'s4) ->'s5 Test2.Cell[() ->{f} Unit]^'s6 |Required: java.io.FileOutputStream^ => Test2.Cell[() ->'s7 Unit]^'s8 | - |Note that capability f cannot be included in outer capture set 's7. + |Note that capability f cannot be included in outer capture set 's7 which is owned by val later2. | |where: => refers to a fresh root capability created in value later2 when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability @@ -28,7 +28,7 @@ |Found: (f: java.io.OutputStream^'s9) ->'s10 Int ->{f} Unit |Required: java.io.OutputStream^ => Int ->'s11 Unit | - |Note that capability f cannot be included in outer capture set 's11. + |Note that capability f cannot be included in outer capture set 's11 which is owned by val later. | |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingFile | ^ refers to the universal root capability @@ -40,7 +40,7 @@ |Found: (_$1: java.io.OutputStream^'s12) ->'s13 () ->{_$1} Unit |Required: java.io.OutputStream^ => () ->'s14 Unit | - |Note that capability _$1 cannot be included in outer capture set 's14. + |Note that capability _$1 cannot be included in outer capture set 's14 which is owned by val later. | |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingFile | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index c992228ca26d..399869e4d96f 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -28,14 +28,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:36:10 ----------------------------------------- 36 | local { cap3 => // error | ^ - | Found: (cap3: CC^) ->'s1 String ->{cap3} String - | Required: CC^ -> String ->{cap3²} String + | Found: (cap3: CC^) ->'s1 String ->{cap3} String + | Required: CC^ -> String ->{cap3²} String | - | Note that capability cap3 cannot be included in outer capture set {cap3²}. + | Note that capability cap3 cannot be included in outer capture set {cap3²} which is owned by method test. | - | where: ^ refers to the universal root capability - | cap3 is a reference to a value parameter - | cap3² is a parameter in an anonymous function in method test + | where: ^ refers to the universal root capability + | cap3 is a reference to a value parameter + | cap3² is a parameter in an anonymous function in method test 37 | def g(x: String): String = if cap3 == cap3 then "" else "a" 38 | g | From 9744afe844fb90f21845a6a181b949b43453000a Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 1 Nov 2025 16:58:13 +0100 Subject: [PATCH 2/7] Refactoring: Replace Addenda by list of Notes --- .../src/dotty/tools/dotc/cc/Capability.scala | 1 - .../src/dotty/tools/dotc/cc/CaptureSet.scala | 1 - .../dotty/tools/dotc/cc/CheckCaptures.scala | 45 ++++++++++--------- .../dotty/tools/dotc/core/TypeComparer.scala | 7 +-- .../dotty/tools/dotc/reporting/messages.scala | 7 ++- .../dotty/tools/dotc/transform/Recheck.scala | 6 +-- .../tools/dotc/typer/ErrorReporting.scala | 25 +++++------ .../dotty/tools/dotc/typer/Implicits.scala | 24 +++++----- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +- 9 files changed, 56 insertions(+), 64 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index 64ca777f1b76..a00f1a4151ac 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -5,7 +5,6 @@ package cc import core.* import Types.*, Symbols.*, Contexts.*, Decorators.* import util.{SimpleIdentitySet, EqHashMap} -import typer.ErrorReporting.Addenda import util.common.alwaysTrue import scala.collection.mutable import CCState.* diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 4026e6f086ed..9ec0961c2a3f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -13,7 +13,6 @@ import reporting.trace import printing.{Showable, Printer} import printing.Texts.* import util.{SimpleIdentitySet, Property, EqHashMap} -import typer.ErrorReporting.Addenda import scala.collection.{mutable, immutable} import TypeComparer.ErrorNote import CCState.* diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 061aabc03f44..a1d7870f15b7 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -14,7 +14,7 @@ import typer.ForceDegree import typer.Inferencing.isFullyDefined import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker} import typer.Checking.{checkBounds, checkAppliedTypesIn} -import typer.ErrorReporting.{Addenda, NothingToAdd, err} +import typer.ErrorReporting.{Note, err} import typer.ProtoTypes.{LhsProto, WildcardSelectionProto, SelectionProto} import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property} import util.chaining.tap @@ -399,7 +399,7 @@ class CheckCaptures extends Recheck, SymTransformer: case (fail: IncludeFailure) :: _ => fail.cs case _ => target def msg(provisional: Boolean) = - def toAdd: String = errorNotes(otherNotes).toAdd.mkString + def toAdd: String = errorNotes(otherNotes).map(_.render).mkString def descr: String = val d = realTarget.description if d.isEmpty then provenance else "" @@ -1208,7 +1208,7 @@ class CheckCaptures extends Recheck, SymTransformer: // too annoying. This is a hole since a defualt getter's result type // might leak into a type variable. - def fail(tree: Tree, expected: Type, addenda: Addenda): Unit = + def fail(tree: Tree, expected: Type, notes: List[Note]): Unit = def maybeResult = if sym.is(Method) then " result" else "" report.error( em"""$sym needs an explicit$maybeResult type because the inferred type does not conform to @@ -1218,7 +1218,7 @@ class CheckCaptures extends Recheck, SymTransformer: | Externally visible type: $expected""", tree.srcPos) - def addenda(expected: Type) = Addenda: + def addendum(expected: Type) = Note: def result = if tree.isInstanceOf[ValDef] then"" else " result" i""" | @@ -1237,7 +1237,7 @@ class CheckCaptures extends Recheck, SymTransformer: val expected = tpt.tpe.dropAllRetains todoAtPostCheck += { () => withCapAsRoot: - testAdapted(tp, expected, tree.rhs, addenda(expected))(fail) + testAdapted(tp, expected, tree.rhs, addendum(expected) :: Nil)(fail) // The check that inferred <: expected is done after recheck so that it // does not interfere with normal rechecking by constraining capture set variables. } @@ -1444,34 +1444,34 @@ class CheckCaptures extends Recheck, SymTransformer: type BoxErrors = mutable.ListBuffer[Message] | Null - private def errorNotes(notes: List[TypeComparer.ErrorNote])(using Context): Addenda = - if notes.isEmpty then NothingToAdd - else new Addenda: - override def toAdd(using Context) = notes.map: note => + private def errorNotes(notes: List[TypeComparer.ErrorNote])(using Context): List[Note] = + notes.map: note => + Note: i""" | |Note that ${note.description}.""" /** Addendas for error messages that show where we have under-approximated by - * mapping a a capability in contravariant position to the empty set because + * mapping of a capability in contravariant position to the empty set because * the original result type of the map was not itself a capability. */ - private def addApproxAddenda(using Context) = - new TypeAccumulator[Addenda]: - def apply(add: Addenda, t: Type) = t match + private def addApproxAddenda(using Context): TypeAccumulator[List[Note]] = + new TypeAccumulator: + def apply(notes: List[Note], t: Type) = t match case CapturingType(t, CaptureSet.EmptyWithProvenance(ref, mapped)) => /* val (origCore, kind) = original match case tp @ AnnotatedType(parent, ann) if ann.hasSymbol(defn.ReachCapabilityAnnot) => (parent, " deep") case _ => (original, "")*/ - add ++ Addenda: + Note: i""" | |Note that a capability $ref in a capture set appearing in contravariant position |was mapped to $mapped which is not a capability. Therefore, it was under-approximated to the empty set.""" + :: notes case _ => - foldOver(add, t) + foldOver(notes, t) /** Massage `actual` and `expected` types before checking conformance. * Massaging is done by the methods following this one: @@ -1480,8 +1480,8 @@ class CheckCaptures extends Recheck, SymTransformer: * If the resulting types are not compatible, try again with an actual type * where local capture roots are instantiated to root variables. */ - override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type = - try testAdapted(actual, expected, tree, addenda)(err.typeMismatch) + override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, notes: List[Note])(using Context): Type = + try testAdapted(actual, expected, tree, notes: List[Note])(err.typeMismatch) catch case ex: AssertionError => println(i"error while checking $tree: $actual against $expected") throw ex @@ -1496,8 +1496,8 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => NoType case _ => NoType - inline def testAdapted(actual: Type, expected: Type, tree: Tree, addenda: Addenda) - (fail: (Tree, Type, Addenda) => Unit)(using Context): Type = + inline def testAdapted(actual: Type, expected: Type, tree: Tree, notes: List[Note]) + (fail: (Tree, Type, List[Note]) => Unit)(using Context): Type = var expected1 = alignDependentFunction(expected, actual.stripCapturing) val falseDeps = expected1 ne expected @@ -1544,11 +1544,12 @@ class CheckCaptures extends Recheck, SymTransformer: } TypeComparer.compareResult(tryCurrentType || tryWidenNamed) match - case TypeComparer.CompareResult.Fail(notes) => + case TypeComparer.CompareResult.Fail(errNotes) => capt.println(i"conforms failed for ${tree}: $actual vs $expected") if falseDeps then expected1 = unalignFunction(expected1) - fail(tree.withType(actualBoxed), expected1, - addApproxAddenda(addenda ++ errorNotes(notes), expected1)) + val toAdd0 = notes ++ errorNotes(errNotes) + val toAdd1 = addApproxAddenda(toAdd0, expected1) + fail(tree.withType(actualBoxed), expected1, toAdd1) actual case /*OK*/ _ => if debugSuccesses then tree match diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 1d1c497b2196..5b7f59cd2364 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3327,7 +3327,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * the same class as `note`. */ def addErrorNote(note: ErrorNote): Unit = - if errorNotes.forall(_._2.kind != note.kind) then + if errorNotes.forall(_._2.getClass != note.getClass) then errorNotes = (recCount, note) :: errorNotes assert(maxErrorLevel <= recCount) maxErrorLevel = recCount @@ -3357,11 +3357,6 @@ object TypeComparer { /** A base trait for data producing addenda to error messages */ trait ErrorNote: - /** A disciminating kind. An error note is not added if it has the same kind - * as an already existing error note. - */ - def kind: Class[?] = getClass - def description(using Context): String end ErrorNote diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index e86538eb8110..4ca9c18aa627 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -18,7 +18,7 @@ import ast.desugar import config.{Feature, MigrationVersion, ScalaVersion} import transform.patmat.Space import transform.patmat.SpaceEngine -import typer.ErrorReporting.{err, matchReductionAddendum, substitutableTypeSymbolsInScope, Addenda, NothingToAdd} +import typer.ErrorReporting.{err, matchReductionAddendum, substitutableTypeSymbolsInScope, Note} import typer.ProtoTypes.{ViewProto, SelectionProto, FunProto} import typer.Implicits.* import typer.Inferencing @@ -298,7 +298,7 @@ extends NotFoundMsg(MissingIdentID) { } } -class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tree], addenda: Addenda = NothingToAdd)(using Context) +class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tree], notes: List[Note] = Nil)(using Context) extends TypeMismatchMsg(found, expected)(TypeMismatchID): private val shouldSuggestNN = @@ -359,8 +359,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre def importSuggestions = if expected.isTopType || found.isBottomType then "" else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected)) - - addenda.toAdd.mkString ++ super.msgPostscript ++ importSuggestions + notes.map(_.render).mkString ++ super.msgPostscript ++ importSuggestions override def explain(using Context) = val treeStr = inTree.map(x => s"\nTree:\n\n${x.show}\n").getOrElse("") diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 34e3773ba147..81cfdba22b2f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -15,7 +15,7 @@ import typer.ErrorReporting.err import typer.ProtoTypes.{AnySelectionProto, LhsProto} import typer.TypeAssigner.seqLitType import typer.ConstFold -import typer.ErrorReporting.{Addenda, NothingToAdd} +import typer.ErrorReporting.Note import config.Printers.recheckr import util.Property import StdNames.nme @@ -633,11 +633,11 @@ abstract class Recheck extends Phase, SymTransformer: println(i"fail while $actual iscompat $expected") throw ex - def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda = NothingToAdd)(using Context): Type = + def checkConformsExpr(actual: Type, expected: Type, tree: Tree, notes: List[Note] = Nil)(using Context): Type = //println(i"check conforms $actual <:< $expected") if !isCompatible(actual, expected) then recheckr.println(i"conforms failed for ${tree}: $actual vs $expected") - err.typeMismatch(tree.withType(actual), expected, addenda) + err.typeMismatch(tree.withType(actual), expected, notes) actual def checkUnit(unit: CompilationUnit)(using Context): Unit = diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index c3393d39ab05..8b96b2f6eb4c 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -71,18 +71,13 @@ object ErrorReporting { case _ => foldOver(s, tp) tps.foldLeft("")(collectMatchTrace) - /** A mixin trait that can produce added elements for an error message */ - trait Addenda: - def toAdd(using Context): List[String] - def ++(follow: Addenda) = new Addenda: - def toAdd(using Context) = Addenda.this.toAdd ++ follow.toAdd + /** A note can produce an added string for an error message */ + abstract class Note(val prefix: Boolean = false): + def render(using Context): String - object Addenda: - def apply(msg: Context ?=> String): Addenda = new Addenda: - def toAdd(using Context) = msg :: Nil - - object NothingToAdd extends Addenda: - def toAdd(using Context): List[String] = Nil + object Note: + def apply(msg: Context ?=> String, prefix: Boolean = false) = new Note(prefix): + def render(using Context) = msg class Errors(using Context) { @@ -180,7 +175,7 @@ object ErrorReporting { def patternConstrStr(tree: Tree): String = ??? - def typeMismatch(tree: Tree, pt: Type, addenda: Addenda = NothingToAdd): Tree = { + def typeMismatch(tree: Tree, pt: Type, notes: List[Note] = Nil): Tree = { val normTp = normalize(tree.tpe, pt) val normPt = normalize(pt, pt) @@ -199,11 +194,11 @@ object ErrorReporting { def missingElse = tree match case If(_, _, elsep @ Literal(Constant(()))) if elsep.span.isSynthetic => - Addenda("\nMaybe you are missing an else part for the conditional?") + Note("\nMaybe you are missing an else part for the conditional?") :: Nil case _ => - NothingToAdd + Nil - errorTree(tree, TypeMismatch(treeTp, expectedTp, Some(tree), addenda ++ missingElse)) + errorTree(tree, TypeMismatch(treeTp, expectedTp, Some(tree), notes ++ missingElse)) } /** A subtype log explaining why `found` does not conform to `expected` */ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index f0569d850cb1..0a151d99cf1a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -467,7 +467,7 @@ object Implicits: } } - abstract class SearchFailureType extends ErrorType, Addenda { + abstract class SearchFailureType extends ErrorType { def expectedType: Type def argument: Tree @@ -485,7 +485,7 @@ object Implicits: else i"convert from ${argument.tpe} to ${clarify(expectedType)}" } - def toAdd(using Context) = Nil + def notes(using Context): List[Note] = Nil } class NoMatchingImplicits(val expectedType: Type, val argument: Tree, constraint: Constraint = OrderingConstraint.empty) @@ -540,10 +540,12 @@ object Implicits: /** A failure value indicating that an implicit search for a conversion was not tried */ case class TooUnspecific(target: Type) extends NoMatchingImplicits(NoType, EmptyTree, OrderingConstraint.empty): - override def toAdd(using Context) = - i""" - |Note that implicit conversions were not tried because the result of an implicit conversion - |must be more specific than $target""" :: Nil + override def notes(using Context) = + Note: + i""" + |Note that implicit conversions were not tried because the result of an implicit conversion + |must be more specific than $target""" + :: Nil override def msg(using Context) = super.msg.append(i"\nThe expected type $target is not specific enough, so no search was attempted") @@ -567,14 +569,16 @@ object Implicits: str2 = alt2.ref.showRef em"both $str1 and $str2 $qualify".withoutDisambiguation() - override def toAdd(using Context) = + override def notes(using Context) = if !argument.isEmpty && argument.tpe.widen.isRef(defn.NothingClass) then Nil else val what = if (expectedType.isInstanceOf[SelectionProto]) "extension methods" else "conversions" - i""" - |Note that implicit $what cannot be applied because they are ambiguous; - |$explanation""" :: Nil + Note: + i""" + |Note that implicit $what cannot be applied because they are ambiguous; + |$explanation""" + :: Nil def asNested = if nested then this else AmbiguousImplicits(alt1, alt2, expectedType, argument, nested = true) end AmbiguousImplicits diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index fc71c0e43034..3294c1d58ce6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4218,7 +4218,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer then tryExtensionOrConversion(tree, pt, mbrProto, qual, locked, compat, inSelect) else - err.typeMismatch(qual, selProto, failure.reason) // TODO: report NotAMember instead, but need to be aware of failure + err.typeMismatch(qual, selProto, failure.reason.notes) // TODO: report NotAMember instead, but need to be aware of failure rememberSearchFailure(qual, failure) catch case ex: TypeError => nestedFailure(ex) @@ -4889,7 +4889,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else val tree1 = healAdapt(tree, pt) if tree1 ne tree then readapt(tree1) - else err.typeMismatch(tree, pt, failure) + else err.typeMismatch(tree, pt, failure.notes) pt match case _: SelectionProto => From ec74abb736d1da3c1da06f9ef88b35cfd89aa19e Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 1 Nov 2025 20:04:00 +0100 Subject: [PATCH 3/7] Refactoring: Merge TypeComparer.ErrorNote and ErrorReporting.Note The merged class is reporting.Message.Note. --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 33 ++++++++++++------- .../dotty/tools/dotc/cc/CheckCaptures.scala | 16 +++------ .../dotty/tools/dotc/core/TypeComparer.scala | 20 +++++------ .../dotty/tools/dotc/reporting/Message.scala | 8 +++++ .../dotty/tools/dotc/reporting/messages.scala | 4 +-- .../dotty/tools/dotc/transform/Recheck.scala | 2 +- .../tools/dotc/typer/ErrorReporting.scala | 10 +----- .../dotty/tools/dotc/typer/Implicits.scala | 1 + .../captures/heal-tparam-cs.check | 2 +- tests/neg-custom-args/captures/i16226.check | 2 +- 10 files changed, 49 insertions(+), 49 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 9ec0961c2a3f..14048213e733 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -10,11 +10,11 @@ import annotation.threadUnsafe import annotation.constructorOnly import annotation.internal.sharable import reporting.trace +import reporting.Message.Note import printing.{Showable, Printer} import printing.Texts.* import util.{SimpleIdentitySet, Property, EqHashMap} import scala.collection.{mutable, immutable} -import TypeComparer.ErrorNote import CCState.* import TypeOps.AvoidMap import compiletime.uninitialized @@ -160,8 +160,8 @@ sealed abstract class CaptureSet extends Showable: final def keepAlways: Boolean = this.isInstanceOf[EmptyWithProvenance] - def failWith(fail: TypeComparer.ErrorNote)(using Context): false = - TypeComparer.addErrorNote(fail) + def failWith(note: Note)(using Context): false = + TypeComparer.addErrorNote(note) false /** Try to include an element in this capture set. @@ -1304,16 +1304,18 @@ object CaptureSet: * when a subsumes check decides that an existential variable `ex` cannot be * instantiated to the other capability `other`. */ - case class ExistentialSubsumesFailure(val ex: ResultCap, val other: Capability) extends ErrorNote: - def description(using Context): String = + case class ExistentialSubsumesFailure(val ex: ResultCap, val other: Capability) extends Note: + def render(using Context): String = def reason = if other.isTerminalCapability then "" else " since that capability is not a `Sharable` capability" - i"""the existential capture root in ${ex.originalBinder.resType} + i""" + | + |Note that the existential capture root in ${ex.originalBinder.resType} |cannot subsume the capability $other$reason.""" /** Failure indicating that `elem` cannot be included in `cs` */ - case class IncludeFailure(cs: CaptureSet, elem: Capability, levelError: Boolean = false) extends ErrorNote, Showable: + case class IncludeFailure(cs: CaptureSet, elem: Capability, levelError: Boolean = false) extends Note, Showable: private var myTrace: List[CaptureSet] = cs :: Nil def trace: List[CaptureSet] = myTrace @@ -1322,7 +1324,12 @@ object CaptureSet: res.myTrace = cs1 :: this.myTrace res - def description(using Context): String = + def render(using Context) = + i""" + | + |Note that $description.""" + + private def description(using Context): String = def why = val reasons = cs.elems.toList.collect: case c: FreshCap if !c.acceptsLevelOf(elem) => @@ -1371,11 +1378,13 @@ object CaptureSet: * @param lo the lower type of the orginal type comparison, or NoType if not known * @param hi the upper type of the orginal type comparison, or NoType if not known */ - case class MutAdaptFailure(cs: CaptureSet, lo: Type = NoType, hi: Type = NoType) extends ErrorNote: - def description(using Context): String = + case class MutAdaptFailure(cs: CaptureSet, lo: Type = NoType, hi: Type = NoType) extends Note: + def render(using Context): String = def ofType(tp: Type) = if tp.exists then i"of the mutable type $tp" else "of a mutable type" - i"""$cs is an exclusive capture set ${ofType(hi)}, - |it cannot subsume a read-only capture set ${ofType(lo)}""" + i""" + | + |Note that $cs is an exclusive capture set ${ofType(hi)}, + |it cannot subsume a read-only capture set ${ofType(lo)}.""" /** A VarState serves as a snapshot mechanism that can undo * additions of elements or super sets if an operation fails diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index a1d7870f15b7..b5b8803a4015 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -14,7 +14,7 @@ import typer.ForceDegree import typer.Inferencing.isFullyDefined import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker} import typer.Checking.{checkBounds, checkAppliedTypesIn} -import typer.ErrorReporting.{Note, err} +import typer.ErrorReporting.err import typer.ProtoTypes.{LhsProto, WildcardSelectionProto, SelectionProto} import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property} import util.chaining.tap @@ -26,6 +26,7 @@ import CCState.* import StdNames.nme import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind} import reporting.{trace, Message, OverrideError} +import reporting.Message.Note import Annotations.Annotation import Capabilities.* import Mutability.* @@ -399,7 +400,7 @@ class CheckCaptures extends Recheck, SymTransformer: case (fail: IncludeFailure) :: _ => fail.cs case _ => target def msg(provisional: Boolean) = - def toAdd: String = errorNotes(otherNotes).map(_.render).mkString + def toAdd: String = otherNotes.map(_.render).mkString def descr: String = val d = realTarget.description if d.isEmpty then provenance else "" @@ -1444,13 +1445,6 @@ class CheckCaptures extends Recheck, SymTransformer: type BoxErrors = mutable.ListBuffer[Message] | Null - private def errorNotes(notes: List[TypeComparer.ErrorNote])(using Context): List[Note] = - notes.map: note => - Note: - i""" - | - |Note that ${note.description}.""" - /** Addendas for error messages that show where we have under-approximated by * mapping of a capability in contravariant position to the empty set because * the original result type of the map was not itself a capability. @@ -1544,10 +1538,10 @@ class CheckCaptures extends Recheck, SymTransformer: } TypeComparer.compareResult(tryCurrentType || tryWidenNamed) match - case TypeComparer.CompareResult.Fail(errNotes) => + case TypeComparer.CompareResult.Fail(cmpNotes) => capt.println(i"conforms failed for ${tree}: $actual vs $expected") if falseDeps then expected1 = unalignFunction(expected1) - val toAdd0 = notes ++ errorNotes(errNotes) + val toAdd0 = notes ++ cmpNotes val toAdd1 = addApproxAddenda(toAdd0, expected1) fail(tree.withType(actualBoxed), expected1, toAdd1) actual diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 5b7f59cd2364..e5292968b249 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -26,6 +26,7 @@ import cc.* import Capabilities.Capability import NameKinds.WildcardParamName import MatchTypes.isConcrete +import reporting.Message.Note import scala.util.boundary, boundary.break /** Provides methods to compare types. @@ -61,7 +62,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling private var monitored = false private var maxErrorLevel: Int = -1 - protected var errorNotes: List[(Int, ErrorNote)] = Nil + protected var errorNotes: List[(Int, Note)] = Nil val undoLog = mutable.ArrayBuffer[() => Unit]() @@ -3323,10 +3324,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def reduceMatchWith[T](op: MatchReducer => T)(using Context): T = inSubComparer(matchReducer)(op) - /** Add given ErrorNote note, provided there is not yet an error note with + /** Add given note, provided there is not yet an error note with * the same class as `note`. */ - def addErrorNote(note: ErrorNote): Unit = + def addErrorNote(note: Note): Unit = if errorNotes.forall(_._2.getClass != note.getClass) then errorNotes = (recCount, note) :: errorNotes assert(maxErrorLevel <= recCount) @@ -3355,15 +3356,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling object TypeComparer { - /** A base trait for data producing addenda to error messages */ - trait ErrorNote: - def description(using Context): String - end ErrorNote - /** A richer compare result, returned by `testSubType` and `test`. */ enum CompareResult: case OK, OKwithGADTUsed, OKwithOpaquesUsed - case Fail(errorNotes: List[ErrorNote]) + case Fail(errorNotes: List[Note]) /** Class for unification variables used in `natValue`. */ private class AnyConstantType extends UncachedGroundType with ValueType { @@ -3541,10 +3537,10 @@ object TypeComparer { def inNestedLevel(op: => Boolean)(using Context): Boolean = currentComparer.inNestedLevel(op) - def addErrorNote(note: ErrorNote)(using Context): Unit = + def addErrorNote(note: Note)(using Context): Unit = currentComparer.addErrorNote(note) - def updateErrorNotes(f: PartialFunction[ErrorNote, ErrorNote])(using Context): Unit = + def updateErrorNotes(f: PartialFunction[Note, Note])(using Context): Unit = currentComparer.errorNotes = currentComparer.errorNotes.mapConserve: p => val (level, note) = p if f.isDefinedAt(note) then (level, f(note)) else p @@ -3958,7 +3954,7 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa private val b = new StringBuilder private var lastForwardGoal: String | Null = null - private def appendFailure(notes: List[ErrorNote]) = + private def appendFailure(notes: List[Note]) = if lastForwardGoal != null then // last was deepest goal that failed b.append(s" = false") for case note: printing.Showable <- notes do diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 32c9bf91f919..47f405636a2b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -41,6 +41,14 @@ object Message: i"\n$what can be rewritten automatically under -rewrite $optionStr." else "" + /** A note can produce an added string for an error message */ + abstract class Note(val prefix: Boolean = false): + def render(using Context): String + + object Note: + def apply(msg: Context ?=> String, prefix: Boolean = false) = new Note(prefix): + def render(using Context) = msg + enum Disambiguation: case All case AllExcept(strs: List[String]) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 4ca9c18aa627..f79857b020a3 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -18,7 +18,7 @@ import ast.desugar import config.{Feature, MigrationVersion, ScalaVersion} import transform.patmat.Space import transform.patmat.SpaceEngine -import typer.ErrorReporting.{err, matchReductionAddendum, substitutableTypeSymbolsInScope, Note} +import typer.ErrorReporting.{err, matchReductionAddendum, substitutableTypeSymbolsInScope} import typer.ProtoTypes.{ViewProto, SelectionProto, FunProto} import typer.Implicits.* import typer.Inferencing @@ -38,7 +38,7 @@ import scala.jdk.CollectionConverters.* import dotty.tools.dotc.util.SourceFile import dotty.tools.dotc.config.SourceVersion import DidYouMean.* -import Message.Disambiguation +import Message.{Disambiguation, Note} /** Messages * ======== diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 81cfdba22b2f..63b9525a88f4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -15,7 +15,7 @@ import typer.ErrorReporting.err import typer.ProtoTypes.{AnySelectionProto, LhsProto} import typer.TypeAssigner.seqLitType import typer.ConstFold -import typer.ErrorReporting.Note +import reporting.Message.Note import config.Printers.recheckr import util.Property import StdNames.nme diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 8b96b2f6eb4c..76e55cda279f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -12,9 +12,9 @@ import util.Spans.NoSpan import util.SrcPos import config.Feature import reporting.* +import Message.Note import collection.mutable - object ErrorReporting { import tpd.* @@ -71,14 +71,6 @@ object ErrorReporting { case _ => foldOver(s, tp) tps.foldLeft("")(collectMatchTrace) - /** A note can produce an added string for an error message */ - abstract class Note(val prefix: Boolean = false): - def render(using Context): String - - object Note: - def apply(msg: Context ?=> String, prefix: Boolean = false) = new Note(prefix): - def render(using Context) = msg - class Errors(using Context) { /** An explanatory note to be added to error messages diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0a151d99cf1a..d3e3a0d06bd8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -31,6 +31,7 @@ import Feature.{migrateTo3, sourceVersion} import config.Printers.{implicits, implicitsDetailed} import collection.mutable import reporting.* +import Message.Note import transform.Splicer import annotation.tailrec diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.check b/tests/neg-custom-args/captures/heal-tparam-cs.check index 1e12956e20c1..9ceeee3d7164 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.check +++ b/tests/neg-custom-args/captures/heal-tparam-cs.check @@ -20,7 +20,7 @@ | Note that capability x$0 is not included in capture set {cap}. | | Note that the existential capture root in () =>² Unit - | cannot subsume the capability (x$0 : Capp^'s4) since that capability is not a `Sharable` capability.. + | cannot subsume the capability (x$0 : Capp^'s4) since that capability is not a `Sharable` capability. | | where: => refers to a root capability associated with the result type of (c: Capp^): () => Unit | =>² and ^ refer to the universal root capability diff --git a/tests/neg-custom-args/captures/i16226.check b/tests/neg-custom-args/captures/i16226.check index d48897126600..479219de5100 100644 --- a/tests/neg-custom-args/captures/i16226.check +++ b/tests/neg-custom-args/captures/i16226.check @@ -25,7 +25,7 @@ |Note that capability f1 is not included in capture set {cap}. | |Note that the existential capture root in LazyRef[B]^² - |cannot subsume the capability (f1 : A^'s15 ->'s16 B^'s17) since that capability is not a `Sharable` capability.. + |cannot subsume the capability (f1 : A^'s15 ->'s16 B^'s17) since that capability is not a `Sharable` capability. | |where: => and cap refer to a root capability associated with the result type of (ref1: LazyRef[A^'s11]{val elem: () ->'s12 A^'s13}^'s14, f1: A^'s15 ->'s16 B^'s17): | LazyRef[B^'s19]{val elem: () => B^'s20}^{f1, ref1} From e5706502adefb73d92cc1b970c5e118abc09a403 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 2 Nov 2025 11:45:00 +0100 Subject: [PATCH 4/7] Show level errors before type mismatches --- .../src/dotty/tools/dotc/cc/Capability.scala | 2 +- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 9 +++- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 49 +++++++++++++------ .../dotty/tools/dotc/cc/CheckCaptures.scala | 3 -- .../dotty/tools/dotc/reporting/Message.scala | 11 ++++- .../dotty/tools/dotc/reporting/messages.scala | 6 +-- tests/neg-custom-args/captures/boundary.check | 5 +- .../captures/effect-swaps-explicit.check | 7 +-- .../captures/effect-swaps.check | 7 +-- tests/neg-custom-args/captures/filevar.check | 7 +-- .../captures/heal-tparam-cs.check | 7 +-- tests/neg-custom-args/captures/i15923.check | 11 +++-- tests/neg-custom-args/captures/i15923a.check | 11 +++-- tests/neg-custom-args/captures/i15923b.check | 5 +- tests/neg-custom-args/captures/i21920.check | 9 ++-- tests/neg-custom-args/captures/i23431.check | 4 +- .../captures/lazylists-exceptions.check | 7 +-- .../captures/leaking-iterators.check | 7 +-- tests/neg-custom-args/captures/reaches.check | 2 +- tests/neg-custom-args/captures/real-try.check | 23 +++++---- .../captures/reference-cc.check | 16 +++--- .../captures/scoped-caps.check | 2 +- .../captures/scoped-caps2.check | 2 +- .../captures/sep-curried-par.check | 9 ++-- .../captures/simple-using.check | 7 +-- tests/neg-custom-args/captures/try.check | 14 +++--- .../captures/usingLogFile.check | 28 ++++++----- tests/neg-custom-args/captures/vars.check | 13 ++--- 28 files changed, 168 insertions(+), 115 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index a00f1a4151ac..fb6a8cff96e8 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -899,7 +899,7 @@ object Capabilities: case _ => c1 def showAsCapability(using Context) = - i"capability ${ctx.printer.toTextCapability(this).show}" + i"${ctx.printer.toTextCapability(this).show}" def toText(printer: Printer): Text = printer.toTextCapability(this) end Capability diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 125569c16033..c4659ea9fa96 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -15,6 +15,7 @@ import CaptureSet.VarState import Capabilities.* import StdNames.nme import config.Feature +import dotty.tools.dotc.core.NameKinds.TryOwnerName /** Attachment key for capturing type trees */ private val Captures: Key[CaptureSet] = Key() @@ -624,9 +625,13 @@ extension (sym: Symbol) || sym.info.hasAnnotation(defn.ConsumeAnnot) def qualString(prefix: String)(using Context): String = + if !sym.exists then "" else i" $prefix ${sym.ownerString}" + + def ownerString(using Context): String = if !sym.exists then "" - else if sym.isAnonymousFunction then i" $prefix enclosing function" - else i" $prefix $sym" + else if sym.isAnonymousFunction then i"an enclosing function" + else if sym.name.is(TryOwnerName) then i"an enclosing try expression" + else sym.show extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 14048213e733..b5c13cedfe80 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1324,12 +1324,25 @@ object CaptureSet: res.myTrace = cs1 :: this.myTrace res - def render(using Context) = + override def prefix(using Context) = cs match + case cs: Var => + !cs.levelOK(elem) + || cs.isBadRoot(elem) && elem.isInstanceOf[FreshCap] + case _ => + false + + def trailing(msg: String)(using Context): String = i""" | - |Note that $description.""" + |Note that $msg.""" - private def description(using Context): String = + def leading(msg: String)(using Context): String = + i"""$msg. + |The leakage occurred when trying to match the following types: + | + |""" + + def render(using Context): String = def why = val reasons = cs.elems.toList.collect: case c: FreshCap if !c.acceptsLevelOf(elem) => @@ -1340,27 +1353,33 @@ object CaptureSet: else reasons.mkString("\nbecause ", "\nand ", "") cs match case cs: Var => + def ownerStr = + if !cs.description.isEmpty then "" else cs.owner.qualString("which is owned by") if !cs.levelOK(elem) then - val levelStr = elem match - case ref: TermRef => i", defined in ${ref.symbol.maybeOwner}\n" - case _ => " " - val ownerStr = - if cs.owner.exists then s" which is owned by ${cs.owner}" else "" - i"""${elem.showAsCapability}${levelStr}cannot be included in outer capture set $cs$ownerStr""" + val outlivesStr = elem match + case ref: TermRef => i"${ref.symbol.maybeOwner.qualString("defined in")} outlives its scope:\n" + case _ => " outlives its scope: " + leading: + i"""Capability ${elem.showAsCapability}${outlivesStr}it leaks into outer capture set $cs$ownerStr""" else if !elem.tryClassifyAs(cs.classifier) then - i"""${elem.showAsCapability} is not classified as ${cs.classifier}, therefore it + trailing: + i"""capability ${elem.showAsCapability} is not classified as ${cs.classifier}, therefore it |cannot be included in capture set $cs of ${cs.classifier.name} elements""" else if cs.isBadRoot(elem) then elem match case elem: FreshCap => - i"""local ${elem.showAsCapability} created in ${elem.ccOwner} - |cannot be included in outer capture set $cs""" + leading: + i"""Local capability ${elem.showAsCapability} created in ${elem.ccOwner} outlives its scope: + |It leaks into outer capture set $cs$ownerStr""" case _ => - i"universal ${elem.showAsCapability} cannot be included in capture set $cs" + trailing: + i"universal capability ${elem.showAsCapability} cannot be included in capture set $cs" else - i"${elem.showAsCapability} cannot be included in capture set $cs" + trailing: + i"capability ${elem.showAsCapability} cannot be included in capture set $cs" case _ => - i"${elem.showAsCapability} is not included in capture set $cs$why" + trailing: + i"capability ${elem.showAsCapability} is not included in capture set $cs$why" override def toText(printer: Printer): Text = inContext(printer.printerContext): diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index b5b8803a4015..5464f9ea2df7 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -201,9 +201,6 @@ object CheckCaptures: && !sym.isOneOf(DeferredOrTermParamOrAccessor) && !sym.hasAnnotation(defn.UntrackedCapturesAnnot) - private def ownerStr(owner: Symbol)(using Context): String = - if owner.isAnonymousFunction then "enclosing function" else owner.show - trait CheckerAPI: /** Complete symbol info of a val or a def */ def completeDef(tree: ValOrDefDef, sym: Symbol, completer: LazyType)(using Context): Type diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 47f405636a2b..33ce11f45425 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -42,11 +42,18 @@ object Message: else "" /** A note can produce an added string for an error message */ - abstract class Note(val prefix: Boolean = false): + abstract class Note: + + /** Should the note be shown before the actual message or after? + * Default is after. + */ + def prefix(using Context): Boolean = false + + /** The note rendered as part of an error message */ def render(using Context): String object Note: - def apply(msg: Context ?=> String, prefix: Boolean = false) = new Note(prefix): + def apply(msg: Context ?=> String) = new Note: def render(using Context) = msg enum Disambiguation: diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index f79857b020a3..3aebf257b5c5 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -343,7 +343,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre mapOver(tp) case _ => mapOver(tp) - + val preface = notes.filter(_.prefix).map(_.render).mkString val found1 = reported(found) reported.setVariance(-1) val expected1 = reported(expected) @@ -351,7 +351,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre if (found1 frozen_<:< expected1) || reported.fbounded then (found, expected) else (found1, expected1) val (foundStr, expectedStr) = Formatting.typeDiff(found2.normalized, expected2.normalized) - i"""|Found: $foundStr + i"""|${preface}Found: $foundStr |Required: $expectedStr${reported.notes}""" end msg @@ -359,7 +359,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre def importSuggestions = if expected.isTopType || found.isBottomType then "" else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected)) - notes.map(_.render).mkString ++ super.msgPostscript ++ importSuggestions + notes.filter(!_.prefix).map(_.render).mkString ++ super.msgPostscript ++ importSuggestions override def explain(using Context) = val treeStr = inTree.map(x => s"\nTree:\n\n${x.show}\n").getOrElse("") diff --git a/tests/neg-custom-args/captures/boundary.check b/tests/neg-custom-args/captures/boundary.check index 62255507f7bd..0c440125a403 100644 --- a/tests/neg-custom-args/captures/boundary.check +++ b/tests/neg-custom-args/captures/boundary.check @@ -2,11 +2,12 @@ 4 | boundary[AnyRef^]: 5 | l1 ?=> // error // error | ^ + | Capability cap outlives its scope: it leaks into outer capture set 's1 which is owned by value local. + | The leakage occurred when trying to match the following types: + | | Found: scala.util.boundary.Label[Object^'s1] | Required: scala.util.boundary.Label[Object^]^² | - | Note that capability cap cannot be included in outer capture set 's1 which is owned by val local. - | | where: ^ and cap refer to the universal root capability | ^² refers to a fresh root capability classified as Control in the type of value local 6 | boundary[Unit]: l2 ?=> diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.check b/tests/neg-custom-args/captures/effect-swaps-explicit.check index 82a5a239807d..098054bc31ed 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.check +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.check @@ -19,10 +19,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:69:10 ------------------------ 69 | Future: fut ?=> // error, type mismatch | ^ - |Found: (contextual$9: boundary.Label[Result[Future[T^'s2]^'s3, E^'s4]^'s5]^'s6) ?->{fr, async} Future[T^'s7]^{fr, contextual$9} - |Required: (boundary.Label[Result[Future[T^'s8]^'s9, E^'s10]]^) ?=> Future[T^'s8]^'s9 + |Capability contextual$9 outlives its scope: it leaks into outer capture set 's2 which is owned by method fail4. + |The leakage occurred when trying to match the following types: | - |Note that capability contextual$9 cannot be included in outer capture set 's9 which is owned by method fail4. + |Found: (contextual$9: boundary.Label[Result[Future[T^'s3]^'s4, E^'s5]^'s6]^'s7) ?->{fr, async} Future[T^'s8]^{fr, contextual$9} + |Required: (boundary.Label[Result[Future[T^'s9]^'s2, E^'s10]]^) ?=> Future[T^'s9]^'s2 | |where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/effect-swaps.check b/tests/neg-custom-args/captures/effect-swaps.check index 4820318a9fb8..fdf00220fa4e 100644 --- a/tests/neg-custom-args/captures/effect-swaps.check +++ b/tests/neg-custom-args/captures/effect-swaps.check @@ -19,10 +19,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:69:10 --------------------------------- 69 | Future: fut ?=> // error, type mismatch | ^ - |Found: (contextual$9: boundary.Label[Result[Future[T^'s2]^'s3, E^'s4]^'s5]^'s6) ?->{fr, async} Future[T^'s7]^{fr, contextual$9} - |Required: (boundary.Label[Result[Future[T^'s8]^'s9, E^'s10]]^) ?=> Future[T^'s8]^'s9 + |Capability contextual$9 outlives its scope: it leaks into outer capture set 's2 which is owned by method fail4. + |The leakage occurred when trying to match the following types: | - |Note that capability contextual$9 cannot be included in outer capture set 's9 which is owned by method fail4. + |Found: (contextual$9: boundary.Label[Result[Future[T^'s3]^'s4, E^'s5]^'s6]^'s7) ?->{fr, async} Future[T^'s8]^{fr, contextual$9} + |Required: (boundary.Label[Result[Future[T^'s9]^'s2, E^'s10]]^) ?=> Future[T^'s9]^'s2 | |where: ?=> refers to a fresh root capability created in method fail4 when checking argument to parameter body of method make | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/filevar.check b/tests/neg-custom-args/captures/filevar.check index 08f035cee2d0..ed3996b1e4d8 100644 --- a/tests/neg-custom-args/captures/filevar.check +++ b/tests/neg-custom-args/captures/filevar.check @@ -1,10 +1,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/filevar.scala:15:12 -------------------------------------- 15 | withFile: f => // error with level checking, was OK under both schemes before | ^ - |Found: (l: scala.caps.Capability^) ?->'s1 File^'s2 ->'s3 Unit - |Required: (l: scala.caps.Capability^) ?-> (f: File^{l}) => Unit + |Capability l outlives its scope: it leaks into outer capture set 's1 of parameter f. + |The leakage occurred when trying to match the following types: | - |Note that capability l cannot be included in outer capture set 's4 of parameter f which is owned by method $anonfun. + |Found: (l: scala.caps.Capability^) ?->'s2 File^'s3 ->'s4 Unit + |Required: (l: scala.caps.Capability^) ?-> (f: File^{l}) => Unit | |where: => refers to a root capability associated with the result type of (using l: scala.caps.Capability^): (f: File^{l}) => Unit | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.check b/tests/neg-custom-args/captures/heal-tparam-cs.check index 9ceeee3d7164..7a36f9654c57 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.check +++ b/tests/neg-custom-args/captures/heal-tparam-cs.check @@ -1,10 +1,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:10:25 ------------------------------- 10 | val test1 = localCap { c => // error | ^ - |Found: (c: Capp^'s1) ->'s2 () ->{c} Unit - |Required: (c: Capp^) => () ->'s3 Unit + |Capability c outlives its scope: it leaks into outer capture set 's1 which is owned by value test1. + |The leakage occurred when trying to match the following types: | - |Note that capability c cannot be included in outer capture set 's3 which is owned by val test1. + |Found: (c: Capp^'s2) ->'s3 () ->{c} Unit + |Required: (c: Capp^) => () ->'s1 Unit | |where: => refers to a fresh root capability created in value test1 when checking argument to parameter op of method localCap | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/i15923.check b/tests/neg-custom-args/captures/i15923.check index d8204d62f836..44c918ff57be 100644 --- a/tests/neg-custom-args/captures/i15923.check +++ b/tests/neg-custom-args/captures/i15923.check @@ -1,13 +1,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15923.scala:12:21 --------------------------------------- 12 | val leak = withCap(cap => mkId(cap)) // error | ^^^^^^^^^^^^^^^^ - |Found: (lcap: scala.caps.Capability^) ?->'s1 Cap^'s2 ->'s3 Id[Cap^'s4]^'s5 - |Required: (lcap: scala.caps.Capability^) ?-> Cap^{lcap} => Id[Cap^'s6]^'s7 + |Capability cap outlives its scope: it leaks into outer capture set 's1 which is owned by value leak. + |The leakage occurred when trying to match the following types: | - |Note that capability cap cannot be included in outer capture set 's6 which is owned by val leak. + |Found: (lcap: scala.caps.Capability^) ?->'s2 Cap^'s3 ->'s4 Id[Cap^'s5]^'s6 + |Required: (lcap: scala.caps.Capability^) ?-> Cap^{lcap} => Id[Cap^'s1]^'s7 | - |where: => refers to a root capability associated with the result type of (using lcap: scala.caps.Capability^): Cap^{lcap} => Id[Cap^'s6]^'s7 + |where: => refers to a root capability associated with the result type of (using lcap: scala.caps.Capability^): Cap^{lcap} => Id[Cap^'s1]^'s7 | ^ refers to the universal root capability - | cap is a root capability associated with the result type of (x$0: Cap^'s2): Id[Cap^'s4]^'s5 + | cap is a root capability associated with the result type of (x$0: Cap^'s3): Id[Cap^'s5]^'s6 | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i15923a.check b/tests/neg-custom-args/captures/i15923a.check index b25395e17ae6..d21ce5cfd04b 100644 --- a/tests/neg-custom-args/captures/i15923a.check +++ b/tests/neg-custom-args/captures/i15923a.check @@ -1,14 +1,15 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15923a.scala:7:21 --------------------------------------- 7 | val leak = withCap(lcap => () => mkId(lcap)) // error | ^^^^^^^^^^^^^^^^^^^^^^^^ - |Found: (lcap: Cap^'s1) ->'s2 () ->'s3 Id[Cap^'s4]^'s5 - |Required: (lcap: Cap^) => () =>² Id[Cap^'s6]^'s7 + |Capability cap outlives its scope: it leaks into outer capture set 's1 which is owned by value leak. + |The leakage occurred when trying to match the following types: | - |Note that capability cap cannot be included in outer capture set 's6 which is owned by val leak. + |Found: (lcap: Cap^'s2) ->'s3 () ->'s4 Id[Cap^'s5]^'s6 + |Required: (lcap: Cap^) => () =>² Id[Cap^'s1]^'s7 | |where: => refers to a fresh root capability created in value leak when checking argument to parameter op of method withCap - | =>² refers to a root capability associated with the result type of (lcap: Cap^): () =>² Id[Cap^'s6]^'s7 + | =>² refers to a root capability associated with the result type of (lcap: Cap^): () =>² Id[Cap^'s1]^'s7 | ^ refers to the universal root capability - | cap is a root capability associated with the result type of (): Id[Cap^'s4]^'s5 + | cap is a root capability associated with the result type of (): Id[Cap^'s5]^'s6 | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i15923b.check b/tests/neg-custom-args/captures/i15923b.check index 4a47cd8bcc7c..ef2e1ed7e16f 100644 --- a/tests/neg-custom-args/captures/i15923b.check +++ b/tests/neg-custom-args/captures/i15923b.check @@ -1,11 +1,12 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15923b.scala:8:21 --------------------------------------- 8 | val leak = withCap(f) // error | ^ + |Capability lcap outlives its scope: it leaks into outer capture set 's1 which is owned by value leak. + |The leakage occurred when trying to match the following types: + | |Found: (x$0: Cap^) -> Id[Cap^{x$0}] |Required: (lcap: Cap^) => Id[Cap^'s1]^'s2 | - |Note that capability lcap cannot be included in outer capture set 's1 which is owned by val leak. - | |where: => refers to a fresh root capability created in value leak when checking argument to parameter op of method withCap | ^ refers to the universal root capability | diff --git a/tests/neg-custom-args/captures/i21920.check b/tests/neg-custom-args/captures/i21920.check index aca23e9da213..6479386622fa 100644 --- a/tests/neg-custom-args/captures/i21920.check +++ b/tests/neg-custom-args/captures/i21920.check @@ -1,13 +1,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21920.scala:34:35 --------------------------------------- 34 | val cell: Cell[File] = File.open(f => Cell(() => Seq(f))) // error | ^^^^^^^^^^^^^^^^^^^^^^^ - |Found: (f: File^'s1) ->'s2 Cell[File^'s3]{val head: () ->'s4 IterableOnce[File^'s5]^'s6}^'s7 - |Required: File^ => Cell[File^'s8]{val head: () ->'s9 IterableOnce[File^'s10]^'s11}^'s12 + |Capability cap outlives its scope: it leaks into outer capture set 's1 which is owned by an enclosing function. + |The leakage occurred when trying to match the following types: | - |Note that capability cap cannot be included in outer capture set 's13 which is owned by method $anonfun. + |Found: (f: File^'s2) ->'s3 Cell[File^'s4]{val head: () ->'s5 IterableOnce[File^'s6]^'s7}^'s8 + |Required: File^ => Cell[File^'s9]{val head: () ->'s10 IterableOnce[File^'s11]^'s12}^'s13 | |where: => refers to a fresh root capability created in value cell when checking argument to parameter f of method open | ^ refers to the universal root capability - | cap is a root capability associated with the result type of (): IterableOnce[File^'s13]^² + | cap is a root capability associated with the result type of (): IterableOnce[File^'s1]^² | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i23431.check b/tests/neg-custom-args/captures/i23431.check index 5bbcb2b720f0..10885339334d 100644 --- a/tests/neg-custom-args/captures/i23431.check +++ b/tests/neg-custom-args/captures/i23431.check @@ -18,7 +18,7 @@ | Required: IO^² | | Note that capability cap is not included in capture set {cap²} - | because cap in enclosing function is not visible from cap² in variable myIO. + | because cap in an enclosing function is not visible from cap² in variable myIO. | | where: ^ and cap refer to a fresh root capability in the type of parameter io2 | ^² and cap² refer to a fresh root capability in the type of variable myIO @@ -31,7 +31,7 @@ |Required: IO^ => Unit | |Note that capability cap is not included in capture set {cap²} - |because cap in enclosing function is not visible from cap² in variable myIO. + |because cap in an enclosing function is not visible from cap² in variable myIO. | |where: => refers to a fresh root capability created in anonymous function of type (io1: IO^): Unit when checking argument to parameter op of method withIO | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.check b/tests/neg-custom-args/captures/lazylists-exceptions.check index d74e019c3677..c0ca2bd3b258 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -1,11 +1,12 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists-exceptions.scala:37:17 ------------------------- 37 | tabulate(10) { i => // error | ^ + | Capability canThrow$1 defined in an enclosing try expression outlives its scope: + | it leaks into outer capture set 's1 of method problem. + | The leakage occurred when trying to match the following types: + | | Found: LazyList[Int]^{canThrow$1} | Required: LazyList[Int]^'s1 - | - | Note that capability canThrow$1, defined in method try$1 - | cannot be included in outer capture set 's1 of method problem which is owned by method problem. 38 | if i > 9 then throw Ex1() 39 | i * i 40 | } diff --git a/tests/neg-custom-args/captures/leaking-iterators.check b/tests/neg-custom-args/captures/leaking-iterators.check index e23d7684cac0..f867fc5d4a79 100644 --- a/tests/neg-custom-args/captures/leaking-iterators.check +++ b/tests/neg-custom-args/captures/leaking-iterators.check @@ -1,10 +1,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/leaking-iterators.scala:56:16 ---------------------------- 56 | usingLogFile: log => // error | ^ - |Found: (log: java.io.FileOutputStream^'s1) ->'s2 cctest.Iterator[Int]^{log} - |Required: java.io.FileOutputStream^ => cctest.Iterator[Int]^'s3 + |Capability log outlives its scope: it leaks into outer capture set 's1 which is owned by method test. + |The leakage occurred when trying to match the following types: | - |Note that capability log cannot be included in outer capture set 's3 which is owned by method test. + |Found: (log: java.io.FileOutputStream^'s2) ->'s3 cctest.Iterator[Int]^{log} + |Required: java.io.FileOutputStream^ => cctest.Iterator[Int]^'s1 | |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index e20a318064bb..8c099c520d60 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -124,7 +124,7 @@ -- Error: tests/neg-custom-args/captures/reaches.scala:60:36 ----------------------------------------------------------- 60 | val leaked = usingFile[File^{id*}]: f => // error: separation | ^ - | Local cap created in type of parameter x leaks into capture scope of enclosing function + | Local cap created in type of parameter x leaks into capture scope of an enclosing function | | where: cap is a fresh root capability created in value id of parameter parameter x of method $anonfun 61 | val f1: File^{id*} = id(f) diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index d58adb1c71e4..7f574644f7ca 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -19,30 +19,33 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/real-try.scala:21:4 -------------------------------------- 21 | () => foo(1) // error | ^^^^^^^^^^^^ + | Capability canThrow$2 defined in an enclosing try expression outlives its scope: + | it leaks into outer capture set 's1 of value x. + | The leakage occurred when trying to match the following types: + | | Found: () ->{canThrow$2} Unit | Required: () ->'s1 Unit | - | Note that capability canThrow$2, defined in method try$2 - | cannot be included in outer capture set 's1 of value x which is owned by val x. - | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/real-try.scala:27:4 -------------------------------------- 27 | () => Cell(foo(1)) // error | ^^^^^^^^^^^^^^^^^^ - | Found: () ->{canThrow$3} Cell[Unit]^'s2 - | Required: () ->'s3 Cell[Unit]^'s4 + | Capability canThrow$3 defined in an enclosing try expression outlives its scope: + | it leaks into outer capture set 's2 of value y. + | The leakage occurred when trying to match the following types: | - | Note that capability canThrow$3, defined in method try$3 - | cannot be included in outer capture set 's3 of value y which is owned by val y. + | Found: () ->{canThrow$3} Cell[Unit]^'s3 + | Required: () ->'s2 Cell[Unit]^'s4 | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/real-try.scala:33:8 -------------------------------------- 33 | Cell(() => foo(1)) // error | ^^^^^^^^^^^^^^^^^^ + | Capability canThrow$4 defined in an enclosing try expression outlives its scope: + | it leaks into outer capture set 's5 of value b. + | The leakage occurred when trying to match the following types: + | | Found: Cell[() ->{canThrow$4} Unit] | Required: Cell[() ->'s5 Unit]^'s6 | - | Note that capability canThrow$4, defined in method try$4 - | cannot be included in outer capture set 's5 of value b which is owned by val b. - | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/reference-cc.check b/tests/neg-custom-args/captures/reference-cc.check index 108d40ea1e37..df4567a5c173 100644 --- a/tests/neg-custom-args/captures/reference-cc.check +++ b/tests/neg-custom-args/captures/reference-cc.check @@ -1,10 +1,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reference-cc.scala:44:29 --------------------------------- 44 | val later = usingLogFile { file => () => file.write(0) } // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |Found: (file: java.io.FileOutputStream^'s1) ->'s2 () ->{file} Unit - |Required: java.io.FileOutputStream^ => () ->'s3 Unit + |Capability file outlives its scope: it leaks into outer capture set 's1 which is owned by value later. + |The leakage occurred when trying to match the following types: | - |Note that capability file cannot be included in outer capture set 's3 which is owned by val later. + |Found: (file: java.io.FileOutputStream^'s2) ->'s3 () ->{file} Unit + |Required: java.io.FileOutputStream^ => () ->'s1 Unit | |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability @@ -13,10 +14,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reference-cc.scala:47:25 --------------------------------- 47 | val xs = usingLogFile: f => // error | ^ - |Found: (f: java.io.FileOutputStream^'s4) ->'s5 LzyList[Int]^{f} - |Required: java.io.FileOutputStream^ => LzyList[Int]^'s6 + |Capability f outlives its scope: it leaks into outer capture set 's4 which is owned by value xs. + |The leakage occurred when trying to match the following types: | - |Note that capability f cannot be included in outer capture set 's6 which is owned by val xs. + |Found: (f: java.io.FileOutputStream^'s5) ->'s6 LzyList[Int]^{f} + |Required: java.io.FileOutputStream^ => LzyList[Int]^'s4 | |where: => refers to a fresh root capability created in value xs when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability @@ -30,7 +32,7 @@ |Required: () => Double | |Note that capability canThrow$1 is not included in capture set {cap} - |because (canThrow$1 : CanThrow[LimitExceeded]) in method try$1 is not visible from cap in enclosing function. + |because (canThrow$1 : CanThrow[LimitExceeded]) in an enclosing try expression is not visible from cap in an enclosing function. | |where: => and cap refer to a fresh root capability created in anonymous function of type (using erased x$1: CanThrow[LimitExceeded]): () => Double when instantiating expected result type () ->{cap²} Double of function literal | diff --git a/tests/neg-custom-args/captures/scoped-caps.check b/tests/neg-custom-args/captures/scoped-caps.check index 4c738997c213..d01b55ad0747 100644 --- a/tests/neg-custom-args/captures/scoped-caps.check +++ b/tests/neg-custom-args/captures/scoped-caps.check @@ -85,7 +85,7 @@ |Required: B^² | |Note that capability cap is not included in capture set {cap²} - |because cap in enclosing function is not visible from cap² in value _$14. + |because cap in an enclosing function is not visible from cap² in value _$14. | |where: ^ and cap refer to a fresh root capability created in anonymous function of type (x: S): B^³ when instantiating method apply's type (x: S^³): B^⁴ | ^² and cap² refer to a fresh root capability in the type of value _$14 diff --git a/tests/neg-custom-args/captures/scoped-caps2.check b/tests/neg-custom-args/captures/scoped-caps2.check index 21867171cb6b..cd739e80bc20 100644 --- a/tests/neg-custom-args/captures/scoped-caps2.check +++ b/tests/neg-custom-args/captures/scoped-caps2.check @@ -61,7 +61,7 @@ |Required: C^² | |Note that capability cap is not included in capture set {cap²} - |because cap in enclosing function is not visible from cap² in value _$8. + |because cap in an enclosing function is not visible from cap² in value _$8. | |where: ^ and cap refer to a fresh root capability classified as SharedCapability created in anonymous function of type (x: C): C when instantiating method apply's type (x: C^³): C^⁴ | ^² and cap² refer to a fresh root capability classified as SharedCapability in the type of value _$8 diff --git a/tests/neg-custom-args/captures/sep-curried-par.check b/tests/neg-custom-args/captures/sep-curried-par.check index be6f0e493adf..746b5d041338 100644 --- a/tests/neg-custom-args/captures/sep-curried-par.check +++ b/tests/neg-custom-args/captures/sep-curried-par.check @@ -1,11 +1,12 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/sep-curried-par.scala:23:32 ------------------------------ 23 | val bar = (p1: () => Unit) => (p2: () ->{p1, cap} Unit) => par(p1, p2) // error, but error message could be better | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (p2: () ->{p1, cap} Unit) ->{p1} Unit - | Required: (() ->'s1 Unit) ->'s2 Unit + | Capability p1 defined in an enclosing function outlives its scope: + | it leaks into outer capture set 's1 which is owned by value bar. + | The leakage occurred when trying to match the following types: | - | Note that capability p1, defined in method $anonfun - | cannot be included in outer capture set 's2 which is owned by val bar. + | Found: (p2: () ->{p1, cap} Unit) ->{p1} Unit + | Required: (() ->'s2 Unit) ->'s1 Unit | | where: cap is the universal root capability | diff --git a/tests/neg-custom-args/captures/simple-using.check b/tests/neg-custom-args/captures/simple-using.check index e8e2948d349d..84206a8f78ce 100644 --- a/tests/neg-custom-args/captures/simple-using.check +++ b/tests/neg-custom-args/captures/simple-using.check @@ -1,10 +1,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/simple-using.scala:8:17 ---------------------------------- 8 | usingLogFile { f => () => f.write(2) } // error | ^^^^^^^^^^^^^^^^^^^^^ - |Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit - |Required: java.io.FileOutputStream^ => () ->'s3 Unit + |Capability f outlives its scope: it leaks into outer capture set 's1 which is owned by method test. + |The leakage occurred when trying to match the following types: | - |Note that capability f cannot be included in outer capture set 's3 which is owned by method test. + |Found: (f: java.io.FileOutputStream^'s2) ->'s3 () ->{f} Unit + |Required: java.io.FileOutputStream^ => () ->'s1 Unit | |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index bfa2cafce460..33b69e0e3fd6 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -34,10 +34,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:36:4 ------------------------------------------- 36 | (x: CanThrow[Exception]) => // error | ^ - |Found: (x: CT[Exception]^) ->'s3 () ->{x} Int - |Required: CT[Exception]^ => () ->'s4 Int + |Capability x outlives its scope: it leaks into outer capture set 's3 which is owned by value xx. + |The leakage occurred when trying to match the following types: | - |Note that capability x cannot be included in outer capture set 's4 which is owned by val xx. + |Found: (x: CT[Exception]^) ->'s4 () ->{x} Int + |Required: CT[Exception]^ => () ->'s3 Int | |where: => refers to a fresh root capability created in value xx when checking argument to parameter op of method handle | ^ refers to the universal root capability @@ -49,10 +50,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:48:2 ------------------------------------------- 48 | (x: CanThrow[Exception]) => // error | ^ - |Found: (x: CT[Exception]^) ->'s5 () ->{x} Int - |Required: CT[Exception]^ => () ->'s6 Int + |Capability x outlives its scope: it leaks into outer capture set 's5 which is owned by value global. + |The leakage occurred when trying to match the following types: | - |Note that capability x cannot be included in outer capture set 's6 which is owned by val global. + |Found: (x: CT[Exception]^) ->'s6 () ->{x} Int + |Required: CT[Exception]^ => () ->'s5 Int | |where: => refers to a fresh root capability created in value global when checking argument to parameter op of method handle | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index 71750865a9e7..51cbe5b334d1 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -1,10 +1,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:22:29 --------------------------------- 22 | val later = usingLogFile { f => () => f.write(0) } // error | ^^^^^^^^^^^^^^^^^^^^^ - |Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit - |Required: java.io.FileOutputStream^ => () ->'s3 Unit + |Capability f outlives its scope: it leaks into outer capture set 's1 which is owned by value later. + |The leakage occurred when trying to match the following types: | - |Note that capability f cannot be included in outer capture set 's3 which is owned by val later. + |Found: (f: java.io.FileOutputStream^'s2) ->'s3 () ->{f} Unit + |Required: java.io.FileOutputStream^ => () ->'s1 Unit | |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability @@ -13,10 +14,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:27:38 --------------------------------- 27 | private val later2 = usingLogFile { f => Cell(() => f.write(0)) } // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |Found: (f: java.io.FileOutputStream^'s4) ->'s5 Test2.Cell[() ->{f} Unit]^'s6 - |Required: java.io.FileOutputStream^ => Test2.Cell[() ->'s7 Unit]^'s8 + |Capability f outlives its scope: it leaks into outer capture set 's4 which is owned by value later2. + |The leakage occurred when trying to match the following types: | - |Note that capability f cannot be included in outer capture set 's7 which is owned by val later2. + |Found: (f: java.io.FileOutputStream^'s5) ->'s6 Test2.Cell[() ->{f} Unit]^'s7 + |Required: java.io.FileOutputStream^ => Test2.Cell[() ->'s4 Unit]^'s8 | |where: => refers to a fresh root capability created in value later2 when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability @@ -25,10 +27,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:43:33 --------------------------------- 43 | val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |Found: (f: java.io.OutputStream^'s9) ->'s10 Int ->{f} Unit - |Required: java.io.OutputStream^ => Int ->'s11 Unit + |Capability f outlives its scope: it leaks into outer capture set 's9 which is owned by value later. + |The leakage occurred when trying to match the following types: | - |Note that capability f cannot be included in outer capture set 's11 which is owned by val later. + |Found: (f: java.io.OutputStream^'s10) ->'s11 Int ->{f} Unit + |Required: java.io.OutputStream^ => Int ->'s9 Unit | |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingFile | ^ refers to the universal root capability @@ -37,10 +40,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:52:6 ---------------------------------- 52 | usingLogger(_, l => () => l.log("test"))) // error after checking mapping scheme | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |Found: (_$1: java.io.OutputStream^'s12) ->'s13 () ->{_$1} Unit - |Required: java.io.OutputStream^ => () ->'s14 Unit + |Capability _$1 outlives its scope: it leaks into outer capture set 's12 which is owned by value later. + |The leakage occurred when trying to match the following types: | - |Note that capability _$1 cannot be included in outer capture set 's14 which is owned by val later. + |Found: (_$1: java.io.OutputStream^'s13) ->'s14 () ->{_$1} Unit + |Required: java.io.OutputStream^ => () ->'s12 Unit | |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingFile | ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index 399869e4d96f..58a6708dc869 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -28,14 +28,15 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:36:10 ----------------------------------------- 36 | local { cap3 => // error | ^ - | Found: (cap3: CC^) ->'s1 String ->{cap3} String - | Required: CC^ -> String ->{cap3²} String + | Capability cap3 outlives its scope: it leaks into outer capture set {cap3²} which is owned by method test. + | The leakage occurred when trying to match the following types: | - | Note that capability cap3 cannot be included in outer capture set {cap3²} which is owned by method test. + | Found: (cap3: CC^) ->'s1 String ->{cap3} String + | Required: CC^ -> String ->{cap3²} String | - | where: ^ refers to the universal root capability - | cap3 is a reference to a value parameter - | cap3² is a parameter in an anonymous function in method test + | where: ^ refers to the universal root capability + | cap3 is a reference to a value parameter + | cap3² is a parameter in an anonymous function in method test 37 | def g(x: String): String = if cap3 == cap3 then "" else "a" 38 | g | From 7ca78b14d1645b2dbcb7fbd9f7ba32472cc94960 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 2 Nov 2025 18:26:06 +0100 Subject: [PATCH 5/7] Drop ExistentialSubsumesFailure Present the information as part of explaining an IncludeFailure instead. --- .../src/dotty/tools/dotc/cc/Capability.scala | 5 +- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 91 ++++++++----------- .../dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- .../captures/heal-tparam-cs.check | 16 ++-- tests/neg-custom-args/captures/i16226.check | 6 +- 5 files changed, 51 insertions(+), 69 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index fb6a8cff96e8..53e272a19ef4 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -786,12 +786,9 @@ object Capabilities: && prefixAllowsAddHidden && vs.addHidden(x.hiddenSet, y) case x: ResultCap => - val result = y match + y match case y: ResultCap => vs.unify(x, y) case _ => y.derivesFromShared - if !result then - TypeComparer.addErrorNote(CaptureSet.ExistentialSubsumesFailure(x, y)) - result case GlobalCap => y match case GlobalCap => true diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index b5c13cedfe80..51883ea305b8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1300,20 +1300,6 @@ object CaptureSet: /** A TypeMap that is the identity on capabilities */ trait IdentityCaptRefMap extends TypeMap - /** A value of this class is produced and added as a note to ccState - * when a subsumes check decides that an existential variable `ex` cannot be - * instantiated to the other capability `other`. - */ - case class ExistentialSubsumesFailure(val ex: ResultCap, val other: Capability) extends Note: - def render(using Context): String = - def reason = - if other.isTerminalCapability then "" - else " since that capability is not a `Sharable` capability" - i""" - | - |Note that the existential capture root in ${ex.originalBinder.resType} - |cannot subsume the capability $other$reason.""" - /** Failure indicating that `elem` cannot be included in `cs` */ case class IncludeFailure(cs: CaptureSet, elem: Capability, levelError: Boolean = false) extends Note, Showable: private var myTrace: List[CaptureSet] = cs :: Nil @@ -1342,44 +1328,47 @@ object CaptureSet: | |""" - def render(using Context): String = - def why = - val reasons = cs.elems.toList.collect: - case c: FreshCap if !c.acceptsLevelOf(elem) => - i"$elem${elem.levelOwner.qualString("in")} is not visible from $c${c.ccOwner.qualString("in")}" - case c: FreshCap if !elem.tryClassifyAs(c.hiddenSet.classifier) => - i"$c is classified as ${c.hiddenSet.classifier} but $elem is not" - if reasons.isEmpty then "" - else reasons.mkString("\nbecause ", "\nand ", "") - cs match - case cs: Var => - def ownerStr = - if !cs.description.isEmpty then "" else cs.owner.qualString("which is owned by") - if !cs.levelOK(elem) then - val outlivesStr = elem match - case ref: TermRef => i"${ref.symbol.maybeOwner.qualString("defined in")} outlives its scope:\n" - case _ => " outlives its scope: " - leading: - i"""Capability ${elem.showAsCapability}${outlivesStr}it leaks into outer capture set $cs$ownerStr""" - else if !elem.tryClassifyAs(cs.classifier) then - trailing: - i"""capability ${elem.showAsCapability} is not classified as ${cs.classifier}, therefore it - |cannot be included in capture set $cs of ${cs.classifier.name} elements""" - else if cs.isBadRoot(elem) then - elem match - case elem: FreshCap => - leading: - i"""Local capability ${elem.showAsCapability} created in ${elem.ccOwner} outlives its scope: - |It leaks into outer capture set $cs$ownerStr""" - case _ => - trailing: - i"universal capability ${elem.showAsCapability} cannot be included in capture set $cs" - else - trailing: - i"capability ${elem.showAsCapability} cannot be included in capture set $cs" - case _ => + def render(using Context): String = cs match + case cs: Var => + def ownerStr = + if !cs.description.isEmpty then "" else cs.owner.qualString("which is owned by") + if !cs.levelOK(elem) then + val outlivesStr = elem match + case ref: TermRef => i"${ref.symbol.maybeOwner.qualString("defined in")} outlives its scope:\n" + case _ => " outlives its scope: " + leading: + i"""Capability ${elem.showAsCapability}${outlivesStr}it leaks into outer capture set $cs$ownerStr""" + else if !elem.tryClassifyAs(cs.classifier) then + trailing: + i"""capability ${elem.showAsCapability} is not classified as ${cs.classifier}, therefore it + |cannot be included in capture set $cs of ${cs.classifier.name} elements""" + else if cs.isBadRoot(elem) then + elem match + case elem: FreshCap => + leading: + i"""Local capability ${elem.showAsCapability} created in ${elem.ccOwner} outlives its scope: + |It leaks into outer capture set $cs$ownerStr""" + case _ => + trailing: + i"universal capability ${elem.showAsCapability} cannot be included in capture set $cs" + else trailing: - i"capability ${elem.showAsCapability} is not included in capture set $cs$why" + i"capability ${elem.showAsCapability} cannot be included in capture set $cs" + case _ => + def why = + val reasons = cs.elems.toList.collect: + case c: FreshCap if !c.acceptsLevelOf(elem) => + i"$elem${elem.levelOwner.qualString("in")} is not visible from $c${c.ccOwner.qualString("in")}" + case c: FreshCap if !elem.tryClassifyAs(c.hiddenSet.classifier) => + i"$c is classified as ${c.hiddenSet.classifier} but ${elem.showAsCapability} is not" + case c: ResultCap if !c.subsumes(elem) => + val toAdd = if elem.isTerminalCapability then "" else " since that capability is not a SharedCapability" + i"$c, which is existentially bound in ${c.originalBinder.resType}, cannot subsume ${elem.showAsCapability}$toAdd" + if reasons.isEmpty then "" + else reasons.mkString("\nbecause ", "\nand ", "") + + trailing: + i"capability ${elem.showAsCapability} is not included in capture set $cs$why" override def toText(printer: Printer): Text = inContext(printer.printerContext): diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 5464f9ea2df7..a08e47b9995d 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -21,7 +21,7 @@ import util.chaining.tap import transform.{Recheck, PreRecheck, CapturedVars} import Recheck.* import scala.collection.mutable -import CaptureSet.{withCaptureSetsExplained, IncludeFailure, ExistentialSubsumesFailure, MutAdaptFailure} +import CaptureSet.{withCaptureSetsExplained, IncludeFailure, MutAdaptFailure} import CCState.* import StdNames.nme import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind} diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.check b/tests/neg-custom-args/captures/heal-tparam-cs.check index 7a36f9654c57..d4077b27e882 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.check +++ b/tests/neg-custom-args/captures/heal-tparam-cs.check @@ -15,17 +15,15 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:15:13 ------------------------------- 15 | localCap { c => // error | ^ - | Found: (x$0: Capp^'s4) ->'s5 () ->{x$0} Unit - | Required: (c: Capp^) -> () => Unit + |Found: (x$0: Capp^'s4) ->'s5 () ->{x$0} Unit + |Required: (c: Capp^) -> () => Unit | - | Note that capability x$0 is not included in capture set {cap}. + |Note that capability x$0 is not included in capture set {cap} + |because cap, which is existentially bound in () =>² Unit, cannot subsume x$0 since that capability is not a SharedCapability. | - | Note that the existential capture root in () =>² Unit - | cannot subsume the capability (x$0 : Capp^'s4) since that capability is not a `Sharable` capability. - | - | where: => refers to a root capability associated with the result type of (c: Capp^): () => Unit - | =>² and ^ refer to the universal root capability - | cap is a root capability associated with the result type of (x$0: Capp^'s4): () ->{x$0} Unit + |where: => refers to a root capability associated with the result type of (c: Capp^): () => Unit + | =>² and ^ refer to the universal root capability + | cap is a root capability associated with the result type of (x$0: Capp^'s4): () ->{x$0} Unit 16 | (c1: Capp^) => () => { c1.use() } 17 | } | diff --git a/tests/neg-custom-args/captures/i16226.check b/tests/neg-custom-args/captures/i16226.check index 479219de5100..860f80ade5d5 100644 --- a/tests/neg-custom-args/captures/i16226.check +++ b/tests/neg-custom-args/captures/i16226.check @@ -22,10 +22,8 @@ | LazyRef[B^'s19]{val elem: () => B^'s20}^{f1, ref1} |Required: (ref: LazyRef[A]^{io}, f: A =>² B) =>³ LazyRef[B]^ | - |Note that capability f1 is not included in capture set {cap}. - | - |Note that the existential capture root in LazyRef[B]^² - |cannot subsume the capability (f1 : A^'s15 ->'s16 B^'s17) since that capability is not a `Sharable` capability. + |Note that capability f1 is not included in capture set {cap} + |because cap, which is existentially bound in LazyRef[B]^², cannot subsume f1 since that capability is not a SharedCapability. | |where: => and cap refer to a root capability associated with the result type of (ref1: LazyRef[A^'s11]{val elem: () ->'s12 A^'s13}^'s14, f1: A^'s15 ->'s16 B^'s17): | LazyRef[B^'s19]{val elem: () => B^'s20}^{f1, ref1} From 744a3d41604306b51760fcfc0de6aeae1952e971 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 3 Nov 2025 11:38:21 +0100 Subject: [PATCH 6/7] Try to pick the most informative note to display --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 18 ++ .../dotty/tools/dotc/core/TypeComparer.scala | 10 +- .../dotty/tools/dotc/reporting/Message.scala | 9 +- .../neg-custom-args/captures/cc-poly-2.check | 6 +- tests/neg-custom-args/captures/i23431.check | 16 +- tests/neg-custom-args/captures/i24137.check | 2 +- .../neg-custom-args/captures/outer-var.check | 12 +- .../captures/scope-extrude-mut.check | 8 +- .../captures/scope-extrusions.check | 163 ++++++++++++++++++ .../captures/scope-extrusions.scala | 33 ++++ 10 files changed, 249 insertions(+), 28 deletions(-) create mode 100644 tests/neg-custom-args/captures/scope-extrusions.check create mode 100644 tests/neg-custom-args/captures/scope-extrusions.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 51883ea305b8..78af0e6c57c8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1317,6 +1317,18 @@ object CaptureSet: case _ => false + /** An include failure F1 covers another include failure F2 unless F2 + * strictly subsumes F1, which means they describe the same capture sets + * and the element in F2 is more specific than the element in F1. + */ + override def covers(other: Note)(using Context) = other match + case other @ IncludeFailure(cs1, elem1, _) => + val strictlySubsumes = + cs.elems == cs1.elems + && elem1.singletonCaptureSet.mightSubcapture(elem.singletonCaptureSet) + !strictlySubsumes + case _ => false + def trailing(msg: String)(using Context): String = i""" | @@ -1387,6 +1399,7 @@ object CaptureSet: * @param hi the upper type of the orginal type comparison, or NoType if not known */ case class MutAdaptFailure(cs: CaptureSet, lo: Type = NoType, hi: Type = NoType) extends Note: + def render(using Context): String = def ofType(tp: Type) = if tp.exists then i"of the mutable type $tp" else "of a mutable type" i""" @@ -1394,6 +1407,11 @@ object CaptureSet: |Note that $cs is an exclusive capture set ${ofType(hi)}, |it cannot subsume a read-only capture set ${ofType(lo)}.""" + // Show only one failure of this kind + override def covers(other: Note)(using Context) = + other.isInstanceOf[MutAdaptFailure] + end MutAdaptFailure + /** A VarState serves as a snapshot mechanism that can undo * additions of elements or super sets if an operation fails */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e5292968b249..77fdc24a01cc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3324,12 +3324,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def reduceMatchWith[T](op: MatchReducer => T)(using Context): T = inSubComparer(matchReducer)(op) - /** Add given note, provided there is not yet an error note with - * the same class as `note`. + /** Add given note, provided there is not yet an error note that covers `note` + * If the new note is added, any existing note covered by it is removed first. */ - def addErrorNote(note: Note): Unit = - if errorNotes.forall(_._2.getClass != note.getClass) then - errorNotes = (recCount, note) :: errorNotes + def addErrorNote(note: Note)(using Context): Unit = + if !errorNotes.exists(_._2.covers(note)) then + errorNotes = (recCount, note) :: errorNotes.filterConserve(n => !note.covers(n._2)) assert(maxErrorLevel <= recCount) maxErrorLevel = recCount diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 33ce11f45425..73e9412830c4 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -43,7 +43,7 @@ object Message: /** A note can produce an added string for an error message */ abstract class Note: - + /** Should the note be shown before the actual message or after? * Default is after. */ @@ -52,6 +52,13 @@ object Message: /** The note rendered as part of an error message */ def render(using Context): String + /** If note N1 covers note N2 then N1 and N2 won't be shown together in + * an error message. Instead we show the note that's strictly better in terms + * of the "covers" partial ordering, or, if there's no strict wionner, the first + * added note. + */ + def covers(other: Note)(using Context): Boolean = false + object Note: def apply(msg: Context ?=> String) = new Note: def render(using Context) = msg diff --git a/tests/neg-custom-args/captures/cc-poly-2.check b/tests/neg-custom-args/captures/cc-poly-2.check index 77cb6e369c2e..62be3ba87085 100644 --- a/tests/neg-custom-args/captures/cc-poly-2.check +++ b/tests/neg-custom-args/captures/cc-poly-2.check @@ -4,9 +4,9 @@ | Found: (d : Test.D^) | Required: Test.D^{c1} | - | Note that capability cap is not included in capture set {c1}. + | Note that capability d is not included in capture set {c1}. | - | where: ^ and cap refer to a fresh root capability in the type of value d + | where: ^ refers to a fresh root capability in the type of value d | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-2.scala:16:20 ------------------------------------ @@ -15,6 +15,6 @@ | Found: (x : Test.D^{d}) | Required: Test.D^{c1} | - | Note that capability d is not included in capture set {c1}. + | Note that capability x is not included in capture set {c1}. | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i23431.check b/tests/neg-custom-args/captures/i23431.check index 10885339334d..3c8f09cdc1b9 100644 --- a/tests/neg-custom-args/captures/i23431.check +++ b/tests/neg-custom-args/captures/i23431.check @@ -4,11 +4,11 @@ | Found: (io : IO^) | Required: IO^² | - | Note that capability cap is not included in capture set {cap²} - | because cap in method setIO is not visible from cap² in variable myIO. + | Note that capability io is not included in capture set {cap} + | because (io : IO^) in method setIO is not visible from cap in variable myIO. | - | where: ^ and cap refer to a fresh root capability in the type of parameter io - | ^² and cap² refer to a fresh root capability in the type of variable myIO + | where: ^ refers to a fresh root capability in the type of parameter io + | ^² and cap refer to a fresh root capability in the type of variable myIO | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i23431.scala:11:13 --------------------------------------- @@ -17,11 +17,11 @@ | Found: (io2 : IO^) | Required: IO^² | - | Note that capability cap is not included in capture set {cap²} - | because cap in an enclosing function is not visible from cap² in variable myIO. + | Note that capability io2 is not included in capture set {cap} + | because (io2 : IO^) in an enclosing function is not visible from cap in variable myIO. | - | where: ^ and cap refer to a fresh root capability in the type of parameter io2 - | ^² and cap² refer to a fresh root capability in the type of variable myIO + | where: ^ refers to a fresh root capability in the type of parameter io2 + | ^² and cap refer to a fresh root capability in the type of variable myIO | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i23431.scala:12:12 --------------------------------------- diff --git a/tests/neg-custom-args/captures/i24137.check b/tests/neg-custom-args/captures/i24137.check index 3f7d14144c32..60919d7b7591 100644 --- a/tests/neg-custom-args/captures/i24137.check +++ b/tests/neg-custom-args/captures/i24137.check @@ -4,7 +4,7 @@ | Found: (b : B{val elem1: A^{a}; val elem2: A^{a}}^{cap, a}) | Required: B^{async} | - | Note that capability cap is not included in capture set {async}. + | Note that capability b is not included in capture set {async}. | | where: cap is a fresh root capability in the type of value b | diff --git a/tests/neg-custom-args/captures/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index 45489ab0ff5b..ae9f6f3619a0 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -4,11 +4,11 @@ | Found: (q : () => Unit) | Required: () ->{p, q²} Unit | - | Note that capability cap is not included in capture set {p, q²}. + | Note that capability q is not included in capture set {p, q²}. | - | where: => and cap refer to a fresh root capability in the type of parameter q - | q is a parameter in method inner - | q² is a parameter in method test + | where: => refers to a fresh root capability in the type of parameter q + | q is a parameter in method inner + | q² is a parameter in method test | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:13:9 ------------------------------------- @@ -39,9 +39,9 @@ | Found: (q : () => Unit) | Required: () ->{p} Unit | - | Note that capability cap cannot be included in capture set {p} of variable y. + | Note that capability q cannot be included in capture set {p} of variable y. | - | where: => and cap refer to a fresh root capability in the type of parameter q + | where: => refers to a fresh root capability in the type of parameter q | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/outer-var.scala:17:57 --------------------------------------------------------- diff --git a/tests/neg-custom-args/captures/scope-extrude-mut.check b/tests/neg-custom-args/captures/scope-extrude-mut.check index 5f1671b19a47..905becd123e4 100644 --- a/tests/neg-custom-args/captures/scope-extrude-mut.check +++ b/tests/neg-custom-args/captures/scope-extrude-mut.check @@ -4,10 +4,10 @@ | Found: (a1 : A^) | Required: A^² | - | Note that capability cap is not included in capture set {cap²} - | because cap in method b is not visible from cap² in variable a. + | Note that capability a1 is not included in capture set {cap} + | because (a1 : A^) in method b is not visible from cap in variable a. | - | where: ^ and cap refer to a fresh root capability classified as Mutable in the type of value a1 - | ^² and cap² refer to a fresh root capability classified as Mutable in the type of variable a + | where: ^ refers to a fresh root capability classified as Mutable in the type of value a1 + | ^² and cap refer to a fresh root capability classified as Mutable in the type of variable a | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/scope-extrusions.check b/tests/neg-custom-args/captures/scope-extrusions.check new file mode 100644 index 000000000000..e20aa8995047 --- /dev/null +++ b/tests/neg-custom-args/captures/scope-extrusions.check @@ -0,0 +1,163 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:9:8 ------------------------------- +9 | v = x // error + | ^ + | Found: (x : IO) + | Required: IO^ + | + | Note that capability x is not included in capture set {cap} + | because (x : IO) in method f1 is not visible from cap in variable v. + | + | where: ^ and cap refer to a fresh root capability classified as SharedCapability in the type of variable v + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:10:8 ------------------------------ +10 | w = g // error + | ^ + | Found: () ->{x} Unit + | Required: () => Unit + | + | Note that capability x is not included in capture set {cap} + | because (x : IO) in method f1 is not visible from cap in variable w. + | + | where: => and cap refer to a fresh root capability in the type of variable w + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:19:11 ----------------------------- +19 | withFile(io => io) // error + | ^^^^^^^^ + |Capability io outlives its scope: it leaks into outer capture set 's1 which is owned by method test. + |The leakage occurred when trying to match the following types: + | + |Found: (io: IO^'s2) ->'s3 IO^{io} + |Required: IO^ => IO^'s1 + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method withFile + | ^ refers to the universal root capability + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:20:11 ----------------------------- +20 | withFile(id) // error + | ^^ + |Capability x outlives its scope: it leaks into outer capture set 's4 which is owned by method test. + |The leakage occurred when trying to match the following types: + | + |Found: (x: IO^) ->'s5 IO^{x} + |Required: IO^ => IO^'s4 + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method withFile + | ^ refers to the universal root capability + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:21:11 ----------------------------- +21 | withFile(x => id(x)) // error + | ^^^^^^^^^^ + |Capability x outlives its scope: it leaks into outer capture set 's6 which is owned by method test. + |The leakage occurred when trying to match the following types: + | + |Found: (x: IO^'s7) ->'s8 IO^{x} + |Required: IO^ => IO^'s6 + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method withFile + | ^ refers to the universal root capability + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:22:11 ----------------------------- +22 | withFile(id2) // error, note mentions cap since we never have a more specific include failure + | ^^^ + |Capability cap outlives its scope: it leaks into outer capture set 's9 which is owned by method test. + |The leakage occurred when trying to match the following types: + | + |Found: (x: IO^) ->'s10 IO^² + |Required: IO^ => IO^'s9 + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method withFile + | ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: IO^): IO^² + | cap is a root capability associated with the result type of (x: IO^): IO^'s9 + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:23:11 ----------------------------- +23 | withFile(x => id2(x)) // error, note mentions cap since we never have a more specific include failure + | ^^^^^^^^^^^ + |Capability cap outlives its scope: it leaks into outer capture set 's11 which is owned by method test. + |The leakage occurred when trying to match the following types: + | + |Found: (x: IO^'s12) ->'s13 IO^ + |Required: IO^² => IO^'s11 + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method withFile + | ^ refers to a root capability associated with the result type of (x: IO^'s12): IO^ + | ^² refers to the universal root capability + | cap is a root capability associated with the result type of (x: IO^²): IO^'s11 + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:24:11 ----------------------------- +24 | withFile(identity) // error, note mentions cap since we never have a more specific include failure + | ^^^^^^^^ + |Capability cap outlives its scope: it leaks into outer capture set 's14 which is owned by method test. + |The leakage occurred when trying to match the following types: + | + |Found: (x: IO^'s15) ->'s16 IO^'s17 + |Required: IO^ => IO^'s14 + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method withFile + | ^ refers to the universal root capability + | cap is a root capability associated with the result type of (x: IO^): IO^'s14 + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:25:11 ----------------------------- +25 | withFile(x => identity(x)) // error, note mentions cap since we never have a more specific include failure + | ^^^^^^^^^^^^^^^^ + |Capability cap outlives its scope: it leaks into outer capture set 's18 which is owned by method test. + |The leakage occurred when trying to match the following types: + | + |Found: (x: IO^'s19) ->'s20 IO^'s21 + |Required: IO^ => IO^'s18 + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method withFile + | ^ refers to the universal root capability + | cap is a root capability associated with the result type of (x: IO^): IO^'s18 + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:27:12 ----------------------------- +27 | withFile: io => // error + | ^ + |Capability io outlives its scope: it leaks into outer capture set 's22 which is owned by method test. + |The leakage occurred when trying to match the following types: + | + |Found: (io: IO^'s23) ->'s24 () ->{io} Unit + |Required: IO^ => () ->'s22 Unit + | + |where: => refers to a fresh root capability created in method test when checking argument to parameter op of method withFile + | ^ refers to the universal root capability +28 | () => println(io) + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:30:21 ----------------------------- +30 | val f2: IO => IO = (x: IO) => x // error + | ^^^^^^^^^^^^ + | Found: (x: IO^) ->'s25 IO^{x} + | Required: IO^ => IO^² + | + | Note that capability x is not included in capture set {cap} + | because (x : IO) is not visible from cap in value f2. + | + | where: => refers to a fresh root capability in the type of value f2 + | ^ refers to the universal root capability + | ^² and cap refer to a fresh root capability classified as SharedCapability in the type of value f2 + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrusions.scala:32:21 ----------------------------- +32 | val f4: IO => IO = f3 // error + | ^^ + | Found: (f3 : (x$0: IO) ->{} IO^{x$0}) + | Required: IO^ => IO^² + | + | Note that capability x$0 is not included in capture set {cap} + | because (x$0 : IO) is not visible from cap in value f4. + | + | where: => refers to a fresh root capability in the type of value f4 + | ^ refers to the universal root capability + | ^² and cap refer to a fresh root capability classified as SharedCapability in the type of value f4 + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/scope-extrusions.scala b/tests/neg-custom-args/captures/scope-extrusions.scala new file mode 100644 index 000000000000..277a8df6cfbb --- /dev/null +++ b/tests/neg-custom-args/captures/scope-extrusions.scala @@ -0,0 +1,33 @@ +// Tests quality of error messages +class IO extends caps.SharedCapability + +def test(io: IO): Unit = + var v: IO = io + var w: () => Unit = () => () + def f1(x: IO) = + def g() = println(x) + v = x // error + w = g // error + + def withFile[T](op: IO => T): T = + val io = IO() + op(io) + + def id(x: IO): x.type = x + def id2(x: IO): IO = x + + withFile(io => io) // error + withFile(id) // error + withFile(x => id(x)) // error + withFile(id2) // error, note mentions cap since we never have a more specific include failure + withFile(x => id2(x)) // error, note mentions cap since we never have a more specific include failure + withFile(identity) // error, note mentions cap since we never have a more specific include failure + withFile(x => identity(x)) // error, note mentions cap since we never have a more specific include failure + + withFile: io => // error + () => println(io) + + val f2: IO => IO = (x: IO) => x // error + val f3 = (x: IO) => x + val f4: IO => IO = f3 // error + From 2ea4ecd1f2223032e1bd751d874dbbd852826805 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 4 Nov 2025 17:01:00 +0100 Subject: [PATCH 7/7] Rename prefix --> showAsPrefix --- compiler/src/dotty/tools/dotc/cc/CaptureSet.scala | 2 +- compiler/src/dotty/tools/dotc/reporting/Message.scala | 2 +- compiler/src/dotty/tools/dotc/reporting/messages.scala | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 78af0e6c57c8..8cc30b03a389 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1310,7 +1310,7 @@ object CaptureSet: res.myTrace = cs1 :: this.myTrace res - override def prefix(using Context) = cs match + override def showAsPrefix(using Context) = cs match case cs: Var => !cs.levelOK(elem) || cs.isBadRoot(elem) && elem.isInstanceOf[FreshCap] diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 73e9412830c4..f0bb57652fd9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -47,7 +47,7 @@ object Message: /** Should the note be shown before the actual message or after? * Default is after. */ - def prefix(using Context): Boolean = false + def showAsPrefix(using Context): Boolean = false /** The note rendered as part of an error message */ def render(using Context): String diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 3aebf257b5c5..c281cbab544e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -343,7 +343,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre mapOver(tp) case _ => mapOver(tp) - val preface = notes.filter(_.prefix).map(_.render).mkString + val preface = notes.filter(_.showAsPrefix).map(_.render).mkString val found1 = reported(found) reported.setVariance(-1) val expected1 = reported(expected) @@ -359,7 +359,7 @@ class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tre def importSuggestions = if expected.isTopType || found.isBottomType then "" else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected)) - notes.filter(!_.prefix).map(_.render).mkString ++ super.msgPostscript ++ importSuggestions + notes.filter(!_.showAsPrefix).map(_.render).mkString ++ super.msgPostscript ++ importSuggestions override def explain(using Context) = val treeStr = inTree.map(x => s"\nTree:\n\n${x.show}\n").getOrElse("")