Skip to content

Commit

Permalink
Merge the regular and capture-checking version of isSubInfo
Browse files Browse the repository at this point in the history
The isSubInfo for capture-checking introduced in scala#15877 had the following TODO:

    // A relaxed version of subtyping for dependent functions where method types
    // are treated as contravariant.
    // TODO: Merge with isSubInfo in hasMatchingMember. Currently, we can't since
    // the isSubinfo of hasMatchingMember has problems dealing with PolyTypes
    // (---> orphan params during pickling)

The orphan params error was due to the recursion on the result of the PolyTypes
being done without first calling `compareTypeLambda`. After fixing this we can
safely merge the two versions while keeping the new behavior for dependent and
polymorphic function types hidden under the `captureChecking` feature since
they're language changes. I'll open a separate PR to create a
`relaxedSubtyping` feature for these improvements since their usefulness is
independent of capture checking.

The isSubInfo for capture-checking got two additional cases involving
CapturingTypes in 3e690a8, I don't know what
they're supposed to do, but moving those to the regular isSubInfo breaks
various capture-checking tests:

-- [E007] Type Mismatch Error: tests/pos-custom-args/captures/classes.scala:11:32 --------------------------------------
11 |  val c1: C{val n: B^{x}}^{x} = c0
   |                                ^^
   |                                Found:    (c0 : C{val n: B^}^{x})
   |                                Required: C{val n: B^{x}}^{x}

So this commit breaks capture-checking and I don't know enough about what this
code is supposed to do to fix it.
  • Loading branch information
smarter authored and Linyxus committed Jul 18, 2023
1 parent a839c03 commit 47f4519
Showing 1 changed file with 32 additions and 36 deletions.
68 changes: 32 additions & 36 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import TypeOps.refineUsingParent
import collection.mutable
import util.{Stats, NoSourcePosition, EqHashMap}
import config.Config
import config.Feature.migrateTo3
import config.Feature.{ccEnabled, migrateTo3}
import config.Printers.{subtyping, gadts, matchTypes, noPrinter}
import TypeErasure.{erasedLub, erasedGlb}
import TypeApplications._
Expand Down Expand Up @@ -641,36 +641,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
def compareRefined: Boolean =
val tp1w = tp1.widen

if ctx.phase == Phases.checkCapturesPhase then

// A relaxed version of subtyping for dependent functions where method types
// are treated as contravariant.
// TODO: Merge with isSubInfo in hasMatchingMember. Currently, we can't since
// the isSubinfo of hasMatchingMember has problems dealing with PolyTypes
// (---> orphan params during pickling)
def isSubInfo(info1: Type, info2: Type): Boolean = (info1, info2) match
case (info1: PolyType, info2: PolyType) =>
info1.paramNames.hasSameLengthAs(info2.paramNames)
&& isSubInfo(info1.resultType, info2.resultType.subst(info2, info1))
case (info1: MethodType, info2: MethodType) =>
matchingMethodParams(info1, info2, precise = false)
&& isSubInfo(info1.resultType, info2.resultType.subst(info2, info1))
case (info1 @ CapturingType(parent1, refs1), info2: Type) =>
subCaptures(refs1, info2.captureSet, frozenConstraint).isOK && sameBoxed(info1, info2, refs1)
&& isSubInfo(parent1, info2)
case (info1: Type, CapturingType(parent2, refs2)) =>
val refs1 = info1.captureSet
(refs1.isAlwaysEmpty || subCaptures(refs1, refs2, frozenConstraint).isOK) && sameBoxed(info1, info2, refs1)
&& isSubInfo(info1, parent2)
case _ =>
isSubType(info1, info2)

if defn.isFunctionType(tp2) then
tp1w.widenDealias match
case tp1: RefinedType =>
return isSubInfo(tp1.refinedInfo, tp2.refinedInfo)
case _ =>

val skipped2 = skipMatching(tp1w, tp2)
if (skipped2 eq tp2) || !Config.fastPathForRefinedSubtype then
if containsAnd(tp1) then
Expand Down Expand Up @@ -2001,8 +1971,15 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
// conceivably dispatch without knowing precise parameter signatures. One can signal
// this by inheriting from the `scala.reflect.SignatureCanBeImprecise` marker trait,
// in which case the signature test is elided.
//
// Additionally, for refined function types we do not need to check
// signatures since they're erased in a special way, so we skip the
// signature check but only under `ccEnabled` for now.
// TODO: add a new relaxedSubtyping feature for this (and make a SIP)
// since this is useful in general, but is a language change.
def sigsOK(symInfo: Type, info2: Type) =
tp2.underlyingClassRef(refinementOK = true).member(name).exists
(ccEnabled && defn.isFunctionType(tp2))
|| tp2.underlyingClassRef(refinementOK = true).member(name).exists
|| tp2.derivesFrom(defn.WithoutPreciseParameterTypesClass)
|| symInfo.isInstanceOf[MethodType]
&& symInfo.signature.consistentParams(info2.signature)
Expand All @@ -2014,17 +1991,36 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
// but under the condition that signatures might have to match (see sigsOK)
// This relaxed version is needed to correctly compare dependent function types.
// See pos/i12211.scala.
def isSubInfo(info1: Type, info2: Type, symInfo: Type): Boolean =
def isSubInfo(info1: Type, info2: Type, symInfo1: Type): Boolean =
def fallback = inFrozenGadtIf(tp1IsSingleton) { isSubType(info1, info2) }
info2 match
case info2: PolyType if ccEnabled => // See TODO about `ccEnabled` in `sigsOK`, this should also go under `relaxedSubtyping`.
info1 match
case info1: PolyType =>
comparingTypeLambdas(info1, info2):
info1.paramNames.hasSameLengthAs(info2.paramNames)
&& isSubInfo(info1.resultType, info2.resultType.subst(info2, info1), symInfo1.resultType)
// Signature checks are never necessary because polymorphic
// refinements are only allowed for the `apply` method of a
// PolyFunction.
case _ => fallback
case info2: MethodType =>
info1 match
case info1: MethodType =>
val symInfo1 = symInfo.stripPoly
matchingMethodParams(info1, info2, precise = false)
&& isSubInfo(info1.resultType, info2.resultType.subst(info2, info1), symInfo1.resultType)
&& sigsOK(symInfo1, info2)
case _ => inFrozenGadtIf(tp1IsSingleton) { isSubType(info1, info2) }
case _ => inFrozenGadtIf(tp1IsSingleton) { isSubType(info1, info2) }
case _ => fallback
case info2 @ CapturingType(parent2, refs2) if ctx.phase == Phases.checkCapturesPhase =>
val refs1 = info1.captureSet
(refs1.isAlwaysEmpty || subCaptures(refs1, refs2, frozenConstraint).isOK) && sameBoxed(info1, info2, refs1)
&& isSubInfo(info1, parent2, symInfo1)
case _ =>
info1 match
case info1 @ CapturingType(parent1, refs1) if ctx.phase == Phases.checkCapturesPhase =>
subCaptures(refs1, info2.captureSet, frozenConstraint).isOK && sameBoxed(info1, info2, refs1)
&& isSubInfo(parent1, info2, symInfo1)
case _ => fallback

def qualifies(m: SingleDenotation): Boolean =
val info2 = tp2.refinedInfo
Expand Down

0 comments on commit 47f4519

Please sign in to comment.