Skip to content

Fix #5202: Refine argForParam #5214

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

Merged
merged 5 commits into from
Oct 26, 2018
Merged
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
42 changes: 30 additions & 12 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1988,7 +1988,8 @@ object Types {
}

/** The argument corresponding to class type parameter `tparam` as seen from
* prefix `pre`.
* prefix `pre`. Can produce a TypeBounds type in case prefix is an & or | type
* and parameter is non-variant.
*/
def argForParam(pre: Type)(implicit ctx: Context): Type = {
val tparam = symbol
Expand All @@ -2010,8 +2011,16 @@ object Types {
idx += 1
}
NoType
case OrType(base1, base2) => argForParam(base1) | argForParam(base2)
case AndType(base1, base2) => argForParam(base1) & argForParam(base2)
case base: AndOrType =>
var tp1 = argForParam(base.tp1)
var tp2 = argForParam(base.tp2)
val variance = tparam.paramVariance
if (tp1.isInstanceOf[TypeBounds] || tp2.isInstanceOf[TypeBounds] || variance == 0) {
// compute argument as a type bounds instead of a point type
tp1 = tp1.bounds
tp2 = tp2.bounds
}
if (base.isAnd == variance >= 0) tp1 & tp2 else tp1 | tp2
case _ =>
if (pre.termSymbol is Package) argForParam(pre.select(nme.PACKAGE))
else if (pre.isBottomType) pre
Expand Down Expand Up @@ -4414,6 +4423,7 @@ object Types {
protected def range(lo: Type, hi: Type): Type =
if (variance > 0) hi
else if (variance < 0) lo
else if (lo `eq` hi) lo
else Range(lower(lo), upper(hi))

protected def isRange(tp: Type): Boolean = tp.isInstanceOf[Range]
Expand Down Expand Up @@ -4462,13 +4472,18 @@ object Types {
* If the expansion is a wildcard parameter reference, convert its
* underlying bounds to a range, otherwise return the expansion.
*/
def expandParam(tp: NamedType, pre: Type): Type = tp.argForParam(pre) match {
case arg @ TypeRef(pre, _) if pre.isArgPrefixOf(arg.symbol) =>
arg.info match {
case TypeBounds(lo, hi) => range(atVariance(-variance)(reapply(lo)), reapply(hi))
case arg => reapply(arg)
}
case arg => reapply(arg)
def expandParam(tp: NamedType, pre: Type): Type = {
def expandBounds(tp: TypeBounds) =
range(atVariance(-variance)(reapply(tp.lo)), reapply(tp.hi))
tp.argForParam(pre) match {
case arg @ TypeRef(pre, _) if pre.isArgPrefixOf(arg.symbol) =>
arg.info match {
case argInfo: TypeBounds => expandBounds(argInfo)
case argInfo => reapply(arg)
}
case arg: TypeBounds => expandBounds(arg)
case arg => reapply(arg)
}
}

/** Derived selection.
Expand All @@ -4482,9 +4497,12 @@ object Types {
if (tp.symbol.is(ClassTypeParam)) expandParam(tp, preHi)
else tryWiden(tp, preHi)
forwarded.orElse(
range(super.derivedSelect(tp, preLo), super.derivedSelect(tp, preHi)))
range(super.derivedSelect(tp, preLo).loBound, super.derivedSelect(tp, preHi).hiBound))
case _ =>
super.derivedSelect(tp, pre)
super.derivedSelect(tp, pre) match {
case TypeBounds(lo, hi) => range(lo, hi)
case tp => tp
}
}

override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type): Type =
Expand Down
29 changes: 29 additions & 0 deletions tests/neg/i5202.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
object Test {
val f: (Int => Int) | (String => Int) = (a: Int) => a + 3

f.apply(5) // error - found: Int expected: Int & String
f("c") // error - found: String expected: Int & String
}

class Foo[A] {
def foo(a: A): Unit = {}
}
class Co[+A] {
def foo(a: A): Unit = {} // error: contravariant occurs in covariant position
def bar: A = ???
}
class Contra[-A] {
def foo(a: A): Unit = {}
}

object Test2 {
def main(args: Array[String]): Unit = {
val x: Foo[Int] | Foo[String] = new Foo[Int]
x.foo("") // error, found: String, required: Int & String
val y: Contra[Int] | Contra[String] = new Contra[Int]
y.foo("") // error, found: String, required: Int & String
val z: Co[Int] | Co[String] = new Co[Int]
z.foo("") // OK
val s: String = z.bar // error: found Int | String, required: String
}
}
Copy link
Member

@smarter smarter Oct 16, 2018

Choose a reason for hiding this comment

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

More examples would be nice, e.g. I had one at #5202 (comment), which could be adapted to try every combination of (covariant, invariant, contravariant) x (and, or)