Skip to content

Commit 6d50a34

Browse files
committed
Keep constraints for subtype tests with ProtoTypes
A subtype test `A <:< P` where `P` is a prototype was implemented as `P.isMatchedBy(A)`. `isMatchedBy` did not keep the constraint which meant that information was lost instead of being propagated.
1 parent 03e0900 commit 6d50a34

File tree

4 files changed

+35
-23
lines changed

4 files changed

+35
-23
lines changed

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1227,7 +1227,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
12271227
/** Defer constraining type variables when compared against prototypes */
12281228
def isMatchedByProto(proto: ProtoType, tp: Type): Boolean = tp.stripTypeVar match {
12291229
case tp: TypeParamRef if constraint contains tp => true
1230-
case _ => proto.isMatchedBy(tp)
1230+
case _ => proto.isMatchedBy(tp, keepConstraint = true)
12311231
}
12321232

12331233
/** Narrow gadt.bounds for the type parameter referenced by `tr` to include

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1665,7 +1665,7 @@ object Types {
16651665

16661666
/** A trait for proto-types, used as expected types in typer */
16671667
trait ProtoType extends Type {
1668-
def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean
1668+
def isMatchedBy(tp: Type, keepConstraint: Boolean = false)(implicit ctx: Context): Boolean
16691669
def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T
16701670
def map(tm: TypeMap)(implicit ctx: Context): ProtoType
16711671

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
277277
private[this] var _ok = true
278278

279279
def ok: Boolean = _ok
280-
def ok_=(x: Boolean): Unit = {
281-
assert(x || ctx.reporter.errorsReported || !ctx.typerState.isCommittable) // !!! DEBUG
282-
_ok = x
283-
}
280+
def ok_=(x: Boolean): Unit = _ok = x
284281

285282
/** The function's type after widening and instantiating polytypes
286283
* with TypeParamRefs in constraint set
@@ -1118,8 +1115,11 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
11181115
/** Is given method reference applicable to type arguments `targs` and argument trees `args`?
11191116
* @param resultType The expected result type of the application
11201117
*/
1121-
def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
1122-
ctx.test(implicit ctx => new ApplicableToTrees(methRef, targs, args, resultType).success)
1118+
def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
1119+
def isApp(implicit ctx: Context): Boolean =
1120+
new ApplicableToTrees(methRef, targs, args, resultType).success
1121+
if (keepConstraint) isApp else ctx.test(implicit ctx => isApp)
1122+
}
11231123

