From 39b2f37e36c1ebf1b6e2703c312ca407ac20bbdd Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 5 Mar 2025 16:44:14 +0100 Subject: [PATCH 1/4] Remove unnecessary `^` in capture set syntax. --- compiler/src/dotty/tools/dotc/core/Mode.scala | 10 ++++++- .../dotty/tools/dotc/parsing/Parsers.scala | 10 ++----- .../dotty/tools/dotc/typer/Applications.scala | 4 ++- .../src/dotty/tools/dotc/typer/Typer.scala | 12 ++++++++ .../captures/capset-bound.scala | 2 +- .../captures/capset-bound2.scala | 3 +- .../captures/capset-members.scala | 3 +- .../captures/capture-parameters.scala | 9 +++--- .../captures/capture-poly.scala | 8 ++--- .../captures/capture-vars-subtyping.scala | 26 ++++++++-------- .../captures/capture-vars-subtyping2.scala | 30 +++++++++---------- .../neg-custom-args/captures/cc-poly-1.scala | 2 +- .../neg-custom-args/captures/cc-poly-2.scala | 2 +- .../captures/cc-poly-source.scala | 6 ++-- tests/neg-custom-args/captures/i21313.scala | 2 +- tests/neg-custom-args/captures/i21347.scala | 2 +- tests/neg-custom-args/captures/i21868.scala | 4 +-- tests/neg-custom-args/captures/i21868b.scala | 6 ++-- .../captures/polyCaptures.check | 4 +-- .../captures/polyCaptures.scala | 6 ++-- .../neg-custom-args/captures/use-capset.check | 10 +++---- .../neg-custom-args/captures/use-capset.scala | 6 ++-- .../pos-custom-args/captures/cc-poly-1.scala | 6 ++-- .../captures/cc-poly-source-capability.scala | 6 ++-- .../captures/cc-poly-varargs.scala | 8 ++--- .../captures/gears-problem-poly.scala | 8 ++--- tests/pos-custom-args/captures/i21313.scala | 8 ++--- tests/pos-custom-args/captures/i21347.scala | 2 +- tests/pos-custom-args/captures/i21507.scala | 8 ++--- .../pos-custom-args/captures/setup/a_1.scala | 2 +- .../pos-custom-args/captures/setup/b_1.scala | 2 +- .../pos-custom-args/captures/setup/b_2.scala | 2 +- 32 files changed, 116 insertions(+), 103 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 6fd76e37977d..e0ab4571667c 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -91,8 +91,16 @@ object Mode { */ val ImplicitExploration: Mode = newMode(12, "ImplicitExploration") + /** We are currently inside a capture set. + * A term name could be a capture variable, so we need to + * check that it is valid to use as type name. + * Since this mode is only used during annotation typing, + * we can reuse the value of `ImplicitExploration` to save bits. + */ + val InCaptureSet: Mode = newMode(13, "InCaptureSet") + /** We are currently unpickling Scala2 info */ - val Scala2Unpickling: Mode = newMode(13, "Scala2Unpickling") + val Scala2Unpickling: Mode = newMode(14, "Scala2Unpickling") /** Signifies one of two possible situations: * 1. We are currently checking bounds to be non-empty, so we should not diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9853107ca8fc..7d695e2291f7 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1591,8 +1591,8 @@ object Parsers { case _ => None } - /** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [`.` rd] - * | [ { SimpleRef `.` } SimpleRef `.` ] id `^` + /** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] + * | [ { SimpleRef `.` } SimpleRef `.` ] id */ def captureRef(): Tree = @@ -1611,12 +1611,6 @@ object Parsers { in.nextToken() derived(reachRef, nme.CC_READONLY) else reachRef - else if isIdent(nme.UPARROW) then - in.nextToken() - atSpan(startOffset(ref)): - convertToTypeId(ref) match - case ref: RefTree => makeCapsOf(ref) - case ref => ref else ref recur(simpleRef()) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 07ddb37d93b7..3cb080428f2c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1116,7 +1116,9 @@ trait Applications extends Compatibility { val fun2 = Applications.retypeSignaturePolymorphicFn(fun1, methType) simpleApply(fun2, proto) case funRef: TermRef => - val app = ApplyTo(tree, fun1, funRef, proto, pt) + // println(i"typedApply: $funRef, ${tree.args}, ${funRef.symbol.maybeOwner.isRetains}") + val applyCtx = if funRef.symbol.maybeOwner.isRetains then ctx.addMode(Mode.InCaptureSet) else ctx + val app = ApplyTo(tree, fun1, funRef, proto, pt)(using applyCtx) convertNewGenericArray( widenEnumCase( postProcessByNameArgs(funRef, app).computeNullable(), diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 428568ecc795..fc6721363b52 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -715,6 +715,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && ctx.owner.owner.unforcedDecls.lookup(tree.name).exists then // we are in the arguments of a this(...) constructor call errorTree(tree, em"$tree is not accessible from constructor arguments") + else if ctx.mode.is(Mode.InCaptureSet) then + // If we are in a capture set and the identifier is not a term name, + // try to type it with the same name but as a type + typed(untpd.makeCapsOf(untpd.cpy.Ident(tree)(name.toTypeName)), pt) else errorTree(tree, MissingIdent(tree, kind, name, pt)) end typedIdent @@ -920,6 +924,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typedCBSelect(tree0, pt, qual) else EmptyTree + // Otherwise, if we are in a capture set, try to type it as a capture variable + // reference (as selecting a type name). + def trySelectTypeInCaptureSet() = + if ctx.mode.is(Mode.InCaptureSet) && tree0.name.isTypeName then + typedSelectWithAdapt(untpd.cpy.Select(tree0)(qual, tree0.name.toTypeName), pt, qual) + else EmptyTree + // Otherwise, report an error def reportAnError() = assignType(tree, @@ -941,6 +952,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer .orElse(tryDynamic()) .orElse(trySelectable()) .orElse(tryCBCompanion()) + .orElse(trySelectTypeInCaptureSet()) .orElse(reportAnError()) end typedSelectWithAdapt diff --git a/tests/neg-custom-args/captures/capset-bound.scala b/tests/neg-custom-args/captures/capset-bound.scala index c00f61240dea..80ee3804776f 100644 --- a/tests/neg-custom-args/captures/capset-bound.scala +++ b/tests/neg-custom-args/captures/capset-bound.scala @@ -5,7 +5,7 @@ class IO case class File(io: IO^) def test(io1: IO^, io2: IO^) = - def f[C >: CapSet^{io1} <: CapSet^](file: File^{C^}) = ??? + def f[C >: CapSet^{io1} <: CapSet^](file: File^{C}) = ??? val f1: File^{io1} = ??? val f2: File^{io2} = ??? val f3: File^{io1, io2} = ??? diff --git a/tests/neg-custom-args/captures/capset-bound2.scala b/tests/neg-custom-args/captures/capset-bound2.scala index 679606f0e43c..4cee6b244de7 100644 --- a/tests/neg-custom-args/captures/capset-bound2.scala +++ b/tests/neg-custom-args/captures/capset-bound2.scala @@ -2,7 +2,7 @@ import caps.* class IO -def f[C^](io: IO^{C^}) = ??? +def f[C^](io: IO^{C}) = ??? def test = f[CapSet](???) @@ -10,4 +10,3 @@ def test = f[CapSet^](???) f[Nothing](???) // error f[String](???) // error - \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members.scala b/tests/neg-custom-args/captures/capset-members.scala index 540216852a43..44720b68ff84 100644 --- a/tests/neg-custom-args/captures/capset-members.scala +++ b/tests/neg-custom-args/captures/capset-members.scala @@ -3,7 +3,7 @@ import caps.* trait Abstract[X^]: type C >: X <: CapSet^ // Don't test the return type using Unit, because it is a pure type. - def boom(): AnyRef^{C^} + def boom(): AnyRef^{C} class Concrete extends Abstract[CapSet^{}]: type C = CapSet^{} @@ -27,4 +27,3 @@ class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: def boom(): AnyRef^{b} = b // error - \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capture-parameters.scala b/tests/neg-custom-args/captures/capture-parameters.scala index d59305ae0cb8..d9288a062ded 100644 --- a/tests/neg-custom-args/captures/capture-parameters.scala +++ b/tests/neg-custom-args/captures/capture-parameters.scala @@ -2,8 +2,7 @@ import caps.* class C -def test[X^, Y^, Z >: X <: Y](x: C^{X^}, y: C^{Y^}, z: C^{Z^}) = - val x2z: C^{Z^} = x - val z2y: C^{Y^} = z - val x2y: C^{Y^} = x // error - \ No newline at end of file +def test[X^, Y^, Z >: X <: Y](x: C^{X}, y: C^{Y}, z: C^{Z}) = + val x2z: C^{Z} = x + val z2y: C^{Y} = z + val x2y: C^{Y} = x // error diff --git a/tests/neg-custom-args/captures/capture-poly.scala b/tests/neg-custom-args/captures/capture-poly.scala index 88989b418726..9276eab0342f 100644 --- a/tests/neg-custom-args/captures/capture-poly.scala +++ b/tests/neg-custom-args/captures/capture-poly.scala @@ -5,8 +5,8 @@ trait Foo extends Capability trait CaptureSet: type C >: CapSet <: CapSet^ -def capturePoly[C^](a: Foo^{C^}): Foo^{C^} = a -def capturePoly2(c: CaptureSet)(a: Foo^{c.C^}): Foo^{c.C^} = a +def capturePoly[C^](a: Foo^{C}): Foo^{C} = a +def capturePoly2(c: CaptureSet)(a: Foo^{c.C}): Foo^{c.C} = a def test = val x: Foo^ = ??? @@ -15,8 +15,8 @@ def test = object X extends CaptureSet: type C = CapSet^{x} - val z1: Foo^{X.C^} = x - val z2: Foo^{X.C^} = y // error + val z1: Foo^{X.C} = x + val z2: Foo^{X.C} = y // error val z3: Foo^{x} = capturePoly(x) val z4: Foo^{x} = capturePoly(y) // error diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping.scala b/tests/neg-custom-args/captures/capture-vars-subtyping.scala index 1986a0aa33fc..c0356bb0873f 100644 --- a/tests/neg-custom-args/captures/capture-vars-subtyping.scala +++ b/tests/neg-custom-args/captures/capture-vars-subtyping.scala @@ -3,32 +3,32 @@ import caps.* def test[C^] = val a: C = ??? - val b: CapSet^{C^} = a + val b: CapSet^{C} = a val c: C = b - val d: CapSet^{C^, c} = a + val d: CapSet^{C, c} = a // TODO: make "CapSet-ness" of type variables somehow contagious? // Then we don't have to spell out the bounds explicitly... def testTrans[C^, D >: CapSet <: C, E >: CapSet <: D, F >: C <: CapSet^] = val d1: D = ??? - val d2: CapSet^{D^} = d1 + val d2: CapSet^{D} = d1 val d3: D = d2 val e1: E = ??? - val e2: CapSet^{E^} = e1 + val e2: CapSet^{E} = e1 val e3: E = e2 val d4: D = e1 val c1: C = d1 val c2: C = e1 val f1: F = c1 - val d_e_f1: CapSet^{D^,E^,F^} = d1 - val d_e_f2: CapSet^{D^,E^,F^} = e1 - val d_e_f3: CapSet^{D^,E^,F^} = f1 + val d_e_f1: CapSet^{D,E,F} = d1 + val d_e_f2: CapSet^{D,E,F} = e1 + val d_e_f3: CapSet^{D,E,F} = f1 val f2: F = d_e_f1 val c3: C = d_e_f1 // error val c4: C = f1 // error val e4: E = f1 // error val e5: E = d1 // error - val c5: CapSet^{C^} = e1 + val c5: CapSet^{C} = e1 trait A[+T] @@ -37,12 +37,12 @@ trait B[-C] def testCong[C^, D^] = val a: A[C] = ??? - val b: A[CapSet^{C^}] = a - val c: A[CapSet^{D^}] = a // error - val d: A[CapSet^{C^,D^}] = a + val b: A[CapSet^{C}] = a + val c: A[CapSet^{D}] = a // error + val d: A[CapSet^{C,D}] = a val e: A[C] = d // error val f: B[C] = ??? - val g: B[CapSet^{C^}] = f + val g: B[CapSet^{C}] = f val h: B[C] = g - val i: B[CapSet^{C^,D^}] = h // error + val i: B[CapSet^{C,D}] = h // error val j: B[C] = i diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala index 205451ee41ed..0a3d776227cb 100644 --- a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala +++ b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala @@ -10,32 +10,32 @@ trait BoundsTest: def testTransMixed[A^, B >: CapSet <: A, - C >: CapSet <: CapSet^{B^}, + C >: CapSet <: CapSet^{B}, D >: CapSet <: C, - E >: CapSet <: CapSet^{D^}, - F >: CapSet <: CapSet^{A^,b}, - X >: CapSet <: CapSet^{F^,D^}, - Y >: CapSet^{F^} <: CapSet^{F^,A^,b}, - Z >: CapSet^{b} <: CapSet^{b,Y^}] = + E >: CapSet <: CapSet^{D}, + F >: CapSet <: CapSet^{A,b}, + X >: CapSet <: CapSet^{F,D}, + Y >: CapSet^{F} <: CapSet^{F,A,b}, + Z >: CapSet^{b} <: CapSet^{b,Y}] = val e: E = ??? - val e2: CapSet^{E^} = e + val e2: CapSet^{E} = e val ed: D = e - val ed2: CapSet^{D^} = e + val ed2: CapSet^{D} = e val ec: C = e - val ec2: CapSet^{C^} = e + val ec2: CapSet^{C} = e val eb: B = e - val eb2: CapSet^{B^} = e + val eb2: CapSet^{B} = e val ea: A = e - val ea2: CapSet^{A^} = e + val ea2: CapSet^{A} = e val ex: X = e // error - val ex2: CapSet^{X^} = e // error + val ex2: CapSet^{X} = e // error val f: F = ??? - val f2: CapSet^{F^} = f + val f2: CapSet^{F} = f val y: Y = f - val y2: CapSet^{Y^} = f + val y2: CapSet^{Y} = f val cb: CapSet^{b} = ??? val z: Z = cb - val z2: CapSet^{Z^} = cb + val z2: CapSet^{Z} = cb def callTransMixed = val x, y, z: Bar^ = ??? diff --git a/tests/neg-custom-args/captures/cc-poly-1.scala b/tests/neg-custom-args/captures/cc-poly-1.scala index 580b124bc8f3..b205b9b25246 100644 --- a/tests/neg-custom-args/captures/cc-poly-1.scala +++ b/tests/neg-custom-args/captures/cc-poly-1.scala @@ -6,7 +6,7 @@ object Test: class C extends Capability class D - def f[X^](x: D^{X^}): D^{X^} = x + def f[X^](x: D^{X}): D^{X} = x def test(c1: C, c2: C) = f[Any](D()) // error diff --git a/tests/neg-custom-args/captures/cc-poly-2.scala b/tests/neg-custom-args/captures/cc-poly-2.scala index c9249ba59437..8fb590f4f769 100644 --- a/tests/neg-custom-args/captures/cc-poly-2.scala +++ b/tests/neg-custom-args/captures/cc-poly-2.scala @@ -6,7 +6,7 @@ object Test: class C extends Capability class D - def f[X^](x: D^{X^}): D^{X^} = x + def f[X^](x: D^{X}): D^{X} = x def test(c1: C, c2: C) = val d: D^ = D() diff --git a/tests/neg-custom-args/captures/cc-poly-source.scala b/tests/neg-custom-args/captures/cc-poly-source.scala index e08ea36a6fc9..ec98c40a4fa9 100644 --- a/tests/neg-custom-args/captures/cc-poly-source.scala +++ b/tests/neg-custom-args/captures/cc-poly-source.scala @@ -10,11 +10,11 @@ import caps.use class Listener class Source[X^]: - private var listeners: Set[Listener^{X^}] = Set.empty - def register(x: Listener^{X^}): Unit = + private var listeners: Set[Listener^{X}] = Set.empty + def register(x: Listener^{X}): Unit = listeners += x - def allListeners: Set[Listener^{X^}] = listeners + def allListeners: Set[Listener^{X}] = listeners def test1(lbl1: Label^, lbl2: Label^) = val src = Source[CapSet^{lbl1, lbl2}] diff --git a/tests/neg-custom-args/captures/i21313.scala b/tests/neg-custom-args/captures/i21313.scala index 01bedb10aefd..4b6d16c4da88 100644 --- a/tests/neg-custom-args/captures/i21313.scala +++ b/tests/neg-custom-args/captures/i21313.scala @@ -6,7 +6,7 @@ trait Async: def foo(x: Async) = x.await(???) // error trait Source[+T, Cap^]: - final def await(using ac: Async^{Cap^}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap. + final def await(using ac: Async^{Cap}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap. def test(using ac1: Async^, ac2: Async^, x: String) = val src1 = new Source[Int, CapSet^{ac1}] {} diff --git a/tests/neg-custom-args/captures/i21347.scala b/tests/neg-custom-args/captures/i21347.scala index 54fe859caedd..2e37511f053c 100644 --- a/tests/neg-custom-args/captures/i21347.scala +++ b/tests/neg-custom-args/captures/i21347.scala @@ -1,6 +1,6 @@ import language.experimental.captureChecking -def runOps[C^](ops: List[() ->{C^} Unit]): Unit = +def runOps[C^](ops: List[() ->{C} Unit]): Unit = ops.foreach: op => // error op() diff --git a/tests/neg-custom-args/captures/i21868.scala b/tests/neg-custom-args/captures/i21868.scala index 876b68ac90a4..de6a9b64b78b 100644 --- a/tests/neg-custom-args/captures/i21868.scala +++ b/tests/neg-custom-args/captures/i21868.scala @@ -2,11 +2,11 @@ import caps.* trait AbstractWrong: type C <: CapSet - def f(): Unit^{C^} // error + def f(): Unit^{C} // error trait Abstract1: type C >: CapSet <: CapSet^ - def f(): Unit^{C^} + def f(): Unit^{C} // class Abstract2: // type C^ diff --git a/tests/neg-custom-args/captures/i21868b.scala b/tests/neg-custom-args/captures/i21868b.scala index 70f4e9c9d59c..5cff4e3fac2e 100644 --- a/tests/neg-custom-args/captures/i21868b.scala +++ b/tests/neg-custom-args/captures/i21868b.scala @@ -7,7 +7,7 @@ class File trait Abstract: type C >: CapSet <: CapSet^ - def f(file: File^{C^}): Unit + def f(file: File^{C}): Unit class Concrete1 extends Abstract: type C = CapSet @@ -23,7 +23,7 @@ class Concrete3(io: IO^) extends Abstract: trait Abstract2(tracked val io: IO^): type C >: CapSet <: CapSet^{io} - def f(file: File^{C^}): Unit + def f(file: File^{C}): Unit class Concrete4(io: IO^) extends Abstract2(io): type C = CapSet @@ -35,7 +35,7 @@ class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): trait Abstract3[X^]: type C >: CapSet <: X - def f(file: File^{C^}): Unit + def f(file: File^{C}): Unit class Concrete6(io: IO^) extends Abstract3[CapSet^{io}]: type C = CapSet diff --git a/tests/neg-custom-args/captures/polyCaptures.check b/tests/neg-custom-args/captures/polyCaptures.check index 8173828b7bc8..08ebaf1c63ee 100644 --- a/tests/neg-custom-args/captures/polyCaptures.check +++ b/tests/neg-custom-args/captures/polyCaptures.check @@ -1,8 +1,8 @@ -- Error: tests/neg-custom-args/captures/polyCaptures.scala:4:22 ------------------------------------------------------- -4 |val runOpsCheck: [C^] -> (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error +4 |val runOpsCheck: [C^] -> (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error | ^ | Implementation restriction: polymorphic function types cannot wrap function types that have capture sets -- Error: tests/neg-custom-args/captures/polyCaptures.scala:5:23 ------------------------------------------------------- -5 |val runOpsCheck2: [C^] => (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error +5 |val runOpsCheck2: [C^] => (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error | ^ | Implementation restriction: polymorphic function types cannot wrap function types that have capture sets diff --git a/tests/neg-custom-args/captures/polyCaptures.scala b/tests/neg-custom-args/captures/polyCaptures.scala index 776af95e5dcf..eb8e50f0b997 100644 --- a/tests/neg-custom-args/captures/polyCaptures.scala +++ b/tests/neg-custom-args/captures/polyCaptures.scala @@ -1,7 +1,7 @@ class Box[X](val elem: X) -val runOps = [C^] => (b: Box[() ->{C^} Unit]) => b.elem() -val runOpsCheck: [C^] -> (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error -val runOpsCheck2: [C^] => (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error +val runOps = [C^] => (b: Box[() ->{C} Unit]) => b.elem() +val runOpsCheck: [C^] -> (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error +val runOpsCheck2: [C^] => (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check index 6789ec306464..d10cf2ccaeb7 100644 --- a/tests/neg-custom-args/captures/use-capset.check +++ b/tests/neg-custom-args/captures/use-capset.check @@ -1,8 +1,8 @@ --- Error: tests/neg-custom-args/captures/use-capset.scala:5:50 --------------------------------------------------------- -5 |private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error - | ^^^^^^^ - | Capture set parameter C leaks into capture scope of method g. - | To allow this, the type C should be declared with a @use annotation +-- Error: tests/neg-custom-args/captures/use-capset.scala:5:49 --------------------------------------------------------- +5 |private def g[C^] = (xs: List[Object^{C}]) => xs.head // error + | ^^^^^^^ + | Capture set parameter C leaks into capture scope of method g. + | To allow this, the type C should be declared with a @use annotation -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:11:22 ----------------------------------- 11 | val _: () -> Unit = h // error: should be ->{io} | ^ diff --git a/tests/neg-custom-args/captures/use-capset.scala b/tests/neg-custom-args/captures/use-capset.scala index 74288d616396..4f2c239247e6 100644 --- a/tests/neg-custom-args/captures/use-capset.scala +++ b/tests/neg-custom-args/captures/use-capset.scala @@ -1,10 +1,10 @@ import caps.{use, CapSet} -def f[C^](@use xs: List[Object^{C^}]): Unit = ??? +def f[C^](@use xs: List[Object^{C}]): Unit = ??? -private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error +private def g[C^] = (xs: List[Object^{C}]) => xs.head // error -private def g2[@use C^] = (xs: List[Object^{C^}]) => xs.head // ok +private def g2[@use C^] = (xs: List[Object^{C}]) => xs.head // ok def test(io: Object^)(@use xs: List[Object^{io}]): Unit = val h = () => f(xs) diff --git a/tests/pos-custom-args/captures/cc-poly-1.scala b/tests/pos-custom-args/captures/cc-poly-1.scala index ed32d94f7a99..e423d94d4000 100644 --- a/tests/pos-custom-args/captures/cc-poly-1.scala +++ b/tests/pos-custom-args/captures/cc-poly-1.scala @@ -7,9 +7,9 @@ import caps.{CapSet, Capability} class C extends Capability class D - def f[X^](x: D^{X^}): D^{X^} = x - def g[X^](x: D^{X^}, y: D^{X^}): D^{X^} = x - def h[X^](): D^{X^} = ??? + def f[X^](x: D^{X}): D^{X} = x + def g[X^](x: D^{X}, y: D^{X}): D^{X} = x + def h[X^](): D^{X} = ??? def test(c1: C, c2: C) = val d: D^{c1, c2} = D() diff --git a/tests/pos-custom-args/captures/cc-poly-source-capability.scala b/tests/pos-custom-args/captures/cc-poly-source-capability.scala index 6f987658923c..1208649193af 100644 --- a/tests/pos-custom-args/captures/cc-poly-source-capability.scala +++ b/tests/pos-custom-args/captures/cc-poly-source-capability.scala @@ -12,11 +12,11 @@ import caps.use class Listener class Source[X^]: - private var listeners: Set[Listener^{X^}] = Set.empty - def register(x: Listener^{X^}): Unit = + private var listeners: Set[Listener^{X}] = Set.empty + def register(x: Listener^{X}): Unit = listeners += x - def allListeners: Set[Listener^{X^}] = listeners + def allListeners: Set[Listener^{X}] = listeners def test1(async1: Async, @use others: List[Async]) = val src = Source[CapSet^{async1, others*}] diff --git a/tests/pos-custom-args/captures/cc-poly-varargs.scala b/tests/pos-custom-args/captures/cc-poly-varargs.scala index 8bd0dc89bc7a..13e4b276df5a 100644 --- a/tests/pos-custom-args/captures/cc-poly-varargs.scala +++ b/tests/pos-custom-args/captures/cc-poly-varargs.scala @@ -1,13 +1,13 @@ abstract class Source[+T, Cap^] extension[T, Cap^](src: Source[T, Cap]^) - def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ??? + def transformValuesWith[U](f: (T -> U)^{Cap}): Source[U, Cap]^{src, f} = ??? -def race[T, Cap^](sources: Source[T, Cap]^{Cap^}*): Source[T, Cap]^{Cap^} = ??? +def race[T, Cap^](sources: Source[T, Cap]^{Cap}*): Source[T, Cap]^{Cap} = ??? def either[T1, T2, Cap^]( - src1: Source[T1, Cap]^{Cap^}, - src2: Source[T2, Cap]^{Cap^}): Source[Either[T1, T2], Cap]^{Cap^} = + src1: Source[T1, Cap]^{Cap}, + src2: Source[T2, Cap]^{Cap}): Source[Either[T1, T2], Cap]^{Cap} = val left = src1.transformValuesWith(Left(_)) val right = src2.transformValuesWith(Right(_)) race(left, right) diff --git a/tests/pos-custom-args/captures/gears-problem-poly.scala b/tests/pos-custom-args/captures/gears-problem-poly.scala index fdbcf37a35a6..f7632bce4124 100644 --- a/tests/pos-custom-args/captures/gears-problem-poly.scala +++ b/tests/pos-custom-args/captures/gears-problem-poly.scala @@ -7,8 +7,8 @@ trait Future[+T]: trait Channel[+T]: def read(): Ok[T] -class Collector[T, C^](val futures: Seq[Future[T]^{C^}]): - val results: Channel[Future[T]^{C^}] = ??? +class Collector[T, C^](val futures: Seq[Future[T]^{C}]): + val results: Channel[Future[T]^{C}] = ??? end Collector class Result[+T, +E]: @@ -17,10 +17,10 @@ class Result[+T, +E]: case class Err[+E](e: E) extends Result[Nothing, E] case class Ok[+T](x: T) extends Result[T, Nothing] -extension [T, C^](@use fs: Seq[Future[T]^{C^}]) +extension [T, C^](@use fs: Seq[Future[T]^{C}]) def awaitAllPoly = val collector = Collector(fs) - val fut: Future[T]^{C^} = collector.results.read().get + val fut: Future[T]^{C} = collector.results.read().get extension [T](@use fs: Seq[Future[T]^]) def awaitAll = fs.awaitAllPoly diff --git a/tests/pos-custom-args/captures/i21313.scala b/tests/pos-custom-args/captures/i21313.scala index b388b6487cb5..e55bf9c7ce62 100644 --- a/tests/pos-custom-args/captures/i21313.scala +++ b/tests/pos-custom-args/captures/i21313.scala @@ -3,17 +3,17 @@ import caps.CapSet trait Async: def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, Cap]^): T = val x: Async^{this} = ??? - val y: Async^{Cap^} = x + val y: Async^{Cap} = x val ac: Async^ = ??? def f(using caps.Contains[Cap, ac.type]) = val x2: Async^{this} = ??? - val y2: Async^{Cap^} = x2 + val y2: Async^{Cap} = x2 val x3: Async^{ac} = ??? - val y3: Async^{Cap^} = x3 + val y3: Async^{Cap} = x3 ??? trait Source[+T, Cap^]: - final def await(using ac: Async^{Cap^}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap. + final def await(using ac: Async^{Cap}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap. def test(using ac1: Async^, ac2: Async^, x: String) = val src1 = new Source[Int, CapSet^{ac1}] {} diff --git a/tests/pos-custom-args/captures/i21347.scala b/tests/pos-custom-args/captures/i21347.scala index a965b7e4f26b..9dbb8b154cfb 100644 --- a/tests/pos-custom-args/captures/i21347.scala +++ b/tests/pos-custom-args/captures/i21347.scala @@ -4,7 +4,7 @@ import language.experimental.captureChecking class Box[Cap^] {} -def run[Cap^](f: Box[Cap]^{Cap^} => Unit): Box[Cap]^{Cap^} = ??? +def run[Cap^](f: Box[Cap]^{Cap} => Unit): Box[Cap]^{Cap} = ??? def main() = val b = run(_ => ()) diff --git a/tests/pos-custom-args/captures/i21507.scala b/tests/pos-custom-args/captures/i21507.scala index bb80dafb3b45..4267795d3a41 100644 --- a/tests/pos-custom-args/captures/i21507.scala +++ b/tests/pos-custom-args/captures/i21507.scala @@ -1,10 +1,10 @@ import language.experimental.captureChecking trait Box[Cap^]: - def store(f: (() -> Unit)^{Cap^}): Unit + def store(f: (() -> Unit)^{Cap}): Unit -def run[Cap^](f: Box[Cap]^{Cap^} => Unit): Box[Cap]^{Cap^} = +def run[Cap^](f: Box[Cap]^{Cap} => Unit): Box[Cap]^{Cap} = new Box[Cap]: - private var item: () ->{Cap^} Unit = () => () - def store(f: () ->{Cap^} Unit): Unit = + private var item: () ->{Cap} Unit = () => () + def store(f: () ->{Cap} Unit): Unit = item = f // was error, now ok diff --git a/tests/pos-custom-args/captures/setup/a_1.scala b/tests/pos-custom-args/captures/setup/a_1.scala index 21afde8be3ea..3a6ec7ffa773 100644 --- a/tests/pos-custom-args/captures/setup/a_1.scala +++ b/tests/pos-custom-args/captures/setup/a_1.scala @@ -3,4 +3,4 @@ import language.experimental.captureChecking import scala.caps.CapSet trait A: - def f[C^](x: AnyRef^{C^}): Unit + def f[C^](x: AnyRef^{C}): Unit diff --git a/tests/pos-custom-args/captures/setup/b_1.scala b/tests/pos-custom-args/captures/setup/b_1.scala index d5ba925970ba..70c72723c84c 100644 --- a/tests/pos-custom-args/captures/setup/b_1.scala +++ b/tests/pos-custom-args/captures/setup/b_1.scala @@ -2,4 +2,4 @@ import language.experimental.captureChecking import scala.caps.CapSet class B extends A: - def f[C^](x: AnyRef^{C^}): Unit = ??? + def f[C^](x: AnyRef^{C}): Unit = ??? diff --git a/tests/pos-custom-args/captures/setup/b_2.scala b/tests/pos-custom-args/captures/setup/b_2.scala index d5ba925970ba..70c72723c84c 100644 --- a/tests/pos-custom-args/captures/setup/b_2.scala +++ b/tests/pos-custom-args/captures/setup/b_2.scala @@ -2,4 +2,4 @@ import language.experimental.captureChecking import scala.caps.CapSet class B extends A: - def f[C^](x: AnyRef^{C^}): Unit = ??? + def f[C^](x: AnyRef^{C}): Unit = ??? From f7f2967c345bdda802a10d3bde617943d856f864 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 5 Mar 2025 16:52:00 +0100 Subject: [PATCH 2/4] Tight the condition for retrying `InCaptureSet` --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index fc6721363b52..518023af8f0c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -715,7 +715,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && ctx.owner.owner.unforcedDecls.lookup(tree.name).exists then // we are in the arguments of a this(...) constructor call errorTree(tree, em"$tree is not accessible from constructor arguments") - else if ctx.mode.is(Mode.InCaptureSet) then + else if name.isTermName && ctx.mode.is(Mode.InCaptureSet) then // If we are in a capture set and the identifier is not a term name, // try to type it with the same name but as a type typed(untpd.makeCapsOf(untpd.cpy.Ident(tree)(name.toTypeName)), pt) @@ -927,7 +927,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Otherwise, if we are in a capture set, try to type it as a capture variable // reference (as selecting a type name). def trySelectTypeInCaptureSet() = - if ctx.mode.is(Mode.InCaptureSet) && tree0.name.isTypeName then + if tree0.name.isTermName && ctx.mode.is(Mode.InCaptureSet) then typedSelectWithAdapt(untpd.cpy.Select(tree0)(qual, tree0.name.toTypeName), pt, qual) else EmptyTree From 1db01aa2999b102c0ff611b36af43c32d0f8ff94 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 5 Mar 2025 16:55:04 +0100 Subject: [PATCH 3/4] Forget to reuse the bit --- compiler/src/dotty/tools/dotc/core/Mode.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index e0ab4571667c..571a786e9106 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -97,10 +97,10 @@ object Mode { * Since this mode is only used during annotation typing, * we can reuse the value of `ImplicitExploration` to save bits. */ - val InCaptureSet: Mode = newMode(13, "InCaptureSet") + val InCaptureSet: Mode = ImplicitExploration /** We are currently unpickling Scala2 info */ - val Scala2Unpickling: Mode = newMode(14, "Scala2Unpickling") + val Scala2Unpickling: Mode = newMode(13, "Scala2Unpickling") /** Signifies one of two possible situations: * 1. We are currently checking bounds to be non-empty, so we should not From eeeeb33db5c8b0885d3f6786778db12edef919a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Tue, 29 Apr 2025 18:26:13 +0200 Subject: [PATCH 4/4] New capture-vars syntax, "higher-kinds" style Capture variables and members are marked by the sticky `CaptureVar` attachment to their TypeDef and the respective rhs. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 3 + compiler/src/dotty/tools/dotc/ast/untpd.scala | 8 - .../dotty/tools/dotc/parsing/Parsers.scala | 140 ++++++++++++------ .../tools/dotc/printing/PlainPrinter.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 32 +++- docs/_docs/internals/syntax.md | 32 +++- .../reference/experimental/cc-advanced.md | 78 ++++++++++ docs/_docs/reference/experimental/cc.md | 83 ++++++++--- docs/sidebar.yml | 2 + .../referenceReplacements/sidebar.yml | 2 + .../reference-expected-links.txt | 1 + tests/neg-custom-args/captures/branding.scala | 39 +++++ .../neg-custom-args/captures/branding2.scala | 39 +++++ .../captures/cap-paramlists6.check | 8 + .../captures/cap-paramlists6.scala | 11 ++ .../captures/capset-bound.scala | 25 +++- .../captures/capset-bound2.scala | 6 +- .../captures/capset-members-nohat.scala | 30 ++++ .../captures/capset-members.scala | 25 ++-- .../captures/capset-members2.check | 12 ++ .../captures/capset-members2.scala | 9 ++ .../captures/capset-members3.check | 6 + .../captures/capset-members3.scala | 6 + .../captures/capset-members4.scala | 18 +++ .../captures/capset-members5.scala | 31 ++++ .../captures/capset-variance.scala | 32 ++++ .../captures/capture-poly.scala | 4 +- .../captures/capture-vars-subtyping.scala | 12 +- .../captures/capture-vars-subtyping2.scala | 22 +-- .../captures/cc-poly-source.scala | 4 +- tests/neg-custom-args/captures/i21313.scala | 10 +- tests/neg-custom-args/captures/i21868b.scala | 27 ++-- tests/neg-custom-args/captures/i22005.scala | 5 +- .../captures/lexical-control.scala | 27 ++++ .../neg-custom-args/captures/use-capset.scala | 2 +- .../captures/cap-paramlist8-desugared.scala | 12 +- .../captures/cap-paramlists.scala | 21 +++ .../captures/cap-paramlists2.scala | 6 + .../captures/cap-paramlists3.scala | 10 ++ .../captures/cap-paramlists4.scala | 6 + .../captures/cap-paramlists5.scala | 16 ++ .../captures/cap-paramlists6.scala | 8 + .../captures/cap-paramlists7.scala | 8 + .../captures/cap-paramlists8.scala | 8 + .../captures/capset-members.scala | 24 +++ .../captures/capset-members2.scala | 16 ++ .../pos-custom-args/captures/cc-poly-1.scala | 2 +- .../captures/cc-poly-source-capability.scala | 2 +- .../captures/cc-poly-varargs.scala | 10 +- .../captures/gears-problem-poly.scala | 2 +- tests/pos-custom-args/captures/i21313.scala | 9 +- tests/pos-custom-args/captures/i21347.scala | 2 +- tests/pos-custom-args/captures/i21507.scala | 4 +- .../captures/i21868.scala | 5 +- tests/pos-custom-args/captures/polycap.scala | 6 +- .../pos-custom-args/captures/setup/a_1.scala | 1 - .../pos-custom-args/captures/setup/b_1.scala | 1 - .../pos-custom-args/captures/setup/b_2.scala | 1 - 60 files changed, 802 insertions(+), 174 deletions(-) create mode 100644 docs/_docs/reference/experimental/cc-advanced.md create mode 100644 tests/neg-custom-args/captures/branding.scala create mode 100644 tests/neg-custom-args/captures/branding2.scala create mode 100644 tests/neg-custom-args/captures/cap-paramlists6.check create mode 100644 tests/neg-custom-args/captures/cap-paramlists6.scala create mode 100644 tests/neg-custom-args/captures/capset-members-nohat.scala create mode 100644 tests/neg-custom-args/captures/capset-members2.check create mode 100644 tests/neg-custom-args/captures/capset-members2.scala create mode 100644 tests/neg-custom-args/captures/capset-members3.check create mode 100644 tests/neg-custom-args/captures/capset-members3.scala create mode 100644 tests/neg-custom-args/captures/capset-members4.scala create mode 100644 tests/neg-custom-args/captures/capset-members5.scala create mode 100644 tests/neg-custom-args/captures/capset-variance.scala create mode 100644 tests/neg-custom-args/captures/lexical-control.scala create mode 100644 tests/pos-custom-args/captures/cap-paramlists.scala create mode 100644 tests/pos-custom-args/captures/cap-paramlists2.scala create mode 100644 tests/pos-custom-args/captures/cap-paramlists3.scala create mode 100644 tests/pos-custom-args/captures/cap-paramlists4.scala create mode 100644 tests/pos-custom-args/captures/cap-paramlists5.scala create mode 100644 tests/pos-custom-args/captures/cap-paramlists6.scala create mode 100644 tests/pos-custom-args/captures/cap-paramlists7.scala create mode 100644 tests/pos-custom-args/captures/cap-paramlists8.scala create mode 100644 tests/pos-custom-args/captures/capset-members.scala create mode 100644 tests/pos-custom-args/captures/capset-members2.scala rename tests/{neg-custom-args => pos-custom-args}/captures/i21868.scala (66%) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 471a9953c4f0..385e74f829e3 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -642,7 +642,7 @@ object desugar { .withMods(mods & (GivenOrImplicit | Erased | hasDefault | Tracked) | Param) } - /** Desugar type def (not param): Under x.moduliity this can expand + /** Desugar type def (not param): Under x.modularity this can expand * context bounds, which are expanded to evidence ValDefs. These will * ultimately map to deferred givens. */ diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index fcc257da27e4..c6cde66374b3 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -34,6 +34,9 @@ object Trees { val SyntheticUnit: Property.StickyKey[Unit] = Property.StickyKey() + /** Property key for marking capture-set variables and members */ + val CaptureVar: Property.StickyKey[Unit] = Property.StickyKey() + /** Trees take a parameter indicating what the type of their `tpe` field * is. Two choices: `Type` or `Untyped`. * Untyped trees have type `Tree[Untyped]`. diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 145c61584fcc..8b4bfc468a94 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -533,14 +533,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def makeCapsOf(tp: RefTree)(using Context): Tree = TypeApply(capsInternalDot(nme.capsOf), tp :: Nil) - // Capture set variable `[C^]` becomes: `[C >: CapSet <: CapSet^{cap}]` - def makeCapsBound()(using Context): TypeBoundsTree = - TypeBoundsTree( - Select(scalaDot(nme.caps), tpnme.CapSet), - makeRetaining( - Select(scalaDot(nme.caps), tpnme.CapSet), - Nil, tpnme.retainsCap)) - def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef = DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7d695e2291f7..2a91dc5090bb 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -227,6 +227,7 @@ object Parsers { def isNumericLit = numericLitTokens contains in.token def isTemplateIntro = templateIntroTokens contains in.token def isDclIntro = dclIntroTokens contains in.token + def isDclIntroNext = dclIntroTokens contains in.lookahead.token def isStatSeqEnd = in.isNestedEnd || in.token == EOF || in.token == RPAREN def mustStartStat = mustStartStatTokens contains in.token @@ -1591,8 +1592,7 @@ object Parsers { case _ => None } - /** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] - * | [ { SimpleRef `.` } SimpleRef `.` ] id + /** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [`.` `rd`] -- under captureChecking */ def captureRef(): Tree = @@ -1618,9 +1618,10 @@ object Parsers { /** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking */ - def captureSet(): List[Tree] = inBraces { - if in.token == RBRACE then Nil else commaSeparated(captureRef) - } + def captureSet(): List[Tree] = + inBraces { + if in.token == RBRACE then Nil else commaSeparated(captureRef) + } def capturesAndResult(core: () => Tree): Tree = if Feature.ccEnabled && in.token == LBRACE && canStartCaptureSetContentsTokens.contains(in.lookahead.token) @@ -1634,9 +1635,9 @@ object Parsers { * | InfixType * FunType ::= (MonoFunType | PolyFunType) * MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type - * | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions + * | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions and captureChecking * PolyFunType ::= TypTypeParamClause '=>' Type - * | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions + * | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions and captureChecking * FunTypeArgs ::= InfixType * | `(' [ FunArgType {`,' FunArgType } ] `)' * | '(' [ TypedFunParam {',' TypedFunParam } ')' @@ -1879,7 +1880,7 @@ object Parsers { if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil /** InfixType ::= RefinedType {id [nl] RefinedType} - * | RefinedType `^` // under capture checking + * | RefinedType `^` -- under captureChecking */ def infixType(inContextBound: Boolean = false): Tree = infixTypeRest(inContextBound)(refinedType()) @@ -1910,6 +1911,12 @@ object Parsers { || !canStartInfixTypeTokens.contains(ahead.token) || ahead.lineOffset > 0 + inline def gobbleHat(): Boolean = + if Feature.ccEnabled && isIdent(nme.UPARROW) then + in.nextToken() + true + else false + def refinedTypeRest(t: Tree): Tree = { argumentStart() if in.isNestedStart then @@ -2166,35 +2173,45 @@ object Parsers { atSpan(startOffset(t), startOffset(id)) { Select(t, id.name) } } - /** ArgTypes ::= Type {`,' Type} - * | NamedTypeArg {`,' NamedTypeArg} - * NamedTypeArg ::= id `=' Type + /** ArgTypes ::= TypeArg {‘,’ TypeArg} + * | NamedTypeArg {‘,’ NamedTypeArg} + * TypeArg ::= Type + * | CaptureSet -- under captureChecking + * NamedTypeArg ::= id ‘=’ TypeArg * NamesAndTypes ::= NameAndType {‘,’ NameAndType} - * NameAndType ::= id ':' Type + * NameAndType ::= id ‘:’ Type */ def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] = - def argType() = - val t = typ() + def wildCardCheck(gen: Tree): Tree = + val t = gen if wildOK then t else rejectWildcardType(t) - def namedArgType() = + def argType() = wildCardCheck(typ()) + + def typeArg() = wildCardCheck: + if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then // is this a capture set and not a refinement type? + // This case is ambiguous w.r.t. an Object literal {}. But since CC is enabled, we probably expect it to designate the empty set + concreteCapsType(captureSet()) + else typ() + + def namedTypeArg() = atSpan(in.offset): val name = ident() accept(EQUALS) - NamedArg(name.toTypeName, argType()) + NamedArg(name.toTypeName, typeArg()) - def namedElem() = + def nameAndType() = atSpan(in.offset): val name = ident() acceptColon() NamedArg(name, argType()) - if namedOK && isIdent && in.lookahead.token == EQUALS then - commaSeparated(() => namedArgType()) + if namedOK && (isIdent && in.lookahead.token == EQUALS) then + commaSeparated(() => namedTypeArg()) else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.enablesNamedTuples then - commaSeparated(() => namedElem()) + commaSeparated(() => nameAndType()) else - commaSeparated(() => argType()) + commaSeparated(() => typeArg()) end argTypes def paramTypeOf(core: () => Tree): Tree = @@ -2238,7 +2255,7 @@ object Parsers { PostfixOp(t, Ident(tpnme.raw.STAR)) else t - /** TypeArgs ::= `[' Type {`,' Type} `]' + /** TypeArgs ::= `[' TypeArg {`,' TypeArg} `]' * NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]' */ def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = @@ -2252,21 +2269,28 @@ object Parsers { else inBraces(refineStatSeq()) - /** TypeBounds ::= [`>:' Type] [`<:' Type] - * | `^` -- under captureChecking + /** TypeBounds ::= [`>:' TypeBound ] [`<:' TypeBound ] + * TypeBound ::= Type + * | CaptureSet -- under captureChecking */ def typeBounds(): TypeBoundsTree = atSpan(in.offset): - if in.isIdent(nme.UPARROW) && Feature.ccEnabled then - in.nextToken() - makeCapsBound() - else - TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE)) + TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE)) private def bound(tok: Int): Tree = - if (in.token == tok) { in.nextToken(); toplevelTyp() } + if in.token == tok then + in.nextToken() + if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then + capsBound(captureSet(), isLowerBound = tok == SUPERTYPE) + else toplevelTyp() else EmptyTree + private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree = + if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet + Select(scalaDot(nme.caps), tpnme.CapSet) + else + concreteCapsType(refs) + /** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds] */ def typeAndCtxBounds(pname: TypeName): Tree = { @@ -3391,7 +3415,7 @@ object Parsers { * | opaque * LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased | * inline | transparent | infix | - * mut -- under cc + * mut -- under captureChecking */ def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = { @tailrec @@ -3480,22 +3504,25 @@ object Parsers { recur(numLeadParams, firstClause = true, prevIsTypeClause = false) end typeOrTermParamClauses - /** ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ - * ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] - * id [HkTypeParamClause] TypeAndCtxBounds + * ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] + * id [HkTypeParamClause] TypeAndCtxBounds + * | {Annotation} [‘+’ | ‘-’] id `^` TypeAndCtxBounds -- under captureChecking * * DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ - * DefTypeParam ::= {Annotation} - * id [HkTypeParamClause] TypeAndCtxBounds + * DefTypeParam ::= {Annotation} + * id [HkTypeParamClause] TypeAndCtxBounds + * | {Annotation} id `^` TypeAndCtxBounds -- under captureChecking * * TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ - * TypTypeParam ::= {Annotation} - * (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds + * TypTypeParam ::= {Annotation} + * (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds + * | {Annotation} (id | ‘_’) `^` TypeAndCtxBounds -- under captureChecking * * HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ - * HkTypeParam ::= {Annotation} [‘+’ | ‘-’] - * (id | ‘_’) [HkTypeParamClause] TypeBounds + * HkTypeParam ::= {Annotation} [‘+’ | ‘-’] + * (id | ‘_’) [HkTypePamClause] TypeBounds + * | {Annotation} [‘+’ | ‘-’] (id | ‘_’) `^` TypeBounds -- under captureChecking */ def typeParamClause(paramOwner: ParamOwner): List[TypeDef] = inBracketsWithCommas { @@ -3520,12 +3547,18 @@ object Parsers { in.nextToken() WildcardParamName.fresh().toTypeName else ident().toTypeName + val isCap = gobbleHat() val hkparams = typeParamClauseOpt(ParamOwner.Hk) val bounds = if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name) else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name) else typeBounds() - TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) + val res = TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) + if isCap then + res.pushAttachment(CaptureVar, ()) + // putting the attachment here as well makes post-processing in the typer easier + bounds.pushAttachment(CaptureVar, ()) + res } } commaSeparated(() => typeParam()) @@ -4046,12 +4079,22 @@ object Parsers { argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs())) } - /** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type] + /** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ TypeDefRHS ] + * | id `^` TypeAndCtxBounds [‘=’ TypeDefRHS ] -- under captureChecking + * TypeDefRHS ::= Type + * | CaptureSet -- under captureChecking */ def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = { + + def typeDefRHS(): Tree = + if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then + concreteCapsType(captureSet()) + else toplevelTyp() + newLinesOpt() atSpan(start, nameStart) { val nameIdent = typeIdent() + val isCapDef = gobbleHat() val tname = nameIdent.name.asTypeName val tparams = typeParamClauseOpt(ParamOwner.Hk) val vparamss = funParamClauses() @@ -4059,20 +4102,24 @@ object Parsers { def makeTypeDef(rhs: Tree): Tree = { val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs) val tdef = TypeDef(nameIdent.name.toTypeName, rhs1) - if (nameIdent.isBackquoted) + if nameIdent.isBackquoted then tdef.pushAttachment(Backquoted, ()) + if isCapDef then + tdef.pushAttachment(CaptureVar, ()) + // putting the attachment here as well makes post-processing in the typer easier + rhs.pushAttachment(CaptureVar, ()) finalizeDef(tdef, mods, start) } in.token match { case EQUALS => in.nextToken() - makeTypeDef(toplevelTyp()) + makeTypeDef(typeDefRHS()) case SUBTYPE | SUPERTYPE => typeAndCtxBounds(tname) match case bounds: TypeBoundsTree if in.token == EQUALS => val eqOffset = in.skipToken() - var rhs = toplevelTyp() + var rhs = typeDefRHS() rhs match { case mtt: MatchTypeTree => bounds match { @@ -4101,6 +4148,9 @@ object Parsers { } } + private def concreteCapsType(refs: List[Tree]): Tree = + makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains) + /** TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef * | [‘case’] ‘object’ ObjectDef * | ‘enum’ EnumDef diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 0dcb06ae8c87..a5843b545117 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -451,7 +451,7 @@ class PlainPrinter(_ctx: Context) extends Printer { homogenize(tp) match case tp: TermRef if tp.symbol == defn.captureRoot => "cap" case tp: SingletonType => toTextRef(tp) - case tp: (TypeRef | TypeParamRef) => toText(tp) ~ "^" + case tp: (TypeRef | TypeParamRef) => toText(tp) case ReadOnlyCapability(tp1) => toTextCaptureRef(tp1) ~ ".rd" case ReachCapability(tp1) => toTextCaptureRef(tp1) ~ "*" case MaybeCapability(tp1) => toTextCaptureRef(tp1) ~ "?" diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 3cb080428f2c..78e2099511d7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -37,6 +37,7 @@ import annotation.threadUnsafe import scala.util.control.NonFatal import dotty.tools.dotc.inlines.Inlines import scala.annotation.tailrec +import dotty.tools.dotc.cc.isRetains object Applications { import tpd.* diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 518023af8f0c..935f90519b7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2524,6 +2524,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val tycon = typedType(tree.tycon) def spliced(tree: Tree) = untpd.TypedSplice(tree) val tparam = untpd.Ident(tree.paramName).withSpan(tree.span.withEnd(tree.span.point)) + if Feature.ccEnabled && typed(tparam).tpe.derivesFrom(defn.Caps_CapSet) then + report.error(em"Capture variable `${tree.paramName}` cannot have a context bound.", tycon.srcPos) if tycon.tpe.typeParams.nonEmpty then val tycon0 = tycon.withType(tycon.tpe.etaCollapse) typed(untpd.AppliedTypeTree(spliced(tycon0), tparam :: Nil)) @@ -2734,13 +2736,28 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer assignType(cpy.ByNameTypeTree(tree)(result1), result1) def typedTypeBoundsTree(tree: untpd.TypeBoundsTree, pt: Type)(using Context): Tree = + lazy val CapSetBot = untpd.TypeTree(defn.Caps_CapSet.typeRef) + lazy val CapSetTop = untpd.makeRetaining(untpd.TypeTree(defn.Caps_CapSet.typeRef), Nil, tpnme.retainsCap).withSpan(tree.span) + val TypeBoundsTree(lo, hi, alias) = tree val lo1 = typed(lo) val hi1 = typed(hi) val alias1 = typed(alias) - val lo2 = if (lo1.isEmpty) typed(untpd.TypeTree(defn.NothingType)) else lo1 - val hi2 = if (hi1.isEmpty) typed(untpd.TypeTree(defn.AnyType)) else hi1 + val isCap = tree.hasAttachment(CaptureVar) + val lo2 = + if lo1.isEmpty then + if Feature.ccEnabled && (isCap || hi1.tpe.derivesFrom(defn.Caps_CapSet)) then + typed(CapSetBot) + else typed(untpd.TypeTree(defn.NothingType)) + else lo1 + val hi2 = + if hi1.isEmpty then + if Feature.ccEnabled && (isCap || lo1.tpe.derivesFrom(defn.Caps_CapSet)) then + typed(CapSetTop) + else typed(untpd.TypeTree(defn.AnyType)) + else hi1 assignType(cpy.TypeBoundsTree(tree)(lo2, hi2, alias1), lo2, hi2, alias1) + end typedTypeBoundsTree def typedBind(tree: untpd.Bind, pt: Type)(using Context): Tree = { if !isFullyDefined(pt, ForceDegree.all) then @@ -3036,6 +3053,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if sym.isOpaqueAlias then checkFullyAppliedType(rhs1, "Opaque type alias must be fully applied, but ") checkNoContextFunctionType(rhs1) + if Feature.ccEnabled then + val isCap = tdef.hasAttachment(CaptureVar) + rhs1 match + case TypeBoundsTree(lo, hi, _) => + if !isCap && (lo.tpe.derivesFrom(defn.Caps_CapSet) ^ hi.tpe.derivesFrom(defn.Caps_CapSet)) then + report.error(em"Illegal type bounds: >: $lo <: $hi. Capture-set bounds cannot be mixed with type bounds of other kinds", rhs.srcPos) + if isCap && !(lo.tpe.derivesFrom(defn.Caps_CapSet) && hi.tpe.derivesFrom(defn.Caps_CapSet)) then + report.error(em"Illegal type bounds: >: $lo <: $hi. $name^ can only have capture sets as bounds", rhs.srcPos) + case LambdaTypeTree(_, _) if isCap => + report.error(em"`$name` cannot have type parameters, because it ranges over capture sets", rhs.srcPos) + case _ => assignType(cpy.TypeDef(tdef)(name, rhs1), sym) } diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 5a0e543a6d42..baf9145ae8a6 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -141,7 +141,7 @@ type val var while with yield ### Soft keywords ``` -as derives end erased extension infix inline opaque open throws tracked transparent using | * + - +as derives end erased extension infix inline mut opaque open throws tracked transparent using | * + - ``` See the [separate section on soft keywords](../reference/soft-modifier.md) for additional @@ -182,7 +182,9 @@ Type ::= FunType | MatchType | InfixType FunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type Function(ts, t) | FunctionWithMods(ts, t, mods, erasedParams) - | TypTypeParamClause '=>' Type PolyFunction(ps, t) + | FunTypeArgs (‘->’ | ‘?->’) [CaptureSet] Type -- under pureFunctions and captureChecking + | TypTypeParamClause ‘=>’ Type PolyFunction(ps, t) + | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions and captureChecking FunTypeArgs ::= InfixType | ‘(’ [ FunArgTypes ] ‘)’ | FunParamClause @@ -190,7 +192,9 @@ FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ TypedFunParam ::= [`erased`] id ‘:’ Type MatchType ::= InfixType `match` <<< TypeCaseClauses >>> InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) + | RefinedType ‘^’ -- under captureChecking RefinedType ::= AnnotType {[nl] Refinement} RefinedTypeTree(t, ds) + | AnnotType {[nl] Refinement} ‘^’ CaptureSet -- under captureChecking AnnotType ::= SimpleType {Annotation} Annotated(t, annot) AnnotType1 ::= SimpleType1 {Annotation} Annotated(t, annot) @@ -210,8 +214,10 @@ Singleton ::= SimpleRef | Singleton ‘.’ id FunArgType ::= Type | ‘=>’ Type PrefixOp(=>, t) + | ‘->’ [CaptureSet] Type -- under captureChecking FunArgTypes ::= FunArgType { ‘,’ FunArgType } ParamType ::= [‘=>’] ParamValueType + | ‘->’ [CaptureSet] ParamValueType -- under captureChecking ParamValueType ::= Type [‘*’] PostfixOp(t, "*") | IntoType | ‘(’ IntoType ‘)’ ‘*’ PostfixOp(t, "*") @@ -219,17 +225,23 @@ IntoType ::= [‘into’] IntoTargetType | ‘(’ IntoType ‘)’ IntoTargetType ::= Type | FunTypeArgs (‘=>’ | ‘?=>’) IntoType -TypeArgs ::= ‘[’ Types ‘]’ ts +TypeArgs ::= ‘[’ TypeArg {‘,’ TypeArg} ‘]’ ts Refinement ::= :<<< [RefineDcl] {semi [RefineDcl]} >>> ds -TypeBounds ::= [‘>:’ Type] [‘<:’ Type] TypeBoundsTree(lo, hi) +TypeBounds ::= [‘>:’ TypeBound] [‘<:’ TypeBound] TypeBoundsTree(lo, hi) TypeAndCtxBounds ::= TypeBounds [‘:’ ContextBounds] ContextBounds(typeBounds, tps) ContextBounds ::= ContextBound | ContextBound `:` ContextBounds -- to be deprecated | '{' ContextBound {',' ContextBound} '}' ContextBound ::= Type ['as' id] Types ::= Type {‘,’ Type} +TypeArg ::= Type + | CaptureSet -- under captureChecking +TypeBound ::= Type + | CaptureSet -- under captureChecking NamesAndTypes ::= NameAndType {‘,’ NameAndType} NameAndType ::= id ':' Type +CaptureSet ::= ‘{’ CaptureRef {‘,’ CaptureRef} ‘}’ -- under captureChecking +CaptureRef ::= { SimpleRef ‘.’ } SimpleRef [‘*’] [‘.’ ‘rd’] -- under captureChecking ``` ### Expressions @@ -365,16 +377,20 @@ ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeDef(Modifiers, name, tparams, bounds) id [HkTypeParamClause] TypeAndCtxBounds Bound(below, above, context) + | {Annotation} [‘+’ | ‘-’] id `^` TypeAndCtxBounds -- under captureChecking DefTypeParamClause::= [nl] ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeAndCtxBounds + | {Annotation} id `^` TypeAndCtxBounds -- under captureChecking TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ -TypTypeParam ::= {Annotation} (id | ‘_’) [HkTypeParamClause] TypeBounds +TypTypeParam ::= {Annotation} (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds + | {Annotation} id `^` TypeAndCtxBounds -- under captureChecking HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (id | ‘_’) [HkTypeParamClause] TypeBounds + | {Annotation} [‘+’ | ‘-’] id `^` TypeBounds -- under captureChecking ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’] ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’ @@ -419,6 +435,7 @@ LocalModifier ::= ‘abstract’ | ‘infix’ | ‘erased’ | ‘tracked’ + | ‘mut’ -- under captureChecking AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ id ‘]’ @@ -462,7 +479,10 @@ DefDef ::= DefSig [‘:’ Type] [‘=’ Expr] | ‘this’ ConstrParamClauses [DefImplicitClause] ‘=’ ConstrExpr DefDef(_, , vparamss, EmptyTree, expr | Block) DefSig ::= id [DefParamClauses] [DefImplicitClause] TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds TypeDefTree(_, name, tparams, bound - [‘=’ Type] + [‘=’ TypeDefRHS] + | id `^` TypeAndCtxBounds [‘=’ TypeDefRHS] -- under captureChecking +TypeDefRHS ::= Type + | CaptureSet -- under captureChecking TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef diff --git a/docs/_docs/reference/experimental/cc-advanced.md b/docs/_docs/reference/experimental/cc-advanced.md new file mode 100644 index 000000000000..52d1956caf49 --- /dev/null +++ b/docs/_docs/reference/experimental/cc-advanced.md @@ -0,0 +1,78 @@ +--- +layout: doc-page +title: "Capture Checking -- Advanced Use Cases" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-advanced.html +--- + + +## Access Control +Analogously to type parameters, we can lower- and upper-bound capability parameters where the bounds consist of concrete capture sets: +```scala +def main() = + // We can close over anything branded by the 'trusted' capability, but nothing else + def runSecure[C^ >: {trusted} <: {trusted}](block: () ->{C} Unit): Unit = ... + + // This is a 'brand" capability to mark what can be mentioned in trusted code + object trusted extends caps.Capability + + // These capabilities are trusted: + val trustedLogger: Logger^{trusted} + val trustedChannel: Channel[String]^{trusted} + // These aren't: + val untrustedLogger: Logger^ + val untrustedChannel: Channel[String]^ + + runSecure: () => + trustedLogger.log("Hello from trusted code") // ok + + runSecure: () => + trustedChannel.send("I can send") // ok + trustedLogger.log(trustedChannel.recv()) // ok + + runSecure: () => "I am pure and that's ok" // ok + + runSecure: () => + untrustedLogger.log("I can't be used") // error + untrustedChannel.send("I can't be used") // error +``` +The idea is that every capability derived from the marker capability `trusted` (and only those) are eligible to be used in the `block` closure +passed to `runSecure`. We can enforce this by an explicit capability parameter `C` constraining the possible captures of `block` to the interval `>: {trusted} <: {trusted}`. + +Note that since capabilities of function types are covariant, we could have equivalently specified `runSecure`'s signature using implicit capture polymorphism to achieve the same behavior: +```scala +def runSecure(block: () ->{trusted} Unit): Unit +``` + +## Capture-safe Lexical Control + +Capability members and paths to these members can prevent leakage +of labels for lexically-delimited control operators: +```scala +trait Label extends Capability: + type Fv^ // the capability set occurring freely in the `block` passed to `boundary` below. + +def boundary[T, C^](block: Label{type Fv = {C} } ->{C} T): T = ??? // ensure free caps of label and block match +def suspend[U](label: Label)[D^ <: {label.Fv}](handler: () ->{D} U): U = ??? // may only capture the free capabilities of label + +def test = + val x = 1 + boundary: outer => + val y = 2 + boundary: inner => + val z = 3 + val w = suspend(outer) {() => z} // ok + val v = suspend(inner) {() => y} // ok + val u = suspend(inner): () => + suspend(outer) {() => w + v} // ok + y + suspend(outer): () => + println(inner) // error (would leak the inner label) + x + y + z +``` +A key property is that `suspend` (think `shift` from delimited continuations) targeting a specific label (such as `outer`) should not accidentally close over labels from a nested `boundary` (such as `inner`), because they would escape their defining scope this way. +By leveraging capability polymorphism, capability members, and path-dependent capabilities, we can prevent such leaks from occurring at compile time: + +* `Label`s store the free capabilities `C` of the `block` passed to `boundary` in their capability member `Fv`. +* When suspending on a given label, the suspension handler can capture at most the capabilities that occur freely at the `boundary` that introduced the label. That prevents mentioning nested bound labels. + +[Back to Capture Checking](cc.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/cc.md index ff480ffb638b..542094b05c96 100644 --- a/docs/_docs/reference/experimental/cc.md +++ b/docs/_docs/reference/experimental/cc.md @@ -726,34 +726,45 @@ Reach capabilities take the form `x*` where `x` is syntactically a regular capab It is sometimes convenient to write operations that are parameterized with a capture set of capabilities. For instance consider a type of event sources `Source` on which `Listener`s can be registered. Listeners can hold certain capabilities, which show up as a parameter to `Source`: ```scala - class Source[X^]: - private var listeners: Set[Listener^{X^}] = Set.empty - def register(x: Listener^{X^}): Unit = - listeners += x +class Source[X^]: + private var listeners: Set[Listener^{X}] = Set.empty + def register(x: Listener^{X}): Unit = + listeners += x - def allListeners: Set[Listener^{X^}] = listeners + def allListeners: Set[Listener^{X}] = listeners ``` The type variable `X^` can be instantiated with a set of capabilities. It can occur in capture sets in its scope. For instance, in the example above -we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X^`. The `register` method takes a listener of this type +we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X`. The `register` method takes a listener of this type and assigns it to the variable. -Capture set variables `X^` are represented as regular type variables with a -special upper bound `CapSet`. For instance, `Source` could be equivalently +Capture-set variables `X^` without user-annotated bounds by default range over the interval `>: {} <: {caps.cap}` which is the universe of capture sets instead of regular types. + +Under the hood, such capture-set variables are represented as regular type variables within the special interval + `>: CapSet <: CapSet^`. +For instance, `Source` from above could be equivalently defined as follows: ```scala - class Source[X <: CapSet^]: - ... +class Source[X >: CapSet <: CapSet^]: + ... ``` -`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only purpose is to identify capture set type variables and types. Capture set variables can be inferred like regular type variables. When they should be instantiated explicitly one uses a capturing -type `CapSet`. For instance: +`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only +purpose is to identify type variables which are capture sets. In non-capture-checked +usage contexts, the type system will treat `CapSet^{a}` and `CapSet^{a,b}` as the type `CapSet`, whereas +with capture checking enabled, it will take the annotated capture sets into account, +so that `CapSet^{a}` and `CapSet^{a,b}` are distinct. +This representation based on `CapSet` is subject to change and +its direct use is discouraged. + +Capture-set variables can be inferred like regular type variables. When they should be instantiated +explicitly one supplies a concrete capture set. For instance: ```scala - class Async extends caps.Capability +class Async extends caps.Capability - def listener(async: Async): Listener^{async} = ??? +def listener(async: Async): Listener^{async} = ??? - def test1(async1: Async, others: List[Async]) = - val src = Source[CapSet^{async1, others*}] - ... +def test1(async1: Async, others: List[Async]) = + val src = Source[{async1, others*}] + ... ``` Here, `src` is created as a `Source` on which listeners can be registered that refer to the `async` capability or to any of the capabilities in list `others`. So we can continue the example code above as follows: ```scala @@ -761,6 +772,44 @@ Here, `src` is created as a `Source` on which listeners can be registered that r others.map(listener).foreach(src.register) val ls: Set[Listener^{async, others*}] = src.allListeners ``` +A common use-case for explicit capture parameters is describing changes to the captures of mutable fields, such as concatenating +effectful iterators: +```scala +class ConcatIterator[A, C^](var iterators: mutable.List[IterableOnce[A]^{C}]): + def concat(it: IterableOnce[A]^): ConcatIterator[A, {C, it}]^{this, it} = + iterators ++= it // ^ + this // track contents of `it` in the result +``` +In such a scenario, we also should ensure that any pre-existing alias of a `ConcatIterator` object should become +inaccessible after invoking its `concat` method. This is achieved with mutation and separation tracking which are +currently in development. + + +## Capability Members + +Just as parametrization by types can be equally expressed with type members, we could +also define the `Source[X^]` class above could using a _capability member_: +```scala +class Source: + type X^ + private var listeners: Set[Listener^{this.X}] = Set.empty + ... // as before +``` +Here, we can refer to capability members using paths in capture sets (such as `{this.X}`). Similarly to type members, +capability members can be upper- and lower-bounded with capture sets: +```scala +trait Thread: + type Cap^ + def run(block: () ->{this.Cap} -> Unit): Unit + +trait GPUThread extends Thread: + type Cap^ >: {cudaMalloc, cudaFree} <: {caps.cap} +``` +Since `caps.cap` is the top element for subcapturing, we could have also left out the +upper bound: `type Cap^ >: {cudaMalloc, cudaFree}`. + + +[More Advanced Use Cases](cc-advanced.md) ## Compilation Options diff --git a/docs/sidebar.yml b/docs/sidebar.yml index aecd974326ab..16957ba979bd 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -162,6 +162,8 @@ subsection: - page: reference/experimental/main-annotation.md - page: reference/experimental/into-modifier.md - page: reference/experimental/cc.md + - page: reference/experimental/cc-advanced.md + hidden: true - page: reference/experimental/purefuns.md - page: reference/experimental/tupled-function.md - page: reference/experimental/modularity.md diff --git a/project/resources/referenceReplacements/sidebar.yml b/project/resources/referenceReplacements/sidebar.yml index 2e84b0b5e433..4e40642858c9 100644 --- a/project/resources/referenceReplacements/sidebar.yml +++ b/project/resources/referenceReplacements/sidebar.yml @@ -150,6 +150,8 @@ subsection: - page: reference/experimental/explicit-nulls.md - page: reference/experimental/main-annotation.md - page: reference/experimental/cc.md + - page: reference/experimental/cc-advanced.md + hidden: true - page: reference/experimental/tupled-function.md - page: reference/syntax.md - title: Language Versions diff --git a/project/scripts/expected-links/reference-expected-links.txt b/project/scripts/expected-links/reference-expected-links.txt index 8be7dba8d4d0..3eb5df97bf14 100644 --- a/project/scripts/expected-links/reference-expected-links.txt +++ b/project/scripts/expected-links/reference-expected-links.txt @@ -68,6 +68,7 @@ ./enums/enums.html ./enums/index.html ./experimental/canthrow.html +./experimental/cc-advanced.html ./experimental/cc.html ./experimental/erased-defs-spec.html ./experimental/erased-defs.html diff --git a/tests/neg-custom-args/captures/branding.scala b/tests/neg-custom-args/captures/branding.scala new file mode 100644 index 000000000000..7b722971e670 --- /dev/null +++ b/tests/neg-custom-args/captures/branding.scala @@ -0,0 +1,39 @@ +import language.experimental.captureChecking +import caps.* + + +def main() = + trait Channel[T] extends caps.Capability: + def send(msg: T): Unit + def recv(): T + + trait Logger extends caps.Capability: + def log(msg: String): Unit + + // we can close over anything subsumed by the 'trusted' brand capability, but nothing else + def runSecure[C >: {trusted} <: {trusted}](block: () ->{C} Unit): Unit = block() + + // This is a 'brand" capability to mark what can be mentioned in trusted code + object trusted extends caps.Capability + + val trustedLogger: Logger^{trusted} = ??? + val trustedChannel: Channel[String]^{trusted} = ??? + + val untrustedLogger: Logger^ = ??? + val untrustedChannel: Channel[String]^ = ??? + + runSecure: () => + trustedLogger.log("Hello from trusted code") // ok + + runSecure: () => + trustedChannel.send("I can send") + trustedLogger.log(trustedChannel.recv()) // ok + + runSecure: () => + "I am pure" // ok + + runSecure: () => // error + untrustedLogger.log("I can't be used here") + + runSecure: () => // error + untrustedChannel.send("I can't be used here") \ No newline at end of file diff --git a/tests/neg-custom-args/captures/branding2.scala b/tests/neg-custom-args/captures/branding2.scala new file mode 100644 index 000000000000..c3091befdc25 --- /dev/null +++ b/tests/neg-custom-args/captures/branding2.scala @@ -0,0 +1,39 @@ +import language.experimental.captureChecking +import caps.* + + +def main() = + trait Channel[T] extends caps.Capability: + def send(msg: T): Unit + def recv(): T + + trait Logger extends caps.Capability: + def log(msg: String): Unit + + // we can close over anything subsumed by the 'trusted' brand capability, but nothing else + def runSecure(block: () ->{trusted} Unit): Unit = block() + + // This is a 'brand" capability to mark what can be mentioned in trusted code + object trusted extends caps.Capability + + val trustedLogger: Logger^{trusted} = ??? + val trustedChannel: Channel[String]^{trusted} = ??? + + val untrustedLogger: Logger^ = ??? + val untrustedChannel: Channel[String]^ = ??? + + runSecure: () => + trustedLogger.log("Hello from trusted code") // ok + + runSecure: () => + trustedChannel.send("I can send") + trustedLogger.log(trustedChannel.recv()) // ok + + runSecure: () => + "I am pure" : Unit // ok + + runSecure: () => // error + untrustedLogger.log("I can't be used here") + + runSecure: () => // error + untrustedChannel.send("I can't be used here") \ No newline at end of file diff --git a/tests/neg-custom-args/captures/cap-paramlists6.check b/tests/neg-custom-args/captures/cap-paramlists6.check new file mode 100644 index 000000000000..ad14cc7f27f7 --- /dev/null +++ b/tests/neg-custom-args/captures/cap-paramlists6.check @@ -0,0 +1,8 @@ +-- Error: tests/neg-custom-args/captures/cap-paramlists6.scala:10:71 --------------------------------------------------- +10 | val baz = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E} : Ctx, // error + | ^^^ + | Capture variable `F` cannot have a context bound. +-- Error: tests/neg-custom-args/captures/cap-paramlists6.scala:11:51 --------------------------------------------------- +11 | G >: {} <: {}, H >: {} <: {} : Ctx] => (x: Int) => 1 // error + | ^^^ + | Capture variable `H` cannot have a context bound. diff --git a/tests/neg-custom-args/captures/cap-paramlists6.scala b/tests/neg-custom-args/captures/cap-paramlists6.scala new file mode 100644 index 000000000000..a83164f5466b --- /dev/null +++ b/tests/neg-custom-args/captures/cap-paramlists6.scala @@ -0,0 +1,11 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E} : Ctx, // error + G >: {} <: {}, H >: {} <: {} : Ctx] => (x: Int) => 1 // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-bound.scala b/tests/neg-custom-args/captures/capset-bound.scala index 80ee3804776f..71250ec1585e 100644 --- a/tests/neg-custom-args/captures/capset-bound.scala +++ b/tests/neg-custom-args/captures/capset-bound.scala @@ -1,3 +1,4 @@ +import language.experimental.captureChecking import caps.* class IO @@ -5,14 +6,22 @@ class IO case class File(io: IO^) def test(io1: IO^, io2: IO^) = - def f[C >: CapSet^{io1} <: CapSet^](file: File^{C}) = ??? + def f[C^ >: {io1}](file: File^{C}) = ??? + def g[C >: {io1}](file: File^{C}) = ??? val f1: File^{io1} = ??? val f2: File^{io2} = ??? val f3: File^{io1, io2} = ??? - f[CapSet^{io1}](f1) - f[CapSet^{io1}](f2) // error - f[CapSet^{io1}](f3) // error - f[CapSet^{io2}](f2) // error - f[CapSet^{io1, io2}](f1) - f[CapSet^{io1, io2}](f2) - f[CapSet^{io1, io2}](f3) \ No newline at end of file + f[{io1}](f1) + f[{io1}](f2) // error + f[{io1}](f3) // error + f[{io2}](f2) // error + f[{io1, io2}](f1) + f[{io1, io2}](f2) + f[{io1, io2}](f3) + g[{io1}](f1) + g[{io1}](f2) // error + g[{io1}](f3) // error + g[{io2}](f2) // error + g[{io1, io2}](f1) + g[{io1, io2}](f2) + g[{io1, io2}](f3) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-bound2.scala b/tests/neg-custom-args/captures/capset-bound2.scala index 4cee6b244de7..803ec6419a21 100644 --- a/tests/neg-custom-args/captures/capset-bound2.scala +++ b/tests/neg-custom-args/captures/capset-bound2.scala @@ -5,8 +5,8 @@ class IO def f[C^](io: IO^{C}) = ??? def test = - f[CapSet](???) - f[CapSet^{}](???) - f[CapSet^](???) + f[{}](???) + f[{}](???) + f[{cap}](???) f[Nothing](???) // error f[String](???) // error diff --git a/tests/neg-custom-args/captures/capset-members-nohat.scala b/tests/neg-custom-args/captures/capset-members-nohat.scala new file mode 100644 index 000000000000..f99c47f23ac0 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members-nohat.scala @@ -0,0 +1,30 @@ +import language.experimental.captureChecking +import caps.* + +trait Abstract[X^]: + type C >: {X} + // Don't test the return type using Unit, because it is a pure type. + def boom(): AnyRef^{C} + +class Concrete extends Abstract[{}]: + type C^ = {} + // TODO: Why do we get error without the return type here? + def boom(): AnyRef = new Object + +class Concrete2 extends Abstract[{}]: + type C^ = {} + def boom(): AnyRef^ = new Object // error + +class Concrete3 extends Abstract[{}]: + def boom(): AnyRef = new Object + +class Concrete4(a: AnyRef^) extends Abstract[{a}]: + type C^ = {} // error + def boom(): AnyRef^{a} = a // error + +class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]: + type C^ = {a} + def boom(): AnyRef^{b} = b // error + +class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]: + def boom(): AnyRef^{b} = b // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members.scala b/tests/neg-custom-args/captures/capset-members.scala index 44720b68ff84..3d931200f326 100644 --- a/tests/neg-custom-args/captures/capset-members.scala +++ b/tests/neg-custom-args/captures/capset-members.scala @@ -1,29 +1,30 @@ +import language.experimental.captureChecking import caps.* trait Abstract[X^]: - type C >: X <: CapSet^ + type C^ >: {X} // Don't test the return type using Unit, because it is a pure type. def boom(): AnyRef^{C} -class Concrete extends Abstract[CapSet^{}]: - type C = CapSet^{} +class Concrete extends Abstract[{}]: + type C^ = {} // TODO: Why do we get error without the return type here? def boom(): AnyRef = new Object -class Concrete2 extends Abstract[CapSet^{}]: - type C = CapSet^{} +class Concrete2 extends Abstract[{}]: + type C^ = {} def boom(): AnyRef^ = new Object // error -class Concrete3 extends Abstract[CapSet^{}]: +class Concrete3 extends Abstract[{}]: def boom(): AnyRef = new Object -class Concrete4(a: AnyRef^) extends Abstract[CapSet^{a}]: - type C = CapSet // error +class Concrete4(a: AnyRef^) extends Abstract[{a}]: + type C^ = {} // error def boom(): AnyRef^{a} = a // error -class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: - type C = CapSet^{a} +class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]: + type C^ = {a} def boom(): AnyRef^{b} = b // error -class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: - def boom(): AnyRef^{b} = b // error +class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]: + def boom(): AnyRef^{b} = b // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members2.check b/tests/neg-custom-args/captures/capset-members2.check new file mode 100644 index 000000000000..903f875432a4 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members2.check @@ -0,0 +1,12 @@ +-- Error: tests/neg-custom-args/captures/capset-members2.scala:5:10 ---------------------------------------------------- +5 | type C^[T] // error + | ^ + | `C` cannot have type parameters, because it ranges over capture sets +-- Error: tests/neg-custom-args/captures/capset-members2.scala:7:13 ---------------------------------------------------- +7 | def foo[C^[_]]: Int // error + | ^ + | `C` cannot have type parameters, because it ranges over capture sets +-- Error: tests/neg-custom-args/captures/capset-members2.scala:9:20 ---------------------------------------------------- +9 | def bar [M[_], C^ <: M]: Int // error + | ^^^^ + | Illegal type bounds: >: scala.caps.CapSet <: M. C^ can only have capture sets as bounds diff --git a/tests/neg-custom-args/captures/capset-members2.scala b/tests/neg-custom-args/captures/capset-members2.scala new file mode 100644 index 000000000000..727dd3e1e742 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members2.scala @@ -0,0 +1,9 @@ +import language.experimental.captureChecking +import caps.* + +trait Foo: + type C^[T] // error + + def foo[C^[_]]: Int // error + + def bar [M[_], C^ <: M]: Int // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members3.check b/tests/neg-custom-args/captures/capset-members3.check new file mode 100644 index 000000000000..246bced980d6 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members3.check @@ -0,0 +1,6 @@ +-- [E095] Syntax Error: tests/neg-custom-args/captures/capset-members3.scala:5:10 -------------------------------------- +5 | type C^ _ // error + | ^ + | =, >:, or <: expected, but '_' found + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/capset-members3.scala b/tests/neg-custom-args/captures/capset-members3.scala new file mode 100644 index 000000000000..c8b767ae167c --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members3.scala @@ -0,0 +1,6 @@ +import language.experimental.captureChecking +import caps.* + +trait Foo: + type C^ _ // error + diff --git a/tests/neg-custom-args/captures/capset-members4.scala b/tests/neg-custom-args/captures/capset-members4.scala new file mode 100644 index 000000000000..bdce213e7de1 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members4.scala @@ -0,0 +1,18 @@ +import language.experimental.captureChecking +import caps.* + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + val z: Any^ = ??? + def onlyWithZ[C^](using c: Contains[C, z.type]) = ??? + + trait Foo: + type C >: {z,x} <: {x,y,z} + + val foo: Foo = ??? + onlyWithZ[{foo.C}] + onlyWithZ[{z}] + onlyWithZ[{x,z}] + onlyWithZ[{x,y,z}] + onlyWithZ[{x,y}] // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members5.scala b/tests/neg-custom-args/captures/capset-members5.scala new file mode 100644 index 000000000000..a779e9041a48 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members5.scala @@ -0,0 +1,31 @@ +import language.experimental.captureChecking +import caps.* + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + trait CaptureSet: + type A^ >: {y} <: {x} + type B = {x} + type C <: {x} + type D^ : Ctx // error + type E <: {C} + type F <: {C} + type G <: {x, y} + type H >: {x} <: {x,y} : Ctx // error + type I^ = {y, G, H} + type J = {O.z} + type K^ = {x, O.z} + type L <: {x, y, O.z} + type M >: {x, y, O.z} <: {C} + type N >: {x} <: {x} + type O >: {O.z} <: {O.z} + type P >: {B,D} // error + type Q >: {E} <: Int // error + type R^ >: {E} <: Int // error + type S^ >: Nothing <: Int // error + type T >: Nothing <: Int \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-variance.scala b/tests/neg-custom-args/captures/capset-variance.scala new file mode 100644 index 000000000000..2ad97a014648 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-variance.scala @@ -0,0 +1,32 @@ +import language.experimental.captureChecking + +trait Test: + type T[-C^] + type U[+C^] + type C^ + type D^ + + def foo[C^](x: T[C]): Unit = ??? + def bar(x: T[{}]): Unit = ??? + def baz(x: T[{caps.cap}]): Unit = ??? + def foo2[C^](x: U[C]): Unit = ??? + def bar2(x: U[{}]): Unit = ??? + def baz2(x: U[{caps.cap}]): Unit = ??? + + def test = + val t: T[{C}] = ??? + val t2: T[C] = ??? + val u: U[{D}] = ??? + val u2: U[D] = ??? + foo(t) + bar(t) + baz(t) // error + foo2(u) + bar2(u) // error + baz2(u) + foo(t2) + bar(t2) + baz(t2) // error + foo2(u2) + bar2(u2) // error + baz2(u2) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capture-poly.scala b/tests/neg-custom-args/captures/capture-poly.scala index 9276eab0342f..c0608ad812de 100644 --- a/tests/neg-custom-args/captures/capture-poly.scala +++ b/tests/neg-custom-args/captures/capture-poly.scala @@ -3,7 +3,7 @@ import caps.* trait Foo extends Capability trait CaptureSet: - type C >: CapSet <: CapSet^ + type C^ def capturePoly[C^](a: Foo^{C}): Foo^{C} = a def capturePoly2(c: CaptureSet)(a: Foo^{c.C}): Foo^{c.C} = a @@ -13,7 +13,7 @@ def test = val y: Foo^ = ??? object X extends CaptureSet: - type C = CapSet^{x} + type C = {x} val z1: Foo^{X.C} = x val z2: Foo^{X.C} = y // error diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping.scala b/tests/neg-custom-args/captures/capture-vars-subtyping.scala index c0356bb0873f..9a32abe0b6e3 100644 --- a/tests/neg-custom-args/captures/capture-vars-subtyping.scala +++ b/tests/neg-custom-args/captures/capture-vars-subtyping.scala @@ -9,7 +9,7 @@ def test[C^] = // TODO: make "CapSet-ness" of type variables somehow contagious? // Then we don't have to spell out the bounds explicitly... -def testTrans[C^, D >: CapSet <: C, E >: CapSet <: D, F >: C <: CapSet^] = +def testTrans[C^, D^ <: C, E^ <: {D}, F^ >: C] = val d1: D = ??? val d2: CapSet^{D} = d1 val d3: D = d2 @@ -37,12 +37,12 @@ trait B[-C] def testCong[C^, D^] = val a: A[C] = ??? - val b: A[CapSet^{C}] = a - val c: A[CapSet^{D}] = a // error - val d: A[CapSet^{C,D}] = a + val b: A[{C}] = a + val c: A[{D}] = a // error + val d: A[{C,D}] = a val e: A[C] = d // error val f: B[C] = ??? - val g: B[CapSet^{C}] = f + val g: B[{C}] = f val h: B[C] = g - val i: B[CapSet^{C,D}] = h // error + val i: B[{C,D}] = h // error val j: B[C] = i diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala index 0a3d776227cb..ccf646f34dca 100644 --- a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala +++ b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala @@ -9,14 +9,14 @@ trait BoundsTest: val b: Bar^ = ??? def testTransMixed[A^, - B >: CapSet <: A, - C >: CapSet <: CapSet^{B}, - D >: CapSet <: C, - E >: CapSet <: CapSet^{D}, - F >: CapSet <: CapSet^{A,b}, - X >: CapSet <: CapSet^{F,D}, - Y >: CapSet^{F} <: CapSet^{F,A,b}, - Z >: CapSet^{b} <: CapSet^{b,Y}] = + B^ <: {A}, + C^ <: {B}, + D^ <: C, + E^ <: {D}, + F^ <: {A,b}, + X^ <: {F,D}, + Y^ >: {F} <: {F,A,b}, + Z^ >: {b} <: {b,Y}, T <: List[Bar^{Z}]] = val e: E = ??? val e2: CapSet^{E} = e val ed: D = e @@ -39,6 +39,6 @@ trait BoundsTest: def callTransMixed = val x, y, z: Bar^ = ??? - testTransMixed[CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{b,x,y,z}] - testTransMixed[CapSet^{x,y,z}, CapSet^{x,y}, CapSet^{x,y}, CapSet^{x}, CapSet^{}, CapSet^{b,x}, CapSet^{b}, CapSet^{b,x}, CapSet^{b}] - testTransMixed[CapSet^{x,y,z}, CapSet^{x,y}, CapSet^{x,y}, CapSet^{x}, CapSet^{}, CapSet^{b,x}, CapSet^{b}, CapSet^{b,x}, CapSet^{b,x,y,z}] // error + testTransMixed[{x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {b,x,y,z}, List[Bar^{b}]] + testTransMixed[{x,y,z}, {x,y}, {x,y}, {x}, {}, {b,x}, {b}, {b,x}, {b}, List[Bar^{b}]] + testTransMixed[{x,y,z}, {x,y}, {x,y}, {x}, {}, {b,x}, {b}, {b,x}, {b,x,y,z}, List[Bar^{}]] // error diff --git a/tests/neg-custom-args/captures/cc-poly-source.scala b/tests/neg-custom-args/captures/cc-poly-source.scala index ec98c40a4fa9..fc47e6504810 100644 --- a/tests/neg-custom-args/captures/cc-poly-source.scala +++ b/tests/neg-custom-args/captures/cc-poly-source.scala @@ -17,7 +17,7 @@ import caps.use def allListeners: Set[Listener^{X}] = listeners def test1(lbl1: Label^, lbl2: Label^) = - val src = Source[CapSet^{lbl1, lbl2}] + val src = Source[{lbl1, lbl2}] def l1: Listener^{lbl1} = ??? val l2: Listener^{lbl2} = ??? src.register{l1} @@ -31,7 +31,7 @@ import caps.use // we get an error here because we no longer allow contravariant cap // to subsume other capabilities. The problem can be solved by declaring // Label a SharedCapability, see cc-poly-source-capability.scala - val src = Source[CapSet^{lbls*}] + val src = Source[{lbls*}] for l <- listeners do src.register(l) val ls = src.allListeners diff --git a/tests/neg-custom-args/captures/i21313.scala b/tests/neg-custom-args/captures/i21313.scala index 4b6d16c4da88..9ee31aa7b196 100644 --- a/tests/neg-custom-args/captures/i21313.scala +++ b/tests/neg-custom-args/captures/i21313.scala @@ -1,15 +1,15 @@ -import caps.CapSet +import language.experimental.captureChecking trait Async: - def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, Cap]^): T + def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, {Cap}]^): T def foo(x: Async) = x.await(???) // error trait Source[+T, Cap^]: - final def await(using ac: Async^{Cap}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap. + final def await(using ac: Async^{Cap}) = ac.await[T, {Cap}](this) // Contains[Cap, ac] is assured because {ac} <: Cap. def test(using ac1: Async^, ac2: Async^, x: String) = - val src1 = new Source[Int, CapSet^{ac1}] {} + val src1 = new Source[Int, {ac1}] {} ac1.await(src1) // ok - val src2 = new Source[Int, CapSet^{ac2}] {} + val src2 = new Source[Int, {ac2}] {} ac1.await(src2) // error diff --git a/tests/neg-custom-args/captures/i21868b.scala b/tests/neg-custom-args/captures/i21868b.scala index 5cff4e3fac2e..3c15f5c1cdf7 100644 --- a/tests/neg-custom-args/captures/i21868b.scala +++ b/tests/neg-custom-args/captures/i21868b.scala @@ -1,3 +1,4 @@ +import language.experimental.captureChecking import language.experimental.modularity import caps.* @@ -6,44 +7,44 @@ class IO class File trait Abstract: - type C >: CapSet <: CapSet^ + type C^ def f(file: File^{C}): Unit class Concrete1 extends Abstract: - type C = CapSet + type C = {} def f(file: File) = () class Concrete2(io: IO^) extends Abstract: - type C = CapSet^{io} + type C = {io} def f(file: File^{io}) = () class Concrete3(io: IO^) extends Abstract: - type C = CapSet^{io} + type C = {io} def f(file: File) = () // error trait Abstract2(tracked val io: IO^): - type C >: CapSet <: CapSet^{io} + type C^ <: {io} def f(file: File^{C}): Unit class Concrete4(io: IO^) extends Abstract2(io): - type C = CapSet + type C = {} def f(file: File) = () class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): - type C = CapSet^{io2} // error + type C = {io2} // error def f(file: File^{io2}) = () trait Abstract3[X^]: - type C >: CapSet <: X + type C^ <: X def f(file: File^{C}): Unit -class Concrete6(io: IO^) extends Abstract3[CapSet^{io}]: - type C = CapSet +class Concrete6(io: IO^) extends Abstract3[{io}]: + type C = {} def f(file: File) = () -class Concrete7(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: - type C = CapSet^{io2} // error +class Concrete7(io1: IO^, io2: IO^) extends Abstract3[{io1}]: + type C = {io2} // error def f(file: File^{io2}) = () -class Concrete8(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: +class Concrete8(io1: IO^, io2: IO^) extends Abstract3[{io1}]: def f(file: File^{io2}) = () // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i22005.scala b/tests/neg-custom-args/captures/i22005.scala index 689246d6f835..fc7d4183f8c3 100644 --- a/tests/neg-custom-args/captures/i22005.scala +++ b/tests/neg-custom-args/captures/i22005.scala @@ -1,9 +1,10 @@ +import language.experimental.captureChecking import caps.* class IO class File(io: IO^) class Handler[C^]: - def f(file: File^): File^{C^} = file // error - def g(@consume file: File^{C^}): File^ = file // ok + def f(file: File^): File^{C} = file // error + def g(@consume file: File^{C}): File^ = file // ok diff --git a/tests/neg-custom-args/captures/lexical-control.scala b/tests/neg-custom-args/captures/lexical-control.scala new file mode 100644 index 000000000000..7a38a6a94600 --- /dev/null +++ b/tests/neg-custom-args/captures/lexical-control.scala @@ -0,0 +1,27 @@ +import language.experimental.captureChecking +import caps.* + +trait Label extends Capability: + type Fv^ // the capability set occurring freely in the `block` passed to `boundary` below. + +def boundary[T, C^](block: Label{type Fv = {C} } ->{C} T): T = ??? // link label and block capture set +def suspend[U](label: Label)[D^ <: {label.Fv}](handler: () ->{D} U): U = ??? // note the path + +def test = + val x = 1 + boundary: outer => + val y = 2 + boundary: inner => + val z = 3 + val w = suspend(outer) {() => z} // ok + val v = suspend(inner) {() => y} // ok + val u = suspend(inner): () => + suspend(outer) {() => y} // ok + suspend(outer) {() => y} // ok + y + suspend(outer) { () => // error + suspend(outer) {() => y } + } + suspend(outer): () => // error (leaks the inner label) + println(inner) + x + y + z \ No newline at end of file diff --git a/tests/neg-custom-args/captures/use-capset.scala b/tests/neg-custom-args/captures/use-capset.scala index 4f2c239247e6..2d489a5061d8 100644 --- a/tests/neg-custom-args/captures/use-capset.scala +++ b/tests/neg-custom-args/captures/use-capset.scala @@ -9,6 +9,6 @@ private def g2[@use C^] = (xs: List[Object^{C}]) => xs.head // ok def test(io: Object^)(@use xs: List[Object^{io}]): Unit = val h = () => f(xs) val _: () -> Unit = h // error: should be ->{io} - val h2 = () => g[CapSet^{io}] + val h2 = () => g[{io}] val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} diff --git a/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala b/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala index e57946bdadc1..d34a81bcfa2f 100644 --- a/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala +++ b/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala @@ -12,17 +12,17 @@ } val baz3: Int -> [C >: caps.CapSet <: caps.CapSet^, - D >: caps.CapSet <: caps.CapSet^{C^}, E >: caps.CapSet <: - caps.CapSet^{C^, x}] => () -> [F >: caps.CapSet^{x, y} <: - caps.CapSet^{C^, E^}] => (x: Int) -> (Ctx[F]) ?-> Int + D >: caps.CapSet <: caps.CapSet^{C}, E >: caps.CapSet <: + caps.CapSet^{C, x}] => () -> [F >: caps.CapSet^{x, y} <: + caps.CapSet^{C, E}] => (x: Int) -> (Ctx[F]) ?-> Int = (i: Int) => [ C >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{cap}, - D >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C^}, - E >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C^, x}] => + D >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C}, + E >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C, x}] => () => [ F >: _root_.scala.caps.CapSet^{x, y} <: - _root_.scala.caps.CapSet^{C^, E^} + _root_.scala.caps.CapSet^{C, E} ] => (x: Int) => (ev: Ctx[F]) ?=> 1 () } diff --git a/tests/pos-custom-args/captures/cap-paramlists.scala b/tests/pos-custom-args/captures/cap-paramlists.scala new file mode 100644 index 000000000000..536b7f1d739a --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists.scala @@ -0,0 +1,21 @@ +import language.experimental.captureChecking + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + def foo[A >: {y} <: {x}, + B^, + C <: {x}, + D^, + E <: {C}, + F <: {C}, + G <: {x, y}, + H >: {x} <: {x,y}, T, U >: {x}]()[I^ <: {y, G, H}, + J <: {O.z}, + K <: {x, O.z}, + L <: {x, y, O.z}, + M >: {x, y, O.z} <: {C}, + N >: {x} <: {x}, + O >: {O.z} <: {O.z}] = ??? \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists2.scala b/tests/pos-custom-args/captures/cap-paramlists2.scala new file mode 100644 index 000000000000..2b62d0f0f34c --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists2.scala @@ -0,0 +1,6 @@ +import language.experimental.captureChecking + +trait Bar: + type C^ + +def useFoo[D^](x: Bar { type C = {D} } ): Any^{x.C} = ??? \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists3.scala b/tests/pos-custom-args/captures/cap-paramlists3.scala new file mode 100644 index 000000000000..29ae98cb98d6 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists3.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val bar = [C^, D <: {C}, E^ <: {C,x}, F >: {x,y} <: {C,E}] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists4.scala b/tests/pos-custom-args/captures/cap-paramlists4.scala new file mode 100644 index 000000000000..179da36ef1ed --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists4.scala @@ -0,0 +1,6 @@ +import language.experimental.captureChecking + +trait Foo[U^, V^, W^]: + type C = {caps.cap} + type D = {caps.cap} + type E >: {V,W} <: {U} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists5.scala b/tests/pos-custom-args/captures/cap-paramlists5.scala new file mode 100644 index 000000000000..ec0933017c36 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists5.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking +import language.experimental.namedTypeArguments + +def test2 = + val x: Any^ = ??? + def foo[A^, B^ >: {A}, T, U](x: Int) = 1 + foo[{x}, {x}, Int, String](0) + foo[{}, {}, { def bar: Int }, { type D^ = {x} }](0) + trait Foo { type D^ } + foo[{}, {}, Foo, Foo](0) + foo[A = {x}, B = {x}](0) + foo[A = {x}](0) + foo[T = Int](0) + foo[T = Int, A = {x}](1) + foo[A = {x}, T = Int](1) + foo[B = {}, U = String, A = {}](1) \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists6.scala b/tests/pos-custom-args/captures/cap-paramlists6.scala new file mode 100644 index 000000000000..d0567089556c --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists6.scala @@ -0,0 +1,8 @@ +import language.experimental.captureChecking + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E}] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists7.scala b/tests/pos-custom-args/captures/cap-paramlists7.scala new file mode 100644 index 000000000000..abe59edd6835 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists7.scala @@ -0,0 +1,8 @@ +import language.experimental.captureChecking + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz2 = (i: Int) => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E}] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists8.scala b/tests/pos-custom-args/captures/cap-paramlists8.scala new file mode 100644 index 000000000000..f300e857282a --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists8.scala @@ -0,0 +1,8 @@ +import language.experimental.captureChecking + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz3 = (i: Int) => [C^, D^ <: {C}, E^ <: {C,x}] => () => [F^ >: {x,y} <: {C,E}] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/capset-members.scala b/tests/pos-custom-args/captures/capset-members.scala new file mode 100644 index 000000000000..00b6486a6819 --- /dev/null +++ b/tests/pos-custom-args/captures/capset-members.scala @@ -0,0 +1,24 @@ +import language.experimental.captureChecking + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + trait CaptureSet: + type A^ >: {y} <: {x} + type B = {x} + type C <: {x} + type D^ + type E <: {C} + type F <: {C} + type G <: {x, y} + type H >: {x} <: {x,y} + type I^ = {y, G, H} + type J = {O.z} + type K^ = {x, O.z} + type L <: {x, y, O.z} + type M >: {x, y, O.z} <: {C} + type N >: {x} <: {x} + type O >: {O.z} <: {O.z} + type P >: {B,D} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/capset-members2.scala b/tests/pos-custom-args/captures/capset-members2.scala new file mode 100644 index 000000000000..13d527d0b556 --- /dev/null +++ b/tests/pos-custom-args/captures/capset-members2.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + + abstract class Foo[A^, T]: + type C^ <: {A} + abstract class Bar extends Foo[{x,y,O.z}, String]: + override type C^ <: {x,y} + class Baz extends Bar: + final override type C = {y} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cc-poly-1.scala b/tests/pos-custom-args/captures/cc-poly-1.scala index e423d94d4000..96bd58d65334 100644 --- a/tests/pos-custom-args/captures/cc-poly-1.scala +++ b/tests/pos-custom-args/captures/cc-poly-1.scala @@ -13,7 +13,7 @@ import caps.{CapSet, Capability} def test(c1: C, c2: C) = val d: D^{c1, c2} = D() - val x = f[CapSet^{c1, c2}](d) + val x = f[{c1, c2}](d) val _: D^{c1, c2} = x val d1: D^{c1} = D() val d2: D^{c2} = D() diff --git a/tests/pos-custom-args/captures/cc-poly-source-capability.scala b/tests/pos-custom-args/captures/cc-poly-source-capability.scala index 1208649193af..c76e6067fbef 100644 --- a/tests/pos-custom-args/captures/cc-poly-source-capability.scala +++ b/tests/pos-custom-args/captures/cc-poly-source-capability.scala @@ -19,7 +19,7 @@ import caps.use def allListeners: Set[Listener^{X}] = listeners def test1(async1: Async, @use others: List[Async]) = - val src = Source[CapSet^{async1, others*}] + val src = Source[{async1, others*}] val _: Set[Listener^{async1, others*}] = src.allListeners val lst1 = listener(async1) val lsts = others.map(listener) diff --git a/tests/pos-custom-args/captures/cc-poly-varargs.scala b/tests/pos-custom-args/captures/cc-poly-varargs.scala index 13e4b276df5a..d39d4a9c30e5 100644 --- a/tests/pos-custom-args/captures/cc-poly-varargs.scala +++ b/tests/pos-custom-args/captures/cc-poly-varargs.scala @@ -1,13 +1,13 @@ abstract class Source[+T, Cap^] -extension[T, Cap^](src: Source[T, Cap]^) - def transformValuesWith[U](f: (T -> U)^{Cap}): Source[U, Cap]^{src, f} = ??? +extension[T, Cap^](src: Source[T, {Cap}]^) + def transformValuesWith[U](f: (T -> U)^{Cap}): Source[U, {Cap}]^{src, f} = ??? -def race[T, Cap^](sources: Source[T, Cap]^{Cap}*): Source[T, Cap]^{Cap} = ??? +def race[T, Cap^](sources: Source[T, {Cap}]^{Cap}*): Source[T, {Cap}]^{Cap} = ??? def either[T1, T2, Cap^]( - src1: Source[T1, Cap]^{Cap}, - src2: Source[T2, Cap]^{Cap}): Source[Either[T1, T2], Cap]^{Cap} = + src1: Source[T1, {Cap}]^{Cap}, + src2: Source[T2, {Cap}]^{Cap}): Source[Either[T1, T2], {Cap}]^{Cap} = val left = src1.transformValuesWith(Left(_)) val right = src2.transformValuesWith(Right(_)) race(left, right) diff --git a/tests/pos-custom-args/captures/gears-problem-poly.scala b/tests/pos-custom-args/captures/gears-problem-poly.scala index f7632bce4124..0b6fe3a9a0e9 100644 --- a/tests/pos-custom-args/captures/gears-problem-poly.scala +++ b/tests/pos-custom-args/captures/gears-problem-poly.scala @@ -26,4 +26,4 @@ extension [T](@use fs: Seq[Future[T]^]) def awaitAll = fs.awaitAllPoly def awaitExplicit[T](@use fs: Seq[Future[T]^]): Unit = - awaitAllPoly[T, CapSet^{fs*}](fs) + awaitAllPoly[T, {fs*}](fs) diff --git a/tests/pos-custom-args/captures/i21313.scala b/tests/pos-custom-args/captures/i21313.scala index e55bf9c7ce62..4113c6a2998e 100644 --- a/tests/pos-custom-args/captures/i21313.scala +++ b/tests/pos-custom-args/captures/i21313.scala @@ -1,11 +1,12 @@ import caps.CapSet trait Async: - def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, Cap]^): T = + def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, {Cap}]^): T = + // FIXME: this is an irregularity: it works if we write caps.Contains[Cap, this.type], but we should expect to write caps.Contains[{Cap}, this.type]! val x: Async^{this} = ??? val y: Async^{Cap} = x val ac: Async^ = ??? - def f(using caps.Contains[Cap, ac.type]) = + def f(using caps.Contains[Cap, ac.type]) = // FIXME dito val x2: Async^{this} = ??? val y2: Async^{Cap} = x2 val x3: Async^{ac} = ??? @@ -13,8 +14,8 @@ trait Async: ??? trait Source[+T, Cap^]: - final def await(using ac: Async^{Cap}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap. + final def await(using ac: Async^{Cap}) = ac.await[T, {Cap}](this) // Contains[Cap, ac] is assured because {ac} <: Cap. def test(using ac1: Async^, ac2: Async^, x: String) = - val src1 = new Source[Int, CapSet^{ac1}] {} + val src1 = new Source[Int, {ac1}] {} ac1.await(src1) diff --git a/tests/pos-custom-args/captures/i21347.scala b/tests/pos-custom-args/captures/i21347.scala index 9dbb8b154cfb..a8fbb4fb6680 100644 --- a/tests/pos-custom-args/captures/i21347.scala +++ b/tests/pos-custom-args/captures/i21347.scala @@ -4,7 +4,7 @@ import language.experimental.captureChecking class Box[Cap^] {} -def run[Cap^](f: Box[Cap]^{Cap} => Unit): Box[Cap]^{Cap} = ??? +def run[Cap^](f: Box[Cap]^{Cap} => Unit): Box[{Cap}]^{Cap} = ??? def main() = val b = run(_ => ()) diff --git a/tests/pos-custom-args/captures/i21507.scala b/tests/pos-custom-args/captures/i21507.scala index 4267795d3a41..455aa3940fb6 100644 --- a/tests/pos-custom-args/captures/i21507.scala +++ b/tests/pos-custom-args/captures/i21507.scala @@ -3,8 +3,8 @@ import language.experimental.captureChecking trait Box[Cap^]: def store(f: (() -> Unit)^{Cap}): Unit -def run[Cap^](f: Box[Cap]^{Cap} => Unit): Box[Cap]^{Cap} = - new Box[Cap]: +def run[Cap^](f: Box[{Cap}]^{Cap} => Unit): Box[{Cap}]^{Cap} = + new Box[{Cap}]: private var item: () ->{Cap} Unit = () => () def store(f: () ->{Cap} Unit): Unit = item = f // was error, now ok diff --git a/tests/neg-custom-args/captures/i21868.scala b/tests/pos-custom-args/captures/i21868.scala similarity index 66% rename from tests/neg-custom-args/captures/i21868.scala rename to tests/pos-custom-args/captures/i21868.scala index de6a9b64b78b..5385902c2c53 100644 --- a/tests/neg-custom-args/captures/i21868.scala +++ b/tests/pos-custom-args/captures/i21868.scala @@ -1,11 +1,12 @@ +import language.experimental.captureChecking import caps.* trait AbstractWrong: type C <: CapSet - def f(): Unit^{C} // error + def f(): Unit^{C} trait Abstract1: - type C >: CapSet <: CapSet^ + type C^ def f(): Unit^{C} // class Abstract2: diff --git a/tests/pos-custom-args/captures/polycap.scala b/tests/pos-custom-args/captures/polycap.scala index 684f46454595..4d830ed279b8 100644 --- a/tests/pos-custom-args/captures/polycap.scala +++ b/tests/pos-custom-args/captures/polycap.scala @@ -2,10 +2,10 @@ import language.experimental.captureChecking class Source[+T, Cap^] -def completed[T, Cap^](result: T): Source[T, Cap] = +def completed[T, Cap^](result: T): Source[T, {Cap}] = //val fut = new Source[T, Cap]() - val fut2 = new Source[T, Cap]() - fut2: Source[T, Cap] + val fut2 = new Source[T, {Cap}]() + fut2: Source[T, {Cap}] diff --git a/tests/pos-custom-args/captures/setup/a_1.scala b/tests/pos-custom-args/captures/setup/a_1.scala index 3a6ec7ffa773..8c7de185e71b 100644 --- a/tests/pos-custom-args/captures/setup/a_1.scala +++ b/tests/pos-custom-args/captures/setup/a_1.scala @@ -1,6 +1,5 @@ // a.scala import language.experimental.captureChecking -import scala.caps.CapSet trait A: def f[C^](x: AnyRef^{C}): Unit diff --git a/tests/pos-custom-args/captures/setup/b_1.scala b/tests/pos-custom-args/captures/setup/b_1.scala index 70c72723c84c..c10048a78cce 100644 --- a/tests/pos-custom-args/captures/setup/b_1.scala +++ b/tests/pos-custom-args/captures/setup/b_1.scala @@ -1,5 +1,4 @@ import language.experimental.captureChecking -import scala.caps.CapSet class B extends A: def f[C^](x: AnyRef^{C}): Unit = ??? diff --git a/tests/pos-custom-args/captures/setup/b_2.scala b/tests/pos-custom-args/captures/setup/b_2.scala index 70c72723c84c..c10048a78cce 100644 --- a/tests/pos-custom-args/captures/setup/b_2.scala +++ b/tests/pos-custom-args/captures/setup/b_2.scala @@ -1,5 +1,4 @@ import language.experimental.captureChecking -import scala.caps.CapSet class B extends A: def f[C^](x: AnyRef^{C}): Unit = ???