Skip to content

Commit 385082b

Browse files
authored
Merge pull request #4782 from dotty-staging/fix/poly-override
Fix #4771: Relax overriding check for polymorphic methods
2 parents cf2c202 + b87e964 commit 385082b

File tree

4 files changed

+134
-44
lines changed

4 files changed

+134
-44
lines changed

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

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -390,33 +390,48 @@ object Denotations {
390390
case tp2: TypeBounds if tp2 contains tp1 => tp1
391391
case _ => mergeConflict(tp1, tp2, that)
392392
}
393-
case tp1: MethodOrPoly =>
393+
394+
// Two remedial strategies:
395+
//
396+
// 1. Prefer method types over poly types. This is necessary to handle
397+
// overloaded definitions like the following
398+
//
399+
// def ++ [B >: A](xs: C[B]): D[B]
400+
// def ++ (xs: C[A]): D[A]
401+
//
402+
// (Code like this is found in the collection strawman)
403+
//
404+
// 2. In the case of two method types or two polytypes with matching
405+
// parameters and implicit status, merge corresponding parameter
406+
// and result types.
407+
case tp1: MethodType =>
408+
tp2 match {
409+
case tp2: PolyType =>
410+
tp1
411+
case tp2: MethodType if ctx.typeComparer.matchingMethodParams(tp1, tp2) &&
412+
tp1.isImplicitMethod == tp2.isImplicitMethod =>
413+
tp1.derivedLambdaType(
414+
mergeParamNames(tp1, tp2),
415+
tp1.paramInfos,
416+
infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1)))
417+
case _ =>
418+
mergeConflict(tp1, tp2, that)
419+
}
420+
case tp1: PolyType =>
394421
tp2 match {
395-
case tp2: MethodOrPoly =>
396-
// Two remedial strategies:
397-
//
398-
// 1. Prefer method types over poly types. This is necessary to handle
399-
// overloaded definitions like the following
400-
//
401-
// def ++ [B >: A](xs: C[B]): D[B]
402-
// def ++ (xs: C[A]): D[A]
403-
//
404-
// (Code like this is found in the collection strawman)
405-
//
406-
// 2. In the case of two method types or two polytypes with matching
407-
// parameters and implicit status, merge corresppnding parameter
408-
// and result types.
409-
if (tp1.isInstanceOf[PolyType] && tp2.isInstanceOf[MethodType]) tp2
410-
else if (tp2.isInstanceOf[PolyType] && tp1.isInstanceOf[MethodType]) tp1
411-
else if (ctx.typeComparer.matchingParams(tp1, tp2) &&
412-
tp1.isImplicitMethod == tp2.isImplicitMethod)
413-
tp1.derivedLambdaType(
414-
mergeParamNames(tp1, tp2), tp1.paramInfos,
415-
infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1)))
416-
else mergeConflict(tp1, tp2, that)
422+
case tp2: MethodType =>
423+
tp2
424+
case tp2: PolyType if ctx.typeComparer.matchingPolyParams(tp1, tp2) =>
425+
tp1.derivedLambdaType(
426+
mergeParamNames(tp1, tp2),
427+
tp1.paramInfos.zipWithConserve(tp2.paramInfos) { (p1, p2) =>
428+
infoMeet(p1, p2.subst(tp2, tp1)).bounds
429+
},
430+
infoMeet(tp1.resultType, tp2.resultType.subst(tp2, tp1)))
417431
case _ =>
418432
mergeConflict(tp1, tp2, that)
419433
}
434+
420435
case _ =>
421436
tp1 & tp2
422437
}
@@ -566,13 +581,27 @@ object Denotations {
566581
case tp2: TypeBounds if tp2 contains tp1 => tp2
567582
case _ => mergeConflict(tp1, tp2, that)
568583
}
569-
case tp1: MethodOrPoly =>
584+
case tp1: MethodType =>
570585
tp2 match {
571-
case tp2: MethodOrPoly
572-
if ctx.typeComparer.matchingParams(tp1, tp2) &&
586+
case tp2: MethodType
587+
if ctx.typeComparer.matchingMethodParams(tp1, tp2) &&
573588
tp1.isImplicitMethod == tp2.isImplicitMethod =>
574589
tp1.derivedLambdaType(
575-
mergeParamNames(tp1, tp2), tp1.paramInfos,
590+
mergeParamNames(tp1, tp2),
591+
tp1.paramInfos,
592+
tp1.resultType | tp2.resultType.subst(tp2, tp1))
593+
case _ =>
594+
mergeConflict(tp1, tp2, that)
595+
}
596+
case tp1: PolyType =>
597+
tp2 match {
598+
case tp2: PolyType
599+
if ctx.typeComparer.matchingPolyParams(tp1, tp2) =>
600+
tp1.derivedLambdaType(
601+
mergeParamNames(tp1, tp2),
602+
tp1.paramInfos.zipWithConserve(tp2.paramInfos) { (p1, p2) =>
603+
(p1 | p2.subst(tp2, tp1)).bounds
604+
},
576605
tp1.resultType | tp2.resultType.subst(tp2, tp1))
577606
case _ =>
578607
mergeConflict(tp1, tp2, that)

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

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -534,13 +534,20 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
534534
either(recur(tp1, tp21), recur(tp1, tp22)) || fourthTry
535535
case tp2: MethodType =>
536536
def compareMethod = tp1 match {
537-
case tp1: MethodType => compareMethodOrPoly(tp1, tp2)
537+
case tp1: MethodType =>
538+
(tp1.signature consistentParams tp2.signature) &&
539+
matchingMethodParams(tp1, tp2) &&
540+
(!tp2.isImplicitMethod || tp1.isImplicitMethod) &&
541+
isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1))
538542
case _ => false
539543
}
540544
compareMethod
541545
case tp2: PolyType =>
542546
def comparePoly = tp1 match {
543-
case tp1: PolyType => compareMethodOrPoly(tp1, tp2)
547+
case tp1: PolyType =>
548+
(tp1.signature consistentParams tp2.signature) &&
549+
matchingPolyParams(tp1, tp2) &&
550+
isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1))
544551
case _ => false
545552
}
546553
comparePoly
@@ -582,12 +589,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
582589
fourthTry
583590
}
584591

