Skip to content

Fix #4382: Disallow wildcard arguments for higher-kinded types #4423

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 4 commits into from
May 1, 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
33 changes: 25 additions & 8 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ object desugar {
else {
def msg =
s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope.toList}"
ErrorType(msg).assertingErrorsReported(msg)
}
ErrorType(msg).assertingErrorsReported(msg)
}
case _ =>
mapOver(tp)
}
Expand Down Expand Up @@ -366,6 +366,12 @@ object desugar {
(if (args.isEmpty) tycon else AppliedTypeTree(tycon, args))
.withPos(cdef.pos.startPos)

def isHK(tparam: Tree): Boolean = tparam match {
case TypeDef(_, LambdaTypeTree(tparams, body)) => true
Copy link
Member

Choose a reason for hiding this comment

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

I don't think that's enough in general, e.g. enum Foo[F <: [X] => Any], but maybe we're ok with that since that's only a temporary solution.

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 think the right solution cannot be done at Desugar, it needs full type information.

case TypeDef(_, rhs: DerivedTypeTree) => isHK(rhs.watched)
case _ => false
}

def appliedRef(tycon: Tree, tparams: List[TypeDef] = constrTparams, widenHK: Boolean = false) = {
val targs = for (tparam <- tparams) yield {
val targ = refOfDef(tparam)
Expand Down Expand Up @@ -467,18 +473,29 @@ object desugar {
// ev1: Eq[T1$1, T1$2], ..., evn: Eq[Tn$1, Tn$2]])
// : Eq[C[T1$, ..., Tn$1], C[T1$2, ..., Tn$2]] = Eq
//
// If any of the T_i are higher-kinded, say `Ti[X1 >: L1 <: U1, ..., Xm >: Lm <: Um]`,
// the corresponding type parameters for $ev_i are `Ti$1[_, ..., _], Ti$2[_, ..., _]`
// (with m underscores `_`).
// Higher-kinded type arguments `Ti` are omitted as evidence parameters.
//
// FIXME: This is too simplistic. Instead of just generating evidence arguments
// for every first-kinded type parameter, we should look instead at the
// actual types occurring in cases and derive parameters from these. E.g. in
//
// enum HK[F[_]] {
// case C1(x: F[Int]) extends HK[F[Int]]
// case C2(y: F[String]) extends HL[F[Int]]
//
// we would need evidence parameters for `F[Int]` and `F[String]`
// We should generate Eq instances with the techniques
// of typeclass derivation once that is available.
def eqInstance = {
val leftParams = constrTparams.map(derivedTypeParam(_, "$1"))
val rightParams = constrTparams.map(derivedTypeParam(_, "$2"))
val subInstances = (leftParams, rightParams).zipped.map((param1, param2) =>
appliedRef(ref(defn.EqType), List(param1, param2), widenHK = true))
val subInstances =
for ((param1, param2) <- leftParams `zip` rightParams if !isHK(param1))
yield appliedRef(ref(defn.EqType), List(param1, param2), widenHK = true)
DefDef(
name = nme.eqInstance,
tparams = leftParams ++ rightParams,
vparamss = List(makeImplicitParameters(subInstances)),
vparamss = if (subInstances.isEmpty) Nil else List(makeImplicitParameters(subInstances)),
tpt = appliedTypeTree(ref(defn.EqType),
appliedRef(classTycon, leftParams) :: appliedRef(classTycon, rightParams) :: Nil),
rhs = ref(defn.EqModule.termRef)).withFlags(Synthetic | Implicit)
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import Symbols._
import Contexts._
import Decorators.PreNamedString
import util.NameTransformer
import scala.collection.breakOut

object StdNames {

Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import io.AbstractFile
import language.implicitConversions
import util.{NoSource, DotClass, Property}
import scala.collection.JavaConverters._
import config.Printers.typr

/** Creation methods for symbols */
trait Symbols { this: Context =>
Expand Down Expand Up @@ -234,8 +235,8 @@ trait Symbols { this: Context =>
def newStubSymbol(owner: Symbol, name: Name, file: AbstractFile = null): Symbol = {
def stubCompleter = new StubInfo()
val normalizedOwner = if (owner is ModuleVal) owner.moduleClass else owner
println(s"creating stub for ${name.show}, owner = ${normalizedOwner.denot.debugString}, file = $file")
println(s"decls = ${normalizedOwner.unforcedDecls.toList.map(_.debugString).mkString("\n ")}") // !!! DEBUG
typr.println(s"creating stub for ${name.show}, owner = ${normalizedOwner.denot.debugString}, file = $file")
typr.println(s"decls = ${normalizedOwner.unforcedDecls.toList.map(_.debugString).mkString("\n ")}") // !!! DEBUG
//if (base.settings.debug.value) throw new Error()
val stub = name match {
case name: TermName =>
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
case EtaExpansion(tycon1) => recur(tycon1, tp2)
case _ => tp2 match {
case tp2: HKTypeLambda => false // this case was covered in thirdTry
case _ => tp2.isLambdaSub && isSubType(tp1.resultType, tp2.appliedTo(tp1.paramRefs))
case _ => tp2.typeParams.hasSameLengthAs(tp1.paramRefs) && isSubType(tp1.resultType, tp2.appliedTo(tp1.paramRefs))
}
}
compareHKLambda
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 @@ -29,7 +29,7 @@ import dotty.tools.dotc.transform.Erasure
import printing.Printer
import Hashable._
import Uniques._
import collection.{mutable, Seq, breakOut}
import collection.{mutable, Seq}
import config.Config
import annotation.tailrec
import Flags.FlagSet
Expand Down
14 changes: 5 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,11 @@ object Checking {
if (boundsCheck) checkBounds(orderedArgs, bounds, instantiate)

def checkWildcardApply(tp: Type, pos: Position): Unit = tp match {
case tp @ AppliedType(tycon, args) if args.exists(_.isInstanceOf[TypeBounds]) =>
tycon match {
case tycon: TypeLambda =>
ctx.errorOrMigrationWarning(
ex"unreducible application of higher-kinded type $tycon to wildcard arguments",
pos)
case _ =>
checkWildcardApply(tp.superType, pos)
}
case tp @ AppliedType(tycon, args) =>
if (tycon.isLambdaSub && args.exists(_.isInstanceOf[TypeBounds]))
ctx.errorOrMigrationWarning(
ex"unreducible application of higher-kinded type $tycon to wildcard arguments",
pos)
case _ =>
}
def checkValidIfApply(implicit ctx: Context): Unit =
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i3976.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Test {
A == A
A == (B: Hoge[_])

A == B // error: cannot be compared
A == B // should be error: cannot be compared, needs proper typeclass drivation of `Eq` to get there.

class C

Expand Down
13 changes: 13 additions & 0 deletions tests/neg/i4382.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
object App {
type Id[A] >: A <: A
def v1: Id[_] = ??? // error

type HkL[A] >: A
def v2: HkL[_] = ??? // error

type HkU[A] <: A
def v3: HkU[_] = ??? // error

type HkAbs[A]
def v4: HkAbs[_] = ??? // error
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.