Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -706,9 +706,34 @@ object TypeOps:
def loop(args: List[Tree], boundss: List[TypeBounds]): Unit = args match
case arg :: args1 => boundss match
case bounds :: boundss1 =>

// Drop caps.Pure from a bound (1) at the top-level, (2) in an `&`, (3) under a type lambda.
def dropPure(tp: Type): Option[Type] = tp match
case tp @ AndType(tp1, tp2) =>
dropPure(tp1) match
case Some(tp1o) =>
dropPure(tp2) match
case Some(tp2o) => Some(tp.derivedAndType(tp1o, tp2o))
case None => Some(tp1o)
case None =>
dropPure(tp2)
case tp: HKTypeLambda =>
for rt <- dropPure(tp.resType) yield
tp.derivedLambdaType(resType = rt)
case _ =>
if tp.typeSymbol == defn.PureClass then None
else Some(tp)

val relevantBounds =
if Feature.ccEnabled then bounds
else
// Drop caps.Pure from bound, it should be checked only when capture checking is enabled
dropPure(bounds.hi).match
case Some(hi1) => bounds.derivedTypeBounds(bounds.lo, hi1)
case None => TypeBounds(bounds.lo, defn.AnyKindType)
arg.tpe match
case TypeBounds(lo, hi) => checkOverlapsBounds(lo, hi, arg, bounds)
case tp => checkOverlapsBounds(tp, tp, arg, bounds)
case TypeBounds(lo, hi) => checkOverlapsBounds(lo, hi, arg, relevantBounds)
case tp => checkOverlapsBounds(tp, tp, arg, relevantBounds)
loop(args1, boundss1)
case _ =>
case _ =>
Expand Down
9 changes: 9 additions & 0 deletions library/src/scala/caps/Pure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import language.experimental.captureChecking
/** A marker trait that declares that all inheriting classes are "pure" in the
* sense that their values retain no capabilities including capabilities needed
* to perform effects. This has formal meaning only under capture checking.
*
* NOTE: If an upper bound is Pure, we check that an argument conforms to it only
* in sources where capture checking is enabled. For instance,
*
* def f[C <: Pure]()
* f[Object]()
*
* would give an error only under capture checking. Pure is also dropped in
* upper bounds if it forms part of an &-type, or is under a type lambda.
*/
trait Pure:
this: Pure =>
12 changes: 12 additions & 0 deletions tests/neg-custom-args/captures/puretest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import caps.Pure
class P extends Pure
def foo[C <: Pure]() = ()
def bar[T, C <: Iterable[T] & Pure]() = ()
def baz[CC[_] <: Pure]() = ()
def bam[CC[A] <: Pure & Iterable[A]]() = ()
def test =
foo[Int]() // error
bar[Int, List[Int]]() // error
baz[Seq]() // error
bam[Seq]() // error
foo[P]() // OK
12 changes: 12 additions & 0 deletions tests/neg/puretest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import caps.Pure
def foo[C <: Pure]() = ()
def bar[T, C <: Iterable[T] & Pure]() = ()
def baz[CC[_] <: Pure]() = ()
def bam[CC[A] <: Pure & Iterable[A]]() = ()
def test =
foo[Int]()
bar[Int, List[Int]]()
bar[Int, Iterator[Int]]() // error
baz[Seq]()
bam[Seq]()
bam[Iterator]() // error
10 changes: 10 additions & 0 deletions tests/pos/puretest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import caps.Pure
def foo[C <: Pure]() = ()
def bar[T, C <: Iterable[T] & Pure]() = ()
def baz[CC[_] <: Pure]() = ()
def bam[CC[A] <: Pure & Iterable[A]]() = ()
def test =
foo[Int]()
bar[Int, List[Int]]()
baz[Seq]()
bam[Seq]()
Loading