585-
def compareMethodOrPoly(tp1: MethodOrPoly, tp2: MethodOrPoly) =
586-
(tp1.signature consistentParams tp2.signature) &&
587-
matchingParams(tp1, tp2) &&
588-
(!tp2.isImplicitMethod || tp1.isImplicitMethod) &&
589-
isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1))
590-
591592
def fourthTry: Boolean = tp1 match {
592593
case tp1: TypeRef =>
593594
tp1.info match {
@@ -1148,7 +1149,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
11481149
tp2.widen match {
11491150
case tp2: MethodType =>
11501151
// implicitness is ignored when matching
1151-
matchingParams(tp1, tp2) &&
1152+
matchingMethodParams(tp1, tp2) &&
11521153
matchesType(tp1.resultType, tp2.resultType.subst(tp2, tp1), relaxed)
11531154
case tp2 =>
11541155
relaxed && tp1.paramNames.isEmpty &&
@@ -1174,27 +1175,47 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
11741175
}
11751176
}
11761177

1177-
/** Do lambda types `lam1` and `lam2` have parameters that have the same types
1178-
* and the same implicit status? (after renaming one set to the other)
1178+
/** Do the parameter types of `tp1` and `tp2` match in a way that allows `tp1`
1179+
* to override `tp2` ? This is the case if they're pairwise =:=, as a special
1180+
* case, we allow `Any` in Java methods to match `Object`.
11791181
*/
1180-
def matchingParams(lam1: MethodOrPoly, lam2: MethodOrPoly): Boolean = {
1181-
/** Are `syms1` and `syms2` parameter lists with pairwise equivalent types? */
1182+
def matchingMethodParams(tp1: MethodType, tp2: MethodType): Boolean = {
11821183
def loop(formals1: List[Type], formals2: List[Type]): Boolean = formals1 match {
11831184
case formal1 :: rest1 =>
11841185
formals2 match {
11851186
case formal2 :: rest2 =>
1186-
val formal2a = if (lam2.isParamDependent) formal2.subst(lam2, lam1) else formal2
1187+
val formal2a = if (tp2.isParamDependent) formal2.subst(tp2, tp1) else formal2
11871188
(isSameTypeWhenFrozen(formal1, formal2a)
1188-
|| lam1.isJavaMethod && (formal2 isRef ObjectClass) && (formal1 isRef AnyClass)
1189-
|| lam2.isJavaMethod && (formal1 isRef ObjectClass) && (formal2 isRef AnyClass)) &&
1189+
|| tp1.isJavaMethod && (formal2 isRef ObjectClass) && (formal1 isRef AnyClass)
1190+
|| tp2.isJavaMethod && (formal1 isRef ObjectClass) && (formal2 isRef AnyClass)) &&
1191+
loop(rest1, rest2)
1192+
case nil =>
1193+
false
1194+
}
1195+
case nil =>
1196+
formals2.isEmpty
1197+
}
1198+
loop(tp1.paramInfos, tp2.paramInfos)
1199+
}
1200+
1201+
/** Do the parameter types of `tp1` and `tp2` match in a way that allows `tp1`
1202+
* to override `tp2` ? This is the case if they're pairwise >:>.
1203+
*/
1204+
def matchingPolyParams(tp1: PolyType, tp2: PolyType): Boolean = {
1205+
def loop(formals1: List[Type], formals2: List[Type]): Boolean = formals1 match {
1206+
case formal1 :: rest1 =>
1207+
formals2 match {
1208+
case formal2 :: rest2 =>
1209+
val formal2a = formal2.subst(tp2, tp1)
1210+
isSubTypeWhenFrozen(formal2a, formal1) &&
11901211
loop(rest1, rest2)
11911212
case nil =>
11921213
false
11931214
}
11941215
case nil =>
11951216
formals2.isEmpty
11961217
}
1197-
loop(lam1.paramInfos, lam2.paramInfos)
1218+
loop(tp1.paramInfos, tp2.paramInfos)
11981219
}
11991220

12001221
// Type equality =:=

tests/neg/poly-override.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
class Super {
2+
def a[T] = {}
3+
def b[T](x: String) = {}
4+
def c[F[_ <: String]] = {}
5+
def c1[F[_ >: String]] = {}
6+
def d[F[_]] = {}
7+
8+
def m(x: String) = {}
9+
def n[T](x: String) = {}
10+
}
11+
class Sub1 extends Super {
12+
override def a[T <: String] = {} // error
13+
override def b[T <: String](x: String) = {} // error
14+
override def c[F[_]] = {} // error
15+
override def c1[F[_]] = {} // error
16+
override def d[F[+_]] = {} // error
17+
18+
override def m(x: Any) = {} // error
19+
override def n[T](x: Any) = {} // error
20+
}
21+
22+
class Sub2 extends Super {
23+
override def a[T >: String] = {} // error
24+
override def b[T >: String](x: String) = {} // error
25+
}

tests/pos/poly-override.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class Super {
2+
def a[T <: String] = {}
3+
def b[T <: String](x: String) = {}
4+
def c[F[_]] = {}
5+
def d[F[+_]] = {}
6+
}
7+
class Sub1 extends Super {
8+
override def a[T <: AnyRef] = {}
9+
override def b[T <: AnyRef](x: String) = {}
10+
override def c[F[_ <: String]] = {}
11+
override def d[F[_]] = {}
12+
}
13+
class Sub2 extends Super {
14+
override def c[F[_ >: String]] = {}
15+
}

0 commit comments

Comments
 (0)