diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala index 85217501d55f..e47c3b08d264 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checking.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checking.scala @@ -326,14 +326,14 @@ object Checking { case Fun(pots, effs) => val name = sym.name.toString - if (name == "apply") Summary(pots, Effects.empty) - else if (name == "tupled") Summary(Set(pot1), Effects.empty) + if (name == "apply") Summary(pots) + else if (name == "tupled") Summary(pot1) else if (name == "curried") { val arity = defn.functionArity(sym.info.finalResultType) - val pots = (1 until arity).foldLeft(Set(pot1)) { (acc, i) => - Set(Fun(acc, Effects.empty)(pot1.source)) + val pots = (1 until arity).foldLeft(Vector(pot1)) { (acc, i) => + Vector(Fun(acc, Effects.empty)(pot1.source)) } - Summary(pots, Effects.empty) + Summary(pots) } else Summary.empty diff --git a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala index 9486263a8af0..ce9d8d8aa497 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Effects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Effects.scala @@ -12,8 +12,8 @@ import core.Contexts._ import Potentials._ object Effects { - type Effects = Set[Effect] - val empty: Effects = Set.empty + type Effects = Vector[Effect] + val empty: Effects = Vector.empty def show(effs: Effects)(using Context): String = effs.map(_.show).mkString(", ") @@ -25,13 +25,15 @@ object Effects { def show(using Context): String def source: Tree + + def toEffs: Effects = Vector(this) } - /** An effect means that a value that's possibly under initialization + /** A promotion effect means that a value that's possibly under initialization * is promoted from the initializing world to the fully-initialized world. * * Essentially, this effect enforces that the object pointed to by - * `potential` is fully initialized. + * `potential` is transitively initialized. * * This effect is trigger in several scenarios: * - a potential is used as arguments to method calls or new-expressions @@ -58,8 +60,6 @@ object Effects { // ------------------ operations on effects ------------------ - extension (eff: Effect) def toEffs: Effects = Effects.empty + eff - def asSeenFrom(eff: Effect, thisValue: Potential)(implicit env: Env): Effect = trace(eff.show + " asSeenFrom " + thisValue.show + ", current = " + currentClass.show, init, _.asInstanceOf[Effect].show) { eff match { case Promote(pot) => diff --git a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala index a7b409b56d5e..39eac29a2741 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Potentials.scala @@ -15,8 +15,8 @@ import Types._, Symbols._, Contexts._ import Effects._, Summary._ object Potentials { - type Potentials = Set[Potential] - val empty: Potentials = Set.empty + type Potentials = Vector[Potential] + val empty: Potentials = Vector.empty def show(pots: Potentials)(using Context): String = pots.map(_.show).mkString(", ") @@ -31,6 +31,8 @@ object Potentials { def show(using Context): String def source: Tree + + def toPots: Potentials = Vector(this) } sealed trait Refinable extends Potential { @@ -153,8 +155,6 @@ object Potentials { // ------------------ operations on potentials ------------------ - extension (pot: Potential) def toPots: Potentials = Potentials.empty + pot - /** Selection on a set of potentials * * @param ignoreSelectEffect Where selection effects should be ignored diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala index 8df4cb7f558d..8da415bf82bb 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summarization.scala @@ -107,7 +107,7 @@ object Summarization { case Typed(expr, tpt) => if (tpt.tpe.hasAnnotation(defn.UncheckedAnnot)) Summary.empty - else analyze(expr) + else analyze(expr) ++ effectsOfType(tpt.tpe, tpt) case NamedArg(name, arg) => analyze(arg) @@ -140,7 +140,7 @@ object Summarization { val Summary(pots, effs) = analyze(selector) val init = Summary(Potentials.empty, pots.promote(selector) ++ effs) cases.foldLeft(init) { (acc, cas) => - acc union analyze(cas.body) + acc + analyze(cas.body) } // case CaseDef(pat, guard, body) => @@ -162,7 +162,7 @@ object Summarization { case Try(block, cases, finalizer) => val Summary(pots, effs) = cases.foldLeft(analyze(block)) { (acc, cas) => - acc union analyze(cas.body) + acc + analyze(cas.body) } val Summary(_, eff2) = if (finalizer.isEmpty) Summary.empty else analyze(finalizer) Summary(pots, effs ++ eff2) @@ -199,8 +199,9 @@ object Summarization { Summary(pots.promote(ddef) ++ effs) } - case _: TypeDef => - Summary.empty + case tdef: TypeDef => + if tdef.isClassDef then Summary.empty + else Summary(effectsOfType(tdef.symbol.info, tdef.rhs)) case _: Import | _: Export => Summary.empty @@ -213,6 +214,19 @@ object Summarization { else summary } + private def effectsOfType(tp: Type, source: Tree)(implicit env: Env): Effects = + var summary = Summary.empty + val traverser = new TypeTraverser { + def traverse(tp: Type): Unit = tp match { + case TermRef(_: SingletonType, _) => + summary = summary + analyze(tp, source) + case _ => + traverseChildren(tp) + } + } + traverser.traverse(tp) + summary.effs + def analyze(tp: Type, source: Tree)(implicit env: Env): Summary = trace("summarizing " + tp.show, init, s => s.asInstanceOf[Summary].show) { val summary: Summary = tp match { @@ -243,8 +257,12 @@ object Summarization { } Summary(pots2, effs) + case _: TermParamRef | _: RecThis => + // possible from checking effects of types + Summary.empty + case _ => - throw new Exception("unexpected type: " + tp.show) + throw new Exception("unexpected type: " + tp) } if (env.isAlwaysInitialized(tp)) Summary(Potentials.empty, summary.effs) @@ -290,7 +308,7 @@ object Summarization { def parentArgEffsWithInit(stats: List[Tree], ctor: Symbol, source: Tree): Effects = val init = if env.canIgnoreMethod(ctor) then Effects.empty - else Effects.empty + MethodCall(ThisRef()(source), ctor)(source) + else Effects.empty :+ MethodCall(ThisRef()(source), ctor)(source) stats.foldLeft(init) { (acc, stat) => val summary = Summarization.analyze(stat) acc ++ summary.effs @@ -320,7 +338,7 @@ object Summarization { if tref.prefix == NoPrefix then Effects.empty else Summarization.analyze(tref.prefix, ref).effs - prefixEff + MethodCall(ThisRef()(ref), ctor)(ref) + prefixEff :+ MethodCall(ThisRef()(ref), ctor)(ref) } }) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala index d4b72d4c0214..1c7a852d4847 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Summary.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Summary.scala @@ -14,14 +14,14 @@ import config.Printers.init import Potentials._, Effects._, Util._ case class Summary(pots: Potentials, effs: Effects) { - def union(summary2: Summary): Summary = + def +(summary2: Summary): Summary = Summary(pots ++ summary2.pots, this.effs ++ summary2.effs) def +(pot: Potential): Summary = - Summary(pots + pot, effs) + Summary(pots :+ pot, effs) def +(eff: Effect): Summary = - Summary(pots, effs + eff) + Summary(pots, effs :+ eff) def dropPotentials: Summary = Summary(Potentials.empty, effs) @@ -44,14 +44,14 @@ case class Summary(pots: Potentials, effs: Effects) { object Summary { val empty: Summary = Summary(Potentials.empty, Effects.empty) - def apply(pots: Potentials): Summary = new Summary(pots, Effects.empty) + def apply(pots: Potentials): Summary = empty ++ pots @targetName("withEffects") - def apply(effs: Effects): Summary = new Summary(Potentials.empty, effs) + def apply(effs: Effects): Summary = empty ++ effs - def apply(pot: Potential): Summary = new Summary(Potentials.empty + pot, Effects.empty) + def apply(pot: Potential): Summary = empty + pot - def apply(eff: Effect): Summary = new Summary(Potentials.empty, Effects.empty + eff) + def apply(eff: Effect): Summary = empty + eff } /** Summary of class. diff --git a/tests/init/neg/i11572.scala b/tests/init/neg/i11572.scala new file mode 100644 index 000000000000..c2ebe6fe47f9 --- /dev/null +++ b/tests/init/neg/i11572.scala @@ -0,0 +1,17 @@ +class A { + trait Cov[+X] { + def get: X + } + trait Bounded { + type T >: Cov[Int] <: Cov[String] + } + val t: Bounded = new Bounded { // error + // Note: using this instead of t produces an error (as expected) + override type T >: t.T <: t.T + } + + val covInt = new Cov[Int] { + override def get: Int = 3 + } + val str: String = ((covInt: t.T): Cov[String]).get // ClassCastException: class Integer cannot be cast to class String +} diff --git a/tests/init/neg/i4031.scala b/tests/init/neg/i4031.scala new file mode 100644 index 000000000000..8340296340e7 --- /dev/null +++ b/tests/init/neg/i4031.scala @@ -0,0 +1,10 @@ +object App { + trait A { type L >: Any} + def upcast(a: A, x: Any): a.L = x + val p: A { type L <: Nothing } = p // error + def coerce(x: Any): Int = upcast(p, x) + + def main(args: Array[String]): Unit = { + println(coerce("Uh oh!")) + } +} diff --git a/tests/init/neg/i4042.scala b/tests/init/neg/i4042.scala new file mode 100644 index 000000000000..58c884a5690d --- /dev/null +++ b/tests/init/neg/i4042.scala @@ -0,0 +1,18 @@ +object App { + def coerce[U, V](u: U): V = { + trait X { type R >: U } + trait Y { type R = V } + + class T[A <: X](val a: A)(val value: a.R) + + object O { val x : Y & X = x } // error + + val a = new T[Y & X](O.x)(u) + a.value + } + + def main(args: Array[String]): Unit = { + val x: Int = coerce[String, Int]("a") + println(x + 1) + } +} diff --git a/tests/init/neg/i50.scala b/tests/init/neg/i50.scala new file mode 100644 index 000000000000..9131fb263f48 --- /dev/null +++ b/tests/init/neg/i50.scala @@ -0,0 +1,5 @@ +class C[T] { + val a: T = method + def method = b + val b: T = a // error +} \ No newline at end of file diff --git a/tests/init/neg/i5854.scala b/tests/init/neg/i5854.scala new file mode 100644 index 000000000000..a52685f63be2 --- /dev/null +++ b/tests/init/neg/i5854.scala @@ -0,0 +1,5 @@ +class B { + val a: String = (((1: Any): b.A): Nothing): String + val b: { type A >: Any <: Nothing } = loop() // error + def loop(): Nothing = loop() +} diff --git a/tests/neg-strict/i5854.scala b/tests/neg-strict/i5854.scala new file mode 100644 index 000000000000..e8c68105bbae --- /dev/null +++ b/tests/neg-strict/i5854.scala @@ -0,0 +1,24 @@ +object bar { + trait Sub { + type M + type L <: M + type U >: M + type M2 >: L <: U + } + class Box[V](val v: V) + + class Caster[LL, UU] { + trait S2 { + type L = LL + type U = UU + } + final lazy val nothing: Nothing = nothing + final lazy val sub: S2 & Sub = nothing + final lazy val box : Box[S2 & Sub] = new Box(nothing) + def upcast(t: box.v.M2): box.v.M2 = t // error // error + } + def main(args : Array[String]) : Unit = { + val zero : String = (new Caster[Int,String]()).upcast(0) + println("...") + } +} \ No newline at end of file