11241124
/** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views?
11251125
* @param resultType The expected result type of the application
@@ -1137,8 +1137,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
11371137
* possibly after inserting an `apply`?
11381138
* @param resultType The expected result type of the application
11391139
*/
1140-
def isApplicable(tp: Type, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
1141-
onMethod(tp, isApplicable(_, targs, args, resultType))
1140+
def isApplicable(tp: Type, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean =
1141+
onMethod(tp, isApplicable(_, targs, args, resultType, keepConstraint))
11421142

11431143
/** Is given type applicable to argument types `args`, possibly after inserting an `apply`?
11441144
* @param resultType The expected result type of the application
@@ -1491,7 +1491,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
14911491
)
14921492
if (alts2.isEmpty && !ctx.isAfterTyper)
14931493
alts.filter(alt =>
1494-
isApplicable(alt, targs, args, resultType)
1494+
isApplicable(alt, targs, args, resultType, keepConstraint = false)
14951495
)
14961496
else
14971497
alts2
@@ -1511,14 +1511,14 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
15111511
}
15121512

15131513
case pt @ PolyProto(targs1, pt1) if targs.isEmpty =>
1514-
val alts1 = alts filter pt.isMatchedBy
1514+
val alts1 = alts.filter(pt.isMatchedBy(_))
15151515
resolveOverloaded(alts1, pt1, targs1.tpes)
15161516

15171517
case defn.FunctionOf(args, resultType, _, _) =>
15181518
narrowByTypes(alts, args, resultType)
15191519

15201520
case pt =>
1521-
val compat = alts.filter(normalizedCompatible(_, pt))
1521+
val compat = alts.filter(normalizedCompatible(_, pt, keepConstraint = false))
15221522
if (compat.isEmpty)
15231523
/*
15241524
* the case should not be moved to the enclosing match

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,21 @@ object ProtoTypes {
3535
def isCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean =
3636
(tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt)
3737

38-
/** Test compatibility after normalization in a fresh typerstate. */
39-
def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean =
40-
ctx.test { implicit ctx =>
38+
/** Test compatibility after normalization.
39+
* Do this in a fresh typerstate unless `keepConstraint` is true.
40+
*/
41+
def normalizedCompatible(tp: Type, pt: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
42+
def testCompat(implicit ctx: Context): Boolean = {
4143
val normTp = normalize(tp, pt)
4244
isCompatible(normTp, pt) || pt.isRef(defn.UnitClass) && normTp.isParameterless
4345
}
46+
if (keepConstraint)
47+
tp.widenSingleton match {
48+
case poly: PolyType => normalizedCompatible(tp, pt, keepConstraint = false)
49+
case _ => testCompat
50+
}
51+
else ctx.test(implicit ctx => testCompat)
52+
}
4453

4554
private def disregardProto(pt: Type)(implicit ctx: Context): Boolean = pt.dealias match {
4655
case _: OrType => true
@@ -89,7 +98,7 @@ object ProtoTypes {
8998

9099
/** A trait for prototypes that match all types */
91100
trait MatchAlways extends ProtoType {
92-
def isMatchedBy(tp1: Type)(implicit ctx: Context): Boolean = true
101+
def isMatchedBy(tp1: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = true
93102
def map(tm: TypeMap)(implicit ctx: Context): ProtoType = this
94103
def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T = x
95104
override def toString: String = getClass.toString
@@ -131,13 +140,13 @@ object ProtoTypes {
131140
case _ => false
132141
}
133142

134-
override def isMatchedBy(tp1: Type)(implicit ctx: Context): Boolean = {
143+
override def isMatchedBy(tp1: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
135144
name == nme.WILDCARD || hasUnknownMembers(tp1) ||
136145
{
137146
val mbr = if (privateOK) tp1.member(name) else tp1.nonPrivateMember(name)
138147
def qualifies(m: SingleDenotation) =
139148
memberProto.isRef(defn.UnitClass) ||
140-
tp1.isValueType && compat.normalizedCompatible(NamedType(tp1, name, m), memberProto)
149+
tp1.isValueType && compat.normalizedCompatible(NamedType(tp1, name, m), memberProto, keepConstraint)
141150
// Note: can't use `m.info` here because if `m` is a method, `m.info`
142151
// loses knowledge about `m`'s default arguments.
143152
mbr match { // hasAltWith inlined for performance
@@ -234,8 +243,11 @@ object ProtoTypes {
234243
extends UncachedGroundType with ApplyingProto with FunOrPolyProto {
235244
override def resultType(implicit ctx: Context): Type = resType
236245

237-
def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean =
238-
typer.isApplicable(tp, Nil, unforcedTypedArgs, resultType)
246+
def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
247+
val args = unforcedTypedArgs
248+
def isPoly(tree: Tree) = tree.tpe.widenSingleton.isInstanceOf[PolyType]
249+
typer.isApplicable(tp, Nil, args, resultType, keepConstraint && !args.exists(isPoly))
250+
}
239251

240252
def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer): FunProto =
241253
if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this
@@ -379,7 +391,7 @@ object ProtoTypes {
379391

380392
override def resultType(implicit ctx: Context): Type = resType
381393

382-
def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean =
394+
def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean =
383395
ctx.typer.isApplicable(tp, argType :: Nil, resultType) || {
384396
resType match {
385397
case SelectionProto(name: TermName, mbrType, _, _) =>
@@ -422,7 +434,7 @@ object ProtoTypes {
422434

423435
override def resultType(implicit ctx: Context): Type = resType
424436

425-
override def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = {
437+
override def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = {
426438
def isInstantiatable(tp: Type) = tp.widen match {
427439
case tp: PolyType => tp.paramNames.length == targs.length
428440
case _ => false

0 commit comments

Comments
 (0)