From 099a06373a4ebba9ab1a360080aeb1be6bfddcac Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Aug 2016 19:02:00 +0200 Subject: [PATCH 1/3] Disallow dependent implicit methods. The following scala script shows that scalac does not support them very well either: scala> trait T { type X; val x: X } defined trait T scala> implicit def f(x: T): x.X = x.x warning: there was one feature warning; re-run with -feature for details f: (x: T)x.X scala> val t = new T { type X = String; val x = "" } t: T{type X = String} = $anon$1@496bc455 scala> val x: String = t :14: error: type mismatch; found : T{type X = String} required: String val x: String = t ^ scala> val x: String = f(t) x: String = "" Instead of doing a half-assed job, we should disallow such implicits completely. They are still supported under -language:Scala2, but type inference is less good than in scalac (i.e. we fail to find the right implicit more often). --- src/dotty/tools/dotc/typer/Checking.scala | 9 ++ src/dotty/tools/dotc/typer/Typer.scala | 9 +- .../not-representable}/protected-t1010.scala | 0 .../depmet_implicit_norm_ret.scala | 4 +- .../pos/depmet_implicit_oopsla_session.scala | 63 -------------- .../depmet_implicit_oopsla_session_2.scala | 87 ------------------- ...pmet_implicit_oopsla_session_simpler.scala | 44 ---------- .../pos/depmet_implicit_oopsla_zipwith.scala | 44 ---------- tests/pending/pos/depsel.scala | 14 --- .../depmet_implicit_chaining_zw.scala | 0 10 files changed, 18 insertions(+), 256 deletions(-) rename tests/{pending/pos => disabled/not-representable}/protected-t1010.scala (100%) rename tests/{pending/pos => neg}/depmet_implicit_norm_ret.scala (94%) delete mode 100644 tests/pending/pos/depmet_implicit_oopsla_session.scala delete mode 100644 tests/pending/pos/depmet_implicit_oopsla_session_2.scala delete mode 100644 tests/pending/pos/depmet_implicit_oopsla_session_simpler.scala delete mode 100644 tests/pending/pos/depmet_implicit_oopsla_zipwith.scala delete mode 100644 tests/pending/pos/depsel.scala rename tests/{pos => pos-scala2}/depmet_implicit_chaining_zw.scala (100%) diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index d77520c778cb..b8c8d68dd519 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -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.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. @@ -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) = () diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 52470ba879ec..035c570a4f69 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -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 @@ -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 } diff --git a/tests/pending/pos/protected-t1010.scala b/tests/disabled/not-representable/protected-t1010.scala similarity index 100% rename from tests/pending/pos/protected-t1010.scala rename to tests/disabled/not-representable/protected-t1010.scala diff --git a/tests/pending/pos/depmet_implicit_norm_ret.scala b/tests/neg/depmet_implicit_norm_ret.scala similarity index 94% rename from tests/pending/pos/depmet_implicit_norm_ret.scala rename to tests/neg/depmet_implicit_norm_ret.scala index 85be750b4296..e0f5ad285e83 100644 --- a/tests/pending/pos/depmet_implicit_norm_ret.scala +++ b/tests/neg/depmet_implicit_norm_ret.scala @@ -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 } } @@ -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 } diff --git a/tests/pending/pos/depmet_implicit_oopsla_session.scala b/tests/pending/pos/depmet_implicit_oopsla_session.scala deleted file mode 100644 index a9c8e56ce3d9..000000000000 --- a/tests/pending/pos/depmet_implicit_oopsla_session.scala +++ /dev/null @@ -1,63 +0,0 @@ -object Sessions { - trait Session[This] { - type Dual - type HasDual[D] = Session[This]{type Dual=D} - def run(p: This, dp: Dual): Unit - } - - implicit object StopSession extends Session[Stop] { - type Dual = Stop - - def run(p: Stop, dp: Stop): Unit = {} - } - - implicit def InDual[A, B](implicit sessionDIn: Session[B]): Sessions.Session[Sessions.In[A,B]]{type Dual = Sessions.Out[A,sessionDIn.Dual]} = - new Session[In[A, B]] { - type Dual = Out[A, sessionDIn.Dual] - - def run(p: In[A, B], dp: Dual): Unit = - sessionDIn.run(p.func(dp.x), dp.y) - } - - implicit def OutDual[A, B](implicit sessionDOut: Session[B]): Sessions.Session[Sessions.Out[A,B]]{type Dual = Sessions.In[A,sessionDOut.Dual]} = - new Session[Out[A, B]] { - type Dual = In[A, sessionDOut.Dual] - - def run(p: Out[A, B], dp: Dual): Unit = - sessionDOut.run(p.y, dp.func(p.x)) - } - - sealed case class Stop() - sealed case class In[-A, +B](func: A => B) - sealed case class Out[+A, +B](x: A, y: B) - - def addServer = - In{x: Int => - In{y: Int => System.out.println("Thinking") - Out(x + y, - Stop())}} - - def addClient = - Out(3, - Out(4, { System.out.println("Waiting") - In{z: Int => System.out.println(z) - Stop()}})) - - 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) -} diff --git a/tests/pending/pos/depmet_implicit_oopsla_session_2.scala b/tests/pending/pos/depmet_implicit_oopsla_session_2.scala deleted file mode 100644 index 29a76d5cf678..000000000000 --- a/tests/pending/pos/depmet_implicit_oopsla_session_2.scala +++ /dev/null @@ -1,87 +0,0 @@ -object Sessions { - def ?[T <: AnyRef](implicit w: T): w.type = w - - // session states - sealed case class Stop() - sealed case class In[-Data, +Cont](recv: Data => Cont) - sealed case class Out[+Data, +Cont](data: Data, cont: Cont) - - // the type theory of communicating sessions: - - // an instance of type Session[S]{type Dual=D} is evidence that S and D are duals - // such a value witnesses this fact by describing how to compose an instance of S with an instance of D (through the run method) - trait Session[S] { type Self = S - type Dual - type HasDual[D] = Session[Self]{type Dual=D} - def run(self: Self, dual: Dual): Unit - } - - // friendly interface to the theory - def runSession[S, D: Session[S]#HasDual](session: S, dual: D) = - ?[Session[S]#HasDual[D]].run(session, dual) - - // facts in the theory: - - // ------------------------[StopDual] - // Stop is the dual of Stop - implicit object StopDual extends Session[Stop] { - type Dual = Stop - - def run(self: Self, dual: Dual): Unit = {} - } - - // CD is the dual of Cont - // -------------------------------------------[InDual] - // Out[Data, CD] is the dual of In[Data, Cont] - implicit def InDual[Data, Cont](implicit cont: Session[Cont]): Sessions.Session[Sessions.In[Data,Cont]]{type Dual = Sessions.Out[Data,cont.Dual]} = new Session[In[Data, Cont]] { - type Dual = Out[Data, cont.Dual] - - def run(self: Self, dual: Dual): Unit = - cont.run(self.recv(dual.data), dual.cont) - } - - // CD is the dual of Cont - // -------------------------------------------[OutDual] - // In[Data, CD] is the dual of Out[Data, Cont] - implicit def OutDual[Data, Cont](implicit cont: Session[Cont]): Sessions.Session[Sessions.Out[Data,Cont]]{type Dual = Sessions.In[Data,cont.Dual]} = new Session[Out[Data, Cont]] { - type Dual = In[Data, cont.Dual] - - def run(self: Self, dual: Dual): Unit = - cont.run(self.cont, dual.recv(self.data)) - } - - // a concrete session - def addServer = - In{x: Int => - In{y: Int => System.out.println("Thinking") - Out(x + y, - Stop())}} - - def addClient = - Out(3, - Out(4, { System.out.println("Waiting") - In{z: Int => System.out.println(z) - Stop()}})) - - def myRun = runSession(addServer, addClient) -} - -/* future improvements: - - - // 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 write - // one possibility: graph of dependencies between arguments must be acyclic - // 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) - - -*/ diff --git a/tests/pending/pos/depmet_implicit_oopsla_session_simpler.scala b/tests/pending/pos/depmet_implicit_oopsla_session_simpler.scala deleted file mode 100644 index fad5eba4009e..000000000000 --- a/tests/pending/pos/depmet_implicit_oopsla_session_simpler.scala +++ /dev/null @@ -1,44 +0,0 @@ -object Sessions { - trait Session { - type Dual <: Session - - def run(dp: Dual): Unit - } - - sealed case class Stop() extends Session { - type Dual = Stop - - def run(dp: Dual): Unit = {} - } - - // can't write B <: Session{type Dual = BDual} due to limitations in type inference algorithm - // (type variables cannot occur on both sides of <:) - // using B#Dual instead of BDual is too imprecise, since it is disconnected from the actual argument that is passed for B - // would be nice if we could introduce a universal quantification over BDual that is not part of the - // type parameter list - sealed case class In[A, B <: Session, BDual <: Session](recv: A => B)(implicit dual: B <:< Session{type Dual=BDual}) extends Session { - type Dual = Out[A, BDual] - - def run(dp: Dual): Unit = recv(dp.data) run dp.cont - } - - sealed case class Out[A, B <: Session](data: A, cont: B) extends Session { - type Dual = In[A, cont.Dual, cont.Dual#Dual] - - def run(dp: Dual): Unit = cont run dp.recv(data) - } - - def addServer = - In{x: Int => - In{y: Int => System.out.println("Thinking") - Out(x + y, - Stop())}} - - def addClient = - Out(3, - Out(4, { System.out.println("Waiting") - In{z: Int => System.out.println(z) - Stop()}})) - - def myRun = addServer run addClient -} diff --git a/tests/pending/pos/depmet_implicit_oopsla_zipwith.scala b/tests/pending/pos/depmet_implicit_oopsla_zipwith.scala deleted file mode 100644 index 83171f8655d7..000000000000 --- a/tests/pending/pos/depmet_implicit_oopsla_zipwith.scala +++ /dev/null @@ -1,44 +0,0 @@ -case class Zero() -case class Succ[N](x: N) -import Stream.{cons, continually} - -trait ZipWith[N, S] { - type T - - def manyApp: N => Stream[S] => T - def zipWith: N => S => T = n => f => manyApp(n)(continually(f)) -} -object ZipWith { - implicit def ZeroZipWith[S]: ZipWith[Zero,S]{type T = Stream[S]} = new ZipWith[Zero, S] { - type T = Stream[S] - - def manyApp = n => xs => xs - } - - implicit def SuccZipWith[N, S, R](implicit zw: ZipWith[N, R]): ZipWith[Succ[N],S => R]{type T = Stream[S] => zw.T; def zapp[A, B](xs: Stream[A => B],ys: Stream[A]): Stream[B]} = - new ZipWith[Succ[N],S => R] { - type T = Stream[S] => zw.T - - def zapp[A, B](xs: Stream[A => B], ys: Stream[A]): Stream[B] = (xs, ys) match { - case (cons(f, fs), cons(s, ss)) => cons(f(s),zapp(fs, ss)) - case (_, _) => Stream.empty - } - - def manyApp = n => xs => ss => n match { - case Succ(i) => zw.manyApp(i)(zapp(xs, ss)) - } - } -} - -object Test { - def zWith[N, S](n: N, s: S)(implicit zw: ZipWith[N, S]): zw.T = zw.zipWith(n)(s) - - def zipWith0: Stream[Int] = zWith(Zero(),0) - -// (Stream[A]) => java.lang.Object with ZipWith[Zero,B]{type T = Stream[B]}#T -// should normalise to: Stream[A] => Stream[B] - def map[A, B](f: A => B) = zWith(Succ(Zero()),f) - - def zipWith3[A, B, C, D](f: A => B => C => D) = //: Stream[A] => Stream[B] => Stream[C] => Stream[D] = // BUG why do we need a return type? - zWith(Succ(Succ(Succ(Zero()))),f) -} diff --git a/tests/pending/pos/depsel.scala b/tests/pending/pos/depsel.scala deleted file mode 100644 index 2cec4349e18d..000000000000 --- a/tests/pending/pos/depsel.scala +++ /dev/null @@ -1,14 +0,0 @@ -// demonstrates selection on non-path types. Needs to be fleshed out to -// become a real test. -object Test { - - class C { - type T - val f: T => T = ??? - } - - var x = new C - val y = x.f - - -} diff --git a/tests/pos/depmet_implicit_chaining_zw.scala b/tests/pos-scala2/depmet_implicit_chaining_zw.scala similarity index 100% rename from tests/pos/depmet_implicit_chaining_zw.scala rename to tests/pos-scala2/depmet_implicit_chaining_zw.scala From f153af8636128aef9c2612fd5ab8f89383688422 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Aug 2016 10:31:17 +0200 Subject: [PATCH 2/3] Allow dependency on implicit parameters Allow result types of implicit methods to depend on implicit parameters. --- src/dotty/tools/dotc/typer/Checking.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index b8c8d68dd519..d2cc7dc6e99f 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -469,7 +469,7 @@ trait Checking { */ def checkNotDependent(resTpt: Tree, vparams: List[Symbol])(implicit ctx: Context): Unit = for (vparam <- vparams) - if (vparam.termRef.occursIn(resTpt.tpe)) + 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) From 1faf82a6894aed855cda451d91f2f85302943baf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Aug 2016 10:46:41 +0200 Subject: [PATCH 3/3] More tests - Add neg test against disallowed implicits - Add pos test for t5070 - Reinstantiate the depmeth oopsla tests in pending; they do not work for other reasons. --- .../pos/depmeth_implicit_oopsla_zipwith.scala | 44 ++++++++++ tests/neg/dependent-implicits.scala | 7 ++ .../pos/depmeth_implicit_oopsla_session.scala | 50 +++++++++++ .../depmeth_implicit_oopsla_session_2.scala | 87 +++++++++++++++++++ ...meth_implicit_oopsla_session_simpler.scala | 44 ++++++++++ tests/pos/t5070.scala | 15 ++++ 6 files changed, 247 insertions(+) create mode 100644 tests/disabled/structural-type/pos/depmeth_implicit_oopsla_zipwith.scala create mode 100644 tests/neg/dependent-implicits.scala create mode 100644 tests/pending/pos/depmeth_implicit_oopsla_session.scala create mode 100644 tests/pending/pos/depmeth_implicit_oopsla_session_2.scala create mode 100644 tests/pending/pos/depmeth_implicit_oopsla_session_simpler.scala create mode 100644 tests/pos/t5070.scala diff --git a/tests/disabled/structural-type/pos/depmeth_implicit_oopsla_zipwith.scala b/tests/disabled/structural-type/pos/depmeth_implicit_oopsla_zipwith.scala new file mode 100644 index 000000000000..83171f8655d7 --- /dev/null +++ b/tests/disabled/structural-type/pos/depmeth_implicit_oopsla_zipwith.scala @@ -0,0 +1,44 @@ +case class Zero() +case class Succ[N](x: N) +import Stream.{cons, continually} + +trait ZipWith[N, S] { + type T + + def manyApp: N => Stream[S] => T + def zipWith: N => S => T = n => f => manyApp(n)(continually(f)) +} +object ZipWith { + implicit def ZeroZipWith[S]: ZipWith[Zero,S]{type T = Stream[S]} = new ZipWith[Zero, S] { + type T = Stream[S] + + def manyApp = n => xs => xs + } + + implicit def SuccZipWith[N, S, R](implicit zw: ZipWith[N, R]): ZipWith[Succ[N],S => R]{type T = Stream[S] => zw.T; def zapp[A, B](xs: Stream[A => B],ys: Stream[A]): Stream[B]} = + new ZipWith[Succ[N],S => R] { + type T = Stream[S] => zw.T + + def zapp[A, B](xs: Stream[A => B], ys: Stream[A]): Stream[B] = (xs, ys) match { + case (cons(f, fs), cons(s, ss)) => cons(f(s),zapp(fs, ss)) + case (_, _) => Stream.empty + } + + def manyApp = n => xs => ss => n match { + case Succ(i) => zw.manyApp(i)(zapp(xs, ss)) + } + } +} + +object Test { + def zWith[N, S](n: N, s: S)(implicit zw: ZipWith[N, S]): zw.T = zw.zipWith(n)(s) + + def zipWith0: Stream[Int] = zWith(Zero(),0) + +// (Stream[A]) => java.lang.Object with ZipWith[Zero,B]{type T = Stream[B]}#T +// should normalise to: Stream[A] => Stream[B] + def map[A, B](f: A => B) = zWith(Succ(Zero()),f) + + def zipWith3[A, B, C, D](f: A => B => C => D) = //: Stream[A] => Stream[B] => Stream[C] => Stream[D] = // BUG why do we need a return type? + zWith(Succ(Succ(Succ(Zero()))),f) +} diff --git a/tests/neg/dependent-implicits.scala b/tests/neg/dependent-implicits.scala new file mode 100644 index 000000000000..dd123ea13940 --- /dev/null +++ b/tests/neg/dependent-implicits.scala @@ -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) +} diff --git a/tests/pending/pos/depmeth_implicit_oopsla_session.scala b/tests/pending/pos/depmeth_implicit_oopsla_session.scala new file mode 100644 index 000000000000..eee5bb7390a0 --- /dev/null +++ b/tests/pending/pos/depmeth_implicit_oopsla_session.scala @@ -0,0 +1,50 @@ +object Sessions { + trait Session[This] { + type Dual + type HasDual[D] = Session[This]{type Dual=D} + def run(p: This, dp: Dual): Unit + } + + implicit object StopSession extends Session[Stop] { + type Dual = Stop + + def run(p: Stop, dp: Stop): Unit = {} + } + + implicit def InDual[A, B](implicit sessionDIn: Session[B]): Sessions.Session[Sessions.In[A,B]]{type Dual = Sessions.Out[A,sessionDIn.Dual]} = + new Session[In[A, B]] { + type Dual = Out[A, sessionDIn.Dual] + + def run(p: In[A, B], dp: Dual): Unit = + sessionDIn.run(p.func(dp.x), dp.y) + } + + implicit def OutDual[A, B](implicit sessionDOut: Session[B]): Sessions.Session[Sessions.Out[A,B]]{type Dual = Sessions.In[A,sessionDOut.Dual]} = + new Session[Out[A, B]] { + type Dual = In[A, sessionDOut.Dual] + + def run(p: Out[A, B], dp: Dual): Unit = + sessionDOut.run(p.y, dp.func(p.x)) + } + + sealed case class Stop() + sealed case class In[-A, +B](func: A => B) + sealed case class Out[+A, +B](x: A, y: B) + + def addServer = + In{x: Int => + In{y: Int => System.out.println("Thinking") + Out(x + y, + Stop())}} + + def addClient = + Out(3, + Out(4, { System.out.println("Waiting") + In{z: Int => System.out.println(z) + Stop()}})) + + def runSession[S, D: Session[S]#HasDual](p: S, dp: D) = + implicitly[Session[S]#HasDual[D]].run(p, dp) + + def myRun = runSession(addServer, addClient) +} diff --git a/tests/pending/pos/depmeth_implicit_oopsla_session_2.scala b/tests/pending/pos/depmeth_implicit_oopsla_session_2.scala new file mode 100644 index 000000000000..29a76d5cf678 --- /dev/null +++ b/tests/pending/pos/depmeth_implicit_oopsla_session_2.scala @@ -0,0 +1,87 @@ +object Sessions { + def ?[T <: AnyRef](implicit w: T): w.type = w + + // session states + sealed case class Stop() + sealed case class In[-Data, +Cont](recv: Data => Cont) + sealed case class Out[+Data, +Cont](data: Data, cont: Cont) + + // the type theory of communicating sessions: + + // an instance of type Session[S]{type Dual=D} is evidence that S and D are duals + // such a value witnesses this fact by describing how to compose an instance of S with an instance of D (through the run method) + trait Session[S] { type Self = S + type Dual + type HasDual[D] = Session[Self]{type Dual=D} + def run(self: Self, dual: Dual): Unit + } + + // friendly interface to the theory + def runSession[S, D: Session[S]#HasDual](session: S, dual: D) = + ?[Session[S]#HasDual[D]].run(session, dual) + + // facts in the theory: + + // ------------------------[StopDual] + // Stop is the dual of Stop + implicit object StopDual extends Session[Stop] { + type Dual = Stop + + def run(self: Self, dual: Dual): Unit = {} + } + + // CD is the dual of Cont + // -------------------------------------------[InDual] + // Out[Data, CD] is the dual of In[Data, Cont] + implicit def InDual[Data, Cont](implicit cont: Session[Cont]): Sessions.Session[Sessions.In[Data,Cont]]{type Dual = Sessions.Out[Data,cont.Dual]} = new Session[In[Data, Cont]] { + type Dual = Out[Data, cont.Dual] + + def run(self: Self, dual: Dual): Unit = + cont.run(self.recv(dual.data), dual.cont) + } + + // CD is the dual of Cont + // -------------------------------------------[OutDual] + // In[Data, CD] is the dual of Out[Data, Cont] + implicit def OutDual[Data, Cont](implicit cont: Session[Cont]): Sessions.Session[Sessions.Out[Data,Cont]]{type Dual = Sessions.In[Data,cont.Dual]} = new Session[Out[Data, Cont]] { + type Dual = In[Data, cont.Dual] + + def run(self: Self, dual: Dual): Unit = + cont.run(self.cont, dual.recv(self.data)) + } + + // a concrete session + def addServer = + In{x: Int => + In{y: Int => System.out.println("Thinking") + Out(x + y, + Stop())}} + + def addClient = + Out(3, + Out(4, { System.out.println("Waiting") + In{z: Int => System.out.println(z) + Stop()}})) + + def myRun = runSession(addServer, addClient) +} + +/* future improvements: + + + // 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 write + // one possibility: graph of dependencies between arguments must be acyclic + // 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) + + +*/ diff --git a/tests/pending/pos/depmeth_implicit_oopsla_session_simpler.scala b/tests/pending/pos/depmeth_implicit_oopsla_session_simpler.scala new file mode 100644 index 000000000000..fad5eba4009e --- /dev/null +++ b/tests/pending/pos/depmeth_implicit_oopsla_session_simpler.scala @@ -0,0 +1,44 @@ +object Sessions { + trait Session { + type Dual <: Session + + def run(dp: Dual): Unit + } + + sealed case class Stop() extends Session { + type Dual = Stop + + def run(dp: Dual): Unit = {} + } + + // can't write B <: Session{type Dual = BDual} due to limitations in type inference algorithm + // (type variables cannot occur on both sides of <:) + // using B#Dual instead of BDual is too imprecise, since it is disconnected from the actual argument that is passed for B + // would be nice if we could introduce a universal quantification over BDual that is not part of the + // type parameter list + sealed case class In[A, B <: Session, BDual <: Session](recv: A => B)(implicit dual: B <:< Session{type Dual=BDual}) extends Session { + type Dual = Out[A, BDual] + + def run(dp: Dual): Unit = recv(dp.data) run dp.cont + } + + sealed case class Out[A, B <: Session](data: A, cont: B) extends Session { + type Dual = In[A, cont.Dual, cont.Dual#Dual] + + def run(dp: Dual): Unit = cont run dp.recv(data) + } + + def addServer = + In{x: Int => + In{y: Int => System.out.println("Thinking") + Out(x + y, + Stop())}} + + def addClient = + Out(3, + Out(4, { System.out.println("Waiting") + In{z: Int => System.out.println(z) + Stop()}})) + + def myRun = addServer run addClient +} diff --git a/tests/pos/t5070.scala b/tests/pos/t5070.scala new file mode 100644 index 000000000000..95a20a570ab1 --- /dev/null +++ b/tests/pos/t5070.scala @@ -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 +}