Skip to content

Disallow dependent implicit methods. #1467

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

Closed
wants to merge 3 commits into from
Closed
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
9 changes: 9 additions & 0 deletions src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,14 @@ trait Checking {
case _ =>
}

/** Check that result type does not refer any of the parameters in `vparams`.
*/
def checkNotDependent(resTpt: Tree, vparams: List[Symbol])(implicit ctx: Context): Unit =
for (vparam <- vparams)
if (!vparam.is(Implicit) && vparam.termRef.occursIn(resTpt.tpe))
ctx.errorOrMigrationWarning(
em"implicit method's result type may not depend on parameter ${vparam.name}", resTpt.pos)

/** Check that any top-level type arguments in this type are feasible, i.e. that
* their lower bound conforms to their upper bound. If a type argument is
* infeasible, issue and error and continue with upper bound.
Expand Down Expand Up @@ -544,6 +552,7 @@ trait NoChecking extends Checking {
override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()
override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp
override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = ()
override def checkNotDependent(resTpt: Tree, vparams: List[Symbol])(implicit ctx: Context): Unit = ()
override def checkFeasible(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp
override def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = ()
override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = ()
Expand Down
9 changes: 7 additions & 2 deletions src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1100,7 +1100,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
completeAnnotations(ddef, sym)
val tparams1 = tparams mapconserve (typed(_).asInstanceOf[TypeDef])
val vparamss1 = vparamss nestedMapconserve (typed(_).asInstanceOf[ValDef])
if (sym is Implicit) checkImplicitParamsNotSingletons(vparamss1)
var tpt1 = checkSimpleKinded(typedType(tpt))

var rhsCtx = ctx
Expand All @@ -1113,12 +1112,18 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef)))
}
val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx)
def vparamSyms = vparamss1.flatMap(_.map(_.symbol))
if (sym.isAnonymousFunction) {
// If we define an anonymous function, make sure the return type does not
// refer to parameters. This is necessary because closure types are
// function types so no dependencies on parameters are allowed.
tpt1 = tpt1.withType(avoid(tpt1.tpe, vparamss1.flatMap(_.map(_.symbol))))
tpt1 = tpt1.withType(avoid(tpt1.tpe, vparamSyms))
}
else if (sym is Implicit) {
checkImplicitParamsNotSingletons(vparamss1)
checkNotDependent(tpt1, vparamSyms)
}

assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym)
//todo: make sure dependent method types do not depend on implicits or by-name params
}
Expand Down
7 changes: 7 additions & 0 deletions tests/neg/dependent-implicits.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
object Test {
trait T { type X; val x: X }
implicit def f(x: T): x.X = x.x // error
val t = new T { type X = String; val x = "" }
val x: String = t
val uy: String = f(t)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object Test{
// def apply[S: ZipWith](s : S) = ?[ZipWith[S]].zipWith(s) // TODO: bug return type should be inferred
def apply[S](s : S)(implicit zw: ZipWith[S]): zw.T = zw.zipWith(s)

implicit def SuccZipWith[S,R](implicit zWith : ZipWith[R]): Test.ZipWith[S => R]{type T = Stream[S] => zWith.T} = new ZipWith[S => R] {
implicit def SuccZipWith[S,R](implicit zWith : ZipWith[R]): Test.ZipWith[S => R]{type T = Stream[S] => zWith.T} = new ZipWith[S => R] { // error: may not be dependent
type T = Stream[S] => zWith.T // dependent types replace the associated types functionality
}
}
Expand All @@ -25,5 +25,5 @@ object Test{
// bug: inferred return type = (Stream[A]) => java.lang.Object with Test.ZipWith[B]{type T = Stream[B]}#T
// this seems incompatible with vvvvvvvvvvvvvvvvvvvvvv -- #3731
def map[A,B](f : A => B) /* : Stream[A] => Stream[B]*/ = ZipWith(f)
val tst: Stream[Int] = map{x: String => x.length}(Stream("a"))
val tst: Stream[Int] = map{x: String => x.length}(Stream("a")) // error // error
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,5 @@ object Sessions {
def runSession[S, D: Session[S]#HasDual](p: S, dp: D) =
implicitly[Session[S]#HasDual[D]].run(p, dp)

// def runSession[S, D](p: S, dp: D)(implicit s: Session[S]#HasDual[D]) =
// s.run(p, dp)
//
// def runSession[S, D](p: S, dp: D)(implicit s: Session[S]{type Dual=D}) =
// s.run(p, dp)

// TODO: can we relax the ordering restrictions on dependencies so that we can use
// def runSession[S](p: S, dp: s.Dual)(implicit s: Session[S]) =
// s.run(p, dp)
// to emphasise similarity of type parameters and implicit arguments:
// def runSession[S][val s: Session[S]](p: S, dp: s.Dual) =
// s.run(p, dp)

def myRun = runSession(addServer, addClient)
}
14 changes: 0 additions & 14 deletions tests/pending/pos/depsel.scala

This file was deleted.

15 changes: 15 additions & 0 deletions tests/pos/t5070.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
trait A {
type T
}

object O {
implicit def b(implicit x: A): x.T = error("")
}

class Test {
import O._
implicit val a: A = new A {}
implicitly[a.T] // works in scalac, not in dotty

implicitly[a.T](b(a)) // works
}