Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore capture sets in certain cases of subtyping #22183

Closed
wants to merge 2 commits into from
Closed
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
15 changes: 12 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
res

case tp1 @ CapturingType(parent1, refs1) =>
if (approx.lowIgnoreCaptures) then return recur(parent1, tp2)
def compareCapturing =
if tp2.isAny then true
else if subCaptures(refs1, tp2.captureSet, frozenConstraint).isOK && sameBoxed(tp1, tp2, refs1)
Expand Down Expand Up @@ -936,7 +937,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
|| tp1.widen.underlyingClassRef(refinementOK = true).exists)
then
def checkBase =
isSubType(base, tp2, if tp1.isRef(cls2) then approx else approx.addLow)
var a = approx
if (!tp1.isRef(cls2)) then a = a.addLow
if (cls2 eq defn.Caps_CapSet) then a = a.addLowIgnoreCaptures //TODO is this the correct place to add this?
isSubType(base, tp2, a)
&& recordGadtUsageIf { MatchType.thatReducesUsingGadt(tp1) }
if tp1.widenDealias.isInstanceOf[AndType] || base.isInstanceOf[OrType] then
// If tp1 is a intersection, it could be that one of the original
Expand Down Expand Up @@ -3325,30 +3329,35 @@ object TypeComparer {
* - `None` : They are still the same types
* - `LoApprox`: The left type is approximated (i.e widened)"
* - `HiApprox`: The right type is approximated (i.e narrowed)"
* - `LoIgnoreCaptures`: (Capture checking): The captures of the left type are ignored in the comparison
*/
object ApproxState:
opaque type Repr = Int

val None: Repr = 0
private val LoApprox = 1
private val HiApprox = 2
private val LoIgnoreCaptures = 4

/** A special approximation state to indicate that this is the first time we
* compare (approximations of) this pair of types. It's converted to `None`
* in `isSubType`, but also leads to `leftRoot` being set there.
*/
val Fresh: Repr = 4
val Fresh: Repr = 8

object Repr:
extension (approx: Repr)
def low: Boolean = (approx & LoApprox) != 0
def high: Boolean = (approx & HiApprox) != 0
def lowIgnoreCaptures: Boolean = (approx & LoIgnoreCaptures) != 0
def addLow: Repr = approx | LoApprox
def addHigh: Repr = approx | HiApprox
def addLowIgnoreCaptures: Repr = approx | LoIgnoreCaptures
def show: String =
val lo = if low then " (left is approximated)" else ""
val hi = if high then " (right is approximated)" else ""
lo ++ hi
val locapt = if lowIgnoreCaptures then " (left captures are ignored)" else ""
lo ++ hi ++ locapt
end ApproxState
type ApproxState = ApproxState.Repr

Expand Down
47 changes: 47 additions & 0 deletions tests/neg-custom-args/captures/capture-vars-subtyping.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import language.experimental.captureChecking
import caps.*

def test[C^] =
val a: C = ???
val b: CapSet^{C^} = a
val c: C = b
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...
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should support that. Say we have def foo[C^, D <: C, E >: C], then we should automatically constraint both D and E to be capture set variables, i.e., D is implicitly lower-bounded by CapSet, and E is implicitly upper-bounded by CapSet^

def testTrans[C^, D >: CapSet <: C, E >: CapSet <: D, F >: C <: CapSet^] =
val d1: D = ???
val d2: CapSet^{D^} = d1
val d3: D = d2
val e1: E = ???
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 f2: F = d_e_f1
val c3: C = d_e_f1 // error
Copy link
Contributor Author

@bracevac bracevac Dec 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line and the three following lines are all not flagged as errors, but they should be. Investigating.

val c4: C = f1 // error
val e4: E = f1 // error
val e5: E = d1 // error


trait A[+T]

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 e: A[C] = d // error
val f: B[C] = ???
val g: B[CapSet^{C^}] = f
val h: B[C] = g
val i: B[CapSet^{C^,D^}] = h // error
val j: B[C] = i
6 changes: 1 addition & 5 deletions tests/pos-custom-args/captures/cc-poly-varargs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,4 @@ def either[T1, T2, Cap^](
src2: Source[T2, Cap]^{Cap^}): Source[Either[T1, T2], Cap]^{Cap^} =
val left = src1.transformValuesWith(Left(_))
val right = src2.transformValuesWith(Right(_))
race[Either[T1, T2], Cap](left, right)
Copy link
Member

@noti0na1 noti0na1 Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extension methods above can be tested as well. I remember they have similar issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It causes an infinite regress now, looking into it.

// Explicit type arguments are required here because the second argument
// is inferred as `CapSet^{Cap^}` instead of `Cap`.
// Although `CapSet^{Cap^}` subsumes `Cap` in terms of capture sets,
// `Cap` is not a subtype of `CapSet^{Cap^}` in terms of subtyping.
race(left, right)
Loading