Skip to content

Fix #4884: Fix handling of bounds of type lambdas #4902

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 8 commits into from
Aug 8, 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
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def seq(stats: List[Tree], expr: Tree)(implicit ctx: Context): Tree =
if (stats.isEmpty) expr
else expr match {
case Block(estats, eexpr) => cpy.Block(expr)(stats ::: estats, eexpr)
case Block(estats, eexpr) =>
cpy.Block(expr)(stats ::: estats, eexpr).withType(ta.avoidingType(eexpr, stats))
case _ => Block(stats, expr)
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/ParamInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ trait ParamInfo {
* For type lambda parameters, it's the same as `paramInfos` as
* `asSeenFrom` has already been applied to the whole type lambda.
*/
def paramInfoAsSeenFrom(pre: Type)(implicit ctx: Context): Type
def paramInfoAsSeenFrom(prefix: Type)(implicit ctx: Context): Type

/** The parameter bounds, or the completer if the type parameter
* is an as-yet uncompleted symbol.
Expand Down
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,18 @@ class TypeApplications(val self: Type) extends AnyVal {
* any type parameter that is-rebound by the refinement.
*/
final def typeParams(implicit ctx: Context): List[TypeParamInfo] = /*>|>*/ track("typeParams") /*<|<*/ {
def isTrivial(prefix: Type, tycon: Symbol) = prefix match {
case prefix: ThisType => prefix.cls `eq` tycon.owner
case NoPrefix => true
case _ => false
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we further refine isTrivial to cover the case when prefix is static?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure we want to do this. The purpose of isTrivial is to fall back to a special case to avoid cyclic reference errors. It seems we don't need to include static this types to achieve this. On the other hand, the isStatic test could prompt cyclic references itself.

try self match {
case self: TypeRef =>
val tsym = self.symbol
if (tsym.isClass) tsym.typeParams
else if (!tsym.exists) self.info.typeParams
else tsym.infoOrCompleter match {
case info: LazyType => info.completerTypeParams(tsym)
case info => info.typeParams
case info: LazyType if isTrivial(self.prefix, tsym) => info.completerTypeParams(tsym)
case _ => self.info.typeParams
}
case self: AppliedType =>
if (self.tycon.typeSymbol.isClass) Nil
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3195,7 +3195,7 @@ object Types {
def isTypeParam(implicit ctx: Context) = tl.paramNames.head.isTypeName
def paramName(implicit ctx: Context) = tl.paramNames(n)
def paramInfo(implicit ctx: Context) = tl.paramInfos(n)
def paramInfoAsSeenFrom(pre: Type)(implicit ctx: Context) = paramInfo.asSeenFrom(pre, pre.classSymbol)
def paramInfoAsSeenFrom(pre: Type)(implicit ctx: Context) = paramInfo
def paramInfoOrCompleter(implicit ctx: Context): Type = paramInfo
def paramVariance(implicit ctx: Context): Int = tl.paramNames(n).variance
def paramRef(implicit ctx: Context): Type = tl.paramRefs(n)
Expand Down
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1238,9 +1238,10 @@ class Typer extends Namer
args = args.take(tparams.length)
Copy link
Contributor

@Blaisorblade Blaisorblade Aug 7, 2018

Choose a reason for hiding this comment

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

The test failure seems genuinely to appear with this new commit (not with my changes) — everything works with the parent.

sbt:dotty> dotc -d out -Ycheck:all tests/pos/t1786-cycle.scala -Yforce-sbt-phases
[warn] Multiple main classes detected.  Run 'show discoveredMainClasses' to see the list
[info] Running (fork) dotty.tools.dotc.Main -classpath /Users/pgiarrusso/git/dotty/library/../out/bootstrap/dotty-library-bootstrapped/scala-0.10/dotty-library_0.10-0.10.0-bin-SNAPSHOT.jar -d out -Ycheck:all tests/pos/t1786-cycle.scala -Yforce-sbt-phases
checking tests/pos/t1786-cycle.scala after phase frontend
exception occurred while compiling tests/pos/t1786-cycle.scala
Exception in thread "main" dotty.tools.dotc.core.TypeError: bad parameter reference LongTraversableLike#Repr at sbt-deps
the parameter is type Repr in trait LongTraversableLike but the prefix LongTraversableLike
does not define any corresponding arguments.
	at dotty.tools.dotc.core.Types$NamedType.argDenot(Types.scala:1834)
	at dotty.tools.dotc.core.Types$NamedType.fromDesignator$1(Types.scala:1751)
	at dotty.tools.dotc.core.Types$NamedType.computeDenot(Types.scala:1769)
	at dotty.tools.dotc.core.Types$NamedType.denot(Types.scala:1725)
	at dotty.tools.dotc.core.Types$NamedType.info(Types.scala:1714)

EDIT: cut stacktrace.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is fixed in the latest commit.

}
def typedArg(arg: untpd.Tree, tparam: ParamInfo) = {
def tparamBounds = tparam.paramInfoAsSeenFrom(tpt1.tpe.appliedTo(tparams.map(_ => TypeBounds.empty)))
val (desugaredArg, argPt) =
if (ctx.mode is Mode.Pattern)
(if (untpd.isVarPattern(arg)) desugar.patternVar(arg) else arg, tparam.paramInfo)
(if (untpd.isVarPattern(arg)) desugar.patternVar(arg) else arg, tparamBounds)
else
(arg, WildcardType)
if (tpt1.symbol.isClass)
Expand All @@ -1259,10 +1260,10 @@ class Typer extends Namer
// An unbounded `_` automatically adapts to type parameter bounds. This means:
// If we have wildcard application C[_], where `C` is a class replace
// with C[_ >: L <: H] where `L` and `H` are the bounds of the corresponding
// type parameter in `C`, avoiding any referemces to parameters of `C`.
// The transform does not apply for patters, where empty bounds translate to
// type parameter in `C`.
// The transform does not apply for patterns, where empty bounds translate to
// wildcard identifiers `_` instead.
res = res.withType(avoid(tparam.paramInfo, tpt1.tpe.typeParamSymbols))
res = res.withType(tparamBounds)
Copy link
Contributor

Choose a reason for hiding this comment

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

IIUC, avoiding C's parameters was a hack, and substituting them with asSeenFrom was always the correct fix? Makes sense to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I believe that's correct.

case _ =>
}
res
Expand Down
48 changes: 48 additions & 0 deletions tests/pos/i4884.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
object Test {
trait A
trait B
trait TestConstructor1 { type F[X <: A] <: TestConstructor2[A] }
trait TestConstructor2[D] {
type F[_ <: D]
class G[X <: D]
trait TestConstructor3[E] {
type G[X <: D & E] <: TestConstructor2.this.F[X] & H[X]
class H[X <: D & E] {
type A <: G[X]
}
}
}
trait TestConstructor4[D] {
trait TestConstructor5[E] {
trait MSetLike[X <: D & E, This <: MSet[X] with MSetLike[X, This]]
trait MSet[X <: D & E] extends MSetLike[X, MSet[X]]
}
}

val v1: TestConstructor1 => Unit = { f =>
type P[a <: A] = f.F[a]
}

val v2: TestConstructor2[A] => Unit = { f =>
type P[a <: A] = f.F[a]
}

def f2(f: TestConstructor2[A]): Unit = {
type P[a <: A] = f.F[a]
}

type C = A & B
def f3(f: TestConstructor2[A], g: f.TestConstructor3[B]): Unit = {
type P1[a <: A] = f.F[a]
type P2[a <: A] = f.G[a]
type Q1[c <: C] = g.G[c]
type Q2[c <: C] = g.H[c]
type R1[c <: C] = f.G[c] & g.H[c]
type R2[c <: C] = f.G[c] | g.H[c]
type S1[c <: C] = ([X <: C] => f.F[X] & g.G[X])[c]
type S2[c <: C] = ([X <: C] => f.F[X] | g.G[X])[c]
}
def f3(f: TestConstructor4[A], g: f.TestConstructor5[B]): Unit = {
type P1[c <: C] = g.MSet[c]
}
}
73 changes: 58 additions & 15 deletions tests/run/Tuple.scala
Original file line number Diff line number Diff line change
@@ -1,38 +1,81 @@
import annotation.showAsInfix

object typelevel {
erased def erasedValue[T]: T = ???
class Typed[T](val value: T) { type Type = T }
}

sealed trait Tuple
object Empty extends Tuple

object Tuple {
object Empty extends Tuple
@showAsInfix
final case class *: [H, T <: Tuple](hd: H, tl: T) extends Tuple

object Tuple {
import typelevel._
type Empty = Empty.type

@showAsInfix
final case class *: [H, T <: Tuple](hd: H, tl: T) extends Tuple
class TupleOps(val xs: Tuple) extends AnyVal {
transparent def *: [H] (x: H): Tuple = new *:(x, xs)
transparent def size: Int = xs match {
case Empty => 0
case _ *: xs1 => xs1.size + 1
}
transparent def apply(n: Int): Any = xs match {
case x *: _ if n == 0 => x
case _ *: xs1 if n > 0 => xs1.apply(n - 1)
}
transparent def **: (ys: Tuple): Tuple = ys match {
case Empty => xs
case y *: ys1 => y *: (ys1 **: xs)
}
transparent def head = xs match {
case x *: _ => x
}
transparent def tail = xs match {
case _ *: xs => xs
}
}

class HListDeco(val xs: Tuple) extends AnyVal {
transparent def *: [H] (x: H): Tuple = Tuple.*:.apply(x, xs)
val emptyArray = Array[Object]()

transparent def size: Int = Tuple.size(xs)
}
transparent def toObj(t: Any) = t.asInstanceOf[Object]

transparent def size(xs: Tuple): Int = xs match {
case Empty => 0
case _ *: xs1 => size(xs1) + 1
transparent def toArray(t: Tuple): Array[Object] = t.size match {
case 0 => emptyArray
case 1 => Array(toObj(t(0)))
case 2 => Array(toObj(t(0)), toObj(t(1)))
case 3 => Array(toObj(t(0)), toObj(t(1)), toObj(t(2)))
case 4 => Array(toObj(t(0)), toObj(t(1)), toObj(t(2)), toObj(t(3)))
}

transparent implicit def hlistDeco(xs: Tuple): HListDeco = new HListDeco(xs)
transparent implicit def tupleDeco(xs: Tuple): TupleOps = new TupleOps(xs)

transparent def apply(): Tuple = Empty
transparent def apply(x1: Any): Tuple = x1 *: Empty
transparent def apply(x1: Any, x2: Any) = x1 *: x2 *: Empty
transparent def apply(x1: Any, x2: Any, x3: Any) = x1 *: x2 *: x3 *: Empty

val xs0 = Tuple()
val xs1 = Tuple(2)
val xs2 = Tuple(2, "a")
val s0 = xs0.size
val s1 = xs1.size
val s2 = xs2.size
val xs3 = Tuple(true, 1, 2.0)
transparent val s0 = xs0.size; val s0c: 0 = s0
transparent val s1 = xs1.size; val s1c: 1 = s1
transparent val s2 = xs2.size; val s2c: 2 = s2
transparent val s3 = xs3.size; val s3c: 3 = s3
val e0 = xs3(0); val e0c: Boolean = e0
val e1 = xs3(1); val e1c: Int = e1
val e2 = xs3(2); val e2c: Double = e2

val conc0 = xs0 **: xs3
val conc1 = xs3 **: xs0
val conc2 = xs2 **: xs3
val e3c: Int = conc0(1)
val e4c: Int = conc1(1)
val e5c: Int = conc2(0)
val e6c: Double = conc2(4)

}

object Test extends App
125 changes: 125 additions & 0 deletions tests/run/TupleImpl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package test {

import annotation.showAsInfix

object typelevel {
erased def erasedValue[T]: T = ???
case class Typed[T](val value: T) { type Type = T }
}

sealed trait Tuple
object Empty extends Tuple

@showAsInfix
case class *: [+H, +T <: Tuple](hd: H, tl: T) extends Tuple

object Tuple {
import typelevel._
type Empty = Empty.type

transparent def _cons[H, T <: Tuple] (x: H, xs: T): Tuple = new *:(x, xs)

transparent def _size(xs: Tuple): Int = xs match {
case Empty => 0
case _ *: xs1 => _size(xs1) + 1
}

transparent def _index(xs: Tuple, n: Int): Any = xs match {
case x *: _ if n == 0 => x
case _ *: xs1 if n > 0 => _index(xs1, n - 1)
}

class TupleOps(val xs: Tuple) extends AnyVal {

transparent def *: [H] (x: H): Tuple = new *:(x, xs)
transparent def size: Int = _size(xs)

transparent def apply(n: Int): Any = {
erased val typed = Typed(_index(xs, n))
val result = _size(xs) match {
case 1 =>
n match {
case 1 => xs.asInstanceOf[Tuple1[_]].__1
}
case 2 =>
n match {
case 1 => xs.asInstanceOf[Tuple2[_, _]].__1
case 2 => xs.asInstanceOf[Tuple2[_, _]].__2
}
case 3 =>
n match {
case 1 => xs.asInstanceOf[Tuple3[_, _, _]].__1
case 2 => xs.asInstanceOf[Tuple3[_, _, _]].__2
case 3 => xs.asInstanceOf[Tuple3[_, _, _]].__3
}
case 4 =>
n match {
case 1 => xs.asInstanceOf[Tuple4[_, _, _, _]].__1
case 2 => xs.asInstanceOf[Tuple4[_, _, _, _]].__2
case 3 => xs.asInstanceOf[Tuple4[_, _, _, _]].__3
case 4 => xs.asInstanceOf[Tuple4[_, _, _, _]].__4
}
}
result.asInstanceOf[typed.Type]
}
transparent def **: (ys: Tuple): Tuple = ys match {
case Empty => xs
case y *: ys1 => y *: (ys1 **: xs)
}
transparent def head = xs match {
case x *: _ => x
}
transparent def tail = xs match {
case _ *: xs => xs
}
}

val emptyArray = Array[Object]()

transparent def toObj(t: Any) = t.asInstanceOf[Object]

transparent def toArray(t: Tuple): Array[Object] = t.size match {
case 0 => emptyArray
case 1 => Array(toObj(t(0)))
case 2 => Array(toObj(t(0)), toObj(t(1)))
case 3 => Array(toObj(t(0)), toObj(t(1)), toObj(t(2)))
case 4 => Array(toObj(t(0)), toObj(t(1)), toObj(t(2)), toObj(t(3)))
}

transparent implicit def tupleDeco(xs: Tuple): TupleOps = new TupleOps(xs)

transparent def apply(): Tuple = Empty
transparent def apply(x1: Any): Tuple = x1 *: Empty
transparent def apply(x1: Any, x2: Any) = x1 *: x2 *: Empty
transparent def apply(x1: Any, x2: Any, x3: Any) = x1 *: x2 *: x3 *: Empty

val xs0 = Tuple()
val xs1 = Tuple(2)
val xs2 = Tuple(2, "a")
val xs3 = Tuple(true, 1, 2.0)
transparent val s0 = xs0.size; val s0c: 0 = s0
transparent val s1 = xs1.size; val s1c: 1 = s1
transparent val s2 = xs2.size; val s2c: 2 = s2
transparent val s3 = xs3.size; val s3c: 3 = s3
val e0 = xs3(0); val e0c: Boolean = e0
val e1 = xs3(1); val e1c: Int = e1
val e2 = xs3(2); val e2c: Double = e2

val conc0 = xs0 **: xs3
val conc1 = xs3 **: xs0
val conc2 = xs2 **: xs3
val e3c: Int = conc0(1)
val e4c: Int = conc1(1)
val e5c: Int = conc2(0)
val e6c: Double = conc2(4)

}

class Tuple1[+T1](val __1: T1) extends *:(__1, Empty)
class Tuple2[+T1, +T2](val __1: T1, val __2: T2) extends *:(__1, *:(__2, Empty))
class Tuple3[+T1, +T2, +T3](val __1: T1, val __2: T2, val __3: T3) extends *:(__1, *:(__2, *:(__3, Empty)))
class Tuple4[+T1, +T2, +T3, +T4](val __1: T1, val __2: T2, val __3: T3, val __4: T4) extends *:(__1, *:(__2, *:(__3, *:(__4, Empty))))

}

object Test extends App