Skip to content

Commit 3a18ff2

Browse files
committed
Plug loophole of unsound refinements
1 parent 68cec5b commit 3a18ff2

File tree

5 files changed

+68
-54
lines changed

5 files changed

+68
-54
lines changed

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

Lines changed: 57 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,58 +1698,68 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
16981698
*/
16991699
protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean =
17001700
trace(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}), mbr: ${tp1.member(name).info}", subtyping) {
1701-
// If the member is an abstract type and the prefix is a path, compare the member itself
1702-
// instead of its bounds. This case is needed situations like:
1703-
//
1704-
// class C { type T }
1705-
// val foo: C
1706-
// foo.type <: C { type T {= , <: , >:} foo.T }
1707-
//
1708-
// or like:
1709-
//
1710-
// class C[T]
1711-
// C[?] <: C[TV]
1712-
//
1713-
// where TV is a type variable. See i2397.scala for an example of the latter.
1714-
def matchAbstractTypeMember(info1: Type) = info1 match {
1715-
case TypeBounds(lo, hi) if lo ne hi =>
1716-
tp2.refinedInfo match {
1717-
case rinfo2: TypeBounds if tp1.isStable =>
1718-
val ref1 = tp1.widenExpr.select(name)
1719-
isSubType(rinfo2.lo, ref1) && isSubType(ref1, rinfo2.hi)
1720-
case _ =>
1721-
false
1722-
}
1723-
case _ => false
1724-
}
17251701

1726-
// A relaxed version of isSubType, which compares method types
1727-
// under the standard arrow rule which is contravarient in the parameter types,
1728-
// but only if `tp2.refinedName` is also defined in the underlying class of tp2.
1729-
// The reason for the "but only" retriction is that if `tp2.refinedName`
1730-
// is not otherwise defined, we will have to resort to reflection to invoke
1731-
// the member. And reflection needs to know exact parameter types. The relaxation is
1732-
// needed to correctly compare dependent function types.
1733-
// See {pos,neg}/i12211.scala as test cases.
1734-
def isSubInfo(info1: Type, info2: Type): Boolean =
1735-
info2 match
1736-
case info2: MethodType
1737-
if tp2.underlyingClassRef(refinementOK = true).member(tp2.refinedName).exists =>
1738-
info1 match
1739-
case info1: MethodType =>
1740-
matchingMethodParams(info1, info2, precise = false)
1741-
&& isSubInfo(info1.resultType, info2.resultType.subst(info2, info1))
1742-
case _ => isSubType(info1, info2)
1743-
case _ => isSubType(info1, info2)
1744-
1745-
def qualifies(m: SingleDenotation) =
1746-
isSubInfo(m.info.widenExpr, tp2.refinedInfo.widenExpr)
1702+
def qualifies(m: SingleDenotation): Boolean =
1703+
// If the member is an abstract type and the prefix is a path, compare the member itself
1704+
// instead of its bounds. This case is needed situations like:
1705+
//
1706+
// class C { type T }
1707+
// val foo: C
1708+
// foo.type <: C { type T {= , <: , >:} foo.T }
1709+
//
1710+
// or like:
1711+
//
1712+
// class C[T]
1713+
// C[?] <: C[TV]
1714+
//
1715+
// where TV is a type variable. See i2397.scala for an example of the latter.
1716+
def matchAbstractTypeMember(info1: Type): Boolean = info1 match {
1717+
case TypeBounds(lo, hi) if lo ne hi =>
1718+
tp2.refinedInfo match {
1719+
case rinfo2: TypeBounds if tp1.isStable =>
1720+
val ref1 = tp1.widenExpr.select(name)
1721+
isSubType(rinfo2.lo, ref1) && isSubType(ref1, rinfo2.hi)
1722+
case _ =>
1723+
false
1724+
}
1725+
case _ => false
1726+
}
1727+
1728+
// An additional check for type member matching: If the refinement of the
1729+
// supertype `tp2` does not refer to a member symbol defined in the parent of `tp2`.
1730+
// then the symbol referred to in the subtype must have a signature that coincides
1731+
// in its parameters with the refinement's signature. The reason for the check
1732+
// is that if the refinement does not refer to a member symbol, we will have to
1733+
// resort to reflection to invoke the member. And reflection needs to know exact
1734+
// erased parameter types. See neg/i12211.scala.
1735+
def sigsOK(symInfo: Type, info2: Type) =
1736+
tp2.underlyingClassRef(refinementOK = true).member(name).exists
1737+
|| symInfo.isInstanceOf[MethodType]
1738+
&& symInfo.signature.consistentParams(info2.signature)
1739+
1740+
// A relaxed version of isSubType, which compares method types
1741+
// under the standard arrow rule which is contravarient in the parameter types,
1742+
// but under the condition that signatures might have to match (see sigsOK)
1743+
// This releaxed version is needed to correctly compare dependent function types.
1744+
// See pos/i12211.scala.
1745+
def isSubInfo(info1: Type, info2: Type, symInfo: Type): Boolean =
1746+
info2 match
1747+
case info2: MethodType =>
1748+
info1 match
1749+
case info1: MethodType =>
1750+
matchingMethodParams(info1, info2, precise = false)
1751+
&& isSubInfo(info1.resultType, info2.resultType.subst(info2, info1), symInfo.stripPoly.resultType)
1752+
&& sigsOK(symInfo, info2)
1753+
case _ => isSubType(info1, info2)
1754+
case _ => isSubType(info1, info2)
1755+
1756+
isSubInfo(m.info.widenExpr, tp2.refinedInfo.widenExpr, m.symbol.info)
17471757
|| matchAbstractTypeMember(m.info)
1758+
end qualifies
17481759

1749-
tp1.member(name) match { // inlined hasAltWith for performance
1760+
tp1.member(name) match // inlined hasAltWith for performance
17501761
case mbr: SingleDenotation => qualifies(mbr)
17511762
case mbr => mbr hasAltWith qualifies
1752-
}
17531763
}
17541764

17551765
final def ensureStableSingleton(tp: Type): SingletonType = tp.stripTypeVar match {

tasty/test/dotty/tools/tasty/TastyHeaderUnpicklerTest.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ object TastyHeaderUnpicklerTest {
5757
buf.writeNat(exp)
5858
buf.writeNat(compilerBytes.length)
5959
buf.writeBytes(compilerBytes, compilerBytes.length)
60-
buf.writeUncompressedLong(237478l)
61-
buf.writeUncompressedLong(324789l)
60+
buf.writeUncompressedLong(237478L)
61+
buf.writeUncompressedLong(324789L)
6262
buf
6363
}
6464

tests/neg/i12211.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
import reflect.Selectable.*
33

44
val x: { def f(x: Any): String } = new { def f(x: Any) = x.toString }
5-
val y: { def f(x: String): String } = x // error: type mismatch (no arrow rule since `f` is not defined in parent)
5+
val y: { def f(x: String): String } = x // error: type mismatch (different signatures)
6+
7+
class Sink[A] { def put(x: A): Unit = {} }
68

79
@main def Test =
810
println(y.f("abc"))
9-
11+
val a = new Sink[String]
12+
val b: { def put(x: String): Unit } = a // error: type mismatch (different signatures)
13+
b.put("") // gave a NoSuchMethodException: Sink.put(java.lang.String)

tests/run/structuralNoSuchMethod.scala renamed to tests/neg/structuralNoSuchMethod.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ object Test {
1111
def f(x: X, y: String): String = "f1"
1212
}
1313

14-
val x: T = new C[String]
14+
val x: T = new C[String] // error
1515

1616
def main(args: Array[String]) =
17-
try println(x.f("", "")) // throws NoSuchMethodException
17+
try println(x.f("", "")) // used to throw NoSuchMethodException
1818
catch {
1919
case ex: NoSuchMethodException =>
2020
println("no such method")

tests/run/enum-values.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ enum ClassOnly: // this should still generate the `ordinal` and `fromOrdinal` co
5050
s"$c does not `eq` companion.fromOrdinal(${c.ordinal}), got ${companion.fromOrdinal(c.ordinal)}")
5151

5252
def notFromOrdinal[T <: AnyRef & reflect.Enum](companion: FromOrdinal[T], compare: T): Unit =
53-
cantFind(companion, compare.ordinal)
53+
cantFind(companion.asInstanceOf[FromOrdinal[Any]], compare.ordinal)
5454

5555
def cantFind[T](companion: FromOrdinal[T], ordinal: Int): Unit =
5656
try

0 commit comments

Comments
 (0)